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 tagRE;
072    /** compiled regexp to match tag content **/
073    private Pattern tagFormatRE;
074
075    /** regexp to match tag */
076    private String tag;
077    /** regexp to match tag content */
078    private String tagFormat;
079    /** the severity level of found tag reports */
080    private SeverityLevel tagSeverityLevel = SeverityLevel.INFO;
081
082    /**
083     * Sets the tag to check.
084     * @param tag tag to check
085     * @throws ConversionException If the tag is not a valid regular exception.
086     */
087    public void setTag(String tag)
088        throws ConversionException
089    {
090        try {
091            this.tag = tag;
092            tagRE = Utils.getPattern(tag + "\\s*(.*$)");
093        }
094        catch (final PatternSyntaxException e) {
095            throw new ConversionException("unable to parse " + tag, e);
096        }
097    }
098
099    /**
100     * Set the tag format.
101     * @param format a <code>String</code> value
102     * @throws ConversionException unable to parse format
103     */
104    public void setTagFormat(String format)
105        throws ConversionException
106    {
107        try {
108            tagFormat = format;
109            tagFormatRE = Utils.getPattern(format);
110        }
111        catch (final PatternSyntaxException e) {
112            throw new ConversionException("unable to parse " + format, 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 severity  The new severity level
121     * @see SeverityLevel
122     */
123    public final void setTagSeverity(String severity)
124    {
125        tagSeverityLevel = SeverityLevel.getInstance(severity);
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 ast)
154    {
155        final FileContents contents = getFileContents();
156        final int lineNo = ast.getLineNo();
157        final TextBlock cmt =
158            contents.getJavadocBefore(lineNo);
159        if (cmt == null) {
160            log(lineNo, "type.missingTag", tag);
161        }
162        else {
163            checkTag(lineNo, cmt.getText(), tag, tagRE, tagFormatRE,
164                tagFormat);
165        }
166    }
167
168    /**
169     * Verifies that a type definition has a required tag.
170     * @param lineNo the line number for the type definition.
171     * @param comment the Javadoc comment for the type definition.
172     * @param tag the required tag name.
173     * @param tagRE regexp for the full tag.
174     * @param formatRE regexp for the tag value.
175     * @param format pattern for the tag value.
176     */
177    private void checkTag(
178            int lineNo,
179            String[] comment,
180            String tag,
181            Pattern tagRE,
182            Pattern formatRE,
183            String format)
184    {
185        if (tagRE == null) {
186            return;
187        }
188
189        int tagCount = 0;
190        for (int i = 0; i < comment.length; i++) {
191            final String s = comment[i];
192            final Matcher matcher = tagRE.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 ((formatRE != null) && !formatRE.matcher(content).find()) {
198                    log(lineNo + i - comment.length, "type.tagFormat", tag,
199                        format);
200                }
201                else {
202                    logTag(lineNo + i - comment.length, tag, content);
203                }
204
205            }
206        }
207        if (tagCount == 0) {
208            log(lineNo, "type.missingTag", tag);
209        }
210
211    }
212
213
214    /**
215     * Log a message.
216     *
217     * @param line the line number where the error was found
218     * @param tag the javadoc tag to be logged
219     * @param tagValue the contents of the tag
220     *
221     * @see java.text.MessageFormat
222     */
223    protected final void logTag(int line, String tag, String tagValue)
224    {
225        final String originalSeverity = getSeverity();
226        setSeverity(tagSeverityLevel.getName());
227
228        log(line, "javadoc.writeTag", tag, tagValue);
229
230        setSeverity(originalSeverity);
231    }
232}