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 examin target of 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 * Check have following options:
054 * </p>
055 * <ul>
056 * <li>allowSamelineMultipleAnnotations - to allow annotation to be located on
057 * the same line as 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 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 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 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 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 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, getAnnotationLevel(modifiersNode));
267        }
268    }
269
270    /**
271     * Some javadoc.
272     * @param modifierNode Some javadoc.
273     * @return Some javadoc.
274     */
275    private static boolean hasAnnotations(DetailAST modifierNode) {
276        return modifierNode != null && modifierNode.findFirstToken(TokenTypes.ANNOTATION) != null;
277    }
278
279    /**
280     * Some javadoc.
281     * @param modifierNode Some javadoc.
282     * @return Some javadoc.
283     */
284    private static int getAnnotationLevel(DetailAST modifierNode) {
285        return modifierNode.getParent().getColumnNo();
286    }
287
288    /**
289     * Some javadoc.
290     * @param modifierNode Some javadoc.
291     * @param correctLevel Some javadoc.
292     */
293    private void checkAnnotations(DetailAST modifierNode, int correctLevel) {
294        DetailAST annotation = modifierNode.getFirstChild();
295
296        while (annotation != null && annotation.getType() == TokenTypes.ANNOTATION) {
297            final boolean hasParameters = isParameterized(annotation);
298
299            if (!isCorrectLocation(annotation, hasParameters)) {
300                log(annotation.getLineNo(),
301                        MSG_KEY_ANNOTATION_LOCATION_ALONE, getAnnotationName(annotation));
302            }
303            else if (annotation.getColumnNo() != correctLevel && !hasNodeBefore(annotation)) {
304                log(annotation.getLineNo(), MSG_KEY_ANNOTATION_LOCATION,
305                    getAnnotationName(annotation), annotation.getColumnNo(), correctLevel);
306            }
307            annotation = annotation.getNextSibling();
308        }
309    }
310
311    /**
312     * Some javadoc.
313     * @param annotation Some javadoc.
314     * @return Some javadoc.
315     */
316    private static boolean isParameterized(DetailAST annotation) {
317        return annotation.findFirstToken(TokenTypes.EXPR) != null;
318    }
319
320    /**
321     * Some javadoc.
322     * @param annotation Some javadoc.
323     * @return Some javadoc.
324     */
325    private static String getAnnotationName(DetailAST annotation) {
326        DetailAST identNode = annotation.findFirstToken(TokenTypes.IDENT);
327        if (identNode == null) {
328            identNode = annotation.findFirstToken(TokenTypes.DOT).findFirstToken(TokenTypes.IDENT);
329        }
330        return identNode.getText();
331    }
332
333    /**
334     * Some javadoc.
335     * @param annotation Some javadoc.
336     * @param hasParams Some javadoc.
337     * @return Some javadoc.
338     */
339    private boolean isCorrectLocation(DetailAST annotation, boolean hasParams) {
340        final boolean allowingCondition;
341
342        if (hasParams) {
343            allowingCondition = allowSamelineParameterizedAnnotation;
344        }
345        else {
346            allowingCondition = allowSamelineSingleParameterlessAnnotation;
347        }
348        return allowSamelineMultipleAnnotations
349            || allowingCondition && !hasNodeBefore(annotation)
350            || !allowingCondition && (!hasNodeBeside(annotation)
351            || isAllowedPosition(annotation, SINGLELINE_ANNOTATION_PARENTS));
352    }
353
354    /**
355     * Some javadoc.
356     * @param annotation Some javadoc.
357     * @return Some javadoc.
358     */
359    private static boolean hasNodeBefore(DetailAST annotation) {
360        final int annotationLineNo = annotation.getLineNo();
361        final DetailAST previousNode = annotation.getPreviousSibling();
362
363        return previousNode != null && annotationLineNo == previousNode.getLineNo();
364    }
365
366    /**
367     * Some javadoc.
368     * @param annotation Some javadoc.
369     * @return Some javadoc.
370     */
371    private static boolean hasNodeBeside(DetailAST annotation) {
372        return hasNodeBefore(annotation) || hasNodeAfter(annotation);
373    }
374
375    /**
376     * Some javadoc.
377     * @param annotation Some javadoc.
378     * @return Some javadoc.
379     */
380    private static boolean hasNodeAfter(DetailAST annotation) {
381        final int annotationLineNo = annotation.getLineNo();
382        DetailAST nextNode = annotation.getNextSibling();
383
384        if (nextNode == null) {
385            nextNode = annotation.getParent().getNextSibling();
386        }
387
388        return annotationLineNo == nextNode.getLineNo();
389    }
390
391    /**
392     * Checks whether position of annotation is allowed.
393     * @param annotation annotation token.
394     * @param allowedPositions an array of allowed annotation positions.
395     * @return true if position of annotation is allowed.
396     */
397    public static boolean isAllowedPosition(DetailAST annotation, int... allowedPositions) {
398        boolean allowed = false;
399        for (int position : allowedPositions) {
400            if (isInSpecificCodeBlock(annotation, position)) {
401                allowed = true;
402                break;
403            }
404        }
405        return allowed;
406    }
407
408    /**
409     * Checks whether the scope of a node is restricted to a specific code block.
410     * @param node node.
411     * @param blockType block type.
412     * @return true if the scope of a node is restricted to a specific code block.
413     */
414    private static boolean isInSpecificCodeBlock(DetailAST node, int blockType) {
415        boolean returnValue = false;
416        for (DetailAST token = node.getParent(); token != null; token = token.getParent()) {
417            final int type = token.getType();
418            if (type == blockType) {
419                returnValue = true;
420                break;
421            }
422        }
423        return returnValue;
424    }
425}