blob: 782f1ef16ad574d43f84aa015503f48749b250ad [file] [log] [blame]
////////////////////////////////////////////////////////////////////////////////
// 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.utils;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
import com.google.common.collect.ImmutableMap;
import com.puppycrawl.tools.checkstyle.api.DetailAST;
import com.puppycrawl.tools.checkstyle.api.DetailNode;
import com.puppycrawl.tools.checkstyle.api.JavadocTokenTypes;
import com.puppycrawl.tools.checkstyle.api.TextBlock;
import com.puppycrawl.tools.checkstyle.api.TokenTypes;
import com.puppycrawl.tools.checkstyle.checks.javadoc.InvalidJavadocTag;
import com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocTag;
import com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocTagInfo;
import com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocTags;
import com.puppycrawl.tools.checkstyle.checks.javadoc.utils.BlockTagUtils;
import com.puppycrawl.tools.checkstyle.checks.javadoc.utils.InlineTagUtils;
import com.puppycrawl.tools.checkstyle.checks.javadoc.utils.TagInfo;
/**
* Contains utility methods for working with Javadoc.
* @author Lyle Hanson
*/
public final class JavadocUtils {
/**
* The type of Javadoc tag we want returned.
*/
public enum JavadocTagType {
/** Block type. */
BLOCK,
/** Inline type. */
INLINE,
/** All validTags. */
ALL
}
/** Maps from a token name to value. */
private static final ImmutableMap<String, Integer> TOKEN_NAME_TO_VALUE;
/** Maps from a token value to name. */
private static final String[] TOKEN_VALUE_TO_NAME;
/** Exception message for unknown JavaDoc token id. */
private static final String UNKNOWN_JAVADOC_TOKEN_ID_EXCEPTION_MESSAGE = "Unknown javadoc"
+ " token id. Given id: ";
/** Newline pattern. */
private static final Pattern NEWLINE = Pattern.compile("\n");
/** Return pattern. */
private static final Pattern RETURN = Pattern.compile("\r");
/** Tab pattern. */
private static final Pattern TAB = Pattern.compile("\t");
// Using reflection gets all token names and values from JavadocTokenTypes class
// and saves to TOKEN_NAME_TO_VALUE and TOKEN_VALUE_TO_NAME collections.
static {
final ImmutableMap.Builder<String, Integer> builder = ImmutableMap.builder();
final Field[] fields = JavadocTokenTypes.class.getDeclaredFields();
String[] tempTokenValueToName = CommonUtils.EMPTY_STRING_ARRAY;
for (final Field field : fields) {
// Only process public int fields.
if (!Modifier.isPublic(field.getModifiers())
|| field.getType() != Integer.TYPE) {
continue;
}
final String name = field.getName();
final int tokenValue = TokenUtils.getIntFromField(field, name);
builder.put(name, tokenValue);
if (tokenValue > tempTokenValueToName.length - 1) {
final String[] temp = new String[tokenValue + 1];
System.arraycopy(tempTokenValueToName, 0, temp, 0, tempTokenValueToName.length);
tempTokenValueToName = temp;
}
if (tokenValue == -1) {
tempTokenValueToName[0] = name;
}
else {
tempTokenValueToName[tokenValue] = name;
}
}
TOKEN_NAME_TO_VALUE = builder.build();
TOKEN_VALUE_TO_NAME = tempTokenValueToName;
}
/** Prevent instantiation. */
private JavadocUtils() {
}
/**
* Gets validTags from a given piece of Javadoc.
* @param textBlock
* the Javadoc comment to process.
* @param tagType
* the type of validTags we're interested in
* @return all standalone validTags from the given javadoc.
*/
public static JavadocTags getJavadocTags(TextBlock textBlock,
JavadocTagType tagType) {
final boolean getBlockTags = tagType == JavadocTagType.ALL
|| tagType == JavadocTagType.BLOCK;
final boolean getInlineTags = tagType == JavadocTagType.ALL
|| tagType == JavadocTagType.INLINE;
final List<TagInfo> tags = new ArrayList<>();
if (getBlockTags) {
tags.addAll(BlockTagUtils.extractBlockTags(textBlock.getText()));
}
if (getInlineTags) {
tags.addAll(InlineTagUtils.extractInlineTags(textBlock.getText()));
}
final List<JavadocTag> validTags = new ArrayList<>();
final List<InvalidJavadocTag> invalidTags = new ArrayList<>();
for (TagInfo tag : tags) {
final int col = tag.getPosition().getColumn();
// Add the starting line of the comment to the line number to get the actual line number
// in the source.
// Lines are one-indexed, so need a off-by-one correction.
final int line = textBlock.getStartLineNo() + tag.getPosition().getLine() - 1;
if (JavadocTagInfo.isValidName(tag.getName())) {
validTags.add(
new JavadocTag(line, col, tag.getName(), tag.getValue()));
}
else {
invalidTags.add(new InvalidJavadocTag(line, col, tag.getName()));
}
}
return new JavadocTags(validTags, invalidTags);
}
/**
* Checks that commentContent starts with '*' javadoc comment identifier.
* @param commentContent
* content of block comment
* @return true if commentContent starts with '*' javadoc comment
* identifier.
*/
public static boolean isJavadocComment(String commentContent) {
boolean result = false;
if (!commentContent.isEmpty()) {
final char docCommentIdentificator = commentContent.charAt(0);
result = docCommentIdentificator == '*';
}
return result;
}
/**
* Checks block comment content starts with '*' javadoc comment identifier.
* @param blockCommentBegin
* block comment AST
* @return true if block comment content starts with '*' javadoc comment
* identifier.
*/
public static boolean isJavadocComment(DetailAST blockCommentBegin) {
final String commentContent = getBlockCommentContent(blockCommentBegin);
return isJavadocComment(commentContent) && isCorrectJavadocPosition(blockCommentBegin);
}
/**
* Gets content of block comment.
* @param blockCommentBegin
* block comment AST.
* @return content of block comment.
*/
private static String getBlockCommentContent(DetailAST blockCommentBegin) {
final DetailAST commentContent = blockCommentBegin.getFirstChild();
return commentContent.getText();
}
/**
* Get content of Javadoc comment.
* @param javadocCommentBegin
* Javadoc comment AST
* @return content of Javadoc comment.
*/
public static String getJavadocCommentContent(DetailAST javadocCommentBegin) {
final DetailAST commentContent = javadocCommentBegin.getFirstChild();
return commentContent.getText().substring(1);
}
/**
* Returns the first child token that has a specified type.
* @param detailNode
* Javadoc AST node
* @param type
* the token type to match
* @return the matching token, or null if no match
*/
public static DetailNode findFirstToken(DetailNode detailNode, int type) {
DetailNode returnValue = null;
DetailNode node = getFirstChild(detailNode);
while (node != null) {
if (node.getType() == type) {
returnValue = node;
break;
}
node = getNextSibling(node);
}
return returnValue;
}
/**
* Gets first child node of specified node.
*
* @param node DetailNode
* @return first child
*/
public static DetailNode getFirstChild(DetailNode node) {
DetailNode resultNode = null;
if (node.getChildren().length > 0) {
resultNode = node.getChildren()[0];
}
return resultNode;
}
/**
* Checks whether node contains any node of specified type among children on any deep level.
*
* @param node DetailNode
* @param type token type
* @return true if node contains any node of type type among children on any deep level.
*/
public static boolean containsInBranch(DetailNode node, int type) {
boolean result = true;
DetailNode curNode = node;
while (type != curNode.getType()) {
DetailNode toVisit = getFirstChild(curNode);
while (curNode != null && toVisit == null) {
toVisit = getNextSibling(curNode);
if (toVisit == null) {
curNode = curNode.getParent();
}
}
if (curNode == toVisit) {
result = false;
break;
}
curNode = toVisit;
}
return result;
}
/**
* Gets next sibling of specified node.
*
* @param node DetailNode
* @return next sibling.
*/
public static DetailNode getNextSibling(DetailNode node) {
DetailNode nextSibling = null;
final DetailNode parent = node.getParent();
if (parent != null) {
final int nextSiblingIndex = node.getIndex() + 1;
final DetailNode[] children = parent.getChildren();
if (nextSiblingIndex <= children.length - 1) {
nextSibling = children[nextSiblingIndex];
}
}
return nextSibling;
}
/**
* Gets next sibling of specified node with the specified type.
*
* @param node DetailNode
* @param tokenType javadoc token type
* @return next sibling.
*/
public static DetailNode getNextSibling(DetailNode node, int tokenType) {
DetailNode nextSibling = getNextSibling(node);
while (nextSibling != null && nextSibling.getType() != tokenType) {
nextSibling = getNextSibling(nextSibling);
}
return nextSibling;
}
/**
* Gets previous sibling of specified node.
* @param node DetailNode
* @return previous sibling
*/
public static DetailNode getPreviousSibling(DetailNode node) {
DetailNode previousSibling = null;
final int previousSiblingIndex = node.getIndex() - 1;
if (previousSiblingIndex >= 0) {
final DetailNode parent = node.getParent();
final DetailNode[] children = parent.getChildren();
previousSibling = children[previousSiblingIndex];
}
return previousSibling;
}
/**
* Returns the name of a token for a given ID.
* @param id
* the ID of the token name to get
* @return a token name
*/
public static String getTokenName(int id) {
final String name;
if (id == JavadocTokenTypes.EOF) {
name = "EOF";
}
else if (id > TOKEN_VALUE_TO_NAME.length - 1) {
throw new IllegalArgumentException(UNKNOWN_JAVADOC_TOKEN_ID_EXCEPTION_MESSAGE + id);
}
else {
name = TOKEN_VALUE_TO_NAME[id];
if (name == null) {
throw new IllegalArgumentException(UNKNOWN_JAVADOC_TOKEN_ID_EXCEPTION_MESSAGE + id);
}
}
return name;
}
/**
* Returns the ID of a token for a given name.
* @param name
* the name of the token ID to get
* @return a token ID
*/
public static int getTokenId(String name) {
final Integer id = TOKEN_NAME_TO_VALUE.get(name);
if (id == null) {
throw new IllegalArgumentException("Unknown javadoc token name. Given name " + name);
}
return id;
}
/**
* Gets tag name from javadocTagSection.
*
* @param javadocTagSection to get tag name from.
* @return name, of the javadocTagSection's tag.
*/
public static String getTagName(DetailNode javadocTagSection) {
final String javadocTagName;
if (javadocTagSection.getType() == JavadocTokenTypes.JAVADOC_INLINE_TAG) {
javadocTagName = getNextSibling(
getFirstChild(javadocTagSection)).getText();
}
else {
javadocTagName = getFirstChild(javadocTagSection).getText();
}
return javadocTagName;
}
/**
* Replace all control chars with escaped symbols.
* @param text the String to process.
* @return the processed String with all control chars escaped.
*/
public static String escapeAllControlChars(String text) {
final String textWithoutNewlines = NEWLINE.matcher(text).replaceAll("\\\\n");
final String textWithoutReturns = RETURN.matcher(textWithoutNewlines).replaceAll("\\\\r");
return TAB.matcher(textWithoutReturns).replaceAll("\\\\t");
}
/**
* Checks Javadoc comment it's in right place.
* <p>From Javadoc util documentation:
* "Placement of comments - Documentation comments are recognized only when placed
* immediately before class, interface, constructor, method, field or annotation field
* declarations -- see the class example, method example, and field example.
* Documentation comments placed in the body of a method are ignored."</p>
* <p>If there are many documentation comments per declaration statement,
* only the last one will be recognized.</p>
*
* @param blockComment Block comment AST
* @return true if Javadoc is in right place
* @see <a href="https://docs.oracle.com/javase/8/docs/technotes/tools/unix/javadoc.html">
* Javadoc util documentation</a>
*/
private static boolean isCorrectJavadocPosition(DetailAST blockComment) {
// We must be sure that after this one there are no other documentation comments.
DetailAST sibling = blockComment.getNextSibling();
while (sibling != null) {
if (sibling.getType() == TokenTypes.BLOCK_COMMENT_BEGIN) {
if (isJavadocComment(getBlockCommentContent(sibling))) {
// Found another javadoc comment, so this one should be ignored.
break;
}
sibling = sibling.getNextSibling();
}
else if (sibling.getType() == TokenTypes.SINGLE_LINE_COMMENT) {
sibling = sibling.getNextSibling();
}
else {
// Annotation, declaration or modifier is here. Do not check further.
sibling = null;
}
}
return sibling == null
&& (BlockCommentPosition.isOnType(blockComment)
|| BlockCommentPosition.isOnMember(blockComment));
}
}