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