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