001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2014  Oliver Burn
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////////////////////////////////////////////////////////////////////////////////
019package com.puppycrawl.tools.checkstyle.checks.annotation;
020
021import com.puppycrawl.tools.checkstyle.api.Check;
022import com.puppycrawl.tools.checkstyle.api.DetailAST;
023import com.puppycrawl.tools.checkstyle.api.TokenTypes;
024
025/**
026 * Check location of annotation on language elements.
027 * By default, Check enforce to locate annotations immediately after
028 * documentation block and before target element, annotation should be located
029 * on separate line from target element.
030 *
031 * <p>
032 * Example:
033 * </p>
034 *
035 * <pre>
036 * &#64;Override
037 * &#64;Nullable
038 * public String getNameIfPresent() { ... }
039 * </pre>
040 *
041 * <p>
042 * Check have following options:
043 * </p>
044 * <ul>
045 * <li>allowSamelineMultipleAnnotations - to allow annotation to be located on
046 * the same line as target element. Default value is false.
047 * </li>
048 *
049 * <li>
050 * allowSamelineSingleParameterlessAnnotation - to allow single parameterless
051 * annotation to be located on the same line as target element. Default value is false.
052 * </li>
053 *
054 * <li>
055 * allowSamelineParameterizedAnnotation - to allow parameterized annotation
056 * to be located on the same line as target element. Default value is false.
057 * </li>
058 * </ul>
059 * <br/>
060 * <p>
061 * Example to allow single parameterless annotation on the same line:
062 * </p>
063 * <pre>
064 * &#64;Override public int hashCode() { ... }
065 * </pre>
066 *
067 * <p>Use following configuration:
068 * <pre>
069 * &lt;module name=&quot;AnnotationLocation&quot;&gt;
070 *    &lt;property name=&quot;allowSamelineMultipleAnnotations&quot; value=&quot;false&quot;/&gt;
071 *    &lt;property name=&quot;allowSamelineSingleParameterlessAnnotation&quot;
072 *    value=&quot;true&quot;/&gt;
073 *    &lt;property name=&quot;allowSamelineParameterizedAnnotation&quot; value=&quot;false&quot;
074 *    /&gt;
075 * &lt;/module&gt;
076 * </pre>
077 * <br/>
078 * <p>
079 * Example to allow multiple parameterized annotations on the same line:
080 * </p>
081 * <pre>
082 * &#64;SuppressWarnings("deprecation") &#64;Mock DataLoader loader;
083 * </pre>
084 *
085 * <p>Use following configuration:
086 * <pre>
087 * &lt;module name=&quot;AnnotationLocation&quot;&gt;
088 *    &lt;property name=&quot;allowSamelineMultipleAnnotations&quot; value=&quot;true&quot;/&gt;
089 *    &lt;property name=&quot;allowSamelineSingleParameterlessAnnotation&quot;
090 *    value=&quot;true&quot;/&gt;
091 *    &lt;property name=&quot;allowSamelineParameterizedAnnotation&quot; value=&quot;true&quot;
092 *    /&gt;
093 * &lt;/module&gt;
094 * </pre>
095 * <br/>
096 * <p>
097 * Example to allow multiple parameterless annotations on the same line:
098 * </p>
099 * <pre>
100 * &#64;Partial &#64;Mock DataLoader loader;
101 * </pre>
102 *
103 * <p>Use following configuration:
104 * <pre>
105 * &lt;module name=&quot;AnnotationLocation&quot;&gt;
106 *    &lt;property name=&quot;allowSamelineMultipleAnnotations&quot; value=&quot;true&quot;/&gt;
107 *    &lt;property name=&quot;allowSamelineSingleParameterlessAnnotation&quot;
108 *    value=&quot;true&quot;/&gt;
109 *    &lt;property name=&quot;allowSamelineParameterizedAnnotation&quot; value=&quot;false&quot;
110 *    /&gt;
111 * &lt;/module&gt;
112 * </pre>
113 *
114 * @author maxvetrenko
115 */
116public class AnnotationLocationCheck extends Check
117{
118    /**
119     * A key is pointing to the warning message text in "messages.properties"
120     * file.
121     */
122    public static final String MSG_KEY_ANNOTATION_LOCATION_ALONE = "annotation.location.alone";
123
124    /**
125     * A key is pointing to the warning message text in "messages.properties"
126     * file.
127     */
128    public static final String MSG_KEY_ANNOTATION_LOCATION = "annotation.location";
129
130    /**
131     * Some javadoc.
132     */
133    private boolean allowSamelineSingleParameterlessAnnotation = true;
134
135    /**
136     * Some javadoc.
137     */
138    private boolean allowSamelineParameterizedAnnotation;
139
140    /**
141     * Some javadoc.
142     */
143    private boolean allowSamelineMultipleAnnotations;
144
145    /**
146     * Some javadoc.
147     * @param allow Some javadoc.
148     */
149    public final void setAllowSamelineSingleParameterlessAnnotation(boolean allow)
150    {
151        allowSamelineSingleParameterlessAnnotation = allow;
152    }
153
154    /**
155     * Some javadoc.
156     * @param allow Some javadoc.
157     */
158    public final void setAllowSamelineParametrizedAnnotation(boolean allow)
159    {
160        allowSamelineParameterizedAnnotation = allow;
161    }
162
163    /**
164     * Some javadoc.
165     * @param allow Some javadoc.
166     */
167    public final void setAllowSamelineMultipleAnnotations(boolean allow)
168    {
169        allowSamelineMultipleAnnotations = allow;
170    }
171
172    @Override
173    public int[] getDefaultTokens()
174    {
175        return new int[] {
176            TokenTypes.CLASS_DEF,
177            TokenTypes.INTERFACE_DEF,
178            TokenTypes.ENUM_DEF,
179            TokenTypes.METHOD_DEF,
180            TokenTypes.CTOR_DEF,
181            TokenTypes.VARIABLE_DEF,
182        };
183    }
184
185    @Override
186    public void visitToken(DetailAST ast)
187    {
188        final DetailAST modifiersNode = ast.findFirstToken(TokenTypes.MODIFIERS);
189
190        if (hasAnnotations(modifiersNode)) {
191            checkAnnotations(modifiersNode, getAnnotationLevel(modifiersNode));
192        }
193    }
194
195    /**
196     * Some javadoc.
197     * @param modifierNode Some javadoc.
198     * @param correctLevel Some javadoc.
199     */
200    private void checkAnnotations(DetailAST modifierNode, int correctLevel)
201    {
202        DetailAST annotation = modifierNode.getFirstChild();
203
204        while (annotation != null && annotation.getType() == TokenTypes.ANNOTATION) {
205            final boolean hasParameters = isParameterized(annotation);
206
207            if (!isCorrectLocation(annotation, hasParameters)) {
208                log(annotation.getLineNo(),
209                        MSG_KEY_ANNOTATION_LOCATION_ALONE, getAnnotationName(annotation));
210            }
211            else if (annotation.getColumnNo() != correctLevel && !hasNodeBefore(annotation)) {
212                log(annotation.getLineNo(), MSG_KEY_ANNOTATION_LOCATION,
213                    getAnnotationName(annotation), annotation.getColumnNo(), correctLevel);
214            }
215            annotation = annotation.getNextSibling();
216        }
217    }
218
219    /**
220     * Some javadoc.
221     * @param annotation Some javadoc.
222     * @param hasParams Some javadoc.
223     * @return Some javadoc.
224     */
225    private boolean isCorrectLocation(DetailAST annotation, boolean hasParams)
226    {
227        final boolean allowingCondition = hasParams ? allowSamelineParameterizedAnnotation
228            : allowSamelineSingleParameterlessAnnotation;
229        return allowingCondition && !hasNodeBefore(annotation)
230            || !allowingCondition && !hasNodeBeside(annotation)
231            || allowSamelineMultipleAnnotations;
232    }
233
234    /**
235     * Some javadoc.
236     * @param annotation Some javadoc.
237     * @return Some javadoc.
238     */
239    private static String getAnnotationName(DetailAST annotation)
240    {
241        DetailAST idenNode = annotation.findFirstToken(TokenTypes.IDENT);
242        if (idenNode == null) {
243            idenNode = annotation.findFirstToken(TokenTypes.DOT).findFirstToken(TokenTypes.IDENT);
244        }
245        return idenNode.getText();
246    }
247
248    /**
249     * Some javadoc.
250     * @param annotation Some javadoc.
251     * @return Some javadoc.
252     */
253    private static boolean hasNodeAfter(DetailAST annotation)
254    {
255        final int annotationLineNo = annotation.getLineNo();
256        DetailAST nextNode = annotation.getNextSibling();
257
258        if (nextNode == null) {
259            nextNode = annotation.getParent().getNextSibling();
260        }
261
262        return nextNode != null && annotationLineNo == nextNode.getLineNo();
263    }
264
265    /**
266     * Some javadoc.
267     * @param annotation Some javadoc.
268     * @return Some javadoc.
269     */
270    private static boolean hasNodeBefore(DetailAST annotation)
271    {
272        final int annotationLineNo = annotation.getLineNo();
273        final DetailAST previousNode = annotation.getPreviousSibling();
274
275        return previousNode != null && annotationLineNo == previousNode.getLineNo();
276    }
277
278    /**
279     * Some javadoc.
280     * @param annotation Some javadoc.
281     * @return Some javadoc.
282     */
283    private static boolean hasNodeBeside(DetailAST annotation)
284    {
285        return hasNodeBefore(annotation) || hasNodeAfter(annotation);
286    }
287
288    /**
289     * Some javadoc.
290     * @param modifierNode Some javadoc.
291     * @return Some javadoc.
292     */
293    private static int getAnnotationLevel(DetailAST modifierNode)
294    {
295        return modifierNode.getParent().getColumnNo();
296    }
297
298    /**
299     * Some javadoc.
300     * @param annotation Some javadoc.
301     * @return Some javadoc.
302     */
303    private static boolean isParameterized(DetailAST annotation)
304    {
305        return annotation.findFirstToken(TokenTypes.EXPR) != null;
306    }
307
308    /**
309     * Some javadoc.
310     * @param modifierNode Some javadoc.
311     * @return Some javadoc.
312     */
313    private static boolean hasAnnotations(DetailAST modifierNode)
314    {
315        return modifierNode.findFirstToken(TokenTypes.ANNOTATION) != null;
316    }
317}