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