org.bbottema.javareflection.ClassUtils Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of java-reflection Show documentation
Show all versions of java-reflection Show documentation
Java Reflection provides a small package with nifty reflection features that will help with finding constructors, methods and value conversions
package org.bbottema.javareflection;
import lombok.experimental.UtilityClass;
import org.bbottema.javareflection.model.MethodModifier;
import org.bbottema.javareflection.util.ExternalClassLoader;
import org.bbottema.javareflection.valueconverter.ValueConversionHelper;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
/**
* Utility with convenience methods that operate on the class level.
*
* - With this helper class you can locate and/or load classes. An advanced
Class
lookup ({@link #locateClass(String, boolean,
* ClassLoader)}), that allows a full scan (to try all
* packages known) and an optional {@link ExternalClassLoader} instance that is able to actually compile a .java file on the fly and load its compile
* .class file
* - create a new instance while handling all the exceptions
* - find fields or assign values to fields, using casting, autoboxing or type conversions
* - simply give back a list of field / method names
*
*/
@UtilityClass
public final class ClassUtils {
/**
* {@link Class} cache optionally used when looking up classes with {@link #locateClass(String, boolean, ClassLoader)}.
*/
private final static Map> classCache = new HashMap<>();
@SuppressWarnings("WeakerAccess")
public static void resetCache() {
classCache.clear();
}
/**
* Searches the JVM and optionally all of its packages
*
* @param className The name of the class to locate.
* @param fullscan Whether a full scan through all available java packages is required.
* @param classLoader Optional user-provided classloader.
* @return The Class
reference if found or null
otherwise.
*/
@Nullable
@SuppressWarnings("WeakerAccess")
public static Class> locateClass(final String className, final boolean fullscan, @Nullable final ClassLoader classLoader) {
final String cacheKey = className + fullscan;
if (classCache.containsKey(cacheKey)) {
return classCache.get(cacheKey);
}
final Class> _class;
if (fullscan) {
_class = locateClass(className, null, classLoader);
} else {
// try standard package used for most common classes
_class = locateClass(className, "java.lang", classLoader);
}
classCache.put(cacheKey, _class);
return _class;
}
@Nullable
@SuppressWarnings("WeakerAccess")
public static Class> locateClass(final String className, @Nullable final String inPackage, @Nullable final ClassLoader classLoader) {
final String cacheKey = className + inPackage;
if (classCache.containsKey(cacheKey)) {
return classCache.get(cacheKey);
}
Class> _class = locateClass(className, classLoader);
if (_class == null) {
_class = PackageUtils.scanPackagesForClass(className, inPackage, classLoader);
}
classCache.put(cacheKey, _class);
return _class;
}
/**
* This function dynamically tries to locate a class. First it searches the class-cache list, then it tries to get it from the Virtual Machine
* using {@code Class.forName(String)}.
*
* @param fullClassName The Class
that needs to be found.
* @param classLoader Optional user-provided classloader.
* @return The {@code Class} object found from cache or VM.
*/
@SuppressWarnings("WeakerAccess")
@Nullable
public static Class> locateClass(final String fullClassName, @Nullable final ClassLoader classLoader) {
try {
Class> _class = null;
if (classLoader != null) {
_class = classLoader.loadClass(fullClassName);
}
if (_class == null) {
_class = Class.forName(fullClassName);
}
return _class;
} catch (final ClassNotFoundException e) {
return null;
}
}
/**
* Simply calls {@link Class#newInstance()} and hides the exception handling boilerplate code.
*
* @param _class The datatype for which we need to create a new instance of.
* @param Type used to parameterize the return instance.
* @return A new parameterized instance of the given type.
*/
@NotNull
@SuppressWarnings("WeakerAccess")
public static T newInstanceSimple(final Class _class) {
try {
return _class.getConstructor().newInstance();
} catch (SecurityException e) {
throw new RuntimeException("unable to invoke parameterless constructor; security problem", e);
} catch (InstantiationException e) {
throw new RuntimeException("unable to complete instantiation of object", e);
} catch (IllegalAccessException e) {
throw new RuntimeException("unable to access parameterless constructor", e);
} catch (InvocationTargetException e) {
throw new RuntimeException("unable to invoke parameterless constructor", e);
} catch (NoSuchMethodException e) {
throw new RuntimeException("unable to find parameterless constructor (not public?)", e);
}
}
/**
* Returns a field from the given object that goes by the name of fieldName
. If o
is a Class object, a static field will
* be returned.
*
* @param o The reference to the object to fetch the property value from.
* @param fieldName The identifier or name of the member field/property.
* @return The value of the Field
.
*/
@Nullable
@SuppressWarnings("WeakerAccess")
public static Field solveField(final Object o, final String fieldName) {
try {
if (o.getClass().equals(Class.class)) {
// Java static field
return ((Class>) o).getField(fieldName);
} else {
// Java instance field
return o.getClass().getField(fieldName);
}
} catch (NoSuchFieldException e) {
return null;
}
}
/**
* Assigns a value to a field id
on the given object o
. If a simple assignment fails, a common conversion will be
* attempted.
*
* @param o The object to find the field on.
* @param property The name of the field we're assigning the value to.
* @param value The value to assign to the field, may be converted to the field's type through common conversion.
* @return The actual value that was assigned (the original or the converted value).
* @throws IllegalAccessException Thrown by {@link Field#set(Object, Object)}
* @throws NoSuchFieldException Thrown if the {@link Field} could not be found, even after trying to convert the value to the target type.
* @see ValueConversionHelper#convert(Object, Class)
*/
@Nullable
@SuppressWarnings("WeakerAccess")
public static Object assignToField(final Object o, final String property, final Object value) throws IllegalAccessException, NoSuchFieldException {
final Field field = solveField(o, property);
if (field != null) {
Object assignedValue = value;
try {
field.set(o, value);
} catch (final IllegalArgumentException ie) {
assignedValue = ValueConversionHelper.convert(value, field.getType());
field.set(o, assignedValue);
}
return assignedValue;
} else {
throw new NoSuchFieldException();
}
}
/**
* Returns a list of names that represent the fields on an Object
.
*
* @param subject The Object
who's properties/fields need to be reflected.
* @return A list of names that represent the fields on the given Object
.
*/
@NotNull
@SuppressWarnings("WeakerAccess")
public static Collection collectPropertyNames(final Object subject) {
final Collection properties = new LinkedHashSet<>();
final Field[] fields = subject.getClass().getFields();
for (final Field f : fields) {
properties.add(f.getName());
}
return properties;
}
/**
* @return Returns the result of {@link #collectMethods(Class, Class, EnumSet)} mapped to the method names.
*/
@SuppressWarnings("WeakerAccess")
public static Set collectMethodNames(Class> dataType, Class> boundaryMarker, EnumSet methodModifiers) {
Set methodNames = new HashSet<>();
for (Method m : collectMethods(dataType, boundaryMarker, methodModifiers)) {
methodNames.add(m.getName());
}
return methodNames;
}
/**
* @return The result of {@link #collectMethodsMappingToName(Class, Class, EnumSet)} filtered on method name.
*/
@SuppressWarnings("WeakerAccess")
public static Set collectMethodsByName(final Class> type, Class> boundaryMarker, EnumSet methodModifiers, final String methodName) {
return collectMethodsMappingToName(type, boundaryMarker, methodModifiers).get(methodName);
}
/**
* @return The result of {@link #collectMethods(Class, Class, EnumSet)} filtered on method name.
*/
@SuppressWarnings("WeakerAccess")
public static Map> collectMethodsMappingToName(Class> type, Class> boundaryMarker, EnumSet methodModifiers) {
Map> methodsMappedToName = new HashMap<>();
for (Method method : collectMethods(type, boundaryMarker, methodModifiers)) {
if (!methodsMappedToName.containsKey(method.getName())) {
methodsMappedToName.put(method.getName(), new HashSet());
}
methodsMappedToName.get(method.getName()).add(method);
}
return methodsMappedToName;
}
/**
* Returns a list of names that represent the methods on an Object
*
* @param methodModifiers List of method modifiers that will match any method that has one of them.
* @param boundaryMarker Optional type to limit (including) how far back up the inheritance chain we go for discovering methods.
* @return Returns a list with methods, either {@link Method}s.
*/
@SuppressWarnings("WeakerAccess")
public static Set collectMethods(Class> dataType, Class> boundaryMarker, EnumSet methodModifiers) {
final Set allMethods = new HashSet<>();
for (Method declaredMethod : dataType.getDeclaredMethods()) {
if (MethodModifier.meetsModifierRequirements(declaredMethod, methodModifiers)) {
allMethods.add(declaredMethod);
}
}
if (dataType != boundaryMarker && dataType.getSuperclass() != null) {
allMethods.addAll(collectMethods(dataType.getSuperclass(), boundaryMarker, methodModifiers));
}
return allMethods;
}
}