org.assertj.assertions.generator.util.ClassUtil Maven / Gradle / Ivy
/**
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* Copyright 2012-2015 the original author or authors.
*/
package org.assertj.assertions.generator.util;
import static com.google.common.collect.Sets.newLinkedHashSet;
import static java.lang.reflect.Modifier.isPublic;
import static org.apache.commons.lang3.StringUtils.substringAfter;
import static org.apache.commons.lang3.StringUtils.uncapitalize;
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.lang3.ClassUtils;
import org.apache.commons.lang3.StringUtils;
import com.google.common.collect.ImmutableSet;
import com.google.common.reflect.ClassPath;
import com.google.common.reflect.ClassPath.ClassInfo;
/**
* Some utilities methods related to classes and packages.
*
* @author Joel Costigliola
*/
public class ClassUtil {
public static final String IS_PREFIX = "is";
public static final String GET_PREFIX = "get";
private static final String CLASS_SUFFIX = ".class";
private static final Comparator GETTER_COMPARATOR = new Comparator() {
@Override
public int compare(Method m1, Method m2) {
return m1.getName().compareTo(m2.getName());
}
};
/**
* Call {@link #collectClasses(ClassLoader, String...)} with Thread.currentThread().getContextClassLoader()
*
*/
public static Set> collectClasses(String... classOrPackageNames) {
return collectClasses(Thread.currentThread().getContextClassLoader(), classOrPackageNames);
}
/**
* Collects all the public classes from given classes names or classes belonging to given a package name
* (recursively).
*
* Note that anonymous and local classes are excluded from the returned classes.
*
* @param classLoader {@link ClassLoader} used to load classes defines in classOrPackageNames
* @param classOrPackageNames classes names or packages names we want to collect classes from (recursively for
* packages)
* @return the set of {@link Class}es found
* @throws RuntimeException if any error occurs
*/
public static Set> collectClasses(ClassLoader classLoader, String... classOrPackageNames) {
Set> classes = newLinkedHashSet();
for (String classOrPackageName : classOrPackageNames) {
Class> clazz = tryToLoadClass(classOrPackageName, classLoader);
if (clazz != null) {
classes.add(clazz);
} else {
// should be a package
classes.addAll(getClassesInPackage(classOrPackageName, classLoader));
}
}
return classes;
}
/**
* Retrieves recursively all the classes belonging to a package.
*
* @param packageName package name we want to load classes from
* @param classLoader the class loader used to load the classes in the given package
* @return the list of Class found
* @throws RuntimeException if any error occurs
*/
private static Set> getClassesInPackage(String packageName, ClassLoader classLoader) {
if (classLoader == null) {
throw new IllegalArgumentException("Null class loader.");
}
// load classes from classpath file system, this won't load classes in jars
Set> packageClasses = getPackageClassesFromClasspathFiles(packageName, classLoader);
// load classes from classpath jars
try {
packageClasses.addAll(getPackageClassesFromClasspathJars(packageName, classLoader));
} catch (IOException e) {
throw new RuntimeException(e);
}
return packageClasses;
}
private static Set> getPackageClassesFromClasspathJars(String packageName, ClassLoader classLoader)
throws IOException {
ImmutableSet classesInfo = ClassPath.from(classLoader).getTopLevelClassesRecursive(packageName);
Set> classesInPackage = new HashSet>();
for (ClassInfo classInfo : classesInfo) {
classesInPackage.add(classInfo.load());
}
Set> filteredClassesInPackage = new HashSet>();
for (Class> classFromJar : classesInPackage) {
if (isClassCandidateToAssertionsGeneration(classFromJar)) {
filteredClassesInPackage.add(classFromJar);
}
}
return filteredClassesInPackage;
}
private static Set> getPackageClassesFromClasspathFiles(String packageName, ClassLoader classLoader) {
try {
String packagePath = packageName.replace('.', File.separatorChar);
// Ask for all resources for the path
Enumeration resources = classLoader.getResources(packagePath);
Set> classes = newLinkedHashSet();
while (resources.hasMoreElements()) {
File directory = new File(URLDecoder.decode(resources.nextElement().getPath(), "UTF-8"));
if (directory.canRead()) {
classes.addAll(getClassesInDirectory(directory, packageName, classLoader));
}
}
return classes;
} catch (UnsupportedEncodingException encex) {
throw new RuntimeException(packageName + " does not appear to be a valid package (Unsupported encoding)", encex);
} catch (IOException ioex) {
throw new RuntimeException("IOException was thrown when trying to get all classes for " + packageName, ioex);
}
}
/**
* Get public classes in given directory (recursively).
*
* Note that anonymous and local classes are excluded from the resulting set.
*
* @param directory directory where to look for classes
* @param packageName package name corresponding to directory
* @param classLoader used classloader
* @return
* @throws UnsupportedEncodingException
*/
private static Set> getClassesInDirectory(File directory, String packageName, ClassLoader classLoader)
throws UnsupportedEncodingException {
Set> classes = newLinkedHashSet();
// Capture all the .class files in this directory
// Get the list of the files contained in the package
File[] files = directory.listFiles();
for (File currentFile : files) {
String currentFileName = currentFile.getName();
if (isClass(currentFileName)) {
// CHECKSTYLE:OFF
try {
// removes the .class extension
String className = packageName + '.' + StringUtils.remove(currentFileName, CLASS_SUFFIX);
Class> loadedClass = loadClass(className, classLoader);
// we are only interested in public classes that are neither anonymous nor local
if (isClassCandidateToAssertionsGeneration(loadedClass)) {
classes.add(loadedClass);
}
} catch (Throwable e) {
// do nothing. this class hasn't been found by the loader, and we don't care.
}
// CHECKSTYLE:ON
} else if (currentFile.isDirectory()) {
// It's another package
String subPackageName = packageName + ClassUtils.PACKAGE_SEPARATOR + currentFileName;
// Ask for all resources for the path
URL resource = classLoader.getResource(subPackageName.replace('.', File.separatorChar));
File subDirectory = new File(URLDecoder.decode(resource.getPath(), "UTF-8"));
Set> classesForSubPackage = getClassesInDirectory(subDirectory, subPackageName, classLoader);
classes.addAll(classesForSubPackage);
}
}
return classes;
}
/**
* @param loadedClass
* @return
*/
private static boolean isClassCandidateToAssertionsGeneration(Class> loadedClass) {
return loadedClass != null && isPublic(loadedClass.getModifiers()) && !loadedClass.isAnonymousClass()
&& !loadedClass.isLocalClass();
}
private static boolean isClass(String fileName) {
return fileName.endsWith(CLASS_SUFFIX);
}
private static Class> tryToLoadClass(String className, ClassLoader classLoader) {
try {
return loadClass(className, classLoader);
} catch (ClassNotFoundException e) {
return null;
}
}
private static Class> loadClass(String className, ClassLoader classLoader) throws ClassNotFoundException {
return Class.forName(className, true, classLoader);
}
/**
* Returns the property name of given getter method, examples :
*
*
*
* getName -> name
*
*
*
*
* isMostValuablePlayer -> mostValuablePlayer
*
*
* @param getter getter method to deduce property from.
* @return the property name of given getter method
*/
public static String propertyNameOf(Method getter) {
String prefixToRemove = isPredicate(getter) ? IS_PREFIX : GET_PREFIX;
String propertyWithCapitalLetter = substringAfter(getter.getName(), prefixToRemove);
return uncapitalize(propertyWithCapitalLetter);
}
public static boolean inheritsCollectionOrIsIterable(Class> returnType) {
return Collection.class.isAssignableFrom(returnType) || Iterable.class.equals(returnType);
}
public static boolean isArray(Class> returnType) {
return returnType.isArray();
}
public static boolean isStandardGetter(Method method) {
return isValidStandardGetterName(method.getName())
&& !Void.TYPE.equals(method.getReturnType())
&& method.getParameterTypes().length == 0;
}
public static boolean isPredicate(Method method) {
return isValidPredicateName(method.getName())
&& (Boolean.TYPE.equals(method.getReturnType()) || Boolean.class.equals(method.getReturnType()))
&& method.getParameterTypes().length == 0;
}
public static boolean isValidGetterName(String methodName) {
return PREFIX_PATTERN.matcher(methodName).find();
}
static private final Pattern PREFIX_PATTERN;
static private final Map PREDICATE_PREFIXES;
static private final Comparator LONGEST_TO_SHORTEST = new Comparator() {
@Override
public int compare(String o1, String o2) {
final int lengthComp = o2.length() - o1.length();
return lengthComp == 0 ? o1.compareTo(o2) : lengthComp;
}
};
static {
String[][] predicates = new String[][] {
{ "is", "isNot" },
{ "was", "wasNot" },
{ "can", "cannot" },
{ "should", "shouldNot" },
{ "has", "doesNotHave" },
{ "will", "willNot" },
};
StringBuilder pattern = new StringBuilder("^(?:get");
Map map = new HashMap();
for (String[] pair : predicates) {
map.put(pair[0], pair[1]);
map.put(pair[1], pair[0]);
}
TreeSet sort = new TreeSet(LONGEST_TO_SHORTEST);
sort.addAll(map.keySet());
for (String prefix : sort) {
pattern.append('|').append(prefix);
}
// next should be an Upper case letter
pattern.append(")(?=\\p{Upper})");
PREFIX_PATTERN = Pattern.compile(pattern.toString());
PREDICATE_PREFIXES = Collections.unmodifiableMap(map);
}
private static boolean isValidStandardGetterName(String name) {
Matcher m = PREFIX_PATTERN.matcher(name);
return m.find() && m.group().equals(GET_PREFIX);
}
public static String getPredicatePrefix(String name) {
Matcher m = PREFIX_PATTERN.matcher(name);
return m.find() ? m.group() : null;
}
public static boolean isValidPredicateName(String name) {
Matcher m = PREFIX_PATTERN.matcher(name);
return m.find() && PREDICATE_PREFIXES.containsKey(m.group());
}
public static String getNegativePredicateFor(String name) {
Matcher m = PREFIX_PATTERN.matcher(name);
if (m.find()) {
return m.replaceFirst(PREDICATE_PREFIXES.get(m.group()));
}
return null;
}
public static Set declaredGetterMethodsOf(Class> clazz) {
return filterGetterMethods(clazz.getDeclaredMethods());
}
public static Set getterMethodsOf(Class> clazz) {
return filterGetterMethods(clazz.getMethods());
}
private static Set filterGetterMethods(Method[] methods) {
Set getters = new TreeSet(GETTER_COMPARATOR);
for (int i = 0; i < methods.length; i++) {
Method method = methods[i];
if (isPublic(method.getModifiers())
&& isNotDefinedInObjectClass(method)
&& isGetter(method)) {
getters.add(method);
}
}
return getters;
}
private static boolean isGetter(Method method) {
return isStandardGetter(method) || isPredicate(method);
}
public static List nonStaticPublicFieldsOf(Class> clazz) {
Field[] fields = clazz.getFields();
List nonStaticPublicFields = new ArrayList();
for (Field field : fields) {
if (isNotStaticPublicField(field)) {
nonStaticPublicFields.add(field);
}
}
return nonStaticPublicFields;
}
public static List declaredPublicFieldsOf(Class> clazz) {
Field[] fields = clazz.getDeclaredFields();
List nonStaticPublicFields = new ArrayList();
for (Field field : fields) {
if (isNotStaticPublicField(field)) {
nonStaticPublicFields.add(field);
}
}
return nonStaticPublicFields;
}
private static boolean isNotStaticPublicField(Field field) {
final int modifiers = field.getModifiers();
return !Modifier.isStatic(modifiers) && Modifier.isPublic(modifiers);
}
private static boolean isNotDefinedInObjectClass(Method method) {
return !method.getDeclaringClass().equals(Object.class);
}
public static Set> getClassesRelatedTo(Type type) {
Set> classes = new HashSet>();
// non generic type : just add current type.
if (type instanceof Class) {
classes.add((Class>) type);
return classes;
}
// generic type : add current type and its parameter types
if (type instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) type;
for (Type actualTypeArgument : parameterizedType.getActualTypeArguments()) {
if (actualTypeArgument instanceof ParameterizedType) {
classes.addAll(getClassesRelatedTo(actualTypeArgument));
} else if (actualTypeArgument instanceof Class) {
classes.add((Class>) actualTypeArgument);
} else if (actualTypeArgument instanceof GenericArrayType) {
classes.addAll(getClassesRelatedTo(actualTypeArgument));
}
}
Type rawType = parameterizedType.getRawType();
if (rawType instanceof Class) {
classes.add((Class>) rawType);
}
}
return classes;
}
/**
* Gets the simple name of the class but, unlike {@link Class#getSimpleName()}, it includes the name of the outer
* class when clazz
is an inner class.
*
* @param clazz
* @return
*/
public static String getSimpleNameWithOuterClass(Class> clazz) {
if (isNotNestedClass(clazz)) {
return clazz.getSimpleName();
}
String nestedClassName = null;
nestedClassName = clazz.getName();
nestedClassName = nestedClassName.substring(clazz.getPackage().getName().length() + 1);
nestedClassName = nestedClassName.replace('$', '.');
return nestedClassName;
}
/**
* Gets the simple name of the class but, unlike {@link Class#getSimpleName()}, it includes the name of the outer
* class when clazz
is an inner class, both class names are concatenated.
*
* Example:
*
*
* Outer.Inner -> OuterInner
*
*
* @param clazz
* @return
*/
public static String getSimpleNameWithOuterClassNotSeparatedByDots(Class> clazz) {
if (isNotNestedClass(clazz)) {
return clazz.getSimpleName();
}
String nestedClassName = null;
nestedClassName = clazz.getName();
nestedClassName = nestedClassName.substring(clazz.getPackage().getName().length() + 1);
nestedClassName = StringUtils.remove(nestedClassName, '$');
return nestedClassName;
}
private static boolean isNotNestedClass(Class> clazz) {
return clazz.getDeclaringClass() == null;
}
/**
* Get the underlying class for a type, or null if the type is a variable type.
*
* @param type the type
* @return the underlying class
*/
public static Class> getClass(final Type type) {
if (type instanceof Class) return (Class>) type;
if (type instanceof ParameterizedType) return getClass(((ParameterizedType) type).getRawType());
if (type instanceof GenericArrayType) {
final Type componentType = ((GenericArrayType) type).getGenericComponentType();
final Class> componentClass = getClass(componentType);
return componentClass == null ? null : Array.newInstance(componentClass, 0).getClass();
} else if (type instanceof WildcardType) {
final WildcardType wildcardType = (WildcardType) type;
return wildcardType.getUpperBounds() != null ? getClass(wildcardType.getUpperBounds()[0])
: wildcardType.getLowerBounds() != null ? getClass(wildcardType.getLowerBounds()[0]) : null;
} else if (type instanceof TypeVariable) {
final TypeVariable> typeVariable = (TypeVariable>) type;
final Type[] bounds = typeVariable.getBounds();
return bounds.length > 0 ? getClass(bounds[0]) : Object.class;
}
return null;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy