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