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