001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2019 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.annotation;
021
022import com.puppycrawl.tools.checkstyle.StatelessCheck;
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;
027import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
028
029/**
030 * Check location of annotation on language elements.
031 * By default, Check enforce to locate annotations immediately after
032 * documentation block and before target element, annotation should be located
033 * on separate line from target element.
034 * <p>
035 * Attention: Annotations among modifiers are ignored (looks like false-negative)
036 * as there might be a problem with annotations for return types.
037 * </p>
038 * <pre>public @Nullable Long getStartTimeOrNull() { ... }</pre>.
039 * <p>
040 * Such annotations are better to keep close to type.
041 * Due to limitations, Checkstyle can not examine the target of an annotation.
042 * </p>
043 *
044 * <p>
045 * Example:
046 * </p>
047 *
048 * <pre>
049 * &#64;Override
050 * &#64;Nullable
051 * public String getNameIfPresent() { ... }
052 * </pre>
053 *
054 * <p>
055 * The check has the following options:
056 * </p>
057 * <ul>
058 * <li>allowSamelineMultipleAnnotations - to allow annotation to be located on
059 * the same line as the target element. Default value is false.
060 * </li>
061 *
062 * <li>
063 * allowSamelineSingleParameterlessAnnotation - to allow single parameterless
064 * annotation to be located on the same line as the target element. Default value is false.
065 * </li>
066 *
067 * <li>
068 * allowSamelineParameterizedAnnotation - to allow parameterized annotation
069 * to be located on the same line as the target element. Default value is false.
070 * </li>
071 * </ul>
072 * <br>
073 * <p>
074 * Example to allow single parameterless annotation on the same line:
075 * </p>
076 * <pre>
077 * &#64;Override public int hashCode() { ... }
078 * </pre>
079 *
080 * <p>Use the following configuration:
081 * <pre>
082 * &lt;module name=&quot;AnnotationLocation&quot;&gt;
083 *    &lt;property name=&quot;allowSamelineMultipleAnnotations&quot; value=&quot;false&quot;/&gt;
084 *    &lt;property name=&quot;allowSamelineSingleParameterlessAnnotation&quot;
085 *    value=&quot;true&quot;/&gt;
086 *    &lt;property name=&quot;allowSamelineParameterizedAnnotation&quot; value=&quot;false&quot;
087 *    /&gt;
088 * &lt;/module&gt;
089 * </pre>
090 * <br>
091 * <p>
092 * Example to allow multiple parameterized annotations on the same line:
093 * </p>
094 * <pre>
095 * &#64;SuppressWarnings("deprecation") &#64;Mock DataLoader loader;
096 * </pre>
097 *
098 * <p>Use the following configuration:
099 * <pre>
100 * &lt;module name=&quot;AnnotationLocation&quot;&gt;
101 *    &lt;property name=&quot;allowSamelineMultipleAnnotations&quot; value=&quot;true&quot;/&gt;
102 *    &lt;property name=&quot;allowSamelineSingleParameterlessAnnotation&quot;
103 *    value=&quot;true&quot;/&gt;
104 *    &lt;property name=&quot;allowSamelineParameterizedAnnotation&quot; value=&quot;true&quot;
105 *    /&gt;
106 * &lt;/module&gt;
107 * </pre>
108 * <br>
109 * <p>
110 * Example to allow multiple parameterless annotations on the same line:
111 * </p>
112 * <pre>
113 * &#64;Partial &#64;Mock DataLoader loader;
114 * </pre>
115 *
116 * <p>Use the following configuration:
117 * <pre>
118 * &lt;module name=&quot;AnnotationLocation&quot;&gt;
119 *    &lt;property name=&quot;allowSamelineMultipleAnnotations&quot; value=&quot;true&quot;/&gt;
120 *    &lt;property name=&quot;allowSamelineSingleParameterlessAnnotation&quot;
121 *    value=&quot;true&quot;/&gt;
122 *    &lt;property name=&quot;allowSamelineParameterizedAnnotation&quot; value=&quot;false&quot;
123 *    /&gt;
124 * &lt;/module&gt;
125 * </pre>
126 * <br>
127 * <p>
128 * The following example demonstrates how the check validates annotation of method parameters,
129 * catch parameters, foreach, for-loop variable definitions.
130 * </p>
131 *
132 * <p>Configuration:
133 * <pre>
134 * &lt;module name=&quot;AnnotationLocation&quot;&gt;
135 *    &lt;property name=&quot;allowSamelineMultipleAnnotations&quot; value=&quot;false&quot;/&gt;
136 *    &lt;property name=&quot;allowSamelineSingleParameterlessAnnotation&quot;
137 *    value=&quot;false&quot;/&gt;
138 *    &lt;property name=&quot;allowSamelineParameterizedAnnotation&quot; value=&quot;false&quot;
139 *    /&gt;
140 *    &lt;property name=&quot;tokens&quot; value=&quot;VARIABLE_DEF, PARAMETER_DEF&quot;/&gt;
141 * &lt;/module&gt;
142 * </pre>
143 *
144 * <p>Code example
145 * {@code
146 * ...
147 * public void test(&#64;MyAnnotation String s) { // OK
148 *   ...
149 *   for (&#64;MyAnnotation char c : s.toCharArray()) { ... }  // OK
150 *   ...
151 *   try { ... }
152 *   catch (&#64;MyAnnotation Exception ex) { ... } // OK
153 *   ...
154 *   for (&#64;MyAnnotation int i = 0; i &lt; 10; i++) { ... } // OK
155 *   ...
156 *   MathOperation c = (&#64;MyAnnotation int a, &#64;MyAnnotation int b) -&gt; a + b; // OK
157 *   ...
158 * }
159 * }
160 *
161 */
162@StatelessCheck
163public class AnnotationLocationCheck extends AbstractCheck {
164
165    /**
166     * A key is pointing to the warning message text in "messages.properties"
167     * file.
168     */
169    public static final String MSG_KEY_ANNOTATION_LOCATION_ALONE = "annotation.location.alone";
170
171    /**
172     * A key is pointing to the warning message text in "messages.properties"
173     * file.
174     */
175    public static final String MSG_KEY_ANNOTATION_LOCATION = "annotation.location";
176
177    /** Array of single line annotation parents. */
178    private static final int[] SINGLELINE_ANNOTATION_PARENTS = {TokenTypes.FOR_EACH_CLAUSE,
179                                                                TokenTypes.PARAMETER_DEF,
180                                                                TokenTypes.FOR_INIT, };
181
182    /**
183     * If true, it allows single prameterless annotation to be located on the same line as
184     * target element.
185     */
186    private boolean allowSamelineSingleParameterlessAnnotation = true;
187
188    /**
189     * If true, it allows parameterized annotation to be located on the same line as
190     * target element.
191     */
192    private boolean allowSamelineParameterizedAnnotation;
193
194    /**
195     * If true, it allows annotation to be located on the same line as
196     * target element.
197     */
198    private boolean allowSamelineMultipleAnnotations;
199
200    /**
201     * Sets if allow same line single parameterless annotation.
202     * @param allow User's value of allowSamelineSingleParameterlessAnnotation.
203     */
204    public final void setAllowSamelineSingleParameterlessAnnotation(boolean allow) {
205        allowSamelineSingleParameterlessAnnotation = allow;
206    }
207
208    /**
209     * Sets if allow parameterized annotation to be located on the same line as
210     * target element.
211     * @param allow User's value of allowSamelineParameterizedAnnotation.
212     */
213    public final void setAllowSamelineParameterizedAnnotation(boolean allow) {
214        allowSamelineParameterizedAnnotation = allow;
215    }
216
217    /**
218     * Sets if allow annotation to be located on the same line as
219     * target element.
220     * @param allow User's value of allowSamelineMultipleAnnotations.
221     */
222    public final void setAllowSamelineMultipleAnnotations(boolean allow) {
223        allowSamelineMultipleAnnotations = allow;
224    }
225
226    @Override
227    public int[] getDefaultTokens() {
228        return new int[] {
229            TokenTypes.CLASS_DEF,
230            TokenTypes.INTERFACE_DEF,
231            TokenTypes.ENUM_DEF,
232            TokenTypes.METHOD_DEF,
233            TokenTypes.CTOR_DEF,
234            TokenTypes.VARIABLE_DEF,
235        };
236    }
237
238    @Override
239    public int[] getAcceptableTokens() {
240        return new int[] {
241            TokenTypes.CLASS_DEF,
242            TokenTypes.INTERFACE_DEF,
243            TokenTypes.ENUM_DEF,
244            TokenTypes.METHOD_DEF,
245            TokenTypes.CTOR_DEF,
246            TokenTypes.VARIABLE_DEF,
247            TokenTypes.PARAMETER_DEF,
248            TokenTypes.ANNOTATION_DEF,
249            TokenTypes.ANNOTATION_FIELD_DEF,
250        };
251    }
252
253    @Override
254    public int[] getRequiredTokens() {
255        return CommonUtil.EMPTY_INT_ARRAY;
256    }
257
258    @Override
259    public void visitToken(DetailAST ast) {
260        final DetailAST modifiersNode = ast.findFirstToken(TokenTypes.MODIFIERS);
261        checkAnnotations(modifiersNode, getExpectedAnnotationIndentation(modifiersNode));
262    }
263
264    /**
265     * Returns an expected annotation indentation.
266     * The expected indentation should be the same as the indentation of the node
267     * which is the parent of the target modifier node.
268     * @param modifierNode modifier node.
269     * @return the annotation indentation.
270     */
271    private static int getExpectedAnnotationIndentation(DetailAST modifierNode) {
272        return modifierNode.getParent().getColumnNo();
273    }
274
275    /**
276     * Checks annotations positions in code:
277     * 1) Checks whether the annotations locations are correct.
278     * 2) Checks whether the annotations have the valid indentation level.
279     * @param modifierNode modifiers node.
280     * @param correctIndentation correct indentation of the annotation.
281     */
282    private void checkAnnotations(DetailAST modifierNode, int correctIndentation) {
283        DetailAST annotation = modifierNode.getFirstChild();
284
285        while (annotation != null && annotation.getType() == TokenTypes.ANNOTATION) {
286            final boolean hasParameters = isParameterized(annotation);
287
288            if (!isCorrectLocation(annotation, hasParameters)) {
289                log(annotation.getLineNo(),
290                        MSG_KEY_ANNOTATION_LOCATION_ALONE, getAnnotationName(annotation));
291            }
292            else if (annotation.getColumnNo() != correctIndentation && !hasNodeBefore(annotation)) {
293                log(annotation.getLineNo(), MSG_KEY_ANNOTATION_LOCATION,
294                    getAnnotationName(annotation), annotation.getColumnNo(), correctIndentation);
295            }
296            annotation = annotation.getNextSibling();
297        }
298    }
299
300    /**
301     * Checks whether an annotation has parameters.
302     * @param annotation annotation node.
303     * @return true if the annotation has parameters.
304     */
305    private static boolean isParameterized(DetailAST annotation) {
306        return TokenUtil.findFirstTokenByPredicate(annotation, ast -> {
307            return ast.getType() == TokenTypes.EXPR
308                || ast.getType() == TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR;
309        }).isPresent();
310    }
311
312    /**
313     * Returns the name of the given annotation.
314     * @param annotation annotation node.
315     * @return annotation name.
316     */
317    private static String getAnnotationName(DetailAST annotation) {
318        DetailAST identNode = annotation.findFirstToken(TokenTypes.IDENT);
319        if (identNode == null) {
320            identNode = annotation.findFirstToken(TokenTypes.DOT).findFirstToken(TokenTypes.IDENT);
321        }
322        return identNode.getText();
323    }
324
325    /**
326     * Checks whether an annotation has a correct location.
327     * Annotation location is considered correct
328     * if {@link AnnotationLocationCheck#allowSamelineMultipleAnnotations} is set to true.
329     * The method also:
330     * 1) checks parameterized annotation location considering
331     * the value of {@link AnnotationLocationCheck#allowSamelineParameterizedAnnotation};
332     * 2) checks parameterless annotation location considering
333     * the value of {@link AnnotationLocationCheck#allowSamelineSingleParameterlessAnnotation};
334     * 3) checks annotation location considering the elements
335     * of {@link AnnotationLocationCheck#SINGLELINE_ANNOTATION_PARENTS};
336     * @param annotation annotation node.
337     * @param hasParams whether an annotation has parameters.
338     * @return true if the annotation has a correct location.
339     */
340    private boolean isCorrectLocation(DetailAST annotation, boolean hasParams) {
341        final boolean allowingCondition;
342
343        if (hasParams) {
344            allowingCondition = allowSamelineParameterizedAnnotation;
345        }
346        else {
347            allowingCondition = allowSamelineSingleParameterlessAnnotation;
348        }
349        return allowSamelineMultipleAnnotations
350            || allowingCondition && !hasNodeBefore(annotation)
351            || !allowingCondition && (!hasNodeBeside(annotation)
352            || isAllowedPosition(annotation, SINGLELINE_ANNOTATION_PARENTS));
353    }
354
355    /**
356     * Checks whether an annotation node has any node before on the same line.
357     * @param annotation annotation node.
358     * @return true if an annotation node has any node before on the same line.
359     */
360    private static boolean hasNodeBefore(DetailAST annotation) {
361        final int annotationLineNo = annotation.getLineNo();
362        final DetailAST previousNode = annotation.getPreviousSibling();
363
364        return previousNode != null && annotationLineNo == previousNode.getLineNo();
365    }
366
367    /**
368     * Checks whether an annotation node has any node before or after on the same line.
369     * @param annotation annotation node.
370     * @return true if an annotation node has any node before or after on the same line.
371     */
372    private static boolean hasNodeBeside(DetailAST annotation) {
373        return hasNodeBefore(annotation) || hasNodeAfter(annotation);
374    }
375
376    /**
377     * Checks whether an annotation node has any node after on the same line.
378     * @param annotation annotation node.
379     * @return true if an annotation node has any node after on the same line.
380     */
381    private static boolean hasNodeAfter(DetailAST annotation) {
382        final int annotationLineNo = annotation.getLineNo();
383        DetailAST nextNode = annotation.getNextSibling();
384
385        if (nextNode == null) {
386            nextNode = annotation.getParent().getNextSibling();
387        }
388
389        return annotationLineNo == nextNode.getLineNo();
390    }
391
392    /**
393     * Checks whether position of annotation is allowed.
394     * @param annotation annotation token.
395     * @param allowedPositions an array of allowed annotation positions.
396     * @return true if position of annotation is allowed.
397     */
398    private static boolean isAllowedPosition(DetailAST annotation, int... allowedPositions) {
399        boolean allowed = false;
400        for (int position : allowedPositions) {
401            if (isInSpecificCodeBlock(annotation, position)) {
402                allowed = true;
403                break;
404            }
405        }
406        return allowed;
407    }
408
409    /**
410     * Checks whether the scope of a node is restricted to a specific code block.
411     * @param node node.
412     * @param blockType block type.
413     * @return true if the scope of a node is restricted to a specific code block.
414     */
415    private static boolean isInSpecificCodeBlock(DetailAST node, int blockType) {
416        boolean returnValue = false;
417        for (DetailAST token = node.getParent(); token != null; token = token.getParent()) {
418            final int type = token.getType();
419            if (type == blockType) {
420                returnValue = true;
421                break;
422            }
423        }
424        return returnValue;
425    }
426
427}