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