• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * checkstyle: Checks Java source code for adherence to a set of rules.
3  * Copyright (C) 2001-2016 the original author or authors.
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  */
19 
20 package com.android.support.checkstyle;
21 
22 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
23 import com.puppycrawl.tools.checkstyle.api.DetailAST;
24 import com.puppycrawl.tools.checkstyle.api.TextBlock;
25 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
26 import com.puppycrawl.tools.checkstyle.utils.AnnotationUtility;
27 import com.puppycrawl.tools.checkstyle.utils.CommonUtils;
28 
29 import java.util.regex.Pattern;
30 
31 /**
32  * This class is used to verify that both an annotation and javadoc tag are
33  * present when either one is present.
34  * <p>
35  * Typically, both ways of flagging APIs serve their own purposes. Annotations
36  * are used for compilers and development tools, while javadoc tags are used
37  * for documentation.
38  * <p>
39  * In some cases, the presence of an annotations implies the presence of a
40  * javadoc tag (or vice versa). For example, in the case of the
41  * {@literal @}Deprecated annotation, the {@literal @}deprecated tag should
42  * also be present. In the case of the {@literal @}RestrictTo tag, the
43  * {@literal @}hide tag should also be present.
44  * <p>
45  * To configure this check, do the following:
46  * <pre>
47  *     &lt;module name="MismatchedAnnotationCheck"&gt;
48  *       &lt;property name="tag" value="hide" /&gt;
49  *       &lt;property name="annotation" value="android.support.annotation.RestrictTo" /&gt;
50  *       &lt;property name="messageKey" value="annotation.missing.hide" /&gt;
51  *       &lt;message key="annotation.missing.hide"
52  *                   value="Must include both {@literal @}RestrictTo annotation
53  *                          and {@literal @}hide Javadoc tag." /&gt;
54  *     &lt;/module&gt;
55  * </pre>
56  */
57 @SuppressWarnings("unused")
58 public final class MismatchedAnnotationCheck extends AbstractCheck {
59 
60     /** Key for the warning message text by check properties. */
61     private static final String MSG_KEY_JAVADOC_DUPLICATE_TAG = "javadoc.duplicateTag";
62 
63     /** Key for the warning message text by check properties. */
64     private static final String MSG_KEY_JAVADOC_MISSING = "javadoc.missing";
65 
66     /** Javadoc tag. */
67     private String mTag;
68 
69     /** Pattern for matching javadoc tag. */
70     private Pattern mMatchTag;
71 
72     /** Simple annotation name. */
73     private String mAnnotationSimpleName;
74 
75     /** Fully-qualified annotation name. */
76     private String mAnnotation;
77 
78     /** Key for the warning message text specified by check properties. */
79     private String mMessageKey;
80 
81     @Override
getDefaultTokens()82     public int[] getDefaultTokens() {
83         return getAcceptableTokens();
84     }
85 
86     /**
87      * Sets javadoc tag.
88      *
89      * @param tag javadoc tag to check
90      */
91     @SuppressWarnings("unused")
setTag(String tag)92     public void setTag(String tag) {
93         mTag = tag;
94 
95         // Tag may either have a description or be on a line by itself.
96         mMatchTag = CommonUtils.createPattern("@" + tag + "(?:\\s|$)");
97     }
98 
99     /**
100      * Sets annotation tag.
101      *
102      * @param annotation annotation to check
103      */
104     @SuppressWarnings("unused")
setAnnotation(String annotation)105     public void setAnnotation(String annotation) {
106         mAnnotation = annotation;
107 
108         // Extract the simple class name.
109         final int lastDollar = annotation.lastIndexOf('$');
110         final int lastSep = lastDollar >= 0 ? lastDollar : annotation.lastIndexOf('.');
111         mAnnotationSimpleName = annotation.substring(lastSep + 1);
112     }
113 
114 
115     /**
116      * Sets annotation tag.
117      *
118      * @param messageKey key to use for failed check message
119      */
120     @SuppressWarnings("unused")
setMessageKey(String messageKey)121     public void setMessageKey(String messageKey) {
122         mMessageKey = messageKey;
123     }
124 
125     @Override
getAcceptableTokens()126     public int[] getAcceptableTokens() {
127         return new int[] {
128                 TokenTypes.INTERFACE_DEF,
129                 TokenTypes.CLASS_DEF,
130                 TokenTypes.ANNOTATION_DEF,
131                 TokenTypes.ENUM_DEF,
132                 TokenTypes.METHOD_DEF,
133                 TokenTypes.CTOR_DEF,
134                 TokenTypes.VARIABLE_DEF,
135                 TokenTypes.ENUM_CONSTANT_DEF,
136                 TokenTypes.ANNOTATION_FIELD_DEF,
137         };
138     }
139 
140     @Override
getRequiredTokens()141     public int[] getRequiredTokens() {
142         return getAcceptableTokens();
143     }
144 
145     @Override
visitToken(final DetailAST ast)146     public void visitToken(final DetailAST ast) {
147         final boolean containsAnnotation =
148                 AnnotationUtility.containsAnnotation(ast, mAnnotationSimpleName)
149                         || AnnotationUtility.containsAnnotation(ast, mAnnotation);
150         final boolean containsJavadocTag = containsJavadocTag(ast);
151         if (containsAnnotation ^ containsJavadocTag) {
152             log(ast.getLineNo(), mMessageKey);
153         }
154     }
155 
156     /**
157      * Checks to see if the text block contains the tag.
158      *
159      * @param ast the AST being visited
160      * @return true if contains the tag
161      */
containsJavadocTag(final DetailAST ast)162     private boolean containsJavadocTag(final DetailAST ast) {
163         final TextBlock javadoc = getFileContents().getJavadocBefore(ast.getLineNo());
164         if (javadoc == null) {
165             return false;
166         }
167 
168         int currentLine = javadoc.getStartLineNo();
169         boolean found = false;
170 
171         final String[] lines = javadoc.getText();
172         for (String line : lines) {
173             if (mMatchTag.matcher(line).find()) {
174                 if (found) {
175                     log(currentLine, MSG_KEY_JAVADOC_DUPLICATE_TAG, mTag);
176                 }
177                 found = true;
178             }
179             currentLine++;
180         }
181 
182         return found;
183     }
184 }
185