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