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.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="http://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 @Override 075 public boolean isValidOn(final DetailAST ast) { 076 final int astType = ast.getType(); 077 return astType == TokenTypes.PACKAGE_DEF 078 || astType == TokenTypes.CLASS_DEF 079 || astType == TokenTypes.INTERFACE_DEF 080 || astType == TokenTypes.ENUM_DEF 081 || astType == TokenTypes.ANNOTATION_DEF; 082 } 083 }, 084 085 /** 086 * {@code {@code}}. 087 */ 088 CODE("{@code}", "code", Type.INLINE) { 089 @Override 090 public boolean isValidOn(final DetailAST ast) { 091 final int astType = ast.getType(); 092 return Arrays.binarySearch(DEF_TOKEN_TYPES, astType) >= 0 093 && !ScopeUtils.isLocalVariableDef(ast); 094 } 095 }, 096 097 /** 098 * {@code {@docRoot}}. 099 */ 100 DOC_ROOT("{@docRoot}", "docRoot", Type.INLINE) { 101 @Override 102 public boolean isValidOn(final DetailAST ast) { 103 final int astType = ast.getType(); 104 return Arrays.binarySearch(DEF_TOKEN_TYPES, astType) >= 0 105 && !ScopeUtils.isLocalVariableDef(ast); 106 } 107 }, 108 109 /** 110 * {@code @deprecated}. 111 */ 112 DEPRECATED("@deprecated", "deprecated", Type.BLOCK) { 113 @Override 114 public boolean isValidOn(final DetailAST ast) { 115 final int astType = ast.getType(); 116 return Arrays.binarySearch(DEF_TOKEN_TYPES_DEPRECATED, astType) >= 0 117 && !ScopeUtils.isLocalVariableDef(ast); 118 } 119 }, 120 121 /** 122 * {@code @exception}. 123 */ 124 EXCEPTION("@exception", "exception", Type.BLOCK) { 125 @Override 126 public boolean isValidOn(final DetailAST ast) { 127 final int astType = ast.getType(); 128 return astType == TokenTypes.METHOD_DEF || astType == TokenTypes.CTOR_DEF; 129 } 130 }, 131 132 /** 133 * {@code {@inheritDoc}}. 134 */ 135 INHERIT_DOC("{@inheritDoc}", "inheritDoc", Type.INLINE) { 136 @Override 137 public boolean isValidOn(final DetailAST ast) { 138 final int astType = ast.getType(); 139 140 return astType == TokenTypes.METHOD_DEF 141 && !ast.branchContains(TokenTypes.LITERAL_STATIC) 142 && ScopeUtils.getScopeFromMods(ast 143 .findFirstToken(TokenTypes.MODIFIERS)) != Scope.PRIVATE; 144 } 145 }, 146 147 /** 148 * {@code {@link}}. 149 */ 150 LINK("{@link}", "link", Type.INLINE) { 151 @Override 152 public boolean isValidOn(final DetailAST ast) { 153 final int astType = ast.getType(); 154 return Arrays.binarySearch(DEF_TOKEN_TYPES, astType) >= 0 155 && !ScopeUtils.isLocalVariableDef(ast); 156 } 157 }, 158 159 /** 160 * {@code {@linkplain}}. 161 */ 162 LINKPLAIN("{@linkplain}", "linkplain", Type.INLINE) { 163 @Override 164 public boolean isValidOn(final DetailAST ast) { 165 final int astType = ast.getType(); 166 return Arrays.binarySearch(DEF_TOKEN_TYPES, astType) >= 0 167 && !ScopeUtils.isLocalVariableDef(ast); 168 } 169 }, 170 171 /** 172 * {@code {@literal}}. 173 */ 174 LITERAL("{@literal}", "literal", Type.INLINE) { 175 @Override 176 public boolean isValidOn(final DetailAST ast) { 177 final int astType = ast.getType(); 178 return Arrays.binarySearch(DEF_TOKEN_TYPES, astType) >= 0 179 && !ScopeUtils.isLocalVariableDef(ast); 180 } 181 }, 182 183 /** 184 * {@code @param}. 185 */ 186 PARAM("@param", "param", Type.BLOCK) { 187 @Override 188 public boolean isValidOn(final DetailAST ast) { 189 final int astType = ast.getType(); 190 return astType == TokenTypes.CLASS_DEF 191 || astType == TokenTypes.INTERFACE_DEF 192 || astType == TokenTypes.METHOD_DEF 193 || astType == TokenTypes.CTOR_DEF; 194 } 195 }, 196 197 /** 198 * {@code @return}. 199 */ 200 RETURN("@return", "return", Type.BLOCK) { 201 @Override 202 public boolean isValidOn(final DetailAST ast) { 203 final int astType = ast.getType(); 204 final DetailAST returnType = ast.findFirstToken(TokenTypes.TYPE); 205 206 return astType == TokenTypes.METHOD_DEF 207 && returnType.getFirstChild().getType() != TokenTypes.LITERAL_VOID; 208 209 } 210 }, 211 212 /** 213 * {@code @see}. 214 */ 215 SEE("@see", "see", Type.BLOCK) { 216 @Override 217 public boolean isValidOn(final DetailAST ast) { 218 final int astType = ast.getType(); 219 return Arrays.binarySearch(DEF_TOKEN_TYPES, astType) >= 0 220 && !ScopeUtils.isLocalVariableDef(ast); 221 } 222 }, 223 224 /** 225 * {@code @serial}. 226 */ 227 SERIAL("@serial", "serial", Type.BLOCK) { 228 @Override 229 public boolean isValidOn(final DetailAST ast) { 230 final int astType = ast.getType(); 231 232 return astType == TokenTypes.VARIABLE_DEF 233 && !ScopeUtils.isLocalVariableDef(ast); 234 } 235 }, 236 237 /** 238 * {@code @serialData}. 239 */ 240 SERIAL_DATA("@serialData", "serialData", Type.BLOCK) { 241 @Override 242 public boolean isValidOn(final DetailAST ast) { 243 final int astType = ast.getType(); 244 final DetailAST methodNameAst = ast.findFirstToken(TokenTypes.IDENT); 245 final String methodName = methodNameAst.getText(); 246 247 return astType == TokenTypes.METHOD_DEF 248 && ("writeObject".equals(methodName) 249 || "readObject".equals(methodName) 250 || "writeExternal".equals(methodName) 251 || "readExternal".equals(methodName) 252 || "writeReplace".equals(methodName) 253 || "readResolve".equals(methodName)); 254 } 255 }, 256 257 /** 258 * {@code @serialField}. 259 */ 260 SERIAL_FIELD("@serialField", "serialField", Type.BLOCK) { 261 @Override 262 public boolean isValidOn(final DetailAST ast) { 263 final int astType = ast.getType(); 264 final DetailAST varType = ast.findFirstToken(TokenTypes.TYPE); 265 266 return astType == TokenTypes.VARIABLE_DEF 267 && varType.getFirstChild().getType() == TokenTypes.ARRAY_DECLARATOR 268 && "ObjectStreamField".equals(varType.getFirstChild().getText()); 269 } 270 }, 271 272 /** 273 * {@code @since}. 274 */ 275 SINCE("@since", "since", Type.BLOCK) { 276 @Override 277 public boolean isValidOn(final DetailAST ast) { 278 final int astType = ast.getType(); 279 return Arrays.binarySearch(DEF_TOKEN_TYPES, astType) >= 0 280 && !ScopeUtils.isLocalVariableDef(ast); 281 } 282 }, 283 284 /** 285 * {@code @throws}. 286 */ 287 THROWS("@throws", "throws", Type.BLOCK) { 288 @Override 289 public boolean isValidOn(final DetailAST ast) { 290 final int astType = ast.getType(); 291 return astType == TokenTypes.METHOD_DEF 292 || astType == TokenTypes.CTOR_DEF; 293 } 294 }, 295 296 /** 297 * {@code {@value}}. 298 */ 299 VALUE("{@value}", "value", Type.INLINE) { 300 @Override 301 public boolean isValidOn(final DetailAST ast) { 302 final int astType = ast.getType(); 303 return Arrays.binarySearch(DEF_TOKEN_TYPES, astType) >= 0 304 && !ScopeUtils.isLocalVariableDef(ast); 305 } 306 }, 307 308 /** 309 * {@code @version}. 310 */ 311 VERSION("@version", "version", Type.BLOCK) { 312 @Override 313 public boolean isValidOn(final DetailAST ast) { 314 final int astType = ast.getType(); 315 return astType == TokenTypes.PACKAGE_DEF 316 || astType == TokenTypes.CLASS_DEF 317 || astType == TokenTypes.INTERFACE_DEF 318 || astType == TokenTypes.ENUM_DEF 319 || astType == TokenTypes.ANNOTATION_DEF; 320 } 321 }; 322 323 /** Default token types for DEPRECATED Javadoc tag.*/ 324 private static final int[] DEF_TOKEN_TYPES_DEPRECATED = { 325 TokenTypes.CTOR_DEF, 326 TokenTypes.METHOD_DEF, 327 TokenTypes.VARIABLE_DEF, 328 TokenTypes.CLASS_DEF, 329 TokenTypes.INTERFACE_DEF, 330 TokenTypes.ENUM_DEF, 331 TokenTypes.ENUM_CONSTANT_DEF, 332 TokenTypes.ANNOTATION_DEF, 333 TokenTypes.ANNOTATION_FIELD_DEF, 334 }; 335 336 /** Default token types.*/ 337 private static final int[] DEF_TOKEN_TYPES = { 338 TokenTypes.CTOR_DEF, 339 TokenTypes.METHOD_DEF, 340 TokenTypes.VARIABLE_DEF, 341 TokenTypes.CLASS_DEF, 342 TokenTypes.INTERFACE_DEF, 343 TokenTypes.PACKAGE_DEF, 344 TokenTypes.ENUM_DEF, 345 TokenTypes.ANNOTATION_DEF, 346 }; 347 348 /** Holds tag text to tag enum mappings. **/ 349 private static final Map<String, JavadocTagInfo> TEXT_TO_TAG; 350 /** Holds tag name to tag enum mappings. **/ 351 private static final Map<String, JavadocTagInfo> NAME_TO_TAG; 352 353 static { 354 TEXT_TO_TAG = Collections.unmodifiableMap(Arrays.stream(JavadocTagInfo.values()) 355 .collect(Collectors.toMap(JavadocTagInfo::getText, tagText -> tagText))); 356 NAME_TO_TAG = Collections.unmodifiableMap(Arrays.stream(JavadocTagInfo.values()) 357 .collect(Collectors.toMap(JavadocTagInfo::getName, tagName -> tagName))); 358 359 //Arrays sorting for binary search 360 Arrays.sort(DEF_TOKEN_TYPES); 361 Arrays.sort(DEF_TOKEN_TYPES_DEPRECATED); 362 } 363 364 /** The tag text. **/ 365 private final String text; 366 /** The tag name. **/ 367 private final String name; 368 /** The tag type. **/ 369 private final Type type; 370 371 /** 372 * Sets the various properties of a Javadoc tag. 373 * 374 * @param text the tag text 375 * @param name the tag name 376 * @param type the type of tag 377 */ 378 JavadocTagInfo(final String text, final String name, 379 final Type type) { 380 this.text = text; 381 this.name = name; 382 this.type = type; 383 } 384 385 /** 386 * Checks if a particular Javadoc tag is valid within a Javadoc block of a 387 * given AST. 388 * 389 * <p> 390 * If passing in a DetailAST representing a non-void METHOD_DEF 391 * {@code true } would be returned. If passing in a DetailAST 392 * representing a CLASS_DEF {@code false } would be returned because 393 * CLASS_DEF's cannot return a value. 394 * </p> 395 * 396 * @param ast the AST representing a type that can be Javadoc'd 397 * @return true if tag is valid. 398 */ 399 public abstract boolean isValidOn(DetailAST ast); 400 401 /** 402 * Gets the tag text. 403 * @return the tag text 404 */ 405 public String getText() { 406 return text; 407 } 408 409 /** 410 * Gets the tag name. 411 * @return the tag name 412 */ 413 public String getName() { 414 return name; 415 } 416 417 /** 418 * Gets the Tag type defined by {@link Type Type}. 419 * @return the Tag type 420 */ 421 public Type getType() { 422 return type; 423 } 424 425 /** 426 * Returns a JavadocTag from the tag text. 427 * @param text String representing the tag text 428 * @return Returns a JavadocTag type from a String representing the tag 429 * @throws NullPointerException if the text is null 430 * @throws IllegalArgumentException if the text is not a valid tag 431 */ 432 public static JavadocTagInfo fromText(final String text) { 433 if (text == null) { 434 throw new IllegalArgumentException("the text is null"); 435 } 436 437 final JavadocTagInfo tag = TEXT_TO_TAG.get(text); 438 439 if (tag == null) { 440 throw new IllegalArgumentException("the text [" + text 441 + "] is not a valid Javadoc tag text"); 442 } 443 444 return tag; 445 } 446 447 /** 448 * Returns a JavadocTag from the tag name. 449 * @param name String name of the tag 450 * @return Returns a JavadocTag type from a String representing the tag 451 * @throws NullPointerException if the text is null 452 * @throws IllegalArgumentException if the text is not a valid tag. The name 453 * can be checked using {@link JavadocTagInfo#isValidName(String)} 454 */ 455 public static JavadocTagInfo fromName(final String name) { 456 if (name == null) { 457 throw new IllegalArgumentException("the name is null"); 458 } 459 460 final JavadocTagInfo tag = NAME_TO_TAG.get(name); 461 462 if (tag == null) { 463 throw new IllegalArgumentException("the name [" + name 464 + "] is not a valid Javadoc tag name"); 465 } 466 467 return tag; 468 } 469 470 /** 471 * Returns whether the provided name is for a valid tag. 472 * @param name the tag name to check. 473 * @return whether the provided name is for a valid tag. 474 */ 475 public static boolean isValidName(final String name) { 476 return NAME_TO_TAG.containsKey(name); 477 } 478 479 @Override 480 public String toString() { 481 return "text [" + text + "] name [" + name 482 + "] type [" + type + "]"; 483 } 484 485 /** 486 * The Javadoc Type. 487 * 488 * <p>For example a {@code @param} tag is a block tag while a 489 * {@code {@link}} tag is a inline tag. 490 * 491 * @author Travis Schneeberger 492 */ 493 public enum Type { 494 /** Block type. **/ 495 BLOCK, 496 497 /** Inline type. **/ 498 INLINE 499 } 500}