001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2022 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 precede 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 * To configure the check:
057 * </p>
058 * <pre>
059 * &lt;module name=&quot;GenericWhitespace&quot;/&gt;
060 * </pre>
061 * <p>
062 * Examples with correct spacing:
063 * </p>
064 * <pre>
065 * // Generic methods definitions
066 * public void &lt;K, V extends Number&gt; boolean foo(K, V) {}
067 * // Generic type definition
068 * class name&lt;T1, T2, ..., Tn&gt; {}
069 * // Generic type reference
070 * OrderedPair&lt;String, Box&lt;Integer&gt;&gt; p;
071 * // Generic preceded method name
072 * boolean same = Util.&lt;Integer, String&gt;compare(p1, p2);
073 * // Diamond operator
074 * Pair&lt;Integer, String&gt; p1 = new Pair&lt;&gt;(1, "apple");
075 * // Method reference
076 * List&lt;T&gt; list = ImmutableList.Builder&lt;T&gt;::new;
077 * // Method reference
078 * sort(list, Comparable::&lt;String&gt;compareTo);
079 * // Constructor call
080 * MyClass obj = new &lt;String&gt;MyClass();
081 * </pre>
082 * <p>
083 * Examples with incorrect spacing:
084 * </p>
085 * <pre>
086 * List&lt; String&gt; l; // violation, "&lt;" followed by whitespace
087 * Box b = Box. &lt;String&gt;of("foo"); // violation, "&lt;" preceded with whitespace
088 * public&lt;T&gt; void foo() {} // violation, "&lt;" not preceded with whitespace
089 *
090 * List a = new ArrayList&lt;&gt; (); // violation, "&gt;" followed by whitespace
091 * Map&lt;Integer, String&gt;m; // violation, "&gt;" not followed by whitespace
092 * Pair&lt;Integer, Integer &gt; p; // violation, "&gt;" preceded with whitespace
093 * </pre>
094 * <p>
095 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
096 * </p>
097 * <p>
098 * Violation Message Keys:
099 * </p>
100 * <ul>
101 * <li>
102 * {@code ws.followed}
103 * </li>
104 * <li>
105 * {@code ws.illegalFollow}
106 * </li>
107 * <li>
108 * {@code ws.notPreceded}
109 * </li>
110 * <li>
111 * {@code ws.preceded}
112 * </li>
113 * </ul>
114 *
115 * @since 5.0
116 */
117@FileStatefulCheck
118public class GenericWhitespaceCheck extends AbstractCheck {
119
120    /**
121     * A key is pointing to the warning message text in "messages.properties"
122     * file.
123     */
124    public static final String MSG_WS_PRECEDED = "ws.preceded";
125
126    /**
127     * A key is pointing to the warning message text in "messages.properties"
128     * file.
129     */
130    public static final String MSG_WS_FOLLOWED = "ws.followed";
131
132    /**
133     * A key is pointing to the warning message text in "messages.properties"
134     * file.
135     */
136    public static final String MSG_WS_NOT_PRECEDED = "ws.notPreceded";
137
138    /**
139     * A key is pointing to the warning message text in "messages.properties"
140     * file.
141     */
142    public static final String MSG_WS_ILLEGAL_FOLLOW = "ws.illegalFollow";
143
144    /** Open angle bracket literal. */
145    private static final String OPEN_ANGLE_BRACKET = "<";
146
147    /** Close angle bracket literal. */
148    private static final String CLOSE_ANGLE_BRACKET = ">";
149
150    /** Used to count the depth of a Generic expression. */
151    private int depth;
152
153    @Override
154    public int[] getDefaultTokens() {
155        return getRequiredTokens();
156    }
157
158    @Override
159    public int[] getAcceptableTokens() {
160        return getRequiredTokens();
161    }
162
163    @Override
164    public int[] getRequiredTokens() {
165        return new int[] {TokenTypes.GENERIC_START, TokenTypes.GENERIC_END};
166    }
167
168    @Override
169    public void beginTree(DetailAST rootAST) {
170        // Reset for each tree, just increase there are violations in preceding
171        // trees.
172        depth = 0;
173    }
174
175    @Override
176    public void visitToken(DetailAST ast) {
177        switch (ast.getType()) {
178            case TokenTypes.GENERIC_START:
179                processStart(ast);
180                depth++;
181                break;
182            case TokenTypes.GENERIC_END:
183                processEnd(ast);
184                depth--;
185                break;
186            default:
187                throw new IllegalArgumentException("Unknown type " + ast);
188        }
189    }
190
191    /**
192     * Checks the token for the end of Generics.
193     *
194     * @param ast the token to check
195     */
196    private void processEnd(DetailAST ast) {
197        final int[] line = getLineCodePoints(ast.getLineNo() - 1);
198        final int before = ast.getColumnNo() - 1;
199        final int after = ast.getColumnNo() + 1;
200
201        if (before >= 0 && CommonUtil.isCodePointWhitespace(line, before)
202                && !containsWhitespaceBefore(before, line)) {
203            log(ast, MSG_WS_PRECEDED, CLOSE_ANGLE_BRACKET);
204        }
205
206        if (after < line.length) {
207            // Check if the last Generic, in which case must be a whitespace
208            // or a '(),[.'.
209            if (depth == 1) {
210                processSingleGeneric(ast, line, after);
211            }
212            else {
213                processNestedGenerics(ast, line, after);
214            }
215        }
216    }
217
218    /**
219     * Process Nested generics.
220     *
221     * @param ast token
222     * @param line unicode code points array of line
223     * @param after position after
224     */
225    private void processNestedGenerics(DetailAST ast, int[] line, int after) {
226        // In a nested Generic type, so can only be a '>' or ',' or '&'
227
228        // In case of several extends definitions:
229        //
230        //   class IntEnumValueType<E extends Enum<E> & IntEnum>
231        //                                          ^
232        //   should be whitespace if followed by & -+
233        //
234        final int indexOfAmp = IntStream.range(after, line.length)
235                .filter(index -> line[index] == '&')
236                .findFirst()
237                .orElse(-1);
238        if (indexOfAmp >= 1
239            && containsWhitespaceBetween(after, indexOfAmp, line)) {
240            if (indexOfAmp - after == 0) {
241                log(ast, MSG_WS_NOT_PRECEDED, "&");
242            }
243            else if (indexOfAmp - after != 1) {
244                log(ast, MSG_WS_FOLLOWED, CLOSE_ANGLE_BRACKET);
245            }
246        }
247        else if (line[after] == ' ') {
248            log(ast, MSG_WS_FOLLOWED, CLOSE_ANGLE_BRACKET);
249        }
250    }
251
252    /**
253     * Process Single-generic.
254     *
255     * @param ast token
256     * @param line unicode code points array of line
257     * @param after position after
258     */
259    private void processSingleGeneric(DetailAST ast, int[] line, int after) {
260        final char charAfter = Character.toChars(line[after])[0];
261        if (isGenericBeforeMethod(ast) || isGenericBeforeCtor(ast)) {
262            if (Character.isWhitespace(charAfter)) {
263                log(ast, MSG_WS_FOLLOWED, CLOSE_ANGLE_BRACKET);
264            }
265        }
266        else if (!isCharacterValidAfterGenericEnd(charAfter)) {
267            log(ast, MSG_WS_ILLEGAL_FOLLOW, CLOSE_ANGLE_BRACKET);
268        }
269    }
270
271    /**
272     * Checks if generic is before constructor invocation.
273     *
274     * @param ast ast
275     * @return true if generic before a constructor invocation
276     */
277    private static boolean isGenericBeforeCtor(DetailAST ast) {
278        final DetailAST parent = ast.getParent();
279        return parent.getParent().getType() == TokenTypes.LITERAL_NEW
280                && (parent.getNextSibling().getType() == TokenTypes.IDENT
281                    || parent.getNextSibling().getType() == TokenTypes.DOT);
282    }
283
284    /**
285     * Is generic before method reference.
286     *
287     * @param ast ast
288     * @return true if generic before a method ref
289     */
290    private static boolean isGenericBeforeMethod(DetailAST ast) {
291        return ast.getParent().getParent().getParent().getType() == TokenTypes.METHOD_CALL
292                || isAfterMethodReference(ast);
293    }
294
295    /**
296     * Checks if current generic end ('&gt;') is located after
297     * {@link TokenTypes#METHOD_REF method reference operator}.
298     *
299     * @param genericEnd {@link TokenTypes#GENERIC_END}
300     * @return true if '&gt;' follows after method reference.
301     */
302    private static boolean isAfterMethodReference(DetailAST genericEnd) {
303        return genericEnd.getParent().getParent().getType() == TokenTypes.METHOD_REF;
304    }
305
306    /**
307     * Checks the token for the start of Generics.
308     *
309     * @param ast the token to check
310     */
311    private void processStart(DetailAST ast) {
312        final int[] line = getLineCodePoints(ast.getLineNo() - 1);
313        final int before = ast.getColumnNo() - 1;
314        final int after = ast.getColumnNo() + 1;
315
316        // Need to handle two cases as in:
317        //
318        //   public static <T> Callable<T> callable(Runnable task, T result)
319        //                 ^           ^
320        //      ws reqd ---+           +--- whitespace NOT required
321        //
322        if (before >= 0) {
323            // Detect if the first case
324            final DetailAST parent = ast.getParent();
325            final DetailAST grandparent = parent.getParent();
326            if (grandparent.getType() == TokenTypes.CTOR_DEF
327                    || grandparent.getType() == TokenTypes.METHOD_DEF
328                    || isGenericBeforeCtor(ast)) {
329                // Require whitespace
330                if (!CommonUtil.isCodePointWhitespace(line, before)) {
331                    log(ast, MSG_WS_NOT_PRECEDED, OPEN_ANGLE_BRACKET);
332                }
333            }
334            // Whitespace not required
335            else if (CommonUtil.isCodePointWhitespace(line, before)
336                && !containsWhitespaceBefore(before, line)) {
337                log(ast, MSG_WS_PRECEDED, OPEN_ANGLE_BRACKET);
338            }
339        }
340
341        if (after < line.length
342                && CommonUtil.isCodePointWhitespace(line, after)) {
343            log(ast, MSG_WS_FOLLOWED, OPEN_ANGLE_BRACKET);
344        }
345    }
346
347    /**
348     * Returns whether the specified string contains only whitespace between
349     * specified indices.
350     *
351     * @param fromIndex the index to start the search from. Inclusive
352     * @param toIndex the index to finish the search. Exclusive
353     * @param line the unicode code points array of line to check
354     * @return whether there are only whitespaces (or nothing)
355     */
356    private static boolean containsWhitespaceBetween(int fromIndex, int toIndex, int... line) {
357        boolean result = true;
358        for (int i = fromIndex; i < toIndex; i++) {
359            if (!CommonUtil.isCodePointWhitespace(line, i)) {
360                result = false;
361                break;
362            }
363        }
364        return result;
365    }
366
367    /**
368     * Returns whether the specified string contains only whitespace up to specified index.
369     *
370     * @param before the index to finish the search. Exclusive
371     * @param line   the unicode code points array of line to check
372     * @return {@code true} if there are only whitespaces,
373     *     false if there is nothing before or some other characters
374     */
375    private static boolean containsWhitespaceBefore(int before, int... line) {
376        return before != 0 && CodePointUtil.hasWhitespaceBefore(before, line);
377    }
378
379    /**
380     * Checks whether given character is valid to be right after generic ends.
381     *
382     * @param charAfter character to check
383     * @return checks if given character is valid
384     */
385    private static boolean isCharacterValidAfterGenericEnd(char charAfter) {
386        return charAfter == '(' || charAfter == ')'
387            || charAfter == ',' || charAfter == '['
388            || charAfter == '.' || charAfter == ':'
389            || charAfter == ';'
390            || Character.isWhitespace(charAfter);
391    }
392
393}