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