com.puppycrawl.tools.checkstyle.utils.CommonUtil Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of checkstyle Show documentation
Show all versions of checkstyle Show documentation
Checkstyle is a development tool to help programmers write Java code
that adheres to a coding standard
////////////////////////////////////////////////////////////////////////////////
// checkstyle: Checks Java source code for adherence to a set of rules.
// Copyright (C) 2001-2021 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.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
/**
* Contains utility methods.
*
*/
public final class CommonUtil {
/** Default tab width for column reporting. */
public static final int DEFAULT_TAB_WIDTH = 8;
/** 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];
/** Pseudo URL protocol for loading from the class path. */
public static final String CLASSPATH_URL_PROTOCOL = "classpath:";
/** Prefix for the exception when unable to find resource. */
private static final String UNABLE_TO_FIND_EXCEPTION_PREFIX = "Unable to find: ";
/** Stop instances being created. **/
private CommonUtil() {
}
/**
* Helper method to create a regular expression.
*
* @param pattern
* the pattern to match
* @return a created regexp object
* @throws IllegalArgumentException
* 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);
}
}
/**
* 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.codePointAt(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.
*
* 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
*
*
* @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.
*
* 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
*
*
* @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 type of the target class object.
* @param targetClass
* from which constructor is returned
* @param parameterTypes
* of constructor
* @return constructor of targetClass
* @throws IllegalStateException if any exception occurs
* @see Class#getConstructor(Class[])
*/
public static Constructor getConstructor(Class targetClass,
Class>... parameterTypes) {
try {
return targetClass.getConstructor(parameterTypes);
}
catch (NoSuchMethodException ex) {
throw new IllegalStateException(ex);
}
}
/**
* Returns new instance of a class.
*
* @param
* type of constructor
* @param constructor
* to invoke
* @param parameters
* to pass to constructor
* @return new instance of class
* @throws IllegalStateException if any exception occurs
* @see Constructor#newInstance(Object...)
*/
public static T invokeConstructor(Constructor 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
* @throws IllegalStateException when any IOException occurs
*/
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 of the file
* @return resolved file URI
* @throws CheckstyleException on failure
*/
public static URI getUriByFilename(String filename) throws CheckstyleException {
URI uri = getWebOrFileProtocolUri(filename);
if (uri == null) {
uri = getFilepathOrClasspathUri(filename);
}
return uri;
}
/**
* Resolves the specified filename containing 'http', 'https', 'ftp',
* and 'file' protocols (or any RFC 2396 compliant URL) to a URI.
*
* @param filename name of the file
* @return resolved file URI or null if URL is malformed or non-existent
*/
public static URI getWebOrFileProtocolUri(String filename) {
URI uri;
try {
final URL url = new URL(filename);
uri = url.toURI();
}
catch (URISyntaxException | MalformedURLException ignored) {
uri = null;
}
return uri;
}
/**
* Resolves the specified local filename, possibly with 'classpath:'
* protocol, to a URI. First we attempt to create a new file with
* given filename, then attempt to load file from class path.
*
* @param filename name of the file
* @return resolved file URI
* @throws CheckstyleException on failure
*/
private static URI getFilepathOrClasspathUri(String filename) throws CheckstyleException {
final URI uri;
final File file = new File(filename);
if (file.exists()) {
uri = file.toURI();
}
else {
final int lastIndexOfClasspathProtocol;
if (filename.lastIndexOf(CLASSPATH_URL_PROTOCOL) == 0) {
lastIndexOfClasspathProtocol = CLASSPATH_URL_PROTOCOL.length();
}
else {
lastIndexOfClasspathProtocol = 0;
}
uri = getResourceFromClassPath(filename
.substring(lastIndexOfClasspathProtocol));
}
return uri;
}
/**
* Gets a resource from the classpath.
*
* @param filename name of file
* @return URI of file in classpath
* @throws CheckstyleException on failure
*/
public static URI getResourceFromClassPath(String filename) throws CheckstyleException {
final URL configUrl;
if (filename.charAt(0) == '/') {
configUrl = CommonUtil.class.getResource(filename);
}
else {
configUrl = ClassLoader.getSystemResource(filename);
}
if (configUrl == null) {
throw new CheckstyleException(UNABLE_TO_FIND_EXCEPTION_PREFIX + filename);
}
final URI uri;
try {
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) {
return Objects.isNull(value)
|| indexOfNonWhitespace(value) >= value.length();
}
/**
* Method to find the index of the first non-whitespace character in a string.
*
* @param value the string to find the first index of a non-whitespace character for.
* @return the index of the first non-whitespace character.
*/
public static int indexOfNonWhitespace(String value) {
final int length = value.length();
int left = 0;
while (left < length) {
final int codePointAt = value.codePointAt(left);
if (!Character.isWhitespace(codePointAt)) {
break;
}
left += Character.charCount(codePointAt);
}
return left;
}
/**
* 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;
}
}