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 * @noinspection NoopMethodInAbstractClass 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 /** 075 * Key is "line:column". Value is {@link DetailNode} tree. Map is stored in {@link ThreadLocal} 076 * to guarantee basic thread safety and avoid shared, mutable state when not necessary. 077 */ 078 private static final ThreadLocal<Map<String, ParseStatus>> TREE_CACHE = 079 ThreadLocal.withInitial(HashMap::new); 080 081 /** 082 * The file context. 083 * @noinspection ThreadLocalNotStaticFinal 084 */ 085 private final ThreadLocal<FileContext> context = ThreadLocal.withInitial(FileContext::new); 086 087 /** The javadoc tokens the check is interested in. */ 088 private final Set<Integer> javadocTokens = new HashSet<>(); 089 090 /** 091 * This property determines if a check should log a violation upon encountering javadoc with 092 * non-tight html. The default return value for this method is set to false since checks 093 * generally tend to be fine with non tight html. It can be set through config file if a check 094 * is to log violation upon encountering non-tight HTML in javadoc. 095 * 096 * @see ParseStatus#firstNonTightHtmlTag 097 * @see ParseStatus#isNonTight() 098 * @see <a href="http://checkstyle.sourceforge.net/writingjavadocchecks.html#Tight-HTML_rules"> 099 * Tight HTML rules</a> 100 */ 101 private boolean violateExecutionOnNonTightHtml; 102 103 /** 104 * Returns the default javadoc token types a check is interested in. 105 * @return the default javadoc token types 106 * @see JavadocTokenTypes 107 */ 108 public abstract int[] getDefaultJavadocTokens(); 109 110 /** 111 * Called to process a Javadoc token. 112 * @param ast 113 * the token to process 114 */ 115 public abstract void visitJavadocToken(DetailNode ast); 116 117 /** 118 * The configurable javadoc token set. 119 * Used to protect Checks against malicious users who specify an 120 * unacceptable javadoc token set in the configuration file. 121 * The default implementation returns the check's default javadoc tokens. 122 * @return the javadoc token set this check is designed for. 123 * @see JavadocTokenTypes 124 */ 125 public int[] getAcceptableJavadocTokens() { 126 final int[] defaultJavadocTokens = getDefaultJavadocTokens(); 127 final int[] copy = new int[defaultJavadocTokens.length]; 128 System.arraycopy(defaultJavadocTokens, 0, copy, 0, defaultJavadocTokens.length); 129 return copy; 130 } 131 132 /** 133 * The javadoc tokens that this check must be registered for. 134 * @return the javadoc token set this must be registered for. 135 * @see JavadocTokenTypes 136 */ 137 public int[] getRequiredJavadocTokens() { 138 return CommonUtils.EMPTY_INT_ARRAY; 139 } 140 141 /** 142 * This method determines if a check should process javadoc containing non-tight html tags. 143 * This method must be overridden in checks extending {@code AbstractJavadocCheck} which 144 * are not supposed to process javadoc containing non-tight html tags. 145 * 146 * @return true if the check should or can process javadoc containing non-tight html tags; 147 * false otherwise 148 * @see ParseStatus#isNonTight() 149 * @see <a href="http://checkstyle.sourceforge.net/writingjavadocchecks.html#Tight-HTML_rules"> 150 * Tight HTML rules</a> 151 */ 152 public boolean acceptJavadocWithNonTightHtml() { 153 return true; 154 } 155 156 /** 157 * Setter for {@link #violateExecutionOnNonTightHtml}. 158 * @param shouldReportViolation value to which the field shall be set to 159 * @see <a href="http://checkstyle.sourceforge.net/writingjavadocchecks.html#Tight-HTML_rules"> 160 * Tight HTML rules</a> 161 */ 162 public final void setViolateExecutionOnNonTightHtml(boolean shouldReportViolation) { 163 violateExecutionOnNonTightHtml = shouldReportViolation; 164 } 165 166 /** 167 * Adds a set of tokens the check is interested in. 168 * @param strRep the string representation of the tokens interested in 169 */ 170 public final void setJavadocTokens(String... strRep) { 171 javadocTokens.clear(); 172 for (String str : strRep) { 173 javadocTokens.add(JavadocUtils.getTokenId(str)); 174 } 175 } 176 177 @Override 178 public void init() { 179 validateDefaultJavadocTokens(); 180 if (javadocTokens.isEmpty()) { 181 for (int id : getDefaultJavadocTokens()) { 182 javadocTokens.add(id); 183 } 184 } 185 else { 186 final int[] acceptableJavadocTokens = getAcceptableJavadocTokens(); 187 Arrays.sort(acceptableJavadocTokens); 188 for (Integer javadocTokenId : javadocTokens) { 189 if (Arrays.binarySearch(acceptableJavadocTokens, javadocTokenId) < 0) { 190 final String message = String.format(Locale.ROOT, "Javadoc Token \"%s\" was " 191 + "not found in Acceptable javadoc tokens list in check %s", 192 JavadocUtils.getTokenName(javadocTokenId), getClass().getName()); 193 throw new IllegalStateException(message); 194 } 195 } 196 } 197 } 198 199 /** 200 * Validates that check's required javadoc tokens are subset of default javadoc tokens. 201 * @throws IllegalStateException when validation of default javadoc tokens fails 202 */ 203 private void validateDefaultJavadocTokens() { 204 if (getRequiredJavadocTokens().length != 0) { 205 final int[] defaultJavadocTokens = getDefaultJavadocTokens(); 206 Arrays.sort(defaultJavadocTokens); 207 for (final int javadocToken : getRequiredJavadocTokens()) { 208 if (Arrays.binarySearch(defaultJavadocTokens, javadocToken) < 0) { 209 final String message = String.format(Locale.ROOT, 210 "Javadoc Token \"%s\" from required javadoc " 211 + "tokens was not found in default " 212 + "javadoc tokens list in check %s", 213 javadocToken, getClass().getName()); 214 throw new IllegalStateException(message); 215 } 216 } 217 } 218 } 219 220 /** 221 * Called before the starting to process a tree. 222 * @param rootAst 223 * the root of the tree 224 * @noinspection WeakerAccess 225 */ 226 public void beginJavadocTree(DetailNode rootAst) { 227 // No code by default, should be overridden only by demand at subclasses 228 } 229 230 /** 231 * Called after finished processing a tree. 232 * @param rootAst 233 * the root of the tree 234 * @noinspection WeakerAccess 235 */ 236 public void finishJavadocTree(DetailNode rootAst) { 237 // No code by default, should be overridden only by demand at subclasses 238 } 239 240 /** 241 * Called after all the child nodes have been process. 242 * @param ast 243 * the token leaving 244 */ 245 public void leaveJavadocToken(DetailNode ast) { 246 // No code by default, should be overridden only by demand at subclasses 247 } 248 249 /** 250 * Defined final to not allow JavadocChecks to change default tokens. 251 * @return default tokens 252 */ 253 @Override 254 public final int[] getDefaultTokens() { 255 return new int[] {TokenTypes.BLOCK_COMMENT_BEGIN }; 256 } 257 258 @Override 259 public final int[] getAcceptableTokens() { 260 return getDefaultTokens(); 261 } 262 263 @Override 264 public final int[] getRequiredTokens() { 265 return getDefaultTokens(); 266 } 267 268 /** 269 * Defined final because all JavadocChecks require comment nodes. 270 * @return true 271 */ 272 @Override 273 public final boolean isCommentNodesRequired() { 274 return true; 275 } 276 277 @Override 278 public final void beginTree(DetailAST rootAST) { 279 TREE_CACHE.get().clear(); 280 } 281 282 @Override 283 public final void finishTree(DetailAST rootAST) { 284 TREE_CACHE.get().clear(); 285 } 286 287 @Override 288 public final void visitToken(DetailAST blockCommentNode) { 289 if (JavadocUtils.isJavadocComment(blockCommentNode)) { 290 // store as field, to share with child Checks 291 context.get().blockCommentAst = blockCommentNode; 292 293 final String treeCacheKey = blockCommentNode.getLineNo() + ":" 294 + blockCommentNode.getColumnNo(); 295 296 final ParseStatus result; 297 298 if (TREE_CACHE.get().containsKey(treeCacheKey)) { 299 result = TREE_CACHE.get().get(treeCacheKey); 300 } 301 else { 302 result = context.get().parser 303 .parseJavadocAsDetailNode(blockCommentNode); 304 TREE_CACHE.get().put(treeCacheKey, result); 305 } 306 307 if (result.getParseErrorMessage() == null) { 308 if (acceptJavadocWithNonTightHtml() || !result.isNonTight()) { 309 processTree(result.getTree()); 310 } 311 312 if (violateExecutionOnNonTightHtml && result.isNonTight()) { 313 log(result.getFirstNonTightHtmlTag().getLine(), 314 JavadocDetailNodeParser.MSG_UNCLOSED_HTML_TAG, 315 result.getFirstNonTightHtmlTag().getText()); 316 } 317 } 318 else { 319 final ParseErrorMessage parseErrorMessage = result.getParseErrorMessage(); 320 log(parseErrorMessage.getLineNumber(), 321 parseErrorMessage.getMessageKey(), 322 parseErrorMessage.getMessageArguments()); 323 } 324 } 325 326 } 327 328 /** 329 * Getter for block comment in Java language syntax tree. 330 * @return A block comment in the syntax tree. 331 */ 332 protected DetailAST getBlockCommentAst() { 333 return context.get().blockCommentAst; 334 } 335 336 /** 337 * Processes JavadocAST tree notifying Check. 338 * @param root 339 * root of JavadocAST tree. 340 */ 341 private void processTree(DetailNode root) { 342 beginJavadocTree(root); 343 walk(root); 344 finishJavadocTree(root); 345 } 346 347 /** 348 * Processes a node calling Check at interested nodes. 349 * @param root 350 * the root of tree for process 351 */ 352 private void walk(DetailNode root) { 353 DetailNode curNode = root; 354 while (curNode != null) { 355 boolean waitsForProcessing = shouldBeProcessed(curNode); 356 357 if (waitsForProcessing) { 358 visitJavadocToken(curNode); 359 } 360 DetailNode toVisit = JavadocUtils.getFirstChild(curNode); 361 while (curNode != null && toVisit == null) { 362 363 if (waitsForProcessing) { 364 leaveJavadocToken(curNode); 365 } 366 367 toVisit = JavadocUtils.getNextSibling(curNode); 368 if (toVisit == null) { 369 curNode = curNode.getParent(); 370 if (curNode != null) { 371 waitsForProcessing = shouldBeProcessed(curNode); 372 } 373 } 374 } 375 curNode = toVisit; 376 } 377 } 378 379 /** 380 * Checks whether the current node should be processed by the check. 381 * @param curNode current node. 382 * @return true if the current node should be processed by the check. 383 */ 384 private boolean shouldBeProcessed(DetailNode curNode) { 385 return javadocTokens.contains(curNode.getType()); 386 } 387 388 /** 389 * The file context holder. 390 */ 391 private static class FileContext { 392 /** 393 * Parses content of Javadoc comment as DetailNode tree. 394 */ 395 private final JavadocDetailNodeParser parser = new JavadocDetailNodeParser(); 396 397 /** 398 * DetailAST node of considered Javadoc comment that is just a block comment 399 * in Java language syntax tree. 400 */ 401 private DetailAST blockCommentAst; 402 } 403}