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.javadoc;
020
021import com.puppycrawl.tools.checkstyle.api.Check;
022import com.puppycrawl.tools.checkstyle.api.DetailAST;
023import com.puppycrawl.tools.checkstyle.api.FileContents;
024import com.puppycrawl.tools.checkstyle.api.JavadocTagInfo;
025import com.puppycrawl.tools.checkstyle.api.Scope;
026import com.puppycrawl.tools.checkstyle.api.ScopeUtils;
027import com.puppycrawl.tools.checkstyle.api.TextBlock;
028import com.puppycrawl.tools.checkstyle.api.TokenTypes;
029import com.puppycrawl.tools.checkstyle.api.Utils;
030import com.puppycrawl.tools.checkstyle.checks.CheckUtils;
031import java.util.List;
032import java.util.regex.Matcher;
033import java.util.regex.Pattern;
034import java.util.regex.PatternSyntaxException;
035import org.apache.commons.beanutils.ConversionException;
036
037/**
038 * Checks the Javadoc of a type.
039 *
040 * @author Oliver Burn
041 * @author Michael Tamm
042 */
043public class JavadocTypeCheck
044    extends Check
045{
046    /** the scope to check for */
047    private Scope scope = Scope.PRIVATE;
048    /** the visibility scope where Javadoc comments shouldn't be checked **/
049    private Scope excludeScope;
050    /** compiled regexp to match author tag content **/
051    private Pattern authorFormatPattern;
052    /** compiled regexp to match version tag content **/
053    private Pattern versionFormatPattern;
054    /** regexp to match author tag content */
055    private String authorFormat;
056    /** regexp to match version tag content */
057    private String versionFormat;
058    /**
059     * controls whether to ignore errors when a method has type parameters but
060     * does not have matching param tags in the javadoc. Defaults to false.
061     */
062    private boolean allowMissingParamTags;
063    /** controls whether to flag errors for unknown tags. Defaults to false. */
064    private boolean allowUnknownTags;
065
066    /**
067     * Sets the scope to check.
068     * @param from string to set scope from
069     */
070    public void setScope(String from)
071    {
072        scope = Scope.getInstance(from);
073    }
074
075    /**
076     * Set the excludeScope.
077     * @param scope a <code>String</code> value
078     */
079    public void setExcludeScope(String scope)
080    {
081        excludeScope = Scope.getInstance(scope);
082    }
083
084    /**
085     * Set the author tag pattern.
086     * @param format a <code>String</code> value
087     * @throws ConversionException unable to parse aFormat
088     */
089    public void setAuthorFormat(String format)
090        throws ConversionException
091    {
092        try {
093            authorFormat = format;
094            authorFormatPattern = Utils.getPattern(format);
095        }
096        catch (final PatternSyntaxException e) {
097            throw new ConversionException("unable to parse " + format, e);
098        }
099    }
100
101    /**
102     * Set the version format pattern.
103     * @param format a <code>String</code> value
104     * @throws ConversionException unable to parse aFormat
105     */
106    public void setVersionFormat(String format)
107        throws ConversionException
108    {
109        try {
110            versionFormat = format;
111            versionFormatPattern = Utils.getPattern(format);
112        }
113        catch (final PatternSyntaxException e) {
114            throw new ConversionException("unable to parse " + format, e);
115        }
116
117    }
118
119    /**
120     * Controls whether to allow a type which has type parameters to
121     * omit matching param tags in the javadoc. Defaults to false.
122     *
123     * @param flag a <code>Boolean</code> value
124     */
125    public void setAllowMissingParamTags(boolean flag)
126    {
127        allowMissingParamTags = flag;
128    }
129
130    /**
131     * Controls whether to flag errors for unknown tags. Defaults to false.
132     * @param flag a <code>Boolean</code> value
133     */
134    public void setAllowUnknownTags(boolean flag)
135    {
136        allowUnknownTags = flag;
137    }
138
139    @Override
140    public int[] getDefaultTokens()
141    {
142        return new int[] {
143            TokenTypes.INTERFACE_DEF,
144            TokenTypes.CLASS_DEF,
145            TokenTypes.ENUM_DEF,
146            TokenTypes.ANNOTATION_DEF,
147        };
148    }
149
150    @Override
151    public void visitToken(DetailAST ast)
152    {
153        if (shouldCheck(ast)) {
154            final FileContents contents = getFileContents();
155            final int lineNo = ast.getLineNo();
156            final TextBlock cmt = contents.getJavadocBefore(lineNo);
157            if (cmt == null) {
158                log(lineNo, "javadoc.missing");
159            }
160            else if (ScopeUtils.isOuterMostType(ast)) {
161                // don't check author/version for inner classes
162                final List<JavadocTag> tags = getJavadocTags(cmt);
163                checkTag(lineNo, tags, JavadocTagInfo.AUTHOR.getName(),
164                         authorFormatPattern, authorFormat);
165                checkTag(lineNo, tags, JavadocTagInfo.VERSION.getName(),
166                         versionFormatPattern, versionFormat);
167
168                final List<String> typeParamNames =
169                    CheckUtils.getTypeParameterNames(ast);
170
171                if (!allowMissingParamTags) {
172                    //Check type parameters that should exist, do
173                    for (final String string : typeParamNames) {
174                        checkTypeParamTag(
175                            lineNo, tags, string);
176                    }
177                }
178
179                checkUnusedTypeParamTags(tags, typeParamNames);
180            }
181        }
182    }
183
184    /**
185     * Whether we should check this node.
186     * @param ast a given node.
187     * @return whether we should check a given node.
188     */
189    private boolean shouldCheck(final DetailAST ast)
190    {
191        final DetailAST mods = ast.findFirstToken(TokenTypes.MODIFIERS);
192        final Scope declaredScope = ScopeUtils.getScopeFromMods(mods);
193        final Scope scope =
194            ScopeUtils.inInterfaceOrAnnotationBlock(ast)
195                ? Scope.PUBLIC : declaredScope;
196        final Scope surroundingScope = ScopeUtils.getSurroundingScope(ast);
197
198        return scope.isIn(this.scope)
199            && ((surroundingScope == null) || surroundingScope.isIn(this.scope))
200            && ((excludeScope == null)
201                || !scope.isIn(excludeScope)
202                || ((surroundingScope != null)
203                && !surroundingScope.isIn(excludeScope)));
204    }
205
206    /**
207     * Gets all standalone tags from a given javadoc.
208     * @param cmt the Javadoc comment to process.
209     * @return all standalone tags from the given javadoc.
210     */
211    private List<JavadocTag> getJavadocTags(TextBlock cmt)
212    {
213        final JavadocTags tags = JavadocUtils.getJavadocTags(cmt,
214            JavadocUtils.JavadocTagType.BLOCK);
215        if (!allowUnknownTags) {
216            for (final InvalidJavadocTag tag : tags.getInvalidTags()) {
217                log(tag.getLine(), tag.getCol(), "javadoc.unknownTag",
218                    tag.getName());
219            }
220        }
221        return tags.getValidTags();
222    }
223
224    /**
225     * Verifies that a type definition has a required tag.
226     * @param lineNo the line number for the type definition.
227     * @param tags tags from the Javadoc comment for the type definition.
228     * @param tagName the required tag name.
229     * @param formatPattern regexp for the tag value.
230     * @param format pattern for the tag value.
231     */
232    private void checkTag(int lineNo, List<JavadocTag> tags, String tagName,
233                          Pattern formatPattern, String format)
234    {
235        if (formatPattern == null) {
236            return;
237        }
238
239        int tagCount = 0;
240        for (int i = tags.size() - 1; i >= 0; i--) {
241            final JavadocTag tag = tags.get(i);
242            if (tag.getTagName().equals(tagName)) {
243                tagCount++;
244                if (!formatPattern.matcher(tag.getArg1()).find()) {
245                    log(lineNo, "type.tagFormat", "@" + tagName, format);
246                }
247            }
248        }
249        if (tagCount == 0) {
250            log(lineNo, "type.missingTag", "@" + tagName);
251        }
252    }
253
254    /**
255     * Verifies that a type definition has the specified param tag for
256     * the specified type parameter name.
257     * @param lineNo the line number for the type definition.
258     * @param tags tags from the Javadoc comment for the type definition.
259     * @param typeParamName the name of the type parameter
260     */
261    private void checkTypeParamTag(final int lineNo,
262            final List<JavadocTag> tags, final String typeParamName)
263    {
264        boolean found = false;
265        for (int i = tags.size() - 1; i >= 0; i--) {
266            final JavadocTag tag = tags.get(i);
267            if (tag.isParamTag()
268                && (tag.getArg1() != null)
269                && (tag.getArg1().indexOf("<" + typeParamName + ">") == 0))
270            {
271                found = true;
272            }
273        }
274        if (!found) {
275            log(lineNo, "type.missingTag",
276                JavadocTagInfo.PARAM.getText() + " <" + typeParamName + ">");
277        }
278    }
279
280    /**
281     * Checks for unused param tags for type parameters.
282     * @param tags tags from the Javadoc comment for the type definition.
283     * @param typeParamNames names of type parameters
284     */
285    private void checkUnusedTypeParamTags(
286        final List<JavadocTag> tags,
287        final List<String> typeParamNames)
288    {
289        final Pattern pattern = Utils.getPattern("\\s*<([^>]+)>.*");
290        for (int i = tags.size() - 1; i >= 0; i--) {
291            final JavadocTag tag = tags.get(i);
292            if (tag.isParamTag()) {
293
294                if (tag.getArg1() != null) {
295
296                    final Matcher matcher = pattern.matcher(tag.getArg1());
297                    String typeParamName = null;
298
299                    if (matcher.matches()) {
300                        typeParamName = matcher.group(1).trim();
301                        if (!typeParamNames.contains(typeParamName)) {
302                            log(tag.getLineNo(), tag.getColumnNo(),
303                                "javadoc.unusedTag",
304                                JavadocTagInfo.PARAM.getText(),
305                                "<" + typeParamName + ">");
306                        }
307                    }
308                    else {
309                        log(tag.getLineNo(), tag.getColumnNo(),
310                            "javadoc.unusedTagGeneral");
311                    }
312                }
313                else {
314                    log(tag.getLineNo(), tag.getColumnNo(),
315                        "javadoc.unusedTagGeneral");
316                }
317            }
318        }
319    }
320}