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