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