001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2020 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.getScopeFromMods(ast
153                    .findFirstToken(TokenTypes.MODIFIERS)) != Scope.PRIVATE;
154        }
155
156    },
157
158    /**
159     * {@code {@link}}.
160     */
161    LINK("{@link}", "link", Type.INLINE) {
162
163        @Override
164        public boolean isValidOn(final DetailAST ast) {
165            final int astType = ast.getType();
166            return Arrays.binarySearch(DEF_TOKEN_TYPES, astType) >= 0
167                && !ScopeUtil.isLocalVariableDef(ast);
168        }
169
170    },
171
172    /**
173     * {@code {@linkplain}}.
174     */
175    LINKPLAIN("{@linkplain}", "linkplain", Type.INLINE) {
176
177        @Override
178        public boolean isValidOn(final DetailAST ast) {
179            final int astType = ast.getType();
180            return Arrays.binarySearch(DEF_TOKEN_TYPES, astType) >= 0
181                && !ScopeUtil.isLocalVariableDef(ast);
182        }
183
184    },
185
186    /**
187     * {@code {@literal}}.
188     */
189    LITERAL("{@literal}", "literal", Type.INLINE) {
190
191        @Override
192        public boolean isValidOn(final DetailAST ast) {
193            final int astType = ast.getType();
194            return Arrays.binarySearch(DEF_TOKEN_TYPES, astType) >= 0
195                && !ScopeUtil.isLocalVariableDef(ast);
196        }
197
198    },
199
200    /**
201     * {@code @param}.
202     */
203    PARAM("@param", "param", Type.BLOCK) {
204
205        @Override
206        public boolean isValidOn(final DetailAST ast) {
207            final int astType = ast.getType();
208            return astType == TokenTypes.CLASS_DEF
209                || astType == TokenTypes.INTERFACE_DEF
210                || astType == TokenTypes.METHOD_DEF
211                || astType == TokenTypes.CTOR_DEF;
212        }
213
214    },
215
216    /**
217     * {@code @return}.
218     */
219    RETURN("@return", "return", Type.BLOCK) {
220
221        @Override
222        public boolean isValidOn(final DetailAST ast) {
223            final int astType = ast.getType();
224            final DetailAST returnType = ast.findFirstToken(TokenTypes.TYPE);
225
226            return astType == TokenTypes.METHOD_DEF
227                && returnType.getFirstChild().getType() != TokenTypes.LITERAL_VOID;
228        }
229
230    },
231
232    /**
233     * {@code @see}.
234     */
235    SEE("@see", "see", Type.BLOCK) {
236
237        @Override
238        public boolean isValidOn(final DetailAST ast) {
239            final int astType = ast.getType();
240            return Arrays.binarySearch(DEF_TOKEN_TYPES, astType) >= 0
241                && !ScopeUtil.isLocalVariableDef(ast);
242        }
243
244    },
245
246    /**
247     * {@code @serial}.
248     */
249    SERIAL("@serial", "serial", Type.BLOCK) {
250
251        @Override
252        public boolean isValidOn(final DetailAST ast) {
253            final int astType = ast.getType();
254
255            return astType == TokenTypes.VARIABLE_DEF
256                && !ScopeUtil.isLocalVariableDef(ast);
257        }
258
259    },
260
261    /**
262     * {@code @serialData}.
263     */
264    SERIAL_DATA("@serialData", "serialData", Type.BLOCK) {
265
266        @Override
267        public boolean isValidOn(final DetailAST ast) {
268            final int astType = ast.getType();
269            final DetailAST methodNameAst = ast.findFirstToken(TokenTypes.IDENT);
270            final String methodName = methodNameAst.getText();
271
272            return astType == TokenTypes.METHOD_DEF
273                && ("writeObject".equals(methodName)
274                    || "readObject".equals(methodName)
275                    || "writeExternal".equals(methodName)
276                    || "readExternal".equals(methodName)
277                    || "writeReplace".equals(methodName)
278                    || "readResolve".equals(methodName));
279        }
280
281    },
282
283    /**
284     * {@code @serialField}.
285     */
286    SERIAL_FIELD("@serialField", "serialField", Type.BLOCK) {
287
288        @Override
289        public boolean isValidOn(final DetailAST ast) {
290            final int astType = ast.getType();
291            final DetailAST varType = ast.findFirstToken(TokenTypes.TYPE);
292
293            return astType == TokenTypes.VARIABLE_DEF
294                && varType.getFirstChild().getType() == TokenTypes.ARRAY_DECLARATOR
295                && "ObjectStreamField".equals(varType.getFirstChild().getText());
296        }
297
298    },
299
300    /**
301     * {@code @since}.
302     */
303    SINCE("@since", "since", Type.BLOCK) {
304
305        @Override
306        public boolean isValidOn(final DetailAST ast) {
307            final int astType = ast.getType();
308            return Arrays.binarySearch(DEF_TOKEN_TYPES, astType) >= 0
309                && !ScopeUtil.isLocalVariableDef(ast);
310        }
311
312    },
313
314    /**
315     * {@code @throws}.
316     */
317    THROWS("@throws", "throws", Type.BLOCK) {
318
319        @Override
320        public boolean isValidOn(final DetailAST ast) {
321            final int astType = ast.getType();
322            return astType == TokenTypes.METHOD_DEF
323                || astType == TokenTypes.CTOR_DEF;
324        }
325
326    },
327
328    /**
329     * {@code {@value}}.
330     */
331    VALUE("{@value}", "value", Type.INLINE) {
332
333        @Override
334        public boolean isValidOn(final DetailAST ast) {
335            final int astType = ast.getType();
336            return Arrays.binarySearch(DEF_TOKEN_TYPES, astType) >= 0
337                && !ScopeUtil.isLocalVariableDef(ast);
338        }
339
340    },
341
342    /**
343     * {@code @version}.
344     */
345    VERSION("@version", "version", Type.BLOCK) {
346
347        @Override
348        public boolean isValidOn(final DetailAST ast) {
349            final int astType = ast.getType();
350            return astType == TokenTypes.PACKAGE_DEF
351                || TokenUtil.isTypeDeclaration(astType);
352        }
353
354    };
355
356    /** Default token types for DEPRECATED Javadoc tag.*/
357    private static final int[] DEF_TOKEN_TYPES_DEPRECATED = {
358        TokenTypes.CTOR_DEF,
359        TokenTypes.METHOD_DEF,
360        TokenTypes.VARIABLE_DEF,
361        TokenTypes.CLASS_DEF,
362        TokenTypes.INTERFACE_DEF,
363        TokenTypes.ENUM_DEF,
364        TokenTypes.ENUM_CONSTANT_DEF,
365        TokenTypes.ANNOTATION_DEF,
366        TokenTypes.ANNOTATION_FIELD_DEF,
367    };
368
369    /** Default token types.*/
370    private static final int[] DEF_TOKEN_TYPES = {
371        TokenTypes.CTOR_DEF,
372        TokenTypes.METHOD_DEF,
373        TokenTypes.VARIABLE_DEF,
374        TokenTypes.CLASS_DEF,
375        TokenTypes.INTERFACE_DEF,
376        TokenTypes.PACKAGE_DEF,
377        TokenTypes.ENUM_DEF,
378        TokenTypes.ANNOTATION_DEF,
379    };
380
381    /** Holds tag text to tag enum mappings. **/
382    private static final Map<String, JavadocTagInfo> TEXT_TO_TAG;
383    /** Holds tag name to tag enum mappings. **/
384    private static final Map<String, JavadocTagInfo> NAME_TO_TAG;
385
386    static {
387        TEXT_TO_TAG = Collections.unmodifiableMap(Arrays.stream(values())
388            .collect(Collectors.toMap(JavadocTagInfo::getText, Function.identity())));
389        NAME_TO_TAG = Collections.unmodifiableMap(Arrays.stream(values())
390            .collect(Collectors.toMap(JavadocTagInfo::getName, Function.identity())));
391
392        // Arrays sorting for binary search
393        Arrays.sort(DEF_TOKEN_TYPES);
394        Arrays.sort(DEF_TOKEN_TYPES_DEPRECATED);
395    }
396
397    /** The tag text. **/
398    private final String text;
399    /** The tag name. **/
400    private final String name;
401    /** The tag type. **/
402    private final Type type;
403
404    /**
405     * Sets the various properties of a Javadoc tag.
406     *
407     * @param text the tag text
408     * @param name the tag name
409     * @param type the type of tag
410     */
411    JavadocTagInfo(final String text, final String name,
412        final Type type) {
413        this.text = text;
414        this.name = name;
415        this.type = type;
416    }
417
418    /**
419     * Checks if a particular Javadoc tag is valid within a Javadoc block of a
420     * given AST.
421     *
422     * <p>
423     * If passing in a DetailAST representing a non-void METHOD_DEF
424     * {@code true } would be returned. If passing in a DetailAST
425     * representing a CLASS_DEF {@code false } would be returned because
426     * CLASS_DEF's cannot return a value.
427     * </p>
428     *
429     * @param ast the AST representing a type that can be Javadoc'd
430     * @return true if tag is valid.
431     */
432    public abstract boolean isValidOn(DetailAST ast);
433
434    /**
435     * Gets the tag text.
436     *
437     * @return the tag text
438     */
439    public String getText() {
440        return text;
441    }
442
443    /**
444     * Gets the tag name.
445     *
446     * @return the tag name
447     */
448    public String getName() {
449        return name;
450    }
451
452    /**
453     * Gets the Tag type defined by {@link Type Type}.
454     *
455     * @return the Tag type
456     */
457    public Type getType() {
458        return type;
459    }
460
461    /**
462     * Returns a JavadocTag from the tag text.
463     *
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     *
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     *
511     * @param name the tag name to check.
512     * @return whether the provided name is for a valid tag.
513     */
514    public static boolean isValidName(final String name) {
515        return NAME_TO_TAG.containsKey(name);
516    }
517
518    @Override
519    public String toString() {
520        return "text [" + text + "] name [" + name
521            + "] type [" + type + "]";
522    }
523
524    /**
525     * The Javadoc Type.
526     *
527     * <p>For example a {@code @param} tag is a block tag while a
528     * {@code {@link}} tag is a inline tag.
529     *
530     */
531    public enum Type {
532
533        /** Block type. **/
534        BLOCK,
535
536        /** Inline type. **/
537        INLINE
538
539    }
540
541}