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.SeverityLevel;
025import com.puppycrawl.tools.checkstyle.api.TextBlock;
026import com.puppycrawl.tools.checkstyle.api.TokenTypes;
027import com.puppycrawl.tools.checkstyle.api.Utils;
028import java.util.regex.Matcher;
029import java.util.regex.Pattern;
030import java.util.regex.PatternSyntaxException;
031import org.apache.commons.beanutils.ConversionException;
032
033/**
034 * <p>
035 * Outputs a JavaDoc tag as information. Can be used e.g. with the stylesheets
036 * that sort the report by author name.
037 * To define the format for a tag, set property tagFormat to a
038 * regular expression.
039 * This check uses two different severity levels. The normal one is used for
040 * reporting when the tag is missing. The additional one (tagSeverity) is used
041 * for the level of reporting when the tag exists. The default value for
042 * tagSeverity is info.
043 * </p>
044 * <p> An example of how to configure the check for printing author name is:
045 *</p>
046 * <pre>
047 * &lt;module name="WriteTag"&gt;
048 *    &lt;property name="tag" value="@author"/&gt;
049 *    &lt;property name="tagFormat" value="\S"/&gt;
050 * &lt;/module&gt;
051 * </pre>
052 * <p> An example of how to configure the check to print warnings if an
053 * "@incomplete" tag is found, and not print anything if it is not found:
054 *</p>
055 * <pre>
056 * &lt;module name="WriteTag"&gt;
057 *    &lt;property name="tag" value="@incomplete"/&gt;
058 *    &lt;property name="tagFormat" value="\S"/&gt;
059 *    &lt;property name="severity" value="ignore"/&gt;
060 *    &lt;property name="tagSeverity" value="warning"/&gt;
061 * &lt;/module&gt;
062 * </pre>
063 *
064 * @author Daniel Grenner
065 * @version 1.0
066 */
067public class WriteTagCheck
068    extends Check
069{
070    /** compiled regexp to match tag **/
071    private Pattern mTagRE;
072    /** compiled regexp to match tag content **/
073    private Pattern mTagFormatRE;
074
075    /** regexp to match tag */
076    private String mTag;
077    /** regexp to match tag content */
078    private String mTagFormat;
079    /** the severity level of found tag reports */
080    private SeverityLevel mTagSeverityLevel = SeverityLevel.INFO;
081
082    /**
083     * Sets the tag to check.
084     * @param aTag tag to check
085     * @throws ConversionException If the tag is not a valid regular exception.
086     */
087    public void setTag(String aTag)
088        throws ConversionException
089    {
090        try {
091            mTag = aTag;
092            mTagRE = Utils.getPattern(aTag + "\\s*(.*$)");
093        }
094        catch (final PatternSyntaxException e) {
095            throw new ConversionException("unable to parse " + aTag, e);
096        }
097    }
098
099    /**
100     * Set the tag format.
101     * @param aFormat a <code>String</code> value
102     * @throws ConversionException unable to parse aFormat
103     */
104    public void setTagFormat(String aFormat)
105        throws ConversionException
106    {
107        try {
108            mTagFormat = aFormat;
109            mTagFormatRE = Utils.getPattern(aFormat);
110        }
111        catch (final PatternSyntaxException e) {
112            throw new ConversionException("unable to parse " + aFormat, e);
113        }
114    }
115
116    /**
117     * Sets the tag severity level.  The string should be one of the names
118     * defined in the <code>SeverityLevel</code> class.
119     *
120     * @param aSeverity  The new severity level
121     * @see SeverityLevel
122     */
123    public final void setTagSeverity(String aSeverity)
124    {
125        mTagSeverityLevel = SeverityLevel.getInstance(aSeverity);
126    }
127
128    @Override
129    public int[] getDefaultTokens()
130    {
131        return new int[] {TokenTypes.INTERFACE_DEF,
132                          TokenTypes.CLASS_DEF,
133                          TokenTypes.ENUM_DEF,
134                          TokenTypes.ANNOTATION_DEF,
135        };
136    }
137
138    @Override
139    public int[] getAcceptableTokens()
140    {
141        return new int[] {TokenTypes.INTERFACE_DEF,
142                          TokenTypes.CLASS_DEF,
143                          TokenTypes.ENUM_DEF,
144                          TokenTypes.ANNOTATION_DEF,
145                          TokenTypes.METHOD_DEF,
146                          TokenTypes.CTOR_DEF,
147                          TokenTypes.ENUM_CONSTANT_DEF,
148                          TokenTypes.ANNOTATION_FIELD_DEF,
149        };
150    }
151
152    @Override
153    public void visitToken(DetailAST aAST)
154    {
155        final FileContents contents = getFileContents();
156        final int lineNo = aAST.getLineNo();
157        final TextBlock cmt =
158            contents.getJavadocBefore(lineNo);
159        if (cmt == null) {
160            log(lineNo, "type.missingTag", mTag);
161        }
162        else {
163            checkTag(lineNo, cmt.getText(), mTag, mTagRE, mTagFormatRE,
164                mTagFormat);
165        }
166    }
167
168    /**
169     * Verifies that a type definition has a required tag.
170     * @param aLineNo the line number for the type definition.
171     * @param aComment the Javadoc comment for the type definition.
172     * @param aTag the required tag name.
173     * @param aTagRE regexp for the full tag.
174     * @param aFormatRE regexp for the tag value.
175     * @param aFormat pattern for the tag value.
176     */
177    private void checkTag(
178            int aLineNo,
179            String[] aComment,
180            String aTag,
181            Pattern aTagRE,
182            Pattern aFormatRE,
183            String aFormat)
184    {
185        if (aTagRE == null) {
186            return;
187        }
188
189        int tagCount = 0;
190        for (int i = 0; i < aComment.length; i++) {
191            final String s = aComment[i];
192            final Matcher matcher = aTagRE.matcher(s);
193            if (matcher.find()) {
194                tagCount += 1;
195                final int contentStart = matcher.start(1);
196                final String content = s.substring(contentStart);
197                if ((aFormatRE != null) && !aFormatRE.matcher(content).find()) {
198                    log(aLineNo + i - aComment.length, "type.tagFormat", aTag,
199                        aFormat);
200                }
201                else {
202                    logTag(aLineNo + i - aComment.length, aTag, content);
203                }
204
205            }
206        }
207        if (tagCount == 0) {
208            log(aLineNo, "type.missingTag", aTag);
209        }
210
211    }
212
213
214    /**
215     * Log a message.
216     *
217     * @param aLine the line number where the error was found
218     * @param aTag the javdoc tag to be logged
219     * @param aTagValue the contents of the tag
220     *
221     * @see java.text.MessageFormat
222     */
223    protected final void logTag(int aLine, String aTag, String aTagValue)
224    {
225        final String originalSeverity = getSeverity();
226        setSeverity(mTagSeverityLevel.getName());
227
228        log(aLine, "javadoc.writeTag", aTag, aTagValue);
229
230        setSeverity(originalSeverity);
231    }
232}