001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2018 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    /**
104     * A key is pointing to the warning message text in "messages.properties"
105     * file.
106     */
107    public static final String MSG_KEY_ANNOTATION_MISSING_DEPRECATED =
108            "annotation.missing.deprecated";
109
110    /**
111     * A key is pointing to the warning message text in "messages.properties"
112     * file.
113     */
114    public static final String MSG_KEY_JAVADOC_DUPLICATE_TAG =
115            "javadoc.duplicateTag";
116
117    /**
118     * A key is pointing to the warning message text in "messages.properties"
119     * file.
120     */
121    public static final String MSG_KEY_JAVADOC_MISSING = "javadoc.missing";
122
123    /** {@link Deprecated Deprecated} annotation name. */
124    private static final String DEPRECATED = "Deprecated";
125
126    /** Fully-qualified {@link Deprecated Deprecated} annotation name. */
127    private static final String FQ_DEPRECATED = "java.lang." + DEPRECATED;
128
129    /** Compiled regexp to match Javadoc tag with no argument. */
130    private static final Pattern MATCH_DEPRECATED =
131            CommonUtils.createPattern("@(deprecated)\\s+\\S");
132
133    /** Compiled regexp to match first part of multilineJavadoc tags. */
134    private static final Pattern MATCH_DEPRECATED_MULTILINE_START =
135            CommonUtils.createPattern("@(deprecated)\\s*$");
136
137    /** Compiled regexp to look for a continuation of the comment. */
138    private static final Pattern MATCH_DEPRECATED_MULTILINE_CONT =
139            CommonUtils.createPattern("(\\*/|@|[^\\s\\*])");
140
141    /** Multiline finished at end of comment. */
142    private static final String END_JAVADOC = "*/";
143    /** Multiline finished at next Javadoc. */
144    private static final String NEXT_TAG = "@";
145
146    /** Is deprecated element valid without javadoc. */
147    private boolean skipNoJavadoc;
148
149    /**
150     * Set skipJavadoc value.
151     * @param skipNoJavadoc user's value of skipJavadoc
152     */
153    public void setSkipNoJavadoc(boolean skipNoJavadoc) {
154        this.skipNoJavadoc = skipNoJavadoc;
155    }
156
157    @Override
158    public int[] getDefaultTokens() {
159        return getRequiredTokens();
160    }
161
162    @Override
163    public int[] getAcceptableTokens() {
164        return getRequiredTokens();
165    }
166
167    @Override
168    public int[] getRequiredTokens() {
169        return new int[] {
170            TokenTypes.INTERFACE_DEF,
171            TokenTypes.CLASS_DEF,
172            TokenTypes.ANNOTATION_DEF,
173            TokenTypes.ENUM_DEF,
174            TokenTypes.METHOD_DEF,
175            TokenTypes.CTOR_DEF,
176            TokenTypes.VARIABLE_DEF,
177            TokenTypes.ENUM_CONSTANT_DEF,
178            TokenTypes.ANNOTATION_FIELD_DEF,
179        };
180    }
181
182    @Override
183    public void visitToken(final DetailAST ast) {
184        final TextBlock javadoc =
185            getFileContents().getJavadocBefore(ast.getLineNo());
186
187        final boolean containsAnnotation =
188            AnnotationUtility.containsAnnotation(ast, DEPRECATED)
189            || AnnotationUtility.containsAnnotation(ast, FQ_DEPRECATED);
190
191        final boolean containsJavadocTag = containsJavadocTag(javadoc);
192
193        if (containsAnnotation ^ containsJavadocTag && !(skipNoJavadoc && javadoc == null)) {
194            log(ast.getLineNo(), MSG_KEY_ANNOTATION_MISSING_DEPRECATED);
195        }
196    }
197
198    /**
199     * Checks to see if the text block contains a deprecated tag.
200     *
201     * @param javadoc the javadoc of the AST
202     * @return true if contains the tag
203     */
204    private boolean containsJavadocTag(final TextBlock javadoc) {
205        boolean found = false;
206        if (javadoc != null) {
207            final String[] lines = javadoc.getText();
208            int currentLine = javadoc.getStartLineNo() - 1;
209
210            for (int i = 0; i < lines.length; i++) {
211                currentLine++;
212                final String line = lines[i];
213
214                final Matcher javadocNoArgMatcher = MATCH_DEPRECATED.matcher(line);
215                final Matcher noArgMultilineStart = MATCH_DEPRECATED_MULTILINE_START.matcher(line);
216
217                if (javadocNoArgMatcher.find()) {
218                    if (found) {
219                        log(currentLine, MSG_KEY_JAVADOC_DUPLICATE_TAG,
220                                JavadocTagInfo.DEPRECATED.getText());
221                    }
222                    found = true;
223                }
224                else if (noArgMultilineStart.find()) {
225                    found = checkTagAtTheRestOfComment(lines, found, currentLine, i);
226                }
227            }
228        }
229        return found;
230    }
231
232    /**
233     * Look for the rest of the comment if all we saw was
234     * the tag and the name. Stop when we see '*' (end of
235     * Javadoc), '{@literal @}' (start of next tag), or anything that's
236     *  not whitespace or '*' characters.
237     * @param lines all lines
238     * @param foundBefore flag from parent method
239     * @param currentLine current line
240     * @param index som index
241     * @return true if Tag is found
242     */
243    private boolean checkTagAtTheRestOfComment(String[] lines, boolean foundBefore,
244            int currentLine, int index) {
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
274}