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 * <module name="JavadocDeprecated"/> 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}