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 /** {@inheritDoc} */ 100 @Override 101 public int[] getDefaultTokens() 102 { 103 return this.getAcceptableTokens(); 104 } 105 106 /** {@inheritDoc} */ 107 @Override 108 public int[] getAcceptableTokens() 109 { 110 return new int[] { 111 TokenTypes.INTERFACE_DEF, 112 TokenTypes.CLASS_DEF, 113 TokenTypes.ANNOTATION_DEF, 114 TokenTypes.ENUM_DEF, 115 TokenTypes.METHOD_DEF, 116 TokenTypes.CTOR_DEF, 117 TokenTypes.VARIABLE_DEF, 118 TokenTypes.ENUM_CONSTANT_DEF, 119 TokenTypes.ANNOTATION_FIELD_DEF, 120 }; 121 } 122 123 /** {@inheritDoc} */ 124 @Override 125 public void visitToken(final DetailAST aAST) 126 { 127 final TextBlock javadoc = 128 this.getFileContents().getJavadocBefore(aAST.getLineNo()); 129 130 final boolean containsAnnotation = 131 AnnotationUtility.containsAnnotation(aAST, DEPRECATED) 132 || AnnotationUtility.containsAnnotation(aAST, FQ_DEPRECATED); 133 134 final boolean containsJavadocTag = this.containsJavadocTag(javadoc); 135 136 if (containsAnnotation ^ containsJavadocTag) { 137 this.log(aAST.getLineNo(), "annotation.missing.deprecated"); 138 } 139 } 140 141 /** 142 * Checks to see if the text block contains a deprecated tag. 143 * 144 * @param aJavadoc the javadoc of the AST 145 * @return true if contains the tag 146 */ 147 private boolean containsJavadocTag(final TextBlock aJavadoc) 148 { 149 if (aJavadoc == null) { 150 return false; 151 } 152 153 final String[] lines = aJavadoc.getText(); 154 155 boolean found = false; 156 157 int currentLine = aJavadoc.getStartLineNo() - 1; 158 159 for (int i = 0; i < lines.length; i++) { 160 currentLine++; 161 final String line = lines[i]; 162 163 final Matcher javadocNoargMatcher = 164 MissingDeprecatedCheck.MATCH_DEPRECATED.matcher(line); 165 final Matcher noargMultilineStart = 166 MissingDeprecatedCheck. 167 MATCH_DEPRECATED_MULTILINE_START.matcher(line); 168 169 if (javadocNoargMatcher.find()) { 170 if (found) { 171 this.log(currentLine, "javadoc.duplicateTag", 172 JavadocTagInfo.DEPRECATED.getText()); 173 } 174 found = true; 175 } 176 else if (noargMultilineStart.find()) { 177 // Look for the rest of the comment if all we saw was 178 // the tag and the name. Stop when we see '*/' (end of 179 // Javadoc), '@' (start of next tag), or anything that's 180 // not whitespace or '*' characters. 181 182 for (int remIndex = i + 1; 183 remIndex < lines.length; remIndex++) 184 { 185 final Matcher multilineCont = 186 MissingDeprecatedCheck.MATCH_DEPRECATED_MULTILINE_CONT 187 .matcher(lines[remIndex]); 188 189 if (multilineCont.find()) { 190 remIndex = lines.length; 191 final String lFin = multilineCont.group(1); 192 if (!lFin.equals(MissingDeprecatedCheck.NEXT_TAG) 193 && !lFin.equals(MissingDeprecatedCheck.END_JAVADOC)) 194 { 195 if (found) { 196 this.log(currentLine, "javadoc.duplicateTag", 197 JavadocTagInfo.DEPRECATED.getText()); 198 } 199 found = true; 200 } 201 else { 202 this.log(currentLine, "javadoc.missing"); 203 if (found) { 204 this.log(currentLine, "javadoc.duplicateTag", 205 JavadocTagInfo.DEPRECATED.getText()); 206 } 207 found = true; 208 } 209 } 210 } 211 } 212 } 213 return found; 214 } 215}