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.Collections; 024import java.util.Map; 025import java.util.stream.Collectors; 026 027import com.puppycrawl.tools.checkstyle.api.DetailAST; 028import com.puppycrawl.tools.checkstyle.api.Scope; 029import com.puppycrawl.tools.checkstyle.api.TokenTypes; 030import com.puppycrawl.tools.checkstyle.utils.ScopeUtils; 031 032/** 033 * This enum defines the various Javadoc tags and there properties. 034 * 035 * <p> 036 * This class was modeled after documentation located at 037 * <a href="https://docs.oracle.com/javase/8/docs/technotes/tools/windows/javadoc.html"> 038 * javadoc</a> 039 * 040 * and 041 * 042 * <a href="http://www.oracle.com/technetwork/java/javase/documentation/index-137868.html"> 043 * how to write</a>. 044 * </p> 045 * 046 * <p> 047 * Some of this documentation was a little incomplete (ex: valid placement of 048 * code, value, and literal tags). 049 * </p> 050 * 051 * <p> 052 * Whenever an inconsistency was found the author's judgment was used. 053 * </p> 054 * 055 * <p> 056 * For now, the number of required/optional tag arguments are not included 057 * because some Javadoc tags have very complex rules for determining this 058 * (ex: {@code {@value}} tag). 059 * </p> 060 * 061 * <p> 062 * Also, the {@link #isValidOn(DetailAST) isValidOn} method does not consider 063 * classes defined in a local code block (method, init block, etc.). 064 * </p> 065 * 066 * @author Travis Schneeberger 067 */ 068public enum JavadocTagInfo { 069 070 /** 071 * {@code @author}. 072 */ 073 AUTHOR("@author", "author", Type.BLOCK) { 074 075 @Override 076 public boolean isValidOn(final DetailAST ast) { 077 final int astType = ast.getType(); 078 return astType == TokenTypes.PACKAGE_DEF 079 || astType == TokenTypes.CLASS_DEF 080 || astType == TokenTypes.INTERFACE_DEF 081 || astType == TokenTypes.ENUM_DEF 082 || astType == TokenTypes.ANNOTATION_DEF; 083 } 084 085 }, 086 087 /** 088 * {@code {@code}}. 089 */ 090 CODE("{@code}", "code", Type.INLINE) { 091 092 @Override 093 public boolean isValidOn(final DetailAST ast) { 094 final int astType = ast.getType(); 095 return Arrays.binarySearch(DEF_TOKEN_TYPES, astType) >= 0 096 && !ScopeUtils.isLocalVariableDef(ast); 097 } 098 099 }, 100 101 /** 102 * {@code {@docRoot}}. 103 */ 104 DOC_ROOT("{@docRoot}", "docRoot", Type.INLINE) { 105 106 @Override 107 public boolean isValidOn(final DetailAST ast) { 108 final int astType = ast.getType(); 109 return Arrays.binarySearch(DEF_TOKEN_TYPES, astType) >= 0 110 && !ScopeUtils.isLocalVariableDef(ast); 111 } 112 113 }, 114 115 /** 116 * {@code @deprecated}. 117 */ 118 DEPRECATED("@deprecated", "deprecated", Type.BLOCK) { 119 120 @Override 121 public boolean isValidOn(final DetailAST ast) { 122 final int astType = ast.getType(); 123 return Arrays.binarySearch(DEF_TOKEN_TYPES_DEPRECATED, astType) >= 0 124 && !ScopeUtils.isLocalVariableDef(ast); 125 } 126 127 }, 128 129 /** 130 * {@code @exception}. 131 */ 132 EXCEPTION("@exception", "exception", Type.BLOCK) { 133 134 @Override 135 public boolean isValidOn(final DetailAST ast) { 136 final int astType = ast.getType(); 137 return astType == TokenTypes.METHOD_DEF || astType == TokenTypes.CTOR_DEF; 138 } 139 140 }, 141 142 /** 143 * {@code {@inheritDoc}}. 144 */ 145 INHERIT_DOC("{@inheritDoc}", "inheritDoc", Type.INLINE) { 146 147 @Override 148 public boolean isValidOn(final DetailAST ast) { 149 final int astType = ast.getType(); 150 151 return astType == TokenTypes.METHOD_DEF 152 && ast.findFirstToken(TokenTypes.MODIFIERS) 153 .findFirstToken(TokenTypes.LITERAL_STATIC) == null 154 && ScopeUtils.getScopeFromMods(ast 155 .findFirstToken(TokenTypes.MODIFIERS)) != Scope.PRIVATE; 156 } 157 158 }, 159 160 /** 161 * {@code {@link}}. 162 */ 163 LINK("{@link}", "link", Type.INLINE) { 164 165 @Override 166 public boolean isValidOn(final DetailAST ast) { 167 final int astType = ast.getType(); 168 return Arrays.binarySearch(DEF_TOKEN_TYPES, astType) >= 0 169 && !ScopeUtils.isLocalVariableDef(ast); 170 } 171 172 }, 173 174 /** 175 * {@code {@linkplain}}. 176 */ 177 LINKPLAIN("{@linkplain}", "linkplain", Type.INLINE) { 178 179 @Override 180 public boolean isValidOn(final DetailAST ast) { 181 final int astType = ast.getType(); 182 return Arrays.binarySearch(DEF_TOKEN_TYPES, astType) >= 0 183 && !ScopeUtils.isLocalVariableDef(ast); 184 } 185 186 }, 187 188 /** 189 * {@code {@literal}}. 190 */ 191 LITERAL("{@literal}", "literal", Type.INLINE) { 192 193 @Override 194 public boolean isValidOn(final DetailAST ast) { 195 final int astType = ast.getType(); 196 return Arrays.binarySearch(DEF_TOKEN_TYPES, astType) >= 0 197 && !ScopeUtils.isLocalVariableDef(ast); 198 } 199 200 }, 201 202 /** 203 * {@code @param}. 204 */ 205 PARAM("@param", "param", Type.BLOCK) { 206 207 @Override 208 public boolean isValidOn(final DetailAST ast) { 209 final int astType = ast.getType(); 210 return astType == TokenTypes.CLASS_DEF 211 || astType == TokenTypes.INTERFACE_DEF 212 || astType == TokenTypes.METHOD_DEF 213 || astType == TokenTypes.CTOR_DEF; 214 } 215 216 }, 217 218 /** 219 * {@code @return}. 220 */ 221 RETURN("@return", "return", Type.BLOCK) { 222 223 @Override 224 public boolean isValidOn(final DetailAST ast) { 225 final int astType = ast.getType(); 226 final DetailAST returnType = ast.findFirstToken(TokenTypes.TYPE); 227 228 return astType == TokenTypes.METHOD_DEF 229 && returnType.getFirstChild().getType() != TokenTypes.LITERAL_VOID; 230 } 231 232 }, 233 234 /** 235 * {@code @see}. 236 */ 237 SEE("@see", "see", Type.BLOCK) { 238 239 @Override 240 public boolean isValidOn(final DetailAST ast) { 241 final int astType = ast.getType(); 242 return Arrays.binarySearch(DEF_TOKEN_TYPES, astType) >= 0 243 && !ScopeUtils.isLocalVariableDef(ast); 244 } 245 246 }, 247 248 /** 249 * {@code @serial}. 250 */ 251 SERIAL("@serial", "serial", Type.BLOCK) { 252 253 @Override 254 public boolean isValidOn(final DetailAST ast) { 255 final int astType = ast.getType(); 256 257 return astType == TokenTypes.VARIABLE_DEF 258 && !ScopeUtils.isLocalVariableDef(ast); 259 } 260 261 }, 262 263 /** 264 * {@code @serialData}. 265 */ 266 SERIAL_DATA("@serialData", "serialData", Type.BLOCK) { 267 268 @Override 269 public boolean isValidOn(final DetailAST ast) { 270 final int astType = ast.getType(); 271 final DetailAST methodNameAst = ast.findFirstToken(TokenTypes.IDENT); 272 final String methodName = methodNameAst.getText(); 273 274 return astType == TokenTypes.METHOD_DEF 275 && ("writeObject".equals(methodName) 276 || "readObject".equals(methodName) 277 || "writeExternal".equals(methodName) 278 || "readExternal".equals(methodName) 279 || "writeReplace".equals(methodName) 280 || "readResolve".equals(methodName)); 281 } 282 283 }, 284 285 /** 286 * {@code @serialField}. 287 */ 288 SERIAL_FIELD("@serialField", "serialField", Type.BLOCK) { 289 290 @Override 291 public boolean isValidOn(final DetailAST ast) { 292 final int astType = ast.getType(); 293 final DetailAST varType = ast.findFirstToken(TokenTypes.TYPE); 294 295 return astType == TokenTypes.VARIABLE_DEF 296 && varType.getFirstChild().getType() == TokenTypes.ARRAY_DECLARATOR 297 && "ObjectStreamField".equals(varType.getFirstChild().getText()); 298 } 299 300 }, 301 302 /** 303 * {@code @since}. 304 */ 305 SINCE("@since", "since", Type.BLOCK) { 306 307 @Override 308 public boolean isValidOn(final DetailAST ast) { 309 final int astType = ast.getType(); 310 return Arrays.binarySearch(DEF_TOKEN_TYPES, astType) >= 0 311 && !ScopeUtils.isLocalVariableDef(ast); 312 } 313 314 }, 315 316 /** 317 * {@code @throws}. 318 */ 319 THROWS("@throws", "throws", Type.BLOCK) { 320 321 @Override 322 public boolean isValidOn(final DetailAST ast) { 323 final int astType = ast.getType(); 324 return astType == TokenTypes.METHOD_DEF 325 || astType == TokenTypes.CTOR_DEF; 326 } 327 328 }, 329 330 /** 331 * {@code {@value}}. 332 */ 333 VALUE("{@value}", "value", Type.INLINE) { 334 335 @Override 336 public boolean isValidOn(final DetailAST ast) { 337 final int astType = ast.getType(); 338 return Arrays.binarySearch(DEF_TOKEN_TYPES, astType) >= 0 339 && !ScopeUtils.isLocalVariableDef(ast); 340 } 341 342 }, 343 344 /** 345 * {@code @version}. 346 */ 347 VERSION("@version", "version", Type.BLOCK) { 348 349 @Override 350 public boolean isValidOn(final DetailAST ast) { 351 final int astType = ast.getType(); 352 return astType == TokenTypes.PACKAGE_DEF 353 || astType == TokenTypes.CLASS_DEF 354 || astType == TokenTypes.INTERFACE_DEF 355 || astType == TokenTypes.ENUM_DEF 356 || astType == TokenTypes.ANNOTATION_DEF; 357 } 358 359 }; 360 361 /** Default token types for DEPRECATED Javadoc tag.*/ 362 private static final int[] DEF_TOKEN_TYPES_DEPRECATED = { 363 TokenTypes.CTOR_DEF, 364 TokenTypes.METHOD_DEF, 365 TokenTypes.VARIABLE_DEF, 366 TokenTypes.CLASS_DEF, 367 TokenTypes.INTERFACE_DEF, 368 TokenTypes.ENUM_DEF, 369 TokenTypes.ENUM_CONSTANT_DEF, 370 TokenTypes.ANNOTATION_DEF, 371 TokenTypes.ANNOTATION_FIELD_DEF, 372 }; 373 374 /** Default token types.*/ 375 private static final int[] DEF_TOKEN_TYPES = { 376 TokenTypes.CTOR_DEF, 377 TokenTypes.METHOD_DEF, 378 TokenTypes.VARIABLE_DEF, 379 TokenTypes.CLASS_DEF, 380 TokenTypes.INTERFACE_DEF, 381 TokenTypes.PACKAGE_DEF, 382 TokenTypes.ENUM_DEF, 383 TokenTypes.ANNOTATION_DEF, 384 }; 385 386 /** Holds tag text to tag enum mappings. **/ 387 private static final Map<String, JavadocTagInfo> TEXT_TO_TAG; 388 /** Holds tag name to tag enum mappings. **/ 389 private static final Map<String, JavadocTagInfo> NAME_TO_TAG; 390 391 static { 392 TEXT_TO_TAG = Collections.unmodifiableMap(Arrays.stream(JavadocTagInfo.values()) 393 .collect(Collectors.toMap(JavadocTagInfo::getText, tagText -> tagText))); 394 NAME_TO_TAG = Collections.unmodifiableMap(Arrays.stream(JavadocTagInfo.values()) 395 .collect(Collectors.toMap(JavadocTagInfo::getName, tagName -> tagName))); 396 397 //Arrays sorting for binary search 398 Arrays.sort(DEF_TOKEN_TYPES); 399 Arrays.sort(DEF_TOKEN_TYPES_DEPRECATED); 400 } 401 402 /** The tag text. **/ 403 private final String text; 404 /** The tag name. **/ 405 private final String name; 406 /** The tag type. **/ 407 private final Type type; 408 409 /** 410 * Sets the various properties of a Javadoc tag. 411 * 412 * @param text the tag text 413 * @param name the tag name 414 * @param type the type of tag 415 */ 416 JavadocTagInfo(final String text, final String name, 417 final Type type) { 418 this.text = text; 419 this.name = name; 420 this.type = type; 421 } 422 423 /** 424 * Checks if a particular Javadoc tag is valid within a Javadoc block of a 425 * given AST. 426 * 427 * <p> 428 * If passing in a DetailAST representing a non-void METHOD_DEF 429 * {@code true } would be returned. If passing in a DetailAST 430 * representing a CLASS_DEF {@code false } would be returned because 431 * CLASS_DEF's cannot return a value. 432 * </p> 433 * 434 * @param ast the AST representing a type that can be Javadoc'd 435 * @return true if tag is valid. 436 */ 437 public abstract boolean isValidOn(DetailAST ast); 438 439 /** 440 * Gets the tag text. 441 * @return the tag text 442 */ 443 public String getText() { 444 return text; 445 } 446 447 /** 448 * Gets the tag name. 449 * @return the tag name 450 */ 451 public String getName() { 452 return name; 453 } 454 455 /** 456 * Gets the Tag type defined by {@link Type Type}. 457 * @return the Tag type 458 */ 459 public Type getType() { 460 return type; 461 } 462 463 /** 464 * Returns a JavadocTag from the tag text. 465 * @param text String representing the tag text 466 * @return Returns a JavadocTag type from a String representing the tag 467 * @throws NullPointerException if the text is null 468 * @throws IllegalArgumentException if the text is not a valid tag 469 */ 470 public static JavadocTagInfo fromText(final String text) { 471 if (text == null) { 472 throw new IllegalArgumentException("the text is null"); 473 } 474 475 final JavadocTagInfo tag = TEXT_TO_TAG.get(text); 476 477 if (tag == null) { 478 throw new IllegalArgumentException("the text [" + text 479 + "] is not a valid Javadoc tag text"); 480 } 481 482 return tag; 483 } 484 485 /** 486 * Returns a JavadocTag from the tag name. 487 * @param name String name of the tag 488 * @return Returns a JavadocTag type from a String representing the tag 489 * @throws NullPointerException if the text is null 490 * @throws IllegalArgumentException if the text is not a valid tag. The name 491 * can be checked using {@link JavadocTagInfo#isValidName(String)} 492 */ 493 public static JavadocTagInfo fromName(final String name) { 494 if (name == null) { 495 throw new IllegalArgumentException("the name is null"); 496 } 497 498 final JavadocTagInfo tag = NAME_TO_TAG.get(name); 499 500 if (tag == null) { 501 throw new IllegalArgumentException("the name [" + name 502 + "] is not a valid Javadoc tag name"); 503 } 504 505 return tag; 506 } 507 508 /** 509 * Returns whether the provided name is for a valid tag. 510 * @param name the tag name to check. 511 * @return whether the provided name is for a valid tag. 512 */ 513 public static boolean isValidName(final String name) { 514 return NAME_TO_TAG.containsKey(name); 515 } 516 517 @Override 518 public String toString() { 519 return "text [" + text + "] name [" + name 520 + "] type [" + type + "]"; 521 } 522 523 /** 524 * The Javadoc Type. 525 * 526 * <p>For example a {@code @param} tag is a block tag while a 527 * {@code {@link}} tag is a inline tag. 528 * 529 * @author Travis Schneeberger 530 */ 531 public enum Type { 532 533 /** Block type. **/ 534 BLOCK, 535 536 /** Inline type. **/ 537 INLINE 538 539 } 540 541}