blob: 122c6f27725d840714fd6db234e95b62fa4c601b [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.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.AbstractMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import org.apache.commons.beanutils.ConversionException;
import antlr.Token;
import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
import com.puppycrawl.tools.checkstyle.api.DetailAST;
import com.puppycrawl.tools.checkstyle.api.TokenTypes;
/**
* Contains utility methods.
*
* @author <a href="mailto:nesterenko-aleksey@list.ru">Aleksey Nesterenko</a>
*/
public final class CommonUtils {
/** Copied from org.apache.commons.lang3.ArrayUtils. */
public static final String[] EMPTY_STRING_ARRAY = new String[0];
/** Copied from org.apache.commons.lang3.ArrayUtils. */
public static final Integer[] EMPTY_INTEGER_OBJECT_ARRAY = new Integer[0];
/** Copied from org.apache.commons.lang3.ArrayUtils. */
public static final Object[] EMPTY_OBJECT_ARRAY = new Object[0];
/** Copied from org.apache.commons.lang3.ArrayUtils. */
public static final int[] EMPTY_INT_ARRAY = new int[0];
/** Copied from org.apache.commons.lang3.ArrayUtils. */
public static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
/** Copied from org.apache.commons.lang3.ArrayUtils. */
public static final double[] EMPTY_DOUBLE_ARRAY = new double[0];
/** Prefix for the exception when unable to find resource. */
private static final String UNABLE_TO_FIND_EXCEPTION_PREFIX = "Unable to find: ";
/** Symbols with which javadoc starts. */
private static final String JAVADOC_START = "/**";
/** Symbols with which multiple comment starts. */
private static final String BLOCK_MULTIPLE_COMMENT_BEGIN = "/*";
/** Symbols with which multiple comment ends. */
private static final String BLOCK_MULTIPLE_COMMENT_END = "*/";
/** Stop instances being created. **/
private CommonUtils() {
}
/**
* Helper method to create a regular expression.
*
* @param pattern
* the pattern to match
* @return a created regexp object
* @throws ConversionException
* if unable to create Pattern object.
**/
public static Pattern createPattern(String pattern) {
return createPattern(pattern, 0);
}
/**
* Helper method to create a regular expression with a specific flags.
*
* @param pattern
* the pattern to match
* @param flags
* the flags to set
* @return a created regexp object
* @throws IllegalArgumentException
* if unable to create Pattern object.
**/
public static Pattern createPattern(String pattern, int flags) {
try {
return Pattern.compile(pattern, flags);
}
catch (final PatternSyntaxException ex) {
throw new IllegalArgumentException(
"Failed to initialise regular expression " + pattern, ex);
}
}
/**
* Create block comment from string content.
* @param content comment content.
* @return DetailAST block comment
*/
public static DetailAST createBlockCommentNode(String content) {
final DetailAST blockCommentBegin = new DetailAST();
blockCommentBegin.setType(TokenTypes.BLOCK_COMMENT_BEGIN);
blockCommentBegin.setText(BLOCK_MULTIPLE_COMMENT_BEGIN);
blockCommentBegin.setLineNo(0);
blockCommentBegin.setColumnNo(-JAVADOC_START.length());
final DetailAST commentContent = new DetailAST();
commentContent.setType(TokenTypes.COMMENT_CONTENT);
commentContent.setText("*" + content);
commentContent.setLineNo(0);
// javadoc should starts at 0 column, so COMMENT_CONTENT node
// that contains javadoc identifier has -1 column
commentContent.setColumnNo(-1);
final DetailAST blockCommentEnd = new DetailAST();
blockCommentEnd.setType(TokenTypes.BLOCK_COMMENT_END);
blockCommentEnd.setText(BLOCK_MULTIPLE_COMMENT_END);
blockCommentBegin.setFirstChild(commentContent);
commentContent.setNextSibling(blockCommentEnd);
return blockCommentBegin;
}
/**
* Create block comment from token.
* @param token
* Token object.
* @return DetailAST with BLOCK_COMMENT type.
*/
public static DetailAST createBlockCommentNode(Token token) {
final DetailAST blockComment = new DetailAST();
blockComment.initialize(TokenTypes.BLOCK_COMMENT_BEGIN, BLOCK_MULTIPLE_COMMENT_BEGIN);
// column counting begins from 0
blockComment.setColumnNo(token.getColumn() - 1);
blockComment.setLineNo(token.getLine());
final DetailAST blockCommentContent = new DetailAST();
blockCommentContent.setType(TokenTypes.COMMENT_CONTENT);
// column counting begins from 0
// plus length of '/*'
blockCommentContent.setColumnNo(token.getColumn() - 1 + 2);
blockCommentContent.setLineNo(token.getLine());
blockCommentContent.setText(token.getText());
final DetailAST blockCommentClose = new DetailAST();
blockCommentClose.initialize(TokenTypes.BLOCK_COMMENT_END, BLOCK_MULTIPLE_COMMENT_END);
final Map.Entry<Integer, Integer> linesColumns = countLinesColumns(
token.getText(), token.getLine(), token.getColumn());
blockCommentClose.setLineNo(linesColumns.getKey());
blockCommentClose.setColumnNo(linesColumns.getValue());
blockComment.addChild(blockCommentContent);
blockComment.addChild(blockCommentClose);
return blockComment;
}
/**
* Count lines and columns (in last line) in text.
* @param text
* String.
* @param initialLinesCnt
* initial value of lines counter.
* @param initialColumnsCnt
* initial value of columns counter.
* @return entry(pair), first element is lines counter, second - columns
* counter.
*/
private static Map.Entry<Integer, Integer> countLinesColumns(
String text, int initialLinesCnt, int initialColumnsCnt) {
int lines = initialLinesCnt;
int columns = initialColumnsCnt;
boolean foundCr = false;
for (char c : text.toCharArray()) {
if (c == '\n') {
foundCr = false;
lines++;
columns = 0;
}
else {
if (foundCr) {
foundCr = false;
lines++;
columns = 0;
}
if (c == '\r') {
foundCr = true;
}
columns++;
}
}
if (foundCr) {
lines++;
columns = 0;
}
return new AbstractMap.SimpleEntry<>(lines, columns);
}
/**
* Returns whether the file extension matches what we are meant to process.
*
* @param file
* the file to be checked.
* @param fileExtensions
* files extensions, empty property in config makes it matches to all.
* @return whether there is a match.
*/
public static boolean matchesFileExtension(File file, String... fileExtensions) {
boolean result = false;
if (fileExtensions == null || fileExtensions.length == 0) {
result = true;
}
else {
// normalize extensions so all of them have a leading dot
final String[] withDotExtensions = new String[fileExtensions.length];
for (int i = 0; i < fileExtensions.length; i++) {
final String extension = fileExtensions[i];
if (startsWithChar(extension, '.')) {
withDotExtensions[i] = extension;
}
else {
withDotExtensions[i] = "." + extension;
}
}
final String fileName = file.getName();
for (final String fileExtension : withDotExtensions) {
if (fileName.endsWith(fileExtension)) {
result = true;
break;
}
}
}
return result;
}
/**
* Returns whether the specified string contains only whitespace up to the specified index.
*
* @param index
* index to check up to
* @param line
* the line to check
* @return whether there is only whitespace
*/
public static boolean hasWhitespaceBefore(int index, String line) {
boolean result = true;
for (int i = 0; i < index; i++) {
if (!Character.isWhitespace(line.charAt(i))) {
result = false;
break;
}
}
return result;
}
/**
* Returns the length of a string ignoring all trailing whitespace.
* It is a pity that there is not a trim() like
* method that only removed the trailing whitespace.
*
* @param line
* the string to process
* @return the length of the string ignoring all trailing whitespace
**/
public static int lengthMinusTrailingWhitespace(String line) {
int len = line.length();
for (int i = len - 1; i >= 0; i--) {
if (!Character.isWhitespace(line.charAt(i))) {
break;
}
len--;
}
return len;
}
/**
* Returns the length of a String prefix with tabs expanded.
* Each tab is counted as the number of characters is
* takes to jump to the next tab stop.
*
* @param inputString
* the input String
* @param toIdx
* index in string (exclusive) where the calculation stops
* @param tabWidth
* the distance between tab stop position.
* @return the length of string.substring(0, toIdx) with tabs expanded.
*/
public static int lengthExpandedTabs(String inputString,
int toIdx,
int tabWidth) {
int len = 0;
for (int idx = 0; idx < toIdx; idx++) {
if (inputString.charAt(idx) == '\t') {
len = (len / tabWidth + 1) * tabWidth;
}
else {
len++;
}
}
return len;
}
/**
* Validates whether passed string is a valid pattern or not.
*
* @param pattern
* string to validate
* @return true if the pattern is valid false otherwise
*/
public static boolean isPatternValid(String pattern) {
boolean isValid = true;
try {
Pattern.compile(pattern);
}
catch (final PatternSyntaxException ignored) {
isValid = false;
}
return isValid;
}
/**
* Returns base class name from qualified name.
* @param type
* the fully qualified name. Cannot be null
* @return the base class name from a fully qualified name
*/
public static String baseClassName(String type) {
final String className;
final int index = type.lastIndexOf('.');
if (index == -1) {
className = type;
}
else {
className = type.substring(index + 1);
}
return className;
}
/**
* Constructs a normalized relative path between base directory and a given path.
*
* @param baseDirectory
* the base path to which given path is relativized
* @param path
* the path to relativize against base directory
* @return the relative normalized path between base directory and
* path or path if base directory is null.
*/
public static String relativizeAndNormalizePath(final String baseDirectory, final String path) {
final String resultPath;
if (baseDirectory == null) {
resultPath = path;
}
else {
final Path pathAbsolute = Paths.get(path).normalize();
final Path pathBase = Paths.get(baseDirectory).normalize();
resultPath = pathBase.relativize(pathAbsolute).toString();
}
return resultPath;
}
/**
* Tests if this string starts with the specified prefix.
* <p>
* It is faster version of {@link String#startsWith(String)} optimized for
* one-character prefixes at the expense of
* some readability. Suggested by SimplifyStartsWith PMD rule:
* http://pmd.sourceforge.net/pmd-5.3.1/pmd-java/rules/java/optimizations.html#SimplifyStartsWith
* </p>
*
* @param value
* the {@code String} to check
* @param prefix
* the prefix to find
* @return {@code true} if the {@code char} is a prefix of the given {@code String};
* {@code false} otherwise.
*/
public static boolean startsWithChar(String value, char prefix) {
return !value.isEmpty() && value.charAt(0) == prefix;
}
/**
* Tests if this string ends with the specified suffix.
* <p>
* It is faster version of {@link String#endsWith(String)} optimized for
* one-character suffixes at the expense of
* some readability. Suggested by SimplifyStartsWith PMD rule:
* http://pmd.sourceforge.net/pmd-5.3.1/pmd-java/rules/java/optimizations.html#SimplifyStartsWith
* </p>
*
* @param value
* the {@code String} to check
* @param suffix
* the suffix to find
* @return {@code true} if the {@code char} is a suffix of the given {@code String};
* {@code false} otherwise.
*/
public static boolean endsWithChar(String value, char suffix) {
return !value.isEmpty() && value.charAt(value.length() - 1) == suffix;
}
/**
* Gets constructor of targetClass.
* @param targetClass
* from which constructor is returned
* @param parameterTypes
* of constructor
* @param <T> type of the target class object.
* @return constructor of targetClass or {@link IllegalStateException} if any exception occurs
* @see Class#getConstructor(Class[])
*/
public static <T> Constructor<T> getConstructor(Class<T> targetClass,
Class<?>... parameterTypes) {
try {
return targetClass.getConstructor(parameterTypes);
}
catch (NoSuchMethodException ex) {
throw new IllegalStateException(ex);
}
}
/**
* Returns new instance of a class.
* @param constructor
* to invoke
* @param parameters
* to pass to constructor
* @param <T>
* type of constructor
* @return new instance of class or {@link IllegalStateException} if any exception occurs
* @see Constructor#newInstance(Object...)
*/
public static <T> T invokeConstructor(Constructor<T> constructor, Object... parameters) {
try {
return constructor.newInstance(parameters);
}
catch (InstantiationException | IllegalAccessException | InvocationTargetException ex) {
throw new IllegalStateException(ex);
}
}
/**
* Closes a stream re-throwing IOException as IllegalStateException.
*
* @param closeable
* Closeable object
*/
public static void close(Closeable closeable) {
if (closeable != null) {
try {
closeable.close();
}
catch (IOException ex) {
throw new IllegalStateException("Cannot close the stream", ex);
}
}
}
/**
* Resolve the specified filename to a URI.
* @param filename name os the file
* @return resolved header file URI
* @throws CheckstyleException on failure
*/
public static URI getUriByFilename(String filename) throws CheckstyleException {
// figure out if this is a File or a URL
URI uri;
try {
final URL url = new URL(filename);
uri = url.toURI();
}
catch (final URISyntaxException | MalformedURLException ignored) {
uri = null;
}
if (uri == null) {
final File file = new File(filename);
if (file.exists()) {
uri = file.toURI();
}
else {
// check to see if the file is in the classpath
try {
final URL configUrl = CommonUtils.class
.getResource(filename);
if (configUrl == null) {
throw new CheckstyleException(UNABLE_TO_FIND_EXCEPTION_PREFIX + filename);
}
uri = configUrl.toURI();
}
catch (final URISyntaxException ex) {
throw new CheckstyleException(UNABLE_TO_FIND_EXCEPTION_PREFIX + filename, ex);
}
}
}
return uri;
}
/**
* Puts part of line, which matches regexp into given template
* on positions $n where 'n' is number of matched part in line.
* @param template the string to expand.
* @param lineToPlaceInTemplate contains expression which should be placed into string.
* @param regexp expression to find in comment.
* @return the string, based on template filled with given lines
*/
public static String fillTemplateWithStringsByRegexp(
String template, String lineToPlaceInTemplate, Pattern regexp) {
final Matcher matcher = regexp.matcher(lineToPlaceInTemplate);
String result = template;
if (matcher.find()) {
for (int i = 0; i <= matcher.groupCount(); i++) {
// $n expands comment match like in Pattern.subst().
result = result.replaceAll("\\$" + i, matcher.group(i));
}
}
return result;
}
/**
* Returns file name without extension.
* We do not use the method from Guava library to reduce Checkstyle's dependencies
* on external libraries.
* @param fullFilename file name with extension.
* @return file name without extension.
*/
public static String getFileNameWithoutExtension(String fullFilename) {
final String fileName = new File(fullFilename).getName();
final int dotIndex = fileName.lastIndexOf('.');
final String fileNameWithoutExtension;
if (dotIndex == -1) {
fileNameWithoutExtension = fileName;
}
else {
fileNameWithoutExtension = fileName.substring(0, dotIndex);
}
return fileNameWithoutExtension;
}
/**
* Returns file extension for the given file name
* or empty string if file does not have an extension.
* We do not use the method from Guava library to reduce Checkstyle's dependencies
* on external libraries.
* @param fileNameWithExtension file name with extension.
* @return file extension for the given file name
* or empty string if file does not have an extension.
*/
public static String getFileExtension(String fileNameWithExtension) {
final String fileName = Paths.get(fileNameWithExtension).toString();
final int dotIndex = fileName.lastIndexOf('.');
final String extension;
if (dotIndex == -1) {
extension = "";
}
else {
extension = fileName.substring(dotIndex + 1);
}
return extension;
}
/**
* Checks whether the given string is a valid identifier.
* @param str A string to check.
* @return true when the given string contains valid identifier.
*/
public static boolean isIdentifier(String str) {
boolean isIdentifier = !str.isEmpty();
for (int i = 0; isIdentifier && i < str.length(); i++) {
if (i == 0) {
isIdentifier = Character.isJavaIdentifierStart(str.charAt(0));
}
else {
isIdentifier = Character.isJavaIdentifierPart(str.charAt(i));
}
}
return isIdentifier;
}
/**
* Checks whether the given string is a valid name.
* @param str A string to check.
* @return true when the given string contains valid name.
*/
public static boolean isName(String str) {
boolean isName = !str.isEmpty();
final String[] identifiers = str.split("\\.", -1);
for (int i = 0; isName && i < identifiers.length; i++) {
isName = isIdentifier(identifiers[i]);
}
return isName;
}
/**
* Checks if the value arg is blank by either being null,
* empty, or contains only whitespace characters.
* @param value A string to check.
* @return true if the arg is blank.
*/
public static boolean isBlank(String value) {
boolean result = true;
if (value != null && !value.isEmpty()) {
for (int i = 0; i < value.length(); i++) {
if (!Character.isWhitespace(value.charAt(i))) {
result = false;
break;
}
}
}
return result;
}
/**
* Checks whether the string contains an integer value.
* @param str a string to check
* @return true if the given string is an integer, false otherwise.
*/
public static boolean isInt(String str) {
boolean isInt;
if (str == null) {
isInt = false;
}
else {
try {
Integer.parseInt(str);
isInt = true;
}
catch (NumberFormatException ignored) {
isInt = false;
}
}
return isInt;
}
}