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