View Javadoc
1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2018 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  ////////////////////////////////////////////////////////////////////////////////
19  
20  package com.puppycrawl.tools.checkstyle.checks.javadoc;
21  
22  import java.util.Arrays;
23  import java.util.Collections;
24  import java.util.Map;
25  import java.util.stream.Collectors;
26  
27  import com.puppycrawl.tools.checkstyle.api.DetailAST;
28  import com.puppycrawl.tools.checkstyle.api.Scope;
29  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
30  import com.puppycrawl.tools.checkstyle.utils.ScopeUtils;
31  
32  /**
33   * This enum defines the various Javadoc tags and there properties.
34   *
35   * <p>
36   * This class was modeled after documentation located at
37   * <a href="https://docs.oracle.com/javase/8/docs/technotes/tools/windows/javadoc.html">
38   * javadoc</a>
39   *
40   * and
41   *
42   * <a href="http://www.oracle.com/technetwork/java/javase/documentation/index-137868.html">
43   * how to write</a>.
44   * </p>
45   *
46   * <p>
47   * Some of this documentation was a little incomplete (ex: valid placement of
48   * code, value, and literal tags).
49   * </p>
50   *
51   * <p>
52   * Whenever an inconsistency was found the author's judgment was used.
53   * </p>
54   *
55   * <p>
56   * For now, the number of required/optional tag arguments are not included
57   * because some Javadoc tags have very complex rules for determining this
58   * (ex: {@code {@value}} tag).
59   * </p>
60   *
61   * <p>
62   * Also, the {@link #isValidOn(DetailAST) isValidOn} method does not consider
63   * classes defined in a local code block (method, init block, etc.).
64   * </p>
65   *
66   * @author Travis Schneeberger
67   */
68  public enum JavadocTagInfo {
69  
70      /**
71       * {@code @author}.
72       */
73      AUTHOR("@author", "author", Type.BLOCK) {
74  
75          @Override
76          public boolean isValidOn(final DetailAST ast) {
77              final int astType = ast.getType();
78              return astType == TokenTypes.PACKAGE_DEF
79                  || astType == TokenTypes.CLASS_DEF
80                  || astType == TokenTypes.INTERFACE_DEF
81                  || astType == TokenTypes.ENUM_DEF
82                  || astType == TokenTypes.ANNOTATION_DEF;
83          }
84  
85      },
86  
87      /**
88       * {@code {@code}}.
89       */
90      CODE("{@code}", "code", Type.INLINE) {
91  
92          @Override
93          public boolean isValidOn(final DetailAST ast) {
94              final int astType = ast.getType();
95              return Arrays.binarySearch(DEF_TOKEN_TYPES, astType) >= 0
96                  && !ScopeUtils.isLocalVariableDef(ast);
97          }
98  
99      },
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 }