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