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.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     * A key is pointing to the warning message text in "messages.properties"
166     * file.
167     */
168    public static final String MSG_KEY_ANNOTATION_LOCATION_ALONE = "annotation.location.alone";
169
170    /**
171     * A key is pointing to the warning message text in "messages.properties"
172     * file.
173     */
174    public static final String MSG_KEY_ANNOTATION_LOCATION = "annotation.location";
175
176    /** Array of single line annotation parents. */
177    private static final int[] SINGLELINE_ANNOTATION_PARENTS = {TokenTypes.FOR_EACH_CLAUSE,
178                                                                TokenTypes.PARAMETER_DEF,
179                                                                TokenTypes.FOR_INIT, };
180
181    /**
182     * If true, it allows single prameterless annotation to be located on the same line as
183     * target element.
184     */
185    private boolean allowSamelineSingleParameterlessAnnotation = true;
186
187    /**
188     * If true, it allows parameterized annotation to be located on the same line as
189     * target element.
190     */
191    private boolean allowSamelineParameterizedAnnotation;
192
193    /**
194     * If true, it allows annotation to be located on the same line as
195     * target element.
196     */
197    private boolean allowSamelineMultipleAnnotations;
198
199    /**
200     * Sets if allow same line single parameterless annotation.
201     * @param allow User's value of allowSamelineSingleParameterlessAnnotation.
202     */
203    public final void setAllowSamelineSingleParameterlessAnnotation(boolean allow) {
204        allowSamelineSingleParameterlessAnnotation = allow;
205    }
206
207    /**
208     * Sets if allow parameterized annotation to be located on the same line as
209     * target element.
210     * @param allow User's value of allowSamelineParameterizedAnnotation.
211     */
212    public final void setAllowSamelineParameterizedAnnotation(boolean allow) {
213        allowSamelineParameterizedAnnotation = allow;
214    }
215
216    /**
217     * Sets if allow annotation to be located on the same line as
218     * target element.
219     * @param allow User's value of allowSamelineMultipleAnnotations.
220     */
221    public final void setAllowSamelineMultipleAnnotations(boolean allow) {
222        allowSamelineMultipleAnnotations = allow;
223    }
224
225    @Override
226    public int[] getDefaultTokens() {
227        return new int[] {
228            TokenTypes.CLASS_DEF,
229            TokenTypes.INTERFACE_DEF,
230            TokenTypes.ENUM_DEF,
231            TokenTypes.METHOD_DEF,
232            TokenTypes.CTOR_DEF,
233            TokenTypes.VARIABLE_DEF,
234        };
235    }
236
237    @Override
238    public int[] getAcceptableTokens() {
239        return new int[] {
240            TokenTypes.CLASS_DEF,
241            TokenTypes.INTERFACE_DEF,
242            TokenTypes.ENUM_DEF,
243            TokenTypes.METHOD_DEF,
244            TokenTypes.CTOR_DEF,
245            TokenTypes.VARIABLE_DEF,
246            TokenTypes.PARAMETER_DEF,
247            TokenTypes.ANNOTATION_DEF,
248            TokenTypes.TYPECAST,
249            TokenTypes.LITERAL_THROWS,
250            TokenTypes.IMPLEMENTS_CLAUSE,
251            TokenTypes.TYPE_ARGUMENT,
252            TokenTypes.LITERAL_NEW,
253            TokenTypes.DOT,
254            TokenTypes.ANNOTATION_FIELD_DEF,
255        };
256    }
257
258    @Override
259    public int[] getRequiredTokens() {
260        return CommonUtils.EMPTY_INT_ARRAY;
261    }
262
263    @Override
264    public void visitToken(DetailAST ast) {
265        final DetailAST modifiersNode = ast.findFirstToken(TokenTypes.MODIFIERS);
266
267        if (hasAnnotations(modifiersNode)) {
268            checkAnnotations(modifiersNode, getExpectedAnnotationIndentation(modifiersNode));
269        }
270    }
271
272    /**
273     * Checks whether a given modifier node has an annotation.
274     * @param modifierNode modifier node.
275     * @return true if the given modifier node has the annotation.
276     */
277    private static boolean hasAnnotations(DetailAST modifierNode) {
278        return modifierNode != null
279            && modifierNode.findFirstToken(TokenTypes.ANNOTATION) != null;
280    }
281
282    /**
283     * Returns an expected annotation indentation.
284     * The expected indentation should be the same as the indentation of the node
285     * which is the parent of the target modifier node.
286     * @param modifierNode modifier node.
287     * @return the annotation indentation.
288     */
289    private static int getExpectedAnnotationIndentation(DetailAST modifierNode) {
290        return modifierNode.getParent().getColumnNo();
291    }
292
293    /**
294     * Checks annotations positions in code:
295     * 1) Checks whether the annotations locations are correct.
296     * 2) Checks whether the annotations have the valid indentation level.
297     * @param modifierNode modifiers node.
298     * @param correctIndentation correct indentation of the annotation.
299     */
300    private void checkAnnotations(DetailAST modifierNode, int correctIndentation) {
301        DetailAST annotation = modifierNode.getFirstChild();
302
303        while (annotation != null && annotation.getType() == TokenTypes.ANNOTATION) {
304            final boolean hasParameters = isParameterized(annotation);
305
306            if (!isCorrectLocation(annotation, hasParameters)) {
307                log(annotation.getLineNo(),
308                        MSG_KEY_ANNOTATION_LOCATION_ALONE, getAnnotationName(annotation));
309            }
310            else if (annotation.getColumnNo() != correctIndentation && !hasNodeBefore(annotation)) {
311                log(annotation.getLineNo(), MSG_KEY_ANNOTATION_LOCATION,
312                    getAnnotationName(annotation), annotation.getColumnNo(), correctIndentation);
313            }
314            annotation = annotation.getNextSibling();
315        }
316    }
317
318    /**
319     * Checks whether an annotation has parameters.
320     * @param annotation annotation node.
321     * @return true if the annotation has parameters.
322     */
323    private static boolean isParameterized(DetailAST annotation) {
324        return annotation.findFirstToken(TokenTypes.EXPR) != null;
325    }
326
327    /**
328     * Returns the name of the given annotation.
329     * @param annotation annotation node.
330     * @return annotation name.
331     */
332    private static String getAnnotationName(DetailAST annotation) {
333        DetailAST identNode = annotation.findFirstToken(TokenTypes.IDENT);
334        if (identNode == null) {
335            identNode = annotation.findFirstToken(TokenTypes.DOT).findFirstToken(TokenTypes.IDENT);
336        }
337        return identNode.getText();
338    }
339
340    /**
341     * Checks whether an annotation has a correct location.
342     * Annotation location is considered correct
343     * if {@link AnnotationLocationCheck#allowSamelineMultipleAnnotations} is set to true.
344     * The method also:
345     * 1) checks parameterized annotation location considering
346     * the value of {@link AnnotationLocationCheck#allowSamelineParameterizedAnnotation};
347     * 2) checks parameterless annotation location considering
348     * the value of {@link AnnotationLocationCheck#allowSamelineSingleParameterlessAnnotation};
349     * 3) checks annotation location considering the elements
350     * of {@link AnnotationLocationCheck#SINGLELINE_ANNOTATION_PARENTS};
351     * @param annotation annotation node.
352     * @param hasParams whether an annotation has parameters.
353     * @return true if the annotation has a correct location.
354     */
355    private boolean isCorrectLocation(DetailAST annotation, boolean hasParams) {
356        final boolean allowingCondition;
357
358        if (hasParams) {
359            allowingCondition = allowSamelineParameterizedAnnotation;
360        }
361        else {
362            allowingCondition = allowSamelineSingleParameterlessAnnotation;
363        }
364        return allowSamelineMultipleAnnotations
365            || allowingCondition && !hasNodeBefore(annotation)
366            || !allowingCondition && (!hasNodeBeside(annotation)
367            || isAllowedPosition(annotation, SINGLELINE_ANNOTATION_PARENTS));
368    }
369
370    /**
371     * Checks whether an annotation node has any node before on the same line.
372     * @param annotation annotation node.
373     * @return true if an annotation node has any node before on the same line.
374     */
375    private static boolean hasNodeBefore(DetailAST annotation) {
376        final int annotationLineNo = annotation.getLineNo();
377        final DetailAST previousNode = annotation.getPreviousSibling();
378
379        return previousNode != null && annotationLineNo == previousNode.getLineNo();
380    }
381
382    /**
383     * Checks whether an annotation node has any node before or after on the same line.
384     * @param annotation annotation node.
385     * @return true if an annotation node has any node before or after on the same line.
386     */
387    private static boolean hasNodeBeside(DetailAST annotation) {
388        return hasNodeBefore(annotation) || hasNodeAfter(annotation);
389    }
390
391    /**
392     * Checks whether an annotation node has any node after on the same line.
393     * @param annotation annotation node.
394     * @return true if an annotation node has any node after on the same line.
395     */
396    private static boolean hasNodeAfter(DetailAST annotation) {
397        final int annotationLineNo = annotation.getLineNo();
398        DetailAST nextNode = annotation.getNextSibling();
399
400        if (nextNode == null) {
401            nextNode = annotation.getParent().getNextSibling();
402        }
403
404        return annotationLineNo == nextNode.getLineNo();
405    }
406
407    /**
408     * Checks whether position of annotation is allowed.
409     * @param annotation annotation token.
410     * @param allowedPositions an array of allowed annotation positions.
411     * @return true if position of annotation is allowed.
412     */
413    private static boolean isAllowedPosition(DetailAST annotation, int... allowedPositions) {
414        boolean allowed = false;
415        for (int position : allowedPositions) {
416            if (isInSpecificCodeBlock(annotation, position)) {
417                allowed = true;
418                break;
419            }
420        }
421        return allowed;
422    }
423
424    /**
425     * Checks whether the scope of a node is restricted to a specific code block.
426     * @param node node.
427     * @param blockType block type.
428     * @return true if the scope of a node is restricted to a specific code block.
429     */
430    private static boolean isInSpecificCodeBlock(DetailAST node, int blockType) {
431        boolean returnValue = false;
432        for (DetailAST token = node.getParent(); token != null; token = token.getParent()) {
433            final int type = token.getType();
434            if (type == blockType) {
435                returnValue = true;
436                break;
437            }
438        }
439        return returnValue;
440    }
441}