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