001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2023 the original author or authors.
004//
005// This library is free software; you can redistribute it and/or
006// modify it under the terms of the GNU Lesser General Public
007// License as published by the Free Software Foundation; either
008// version 2.1 of the License, or (at your option) any later version.
009//
010// This library is distributed in the hope that it will be useful,
011// but WITHOUT ANY WARRANTY; without even the implied warranty of
012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013// Lesser General Public License for more details.
014//
015// You should have received a copy of the GNU Lesser General Public
016// License along with this library; if not, write to the Free Software
017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
018///////////////////////////////////////////////////////////////////////////////////////////////
019
020package com.puppycrawl.tools.checkstyle.checks.whitespace;
021
022import java.util.stream.IntStream;
023
024import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
025import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
026import com.puppycrawl.tools.checkstyle.api.DetailAST;
027import com.puppycrawl.tools.checkstyle.api.TokenTypes;
028import com.puppycrawl.tools.checkstyle.utils.CodePointUtil;
029import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
030
031/**
032 * <p>
033 * Checks that the whitespace around the Generic tokens (angle brackets)
034 * "&lt;" and "&gt;" are correct to the <i>typical</i> convention.
035 * The convention is not configurable.
036 * </p>
037 * <p>
038 * Left angle bracket ("&lt;"):
039 * </p>
040 * <ul>
041 * <li> should be preceded with whitespace only
042 *   in generic methods definitions.</li>
043 * <li> should not be preceded with whitespace
044 *   when it is preceded method name or constructor.</li>
045 * <li> should not be preceded with whitespace when following type name.</li>
046 * <li> should not be followed with whitespace in all cases.</li>
047 * </ul>
048 * <p>
049 * Right angle bracket ("&gt;"):
050 * </p>
051 * <ul>
052 * <li> should not be preceded with whitespace in all cases.</li>
053 * <li> should be followed with whitespace in almost all cases,
054 *   except diamond operators and when preceding method name or constructor.</li></ul>
055 * <p>
056 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
057 * </p>
058 * <p>
059 * Violation Message Keys:
060 * </p>
061 * <ul>
062 * <li>
063 * {@code ws.followed}
064 * </li>
065 * <li>
066 * {@code ws.illegalFollow}
067 * </li>
068 * <li>
069 * {@code ws.notPreceded}
070 * </li>
071 * <li>
072 * {@code ws.preceded}
073 * </li>
074 * </ul>
075 *
076 * @since 5.0
077 */
078@FileStatefulCheck
079public class GenericWhitespaceCheck extends AbstractCheck {
080
081    /**
082     * A key is pointing to the warning message text in "messages.properties"
083     * file.
084     */
085    public static final String MSG_WS_PRECEDED = "ws.preceded";
086
087    /**
088     * A key is pointing to the warning message text in "messages.properties"
089     * file.
090     */
091    public static final String MSG_WS_FOLLOWED = "ws.followed";
092
093    /**
094     * A key is pointing to the warning message text in "messages.properties"
095     * file.
096     */
097    public static final String MSG_WS_NOT_PRECEDED = "ws.notPreceded";
098
099    /**
100     * A key is pointing to the warning message text in "messages.properties"
101     * file.
102     */
103    public static final String MSG_WS_ILLEGAL_FOLLOW = "ws.illegalFollow";
104
105    /** Open angle bracket literal. */
106    private static final String OPEN_ANGLE_BRACKET = "<";
107
108    /** Close angle bracket literal. */
109    private static final String CLOSE_ANGLE_BRACKET = ">";
110
111    /** Used to count the depth of a Generic expression. */
112    private int depth;
113
114    @Override
115    public int[] getDefaultTokens() {
116        return getRequiredTokens();
117    }
118
119    @Override
120    public int[] getAcceptableTokens() {
121        return getRequiredTokens();
122    }
123
124    @Override
125    public int[] getRequiredTokens() {
126        return new int[] {TokenTypes.GENERIC_START, TokenTypes.GENERIC_END};
127    }
128
129    @Override
130    public void beginTree(DetailAST rootAST) {
131        // Reset for each tree, just increase there are violations in preceding
132        // trees.
133        depth = 0;
134    }
135
136    @Override
137    public void visitToken(DetailAST ast) {
138        switch (ast.getType()) {
139            case TokenTypes.GENERIC_START:
140                processStart(ast);
141                depth++;
142                break;
143            case TokenTypes.GENERIC_END:
144                processEnd(ast);
145                depth--;
146                break;
147            default:
148                throw new IllegalArgumentException("Unknown type " + ast);
149        }
150    }
151
152    /**
153     * Checks the token for the end of Generics.
154     *
155     * @param ast the token to check
156     */
157    private void processEnd(DetailAST ast) {
158        final int[] line = getLineCodePoints(ast.getLineNo() - 1);
159        final int before = ast.getColumnNo() - 1;
160        final int after = ast.getColumnNo() + 1;
161
162        if (before >= 0 && CommonUtil.isCodePointWhitespace(line, before)
163                && !containsWhitespaceBefore(before, line)) {
164            log(ast, MSG_WS_PRECEDED, CLOSE_ANGLE_BRACKET);
165        }
166
167        if (after < line.length) {
168            // Check if the last Generic, in which case must be a whitespace
169            // or a '(),[.'.
170            if (depth == 1) {
171                processSingleGeneric(ast, line, after);
172            }
173            else {
174                processNestedGenerics(ast, line, after);
175            }
176        }
177    }
178
179    /**
180     * Process Nested generics.
181     *
182     * @param ast token
183     * @param line unicode code points array of line
184     * @param after position after
185     */
186    private void processNestedGenerics(DetailAST ast, int[] line, int after) {
187        // In a nested Generic type, so can only be a '>' or ',' or '&'
188
189        // In case of several extends definitions:
190        //
191        //   class IntEnumValueType<E extends Enum<E> & IntEnum>
192        //                                          ^
193        //   should be whitespace if followed by & -+
194        //
195        final int indexOfAmp = IntStream.range(after, line.length)
196                .filter(index -> line[index] == '&')
197                .findFirst()
198                .orElse(-1);
199        if (indexOfAmp >= 1
200            && containsWhitespaceBetween(after, indexOfAmp, line)) {
201            if (indexOfAmp - after == 0) {
202                log(ast, MSG_WS_NOT_PRECEDED, "&");
203            }
204            else if (indexOfAmp - after != 1) {
205                log(ast, MSG_WS_FOLLOWED, CLOSE_ANGLE_BRACKET);
206            }
207        }
208        else if (line[after] == ' ') {
209            log(ast, MSG_WS_FOLLOWED, CLOSE_ANGLE_BRACKET);
210        }
211    }
212
213    /**
214     * Process Single-generic.
215     *
216     * @param ast token
217     * @param line unicode code points array of line
218     * @param after position after
219     */
220    private void processSingleGeneric(DetailAST ast, int[] line, int after) {
221        final char charAfter = Character.toChars(line[after])[0];
222        if (isGenericBeforeMethod(ast) || isGenericBeforeCtor(ast)) {
223            if (Character.isWhitespace(charAfter)) {
224                log(ast, MSG_WS_FOLLOWED, CLOSE_ANGLE_BRACKET);
225            }
226        }
227        else if (!isCharacterValidAfterGenericEnd(charAfter)) {
228            log(ast, MSG_WS_ILLEGAL_FOLLOW, CLOSE_ANGLE_BRACKET);
229        }
230    }
231
232    /**
233     * Checks if generic is before constructor invocation.
234     *
235     * @param ast ast
236     * @return true if generic before a constructor invocation
237     */
238    private static boolean isGenericBeforeCtor(DetailAST ast) {
239        final DetailAST parent = ast.getParent();
240        return parent.getParent().getType() == TokenTypes.LITERAL_NEW
241                && (parent.getNextSibling().getType() == TokenTypes.IDENT
242                    || parent.getNextSibling().getType() == TokenTypes.DOT);
243    }
244
245    /**
246     * Is generic before method reference.
247     *
248     * @param ast ast
249     * @return true if generic before a method ref
250     */
251    private static boolean isGenericBeforeMethod(DetailAST ast) {
252        return ast.getParent().getParent().getParent().getType() == TokenTypes.METHOD_CALL
253                || isAfterMethodReference(ast);
254    }
255
256    /**
257     * Checks if current generic end ('&gt;') is located after
258     * {@link TokenTypes#METHOD_REF method reference operator}.
259     *
260     * @param genericEnd {@link TokenTypes#GENERIC_END}
261     * @return true if '&gt;' follows after method reference.
262     */
263    private static boolean isAfterMethodReference(DetailAST genericEnd) {
264        return genericEnd.getParent().getParent().getType() == TokenTypes.METHOD_REF;
265    }
266
267    /**
268     * Checks the token for the start of Generics.
269     *
270     * @param ast the token to check
271     */
272    private void processStart(DetailAST ast) {
273        final int[] line = getLineCodePoints(ast.getLineNo() - 1);
274        final int before = ast.getColumnNo() - 1;
275        final int after = ast.getColumnNo() + 1;
276
277        // Need to handle two cases as in:
278        //
279        //   public static <T> Callable<T> callable(Runnable task, T result)
280        //                 ^           ^
281        //      ws reqd ---+           +--- whitespace NOT required
282        //
283        if (before >= 0) {
284            // Detect if the first case
285            final DetailAST parent = ast.getParent();
286            final DetailAST grandparent = parent.getParent();
287            if (grandparent.getType() == TokenTypes.CTOR_DEF
288                    || grandparent.getType() == TokenTypes.METHOD_DEF
289                    || isGenericBeforeCtor(ast)) {
290                // Require whitespace
291                if (!CommonUtil.isCodePointWhitespace(line, before)) {
292                    log(ast, MSG_WS_NOT_PRECEDED, OPEN_ANGLE_BRACKET);
293                }
294            }
295            // Whitespace not required
296            else if (CommonUtil.isCodePointWhitespace(line, before)
297                && !containsWhitespaceBefore(before, line)) {
298                log(ast, MSG_WS_PRECEDED, OPEN_ANGLE_BRACKET);
299            }
300        }
301
302        if (after < line.length
303                && CommonUtil.isCodePointWhitespace(line, after)) {
304            log(ast, MSG_WS_FOLLOWED, OPEN_ANGLE_BRACKET);
305        }
306    }
307
308    /**
309     * Returns whether the specified string contains only whitespace between
310     * specified indices.
311     *
312     * @param fromIndex the index to start the search from. Inclusive
313     * @param toIndex the index to finish the search. Exclusive
314     * @param line the unicode code points array of line to check
315     * @return whether there are only whitespaces (or nothing)
316     */
317    private static boolean containsWhitespaceBetween(int fromIndex, int toIndex, int... line) {
318        boolean result = true;
319        for (int i = fromIndex; i < toIndex; i++) {
320            if (!CommonUtil.isCodePointWhitespace(line, i)) {
321                result = false;
322                break;
323            }
324        }
325        return result;
326    }
327
328    /**
329     * Returns whether the specified string contains only whitespace up to specified index.
330     *
331     * @param before the index to finish the search. Exclusive
332     * @param line   the unicode code points array of line to check
333     * @return {@code true} if there are only whitespaces,
334     *     false if there is nothing before or some other characters
335     */
336    private static boolean containsWhitespaceBefore(int before, int... line) {
337        return before != 0 && CodePointUtil.hasWhitespaceBefore(before, line);
338    }
339
340    /**
341     * Checks whether given character is valid to be right after generic ends.
342     *
343     * @param charAfter character to check
344     * @return checks if given character is valid
345     */
346    private static boolean isCharacterValidAfterGenericEnd(char charAfter) {
347        return charAfter == '(' || charAfter == ')'
348            || charAfter == ',' || charAfter == '['
349            || charAfter == '.' || charAfter == ':'
350            || charAfter == ';'
351            || Character.isWhitespace(charAfter);
352    }
353
354}