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