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