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