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.javadoc; 021 022import java.util.Arrays; 023import java.util.HashMap; 024import java.util.HashSet; 025import java.util.Locale; 026import java.util.Map; 027import java.util.Set; 028 029import com.puppycrawl.tools.checkstyle.JavadocDetailNodeParser; 030import com.puppycrawl.tools.checkstyle.JavadocDetailNodeParser.ParseErrorMessage; 031import com.puppycrawl.tools.checkstyle.JavadocDetailNodeParser.ParseStatus; 032import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 033import com.puppycrawl.tools.checkstyle.api.DetailAST; 034import com.puppycrawl.tools.checkstyle.api.DetailNode; 035import com.puppycrawl.tools.checkstyle.api.JavadocTokenTypes; 036import com.puppycrawl.tools.checkstyle.api.TokenTypes; 037import com.puppycrawl.tools.checkstyle.utils.BlockCommentPosition; 038import com.puppycrawl.tools.checkstyle.utils.CommonUtils; 039import com.puppycrawl.tools.checkstyle.utils.JavadocUtils; 040 041/** 042 * Base class for Checks that process Javadoc comments. 043 * @author Baratali Izmailov 044 */ 045public abstract class AbstractJavadocCheck extends AbstractCheck { 046 /** 047 * Message key of error message. Missed close HTML tag breaks structure 048 * of parse tree, so parser stops parsing and generates such error 049 * message. This case is special because parser prints error like 050 * {@code "no viable alternative at input 'b \n *\n'"} and it is not 051 * clear that error is about missed close HTML tag. 052 */ 053 public static final String MSG_JAVADOC_MISSED_HTML_CLOSE = 054 JavadocDetailNodeParser.MSG_JAVADOC_MISSED_HTML_CLOSE; 055 056 /** 057 * Message key of error message. 058 */ 059 public static final String MSG_JAVADOC_WRONG_SINGLETON_TAG = 060 JavadocDetailNodeParser.MSG_JAVADOC_WRONG_SINGLETON_TAG; 061 062 /** 063 * Parse error while rule recognition. 064 */ 065 public static final String MSG_JAVADOC_PARSE_RULE_ERROR = 066 JavadocDetailNodeParser.MSG_JAVADOC_PARSE_RULE_ERROR; 067 068 /** 069 * Error message key for common javadoc errors. 070 */ 071 public static final String MSG_KEY_PARSE_ERROR = 072 JavadocDetailNodeParser.MSG_KEY_PARSE_ERROR; 073 /** 074 * Unrecognized error from antlr parser. 075 */ 076 public static final String MSG_KEY_UNRECOGNIZED_ANTLR_ERROR = 077 JavadocDetailNodeParser.MSG_KEY_UNRECOGNIZED_ANTLR_ERROR; 078 079 /** 080 * Key is "line:column". Value is {@link DetailNode} tree. Map is stored in {@link ThreadLocal} 081 * to guarantee basic thread safety and avoid shared, mutable state when not necessary. 082 */ 083 private static final ThreadLocal<Map<String, ParseStatus>> TREE_CACHE = 084 new ThreadLocal<Map<String, ParseStatus>>() { 085 @Override 086 protected Map<String, ParseStatus> initialValue() { 087 return new HashMap<>(); 088 } 089 }; 090 091 /** 092 * Parses content of Javadoc comment as DetailNode tree. 093 */ 094 private final JavadocDetailNodeParser parser = new JavadocDetailNodeParser(); 095 096 /** The javadoc tokens the check is interested in. */ 097 private final Set<Integer> javadocTokens = new HashSet<>(); 098 099 /** 100 * DetailAST node of considered Javadoc comment that is just a block comment 101 * in Java language syntax tree. 102 */ 103 private DetailAST blockCommentAst; 104 105 /** 106 * Returns the default javadoc token types a check is interested in. 107 * @return the default javadoc token types 108 * @see JavadocTokenTypes 109 */ 110 public abstract int[] getDefaultJavadocTokens(); 111 112 /** 113 * Called to process a Javadoc token. 114 * @param ast 115 * the token to process 116 */ 117 public abstract void visitJavadocToken(DetailNode ast); 118 119 /** 120 * The configurable javadoc token set. 121 * Used to protect Checks against malicious users who specify an 122 * unacceptable javadoc token set in the configuration file. 123 * The default implementation returns the check's default javadoc tokens. 124 * @return the javadoc token set this check is designed for. 125 * @see JavadocTokenTypes 126 */ 127 public int[] getAcceptableJavadocTokens() { 128 final int[] defaultJavadocTokens = getDefaultJavadocTokens(); 129 final int[] copy = new int[defaultJavadocTokens.length]; 130 System.arraycopy(defaultJavadocTokens, 0, copy, 0, defaultJavadocTokens.length); 131 return copy; 132 } 133 134 /** 135 * The javadoc tokens that this check must be registered for. 136 * @return the javadoc token set this must be registered for. 137 * @see JavadocTokenTypes 138 */ 139 public int[] getRequiredJavadocTokens() { 140 return CommonUtils.EMPTY_INT_ARRAY; 141 } 142 143 /** 144 * Adds a set of tokens the check is interested in. 145 * @param strRep the string representation of the tokens interested in 146 */ 147 public final void setJavadocTokens(String... strRep) { 148 javadocTokens.clear(); 149 for (String str : strRep) { 150 javadocTokens.add(JavadocUtils.getTokenId(str)); 151 } 152 } 153 154 @Override 155 public void init() { 156 validateDefaultJavadocTokens(); 157 if (javadocTokens.isEmpty()) { 158 for (int id : getDefaultJavadocTokens()) { 159 javadocTokens.add(id); 160 } 161 } 162 else { 163 final int[] acceptableJavadocTokens = getAcceptableJavadocTokens(); 164 Arrays.sort(acceptableJavadocTokens); 165 for (Integer javadocTokenId : javadocTokens) { 166 if (Arrays.binarySearch(acceptableJavadocTokens, javadocTokenId) < 0) { 167 final String message = String.format(Locale.ROOT, "Javadoc Token \"%s\" was " 168 + "not found in Acceptable javadoc tokens list in check %s", 169 JavadocUtils.getTokenName(javadocTokenId), getClass().getName()); 170 throw new IllegalStateException(message); 171 } 172 } 173 } 174 } 175 176 /** 177 * Validates that check's required javadoc tokens are subset of default javadoc tokens. 178 * @throws IllegalStateException when validation of default javadoc tokens fails 179 */ 180 private void validateDefaultJavadocTokens() { 181 if (getRequiredJavadocTokens().length != 0) { 182 final int[] defaultJavadocTokens = getDefaultJavadocTokens(); 183 Arrays.sort(defaultJavadocTokens); 184 for (final int javadocToken : getRequiredJavadocTokens()) { 185 if (Arrays.binarySearch(defaultJavadocTokens, javadocToken) < 0) { 186 final String message = String.format(Locale.ROOT, 187 "Javadoc Token \"%s\" from required javadoc " 188 + "tokens was not found in default " 189 + "javadoc tokens list in check %s", 190 javadocToken, getClass().getName()); 191 throw new IllegalStateException(message); 192 } 193 } 194 } 195 } 196 197 /** 198 * Called before the starting to process a tree. 199 * @param rootAst 200 * the root of the tree 201 */ 202 public void beginJavadocTree(DetailNode rootAst) { 203 // No code by default, should be overridden only by demand at subclasses 204 } 205 206 /** 207 * Called after finished processing a tree. 208 * @param rootAst 209 * the root of the tree 210 */ 211 public void finishJavadocTree(DetailNode rootAst) { 212 // No code by default, should be overridden only by demand at subclasses 213 } 214 215 /** 216 * Called after all the child nodes have been process. 217 * @param ast 218 * the token leaving 219 */ 220 public void leaveJavadocToken(DetailNode ast) { 221 // No code by default, should be overridden only by demand at subclasses 222 } 223 224 /** 225 * Defined final to not allow JavadocChecks to change default tokens. 226 * @return default tokens 227 */ 228 @Override 229 public final int[] getDefaultTokens() { 230 return new int[] {TokenTypes.BLOCK_COMMENT_BEGIN }; 231 } 232 233 /** 234 * Defined final because all JavadocChecks require comment nodes. 235 * @return true 236 */ 237 @Override 238 public final boolean isCommentNodesRequired() { 239 return true; 240 } 241 242 @Override 243 public final void beginTree(DetailAST rootAST) { 244 TREE_CACHE.get().clear(); 245 } 246 247 @Override 248 public final void finishTree(DetailAST rootAST) { 249 TREE_CACHE.get().clear(); 250 } 251 252 @Override 253 public final void visitToken(DetailAST blockCommentNode) { 254 if (JavadocUtils.isJavadocComment(blockCommentNode) 255 && isCorrectJavadocPosition(blockCommentNode)) { 256 // store as field, to share with child Checks 257 blockCommentAst = blockCommentNode; 258 259 final String treeCacheKey = blockCommentNode.getLineNo() + ":" 260 + blockCommentNode.getColumnNo(); 261 262 final ParseStatus result; 263 264 if (TREE_CACHE.get().containsKey(treeCacheKey)) { 265 result = TREE_CACHE.get().get(treeCacheKey); 266 } 267 else { 268 result = parser.parseJavadocAsDetailNode(blockCommentNode); 269 TREE_CACHE.get().put(treeCacheKey, result); 270 } 271 272 if (result.getParseErrorMessage() == null) { 273 processTree(result.getTree()); 274 } 275 else { 276 final ParseErrorMessage parseErrorMessage = result.getParseErrorMessage(); 277 log(parseErrorMessage.getLineNumber(), 278 parseErrorMessage.getMessageKey(), 279 parseErrorMessage.getMessageArguments()); 280 } 281 } 282 283 } 284 285 /** 286 * Getter for block comment in Java language syntax tree. 287 * @return A block comment in the syntax tree. 288 */ 289 protected DetailAST getBlockCommentAst() { 290 return blockCommentAst; 291 } 292 293 /** 294 * Checks Javadoc comment it's in right place. 295 * From Javadoc util documentation: 296 * "Placement of comments - Documentation comments are recognized only when placed 297 * immediately before class, interface, constructor, method, or field 298 * declarations -- see the class example, method example, and field example. 299 * Documentation comments placed in the body of a method are ignored. Only one 300 * documentation comment per declaration statement is recognized by the Javadoc tool." 301 * 302 * @param blockComment Block comment AST 303 * @return true if Javadoc is in right place 304 */ 305 private static boolean isCorrectJavadocPosition(DetailAST blockComment) { 306 return BlockCommentPosition.isOnClass(blockComment) 307 || BlockCommentPosition.isOnInterface(blockComment) 308 || BlockCommentPosition.isOnEnum(blockComment) 309 || BlockCommentPosition.isOnMethod(blockComment) 310 || BlockCommentPosition.isOnField(blockComment) 311 || BlockCommentPosition.isOnConstructor(blockComment) 312 || BlockCommentPosition.isOnEnumConstant(blockComment) 313 || BlockCommentPosition.isOnAnnotationDef(blockComment); 314 } 315 316 /** 317 * Processes JavadocAST tree notifying Check. 318 * @param root 319 * root of JavadocAST tree. 320 */ 321 private void processTree(DetailNode root) { 322 beginJavadocTree(root); 323 walk(root); 324 finishJavadocTree(root); 325 } 326 327 /** 328 * Processes a node calling Check at interested nodes. 329 * @param root 330 * the root of tree for process 331 */ 332 private void walk(DetailNode root) { 333 DetailNode curNode = root; 334 while (curNode != null) { 335 boolean waitsForProcessing = shouldBeProcessed(curNode); 336 337 if (waitsForProcessing) { 338 visitJavadocToken(curNode); 339 } 340 DetailNode toVisit = JavadocUtils.getFirstChild(curNode); 341 while (curNode != null && toVisit == null) { 342 343 if (waitsForProcessing) { 344 leaveJavadocToken(curNode); 345 } 346 347 toVisit = JavadocUtils.getNextSibling(curNode); 348 if (toVisit == null) { 349 curNode = curNode.getParent(); 350 if (curNode != null) { 351 waitsForProcessing = shouldBeProcessed(curNode); 352 } 353 } 354 } 355 curNode = toVisit; 356 } 357 } 358 359 /** 360 * Checks whether the current node should be processed by the check. 361 * @param curNode current node. 362 * @return true if the current node should be processed by the check. 363 */ 364 private boolean shouldBeProcessed(DetailNode curNode) { 365 return javadocTokens.contains(curNode.getType()); 366 } 367 368}