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 java.util.regex.Matcher;
022import java.util.regex.Pattern;
023
024import com.puppycrawl.tools.checkstyle.api.AnnotationUtility;
025import com.puppycrawl.tools.checkstyle.api.Check;
026import com.puppycrawl.tools.checkstyle.api.DetailAST;
027import com.puppycrawl.tools.checkstyle.api.JavadocTagInfo;
028import com.puppycrawl.tools.checkstyle.api.TextBlock;
029import com.puppycrawl.tools.checkstyle.api.TokenTypes;
030import com.puppycrawl.tools.checkstyle.api.Utils;
031
032/**
033 * <p>
034 * This class is used to verify that both the
035 * {@link java.lang.Deprecated Deprecated} annotation
036 * and the deprecated javadoc tag are present when
037 * either one is present.
038 * </p>
039 *
040 * <p>
041 * Both ways of flagging deprecation serve their own purpose.  The
042 * {@link java.lang.Deprecated Deprecated} annotation is used for
043 * compilers and development tools.  The deprecated javadoc tag is
044 * used to document why something is deprecated and what, if any,
045 * alternatives exist.
046 * </p>
047 *
048 * <p>
049 * In order to properly mark something as deprecated both forms of
050 * deprecation should be present.
051 * </p>
052 *
053 * <p>
054 * Package deprecation is a exception to the rule of always using the
055 * javadoc tag and annotation to deprecate.  Only the package-info.java
056 * file can contain a Deprecated annotation and it CANNOT contain
057 * a deprecated javadoc tag.  This is the case with
058 * Sun's javadoc tool released with JDK 1.6.0_11.  As a result, this check
059 * does not deal with Deprecated packages in any way.  <b>No official
060 * documentation was found confirming this behavior is correct
061 * (of the javadoc tool).</b>
062 * </p>
063 *
064 * <p>
065 * To configure this check do the following:
066 * </p>
067 *
068 * <pre>
069 * &lt;module name="JavadocDeprecated"/&gt;
070 * </pre>
071 *
072 * @author Travis Schneeberger
073 */
074public final class MissingDeprecatedCheck extends Check
075{
076    /** {@link Deprecated Deprecated} annotation name */
077    private static final String DEPRECATED = "Deprecated";
078
079    /** fully-qualified {@link Deprecated Deprecated} annotation name */
080    private static final String FQ_DEPRECATED = "java.lang." + DEPRECATED;
081
082    /** compiled regexp to match Javadoc tag with no argument * */
083    private static final Pattern MATCH_DEPRECATED =
084        Utils.createPattern("@(deprecated)\\s+\\S");
085
086    /** compiled regexp to match first part of multilineJavadoc tags * */
087    private static final Pattern MATCH_DEPRECATED_MULTILINE_START =
088        Utils.createPattern("@(deprecated)\\s*$");
089
090    /** compiled regexp to look for a continuation of the comment * */
091    private static final Pattern MATCH_DEPRECATED_MULTILINE_CONT =
092        Utils.createPattern("(\\*/|@|[^\\s\\*])");
093
094    /** Multiline finished at end of comment * */
095    private static final String END_JAVADOC = "*/";
096    /** Multiline finished at next Javadoc * */
097    private static final String NEXT_TAG = "@";
098
099    /**
100     * A key is pointing to the warning message text in "messages.properties"
101     * file.
102     */
103    public static final String MSG_KEY_ANNOTATION_MISSING_DEPRECATED =
104        "annotation.missing.deprecated";
105
106    /**
107     * A key is pointing to the warning message text in "messages.properties"
108     * file.
109     */
110    public static final String MSG_KEY_JAVADOC_DUPLICATE_TAG =
111        "javadoc.duplicateTag";
112
113    /**
114     * A key is pointing to the warning message text in "messages.properties"
115     * file.
116     */
117    public static final String MSG_KEY_JAVADOC_MISSING = "javadoc.missing";
118
119    /** {@inheritDoc} */
120    @Override
121    public int[] getDefaultTokens()
122    {
123        return this.getAcceptableTokens();
124    }
125
126    /** {@inheritDoc} */
127    @Override
128    public int[] getAcceptableTokens()
129    {
130        return new int[] {
131            TokenTypes.INTERFACE_DEF,
132            TokenTypes.CLASS_DEF,
133            TokenTypes.ANNOTATION_DEF,
134            TokenTypes.ENUM_DEF,
135            TokenTypes.METHOD_DEF,
136            TokenTypes.CTOR_DEF,
137            TokenTypes.VARIABLE_DEF,
138            TokenTypes.ENUM_CONSTANT_DEF,
139            TokenTypes.ANNOTATION_FIELD_DEF,
140        };
141    }
142
143    /** {@inheritDoc} */
144    @Override
145    public void visitToken(final DetailAST ast)
146    {
147        final TextBlock javadoc =
148            this.getFileContents().getJavadocBefore(ast.getLineNo());
149
150        final boolean containsAnnotation =
151            AnnotationUtility.containsAnnotation(ast, DEPRECATED)
152            || AnnotationUtility.containsAnnotation(ast, FQ_DEPRECATED);
153
154        final boolean containsJavadocTag = this.containsJavadocTag(javadoc);
155
156        if (containsAnnotation ^ containsJavadocTag) {
157            this.log(ast.getLineNo(), MSG_KEY_ANNOTATION_MISSING_DEPRECATED);
158        }
159    }
160
161    /**
162     * Checks to see if the text block contains a deprecated tag.
163     *
164     * @param javadoc the javadoc of the AST
165     * @return true if contains the tag
166     */
167    private boolean containsJavadocTag(final TextBlock javadoc)
168    {
169        if (javadoc == null) {
170            return false;
171        }
172
173        final String[] lines = javadoc.getText();
174
175        boolean found = false;
176
177        int currentLine = javadoc.getStartLineNo() - 1;
178
179        for (int i = 0; i < lines.length; i++) {
180            currentLine++;
181            final String line = lines[i];
182
183            final Matcher javadocNoargMatcher =
184                MissingDeprecatedCheck.MATCH_DEPRECATED.matcher(line);
185            final Matcher noargMultilineStart =
186                MissingDeprecatedCheck.
187                    MATCH_DEPRECATED_MULTILINE_START.matcher(line);
188
189            if (javadocNoargMatcher.find()) {
190                if (found) {
191                    this.log(currentLine, MSG_KEY_JAVADOC_DUPLICATE_TAG,
192                        JavadocTagInfo.DEPRECATED.getText());
193                }
194                found = true;
195            }
196            else if (noargMultilineStart.find()) {
197                // Look for the rest of the comment if all we saw was
198                // the tag and the name. Stop when we see '*/' (end of
199                // Javadoc), '@' (start of next tag), or anything that's
200                // not whitespace or '*' characters.
201
202                for (int reindex = i + 1;
203                    reindex < lines.length; reindex++)
204                {
205                    final Matcher multilineCont =
206                        MissingDeprecatedCheck.MATCH_DEPRECATED_MULTILINE_CONT
207                        .matcher(lines[reindex]);
208
209                    if (multilineCont.find()) {
210                        reindex = lines.length;
211                        final String lFin = multilineCont.group(1);
212                        if (!lFin.equals(MissingDeprecatedCheck.NEXT_TAG)
213                            && !lFin.equals(MissingDeprecatedCheck.END_JAVADOC))
214                        {
215                            if (found) {
216                                this.log(currentLine, MSG_KEY_JAVADOC_DUPLICATE_TAG,
217                                    JavadocTagInfo.DEPRECATED.getText());
218                            }
219                            found = true;
220                        }
221                        else {
222                            this.log(currentLine, MSG_KEY_JAVADOC_MISSING);
223                            if (found) {
224                                this.log(currentLine, MSG_KEY_JAVADOC_DUPLICATE_TAG,
225                                    JavadocTagInfo.DEPRECATED.getText());
226                            }
227                            found = true;
228                        }
229                    }
230                }
231            }
232        }
233        return found;
234    }
235}