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