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