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 *
092 * @since 5.0
093 */
094@FileStatefulCheck
095public class GenericWhitespaceCheck extends AbstractCheck {
096
097    /**
098     * A key is pointing to the warning message text in "messages.properties"
099     * file.
100     */
101    public static final String MSG_WS_PRECEDED = "ws.preceded";
102
103    /**
104     * A key is pointing to the warning message text in "messages.properties"
105     * file.
106     */
107    public static final String MSG_WS_FOLLOWED = "ws.followed";
108
109    /**
110     * A key is pointing to the warning message text in "messages.properties"
111     * file.
112     */
113    public static final String MSG_WS_NOT_PRECEDED = "ws.notPreceded";
114
115    /**
116     * A key is pointing to the warning message text in "messages.properties"
117     * file.
118     */
119    public static final String MSG_WS_ILLEGAL_FOLLOW = "ws.illegalFollow";
120
121    /** Open angle bracket literal. */
122    private static final String OPEN_ANGLE_BRACKET = "<";
123
124    /** Close angle bracket literal. */
125    private static final String CLOSE_ANGLE_BRACKET = ">";
126
127    /** Used to count the depth of a Generic expression. */
128    private int depth;
129
130    @Override
131    public int[] getDefaultTokens() {
132        return getRequiredTokens();
133    }
134
135    @Override
136    public int[] getAcceptableTokens() {
137        return getRequiredTokens();
138    }
139
140    @Override
141    public int[] getRequiredTokens() {
142        return new int[] {TokenTypes.GENERIC_START, TokenTypes.GENERIC_END};
143    }
144
145    @Override
146    public void beginTree(DetailAST rootAST) {
147        // Reset for each tree, just increase there are violations in preceding
148        // trees.
149        depth = 0;
150    }
151
152    @Override
153    public void visitToken(DetailAST ast) {
154        switch (ast.getType()) {
155            case TokenTypes.GENERIC_START:
156                processStart(ast);
157                depth++;
158                break;
159            case TokenTypes.GENERIC_END:
160                processEnd(ast);
161                depth--;
162                break;
163            default:
164                throw new IllegalArgumentException("Unknown type " + ast);
165        }
166    }
167
168    /**
169     * Checks the token for the end of Generics.
170     *
171     * @param ast the token to check
172     */
173    private void processEnd(DetailAST ast) {
174        final String line = getLine(ast.getLineNo() - 1);
175        final int before = ast.getColumnNo() - 1;
176        final int after = ast.getColumnNo() + 1;
177
178        if (before >= 0 && Character.isWhitespace(line.charAt(before))
179                && !containsWhitespaceBefore(before, line)) {
180            log(ast, MSG_WS_PRECEDED, CLOSE_ANGLE_BRACKET);
181        }
182
183        if (after < line.length()) {
184            // Check if the last Generic, in which case must be a whitespace
185            // or a '(),[.'.
186            if (depth == 1) {
187                processSingleGeneric(ast, line, after);
188            }
189            else {
190                processNestedGenerics(ast, line, after);
191            }
192        }
193    }
194
195    /**
196     * Process Nested generics.
197     *
198     * @param ast token
199     * @param line line content
200     * @param after position after
201     */
202    private void processNestedGenerics(DetailAST ast, String line, int after) {
203        // In a nested Generic type, so can only be a '>' or ',' or '&'
204
205        // In case of several extends definitions:
206        //
207        //   class IntEnumValueType<E extends Enum<E> & IntEnum>
208        //                                          ^
209        //   should be whitespace if followed by & -+
210        //
211        final int indexOfAmp = line.indexOf('&', after);
212        if (indexOfAmp >= 1
213            && containsWhitespaceBetween(after, indexOfAmp, line)) {
214            if (indexOfAmp - after == 0) {
215                log(ast, MSG_WS_NOT_PRECEDED, "&");
216            }
217            else if (indexOfAmp - after != 1) {
218                log(ast, MSG_WS_FOLLOWED, CLOSE_ANGLE_BRACKET);
219            }
220        }
221        else if (line.charAt(after) == ' ') {
222            log(ast, MSG_WS_FOLLOWED, CLOSE_ANGLE_BRACKET);
223        }
224    }
225
226    /**
227     * Process Single-generic.
228     *
229     * @param ast token
230     * @param line line content
231     * @param after position after
232     */
233    private void processSingleGeneric(DetailAST ast, String line, int after) {
234        final char charAfter = line.charAt(after);
235        if (isGenericBeforeMethod(ast) || isGenericBeforeCtor(ast)) {
236            if (Character.isWhitespace(charAfter)) {
237                log(ast, MSG_WS_FOLLOWED, CLOSE_ANGLE_BRACKET);
238            }
239        }
240        else if (!isCharacterValidAfterGenericEnd(charAfter)) {
241            log(ast, MSG_WS_ILLEGAL_FOLLOW, CLOSE_ANGLE_BRACKET);
242        }
243    }
244
245    /**
246     * Checks if generic is before constructor invocation.
247     *
248     * @param ast ast
249     * @return true if generic before a constructor invocation
250     */
251    private static boolean isGenericBeforeCtor(DetailAST ast) {
252        final DetailAST parent = ast.getParent();
253        return parent.getParent().getType() == TokenTypes.LITERAL_NEW
254                && (parent.getNextSibling().getType() == TokenTypes.IDENT
255                    || parent.getNextSibling().getType() == TokenTypes.DOT);
256    }
257
258    /**
259     * Is generic before method reference.
260     *
261     * @param ast ast
262     * @return true if generic before a method ref
263     */
264    private static boolean isGenericBeforeMethod(DetailAST ast) {
265        return ast.getParent().getParent().getType() == TokenTypes.DOT
266                && ast.getParent().getParent().getParent().getType() == TokenTypes.METHOD_CALL
267                || isAfterMethodReference(ast);
268    }
269
270    /**
271     * Checks if current generic end ('>') is located after
272     * {@link TokenTypes#METHOD_REF method reference operator}.
273     *
274     * @param genericEnd {@link TokenTypes#GENERIC_END}
275     * @return true if '>' follows after method reference.
276     */
277    private static boolean isAfterMethodReference(DetailAST genericEnd) {
278        return genericEnd.getParent().getParent().getType() == TokenTypes.METHOD_REF;
279    }
280
281    /**
282     * Checks the token for the start of Generics.
283     *
284     * @param ast the token to check
285     */
286    private void processStart(DetailAST ast) {
287        final String line = getLine(ast.getLineNo() - 1);
288        final int before = ast.getColumnNo() - 1;
289        final int after = ast.getColumnNo() + 1;
290
291        // Need to handle two cases as in:
292        //
293        //   public static <T> Callable<T> callable(Runnable task, T result)
294        //                 ^           ^
295        //      ws reqd ---+           +--- whitespace NOT required
296        //
297        if (before >= 0) {
298            // Detect if the first case
299            final DetailAST parent = ast.getParent();
300            final DetailAST grandparent = parent.getParent();
301            if (grandparent.getType() == TokenTypes.CTOR_DEF
302                    || grandparent.getType() == TokenTypes.METHOD_DEF
303                    || isGenericBeforeCtor(ast)) {
304                // Require whitespace
305                if (!Character.isWhitespace(line.charAt(before))) {
306                    log(ast, MSG_WS_NOT_PRECEDED, OPEN_ANGLE_BRACKET);
307                }
308            }
309            // Whitespace not required
310            else if (Character.isWhitespace(line.charAt(before))
311                && !containsWhitespaceBefore(before, line)) {
312                log(ast, MSG_WS_PRECEDED, OPEN_ANGLE_BRACKET);
313            }
314        }
315
316        if (after < line.length()
317                && Character.isWhitespace(line.charAt(after))) {
318            log(ast, MSG_WS_FOLLOWED, OPEN_ANGLE_BRACKET);
319        }
320    }
321
322    /**
323     * Returns whether the specified string contains only whitespace between
324     * specified indices.
325     *
326     * @param fromIndex the index to start the search from. Inclusive
327     * @param toIndex the index to finish the search. Exclusive
328     * @param line the line to check
329     * @return whether there are only whitespaces (or nothing)
330     */
331    private static boolean containsWhitespaceBetween(int fromIndex, int toIndex, String line) {
332        boolean result = true;
333        for (int i = fromIndex; i < toIndex; i++) {
334            if (!Character.isWhitespace(line.charAt(i))) {
335                result = false;
336                break;
337            }
338        }
339        return result;
340    }
341
342    /**
343     * Returns whether the specified string contains only whitespace up to specified index.
344     *
345     * @param before the index to start the search from. Inclusive
346     * @param line   the index to finish the search. Exclusive
347     * @return {@code true} if there are only whitespaces,
348     *     false if there is nothing before or some other characters
349     */
350    private static boolean containsWhitespaceBefore(int before, String line) {
351        return before != 0 && CommonUtil.hasWhitespaceBefore(before, line);
352    }
353
354    /**
355     * Checks whether given character is valid to be right after generic ends.
356     *
357     * @param charAfter character to check
358     * @return checks if given character is valid
359     */
360    private static boolean isCharacterValidAfterGenericEnd(char charAfter) {
361        return charAfter == '(' || charAfter == ')'
362            || charAfter == ',' || charAfter == '['
363            || charAfter == '.' || charAfter == ':'
364            || charAfter == ';'
365            || Character.isWhitespace(charAfter);
366    }
367
368}