////////////////////////////////////////////////////////////////////////////////
// checkstyle: Checks Java source code for adherence to a set of rules.
// Copyright (C) 2001-2017 the original author or authors.
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
////////////////////////////////////////////////////////////////////////////////
package com.puppycrawl.tools.checkstyle.checks.javadoc;
import java.util.Arrays;
import java.util.Collections;
import java.util.Map;
import java.util.stream.Collectors;
import com.puppycrawl.tools.checkstyle.api.DetailAST;
import com.puppycrawl.tools.checkstyle.api.Scope;
import com.puppycrawl.tools.checkstyle.api.TokenTypes;
import com.puppycrawl.tools.checkstyle.utils.ScopeUtils;
/**
* This enum defines the various Javadoc tags and there properties.
*
* <p>
* This class was modeled after documentation located at
* <a href="http://docs.oracle.com/javase/8/docs/technotes/tools/windows/javadoc.html">
* javadoc</a>
*
* and
*
* <a href="http://www.oracle.com/technetwork/java/javase/documentation/index-137868.html">
* how to write</a>.
* </p>
*
* <p>
* Some of this documentation was a little incomplete (ex: valid placement of
* code, value, and literal tags).
* </p>
*
* <p>
* Whenever an inconsistency was found the author's judgment was used.
* </p>
*
* <p>
* For now, the number of required/optional tag arguments are not included
* because some Javadoc tags have very complex rules for determining this
* (ex: {@code {@value}} tag).
* </p>
*
* <p>
* Also, the {@link #isValidOn(DetailAST) isValidOn} method does not consider
* classes defined in a local code block (method, init block, etc.).
* </p>
*
* @author Travis Schneeberger
*/
public enum JavadocTagInfo {
/**
* {@code @author}.
*/
AUTHOR("@author", "author", Type.BLOCK) {
@Override
public boolean isValidOn(final DetailAST ast) {
final int astType = ast.getType();
return astType == TokenTypes.PACKAGE_DEF
|| astType == TokenTypes.CLASS_DEF
|| astType == TokenTypes.INTERFACE_DEF
|| astType == TokenTypes.ENUM_DEF
|| astType == TokenTypes.ANNOTATION_DEF;
}
},
/**
* {@code {@code}}.
*/
CODE("{@code}", "code", Type.INLINE) {
@Override
public boolean isValidOn(final DetailAST ast) {
final int astType = ast.getType();
return Arrays.binarySearch(DEF_TOKEN_TYPES, astType) >= 0
&& !ScopeUtils.isLocalVariableDef(ast);
}
},
/**
* {@code {@docRoot}}.
*/
DOC_ROOT("{@docRoot}", "docRoot", Type.INLINE) {
@Override
public boolean isValidOn(final DetailAST ast) {
final int astType = ast.getType();
return Arrays.binarySearch(DEF_TOKEN_TYPES, astType) >= 0
&& !ScopeUtils.isLocalVariableDef(ast);
}
},
/**
* {@code @deprecated}.
*/
DEPRECATED("@deprecated", "deprecated", Type.BLOCK) {
@Override
public boolean isValidOn(final DetailAST ast) {
final int astType = ast.getType();
return Arrays.binarySearch(DEF_TOKEN_TYPES_DEPRECATED, astType) >= 0
&& !ScopeUtils.isLocalVariableDef(ast);
}
},
/**
* {@code @exception}.
*/
EXCEPTION("@exception", "exception", Type.BLOCK) {
@Override
public boolean isValidOn(final DetailAST ast) {
final int astType = ast.getType();
return astType == TokenTypes.METHOD_DEF || astType == TokenTypes.CTOR_DEF;
}
},
/**
* {@code {@inheritDoc}}.
*/
INHERIT_DOC("{@inheritDoc}", "inheritDoc", Type.INLINE) {
@Override
public boolean isValidOn(final DetailAST ast) {
final int astType = ast.getType();
return astType == TokenTypes.METHOD_DEF
&& !ast.branchContains(TokenTypes.LITERAL_STATIC)
&& ScopeUtils.getScopeFromMods(ast
.findFirstToken(TokenTypes.MODIFIERS)) != Scope.PRIVATE;
}
},
/**
* {@code {@link}}.
*/
LINK("{@link}", "link", Type.INLINE) {
@Override
public boolean isValidOn(final DetailAST ast) {
final int astType = ast.getType();
return Arrays.binarySearch(DEF_TOKEN_TYPES, astType) >= 0
&& !ScopeUtils.isLocalVariableDef(ast);
}
},
/**
* {@code {@linkplain}}.
*/
LINKPLAIN("{@linkplain}", "linkplain", Type.INLINE) {
@Override
public boolean isValidOn(final DetailAST ast) {
final int astType = ast.getType();
return Arrays.binarySearch(DEF_TOKEN_TYPES, astType) >= 0
&& !ScopeUtils.isLocalVariableDef(ast);
}
},
/**
* {@code {@literal}}.
*/
LITERAL("{@literal}", "literal", Type.INLINE) {
@Override
public boolean isValidOn(final DetailAST ast) {
final int astType = ast.getType();
return Arrays.binarySearch(DEF_TOKEN_TYPES, astType) >= 0
&& !ScopeUtils.isLocalVariableDef(ast);
}
},
/**
* {@code @param}.
*/
PARAM("@param", "param", Type.BLOCK) {
@Override
public boolean isValidOn(final DetailAST ast) {
final int astType = ast.getType();
return astType == TokenTypes.CLASS_DEF
|| astType == TokenTypes.INTERFACE_DEF
|| astType == TokenTypes.METHOD_DEF
|| astType == TokenTypes.CTOR_DEF;
}
},
/**
* {@code @return}.
*/
RETURN("@return", "return", Type.BLOCK) {
@Override
public boolean isValidOn(final DetailAST ast) {
final int astType = ast.getType();
final DetailAST returnType = ast.findFirstToken(TokenTypes.TYPE);
return astType == TokenTypes.METHOD_DEF
&& returnType.getFirstChild().getType() != TokenTypes.LITERAL_VOID;
}
},
/**
* {@code @see}.
*/
SEE("@see", "see", Type.BLOCK) {
@Override
public boolean isValidOn(final DetailAST ast) {
final int astType = ast.getType();
return Arrays.binarySearch(DEF_TOKEN_TYPES, astType) >= 0
&& !ScopeUtils.isLocalVariableDef(ast);
}
},
/**
* {@code @serial}.
*/
SERIAL("@serial", "serial", Type.BLOCK) {
@Override
public boolean isValidOn(final DetailAST ast) {
final int astType = ast.getType();
return astType == TokenTypes.VARIABLE_DEF
&& !ScopeUtils.isLocalVariableDef(ast);
}
},
/**
* {@code @serialData}.
*/
SERIAL_DATA("@serialData", "serialData", Type.BLOCK) {
@Override
public boolean isValidOn(final DetailAST ast) {
final int astType = ast.getType();
final DetailAST methodNameAst = ast.findFirstToken(TokenTypes.IDENT);
final String methodName = methodNameAst.getText();
return astType == TokenTypes.METHOD_DEF
&& ("writeObject".equals(methodName)
|| "readObject".equals(methodName)
|| "writeExternal".equals(methodName)
|| "readExternal".equals(methodName)
|| "writeReplace".equals(methodName)
|| "readResolve".equals(methodName));
}
},
/**
* {@code @serialField}.
*/
SERIAL_FIELD("@serialField", "serialField", Type.BLOCK) {
@Override
public boolean isValidOn(final DetailAST ast) {
final int astType = ast.getType();
final DetailAST varType = ast.findFirstToken(TokenTypes.TYPE);
return astType == TokenTypes.VARIABLE_DEF
&& varType.getFirstChild().getType() == TokenTypes.ARRAY_DECLARATOR
&& "ObjectStreamField".equals(varType.getFirstChild().getText());
}
},
/**
* {@code @since}.
*/
SINCE("@since", "since", Type.BLOCK) {
@Override
public boolean isValidOn(final DetailAST ast) {
final int astType = ast.getType();
return Arrays.binarySearch(DEF_TOKEN_TYPES, astType) >= 0
&& !ScopeUtils.isLocalVariableDef(ast);
}
},
/**
* {@code @throws}.
*/
THROWS("@throws", "throws", Type.BLOCK) {
@Override
public boolean isValidOn(final DetailAST ast) {
final int astType = ast.getType();
return astType == TokenTypes.METHOD_DEF
|| astType == TokenTypes.CTOR_DEF;
}
},
/**
* {@code {@value}}.
*/
VALUE("{@value}", "value", Type.INLINE) {
@Override
public boolean isValidOn(final DetailAST ast) {
final int astType = ast.getType();
return Arrays.binarySearch(DEF_TOKEN_TYPES, astType) >= 0
&& !ScopeUtils.isLocalVariableDef(ast);
}
},
/**
* {@code @version}.
*/
VERSION("@version", "version", Type.BLOCK) {
@Override
public boolean isValidOn(final DetailAST ast) {
final int astType = ast.getType();
return astType == TokenTypes.PACKAGE_DEF
|| astType == TokenTypes.CLASS_DEF
|| astType == TokenTypes.INTERFACE_DEF
|| astType == TokenTypes.ENUM_DEF
|| astType == TokenTypes.ANNOTATION_DEF;
}
};
/** Default token types for DEPRECATED Javadoc tag.*/
private static final int[] DEF_TOKEN_TYPES_DEPRECATED = {
TokenTypes.CTOR_DEF,
TokenTypes.METHOD_DEF,
TokenTypes.VARIABLE_DEF,
TokenTypes.CLASS_DEF,
TokenTypes.INTERFACE_DEF,
TokenTypes.ENUM_DEF,
TokenTypes.ENUM_CONSTANT_DEF,
TokenTypes.ANNOTATION_DEF,
TokenTypes.ANNOTATION_FIELD_DEF,
};
/** Default token types.*/
private static final int[] DEF_TOKEN_TYPES = {
TokenTypes.CTOR_DEF,
TokenTypes.METHOD_DEF,
TokenTypes.VARIABLE_DEF,
TokenTypes.CLASS_DEF,
TokenTypes.INTERFACE_DEF,
TokenTypes.PACKAGE_DEF,
TokenTypes.ENUM_DEF,
TokenTypes.ANNOTATION_DEF,
};
/** Holds tag text to tag enum mappings. **/
private static final Map<String, JavadocTagInfo> TEXT_TO_TAG;
/** Holds tag name to tag enum mappings. **/
private static final Map<String, JavadocTagInfo> NAME_TO_TAG;
static {
TEXT_TO_TAG = Collections.unmodifiableMap(Arrays.stream(JavadocTagInfo.values())
.collect(Collectors.toMap(JavadocTagInfo::getText, tagText -> tagText)));
NAME_TO_TAG = Collections.unmodifiableMap(Arrays.stream(JavadocTagInfo.values())
.collect(Collectors.toMap(JavadocTagInfo::getName, tagName -> tagName)));
//Arrays sorting for binary search
Arrays.sort(DEF_TOKEN_TYPES);
Arrays.sort(DEF_TOKEN_TYPES_DEPRECATED);
}
/** The tag text. **/
private final String text;
/** The tag name. **/
private final String name;
/** The tag type. **/
private final Type type;
/**
* Sets the various properties of a Javadoc tag.
*
* @param text the tag text
* @param name the tag name
* @param type the type of tag
*/
JavadocTagInfo(final String text, final String name,
final Type type) {
this.text = text;
this.name = name;
this.type = type;
}
/**
* Checks if a particular Javadoc tag is valid within a Javadoc block of a
* given AST.
*
* <p>
* If passing in a DetailAST representing a non-void METHOD_DEF
* {@code true } would be returned. If passing in a DetailAST
* representing a CLASS_DEF {@code false } would be returned because
* CLASS_DEF's cannot return a value.
* </p>
*
* @param ast the AST representing a type that can be Javadoc'd
* @return true if tag is valid.
*/
public abstract boolean isValidOn(DetailAST ast);
/**
* Gets the tag text.
* @return the tag text
*/
public String getText() {
return text;
}
/**
* Gets the tag name.
* @return the tag name
*/
public String getName() {
return name;
}
/**
* Gets the Tag type defined by {@link Type Type}.
* @return the Tag type
*/
public Type getType() {
return type;
}
/**
* Returns a JavadocTag from the tag text.
* @param text String representing the tag text
* @return Returns a JavadocTag type from a String representing the tag
* @throws NullPointerException if the text is null
* @throws IllegalArgumentException if the text is not a valid tag
*/
public static JavadocTagInfo fromText(final String text) {
if (text == null) {
throw new IllegalArgumentException("the text is null");
}
final JavadocTagInfo tag = TEXT_TO_TAG.get(text);
if (tag == null) {
throw new IllegalArgumentException("the text [" + text
+ "] is not a valid Javadoc tag text");
}
return tag;
}
/**
* Returns a JavadocTag from the tag name.
* @param name String name of the tag
* @return Returns a JavadocTag type from a String representing the tag
* @throws NullPointerException if the text is null
* @throws IllegalArgumentException if the text is not a valid tag. The name
* can be checked using {@link JavadocTagInfo#isValidName(String)}
*/
public static JavadocTagInfo fromName(final String name) {
if (name == null) {
throw new IllegalArgumentException("the name is null");
}
final JavadocTagInfo tag = NAME_TO_TAG.get(name);
if (tag == null) {
throw new IllegalArgumentException("the name [" + name
+ "] is not a valid Javadoc tag name");
}
return tag;
}
/**
* Returns whether the provided name is for a valid tag.
* @param name the tag name to check.
* @return whether the provided name is for a valid tag.
*/
public static boolean isValidName(final String name) {
return NAME_TO_TAG.containsKey(name);
}
@Override
public String toString() {
return "text [" + text + "] name [" + name
+ "] type [" + type + "]";
}
/**
* The Javadoc Type.
*
* <p>For example a {@code @param} tag is a block tag while a
* {@code {@link}} tag is a inline tag.
*
* @author Travis Schneeberger
*/
public enum Type {
/** Block type. **/
BLOCK,
/** Inline type. **/
INLINE
}
}