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