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