com.davidbracewell.reflection.ReflectionUtils Maven / Gradle / Ivy
Show all versions of mango Show documentation
/*
* (c) 2005 David B. Bracewell
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/
package com.davidbracewell.reflection;
import com.davidbracewell.conversion.Val;
import com.davidbracewell.logging.Logger;
import com.google.common.base.*;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.common.primitives.Primitives;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.List;
import java.util.Queue;
import java.util.Set;
/**
* Static classes to make reflection easier.
*/
public final class ReflectionUtils {
private final static Logger log = Logger.getLogger(ReflectionUtils.class);
private ReflectionUtils() {
}
/**
* Gets field.
*
* @param clazz the clazz
* @param name the name
* @param privileged the privalged
* @return the field
*/
public static Field getField(Class> clazz, String name, boolean privileged) {
if (clazz == null || name == null) {
return null;
}
return ClassDescriptorCache.getInstance()
.getClassDescriptor(clazz)
.getFields(privileged)
.stream()
.filter(f -> f.getName().equals(name))
.findFirst().orElse(null);
}
/**
* Gets all interfaces that the given object implements
*
* @param o The object
* @return A list of interfaces implemented by the object
*/
public static List> getAllInterfaces(Object o) {
Preconditions.checkNotNull(o);
return getAllClasses(Preconditions.checkNotNull(o), IsInterface.INSTANCE);
}
/**
* Gets all classes associated with (superclasses and interfaces) the given object.
*
* @param o The object
* @return List of classes including the class of the given object that match the given predicate
*/
public static List> getAllClasses(Object o) {
Preconditions.checkNotNull(o);
return getAllClasses(Preconditions.checkNotNull(o), Predicates.>alwaysTrue());
}
/**
* Gets all classes associated with (superclasses and interfaces) the given object that pass the given
* predicate.
*
* @param o The object
* @param predicate The predicate that ancestors must pass.
* @return List of classes including the class of the given object that match the given predicate
*/
private static List> getAllClasses(Object o, Predicate super Class>> predicate) {
List> matches = Lists.newArrayList();
Set> seen = Sets.newHashSet();
Queue> queue = Lists.newLinkedList();
queue.add(o.getClass());
while (!queue.isEmpty()) {
Class> clazz = queue.remove();
if (predicate.apply(clazz)) {
matches.add(clazz);
}
seen.add(clazz);
if (clazz.getSuperclass() != null && !seen.contains(clazz.getSuperclass())) {
queue.add(clazz.getSuperclass());
}
for (Class> iface : clazz.getInterfaces()) {
if (!seen.contains(iface)) {
queue.add(iface);
}
}
}
return matches;
}
/**
* Creates an object from a string. It first checks if the string is a class name and if so attempts to create an
* instance or get a singleton instance of the class. Next it checks if the string is class name and a static method
* or field name and if so invokes the static method or gets the value of the static field.
*
* @param string The string containing information about the object to create
* @return An object or null if the object the string maps to cannot be determined.
*/
public static Object createObject(String string) {
if (Strings.isNullOrEmpty(string)) {
return null;
}
if (ReflectionUtils.isClassName(string)) {
try {
Class> clazz = ReflectionUtils.getClassForNameQuietly(string);
if (ReflectionUtils.isSingleton(clazz)) {
return ReflectionUtils.getSingletonFor(clazz);
} else {
return BeanUtils.getBean(clazz);
}
} catch (Exception e) {
return null;
}
}
int index = string.lastIndexOf(".");
if (index != -1) {
String field = string.substring(string.lastIndexOf('.') + 1);
String cStr = string.substring(0, string.lastIndexOf('.'));
if (ReflectionUtils.isClassName(cStr)) {
Class> clazz = ReflectionUtils.getClassForNameQuietly(cStr);
if (Reflect.onClass(clazz).containsField(field)) {
try {
return Reflect.onClass(clazz).get(field).get();
} catch (ReflectionException e) {
//ignore this;
}
}
if (Reflect.onClass(clazz).containsMethod(field)) {
try {
return Reflect.onClass(clazz).invoke(field).get();
} catch (ReflectionException e) {
//ignore the error
}
}
try {
return Reflect.onClass(clazz).create(field).get();
} catch (ReflectionException e) {
return null;
}
}
}
return null;
}
/**
* Determines if a class is a singleton by looking for certain methods on the class. Looks for a
* getInstance, getSingleton or createInstance method.
*
* @param clazz The class
* @return True if it appears to be a singleton.
*/
public static boolean isSingleton(Class> clazz) {
return clazz != null && (Reflect.onClass(clazz).containsMethod("getInstance") || Reflect.onClass(clazz).containsMethod("getSingleton") || Reflect.onClass(clazz).containsMethod("createInstance"));
}
/**
* Determines if a string is a class name.
*
* @param string The string
* @return True if value of the string is a class name.
*/
public static boolean isClassName(String string) {
return !Strings.isNullOrEmpty(string) && getClassForNameQuietly(string) != null;
}
/**
* Gets the singleton instance of a class. Looks for a getInstance, getSingleton or
* createInstance method.
*
* @param cz The class
* @return The singleton instance or null if the class is not a singleton.
*/
public static T getSingletonFor(Class> cz) {
try {
if (Reflect.onClass(cz).containsMethod("getInstance")) {
return Reflect.onClass(cz).invoke("getInstance").get();
} else if (Reflect.onClass(cz).containsMethod("getSingleton")) {
return Reflect.onClass(cz).invoke("getSingleton").get();
} else if (Reflect.onClass(cz).containsMethod("createInstance")) {
return Reflect.onClass(cz).invoke("createInstance").get();
}
return null;
} catch (ReflectionException e) {
throw Throwables.propagate(e);
}
}
/**
* Determines if two constructors have the same signature.
*
* @param c1 Constructor 1
* @param c2 Constructor 2
* @return True if they have the same signature, false if not
*/
public static boolean constructorsEqual(Constructor> c1, Constructor> c2) {
return c1.getName().equals(c2.getName()) && typesMatch(c1.getParameterTypes(), c2.getParameterTypes());
}
/**
* Determines if two fields have the same signature (i.e. name)
*
* @param f1 Field 1
* @param f2 Field 2
* @return True if they have the same signature, false if not
*/
public static boolean fieldsEqual(Field f1, Field f2) {
return f1.getName().equals(f2.getName());
}
/**
* Attempts to determine the Class for a given name in string form. For convenience the String, primitives and
* Object versions of the primitives can be called with just their name. The following types also have a short hand
*
- List - java.util.List
- ArrayList - java.util.ArrayList
- Set - java.util.Set
* - HashSet - java.util.HashSet
- Map - java.util.Map
- HashMap - java.util.HashMap
* Array versions can be created by appending [] to the end. For example, String[] refers to an array of
* java.lang.String
*
* @param name The class name
* @return The represented by the name
* @throws ClassNotFoundException The class doesn't exist
*/
public static Class> getClassForName(String name) throws Exception {
return ClassDescriptorCache.getInstance().getClassForName(name);
}
/**
* Calls {@link #getClassForName(String)}, but suppresses exception to a log warning and returns null instead.
* Any exceptions are logged to the default logger.(at DEBUG level) and a null is returned.
*
* @param name Name of class
* @return The Class information or null
*/
public static Class> getClassForNameQuietly(String name) {
try {
return getClassForName(name);
} catch (Exception cnfe) {
log.finest(cnfe);
return null;
}
}
public static Constructor> bestMatchingConstructor(Class> clazz, int numberOfParameters) {
if (numberOfParameters <= 0) {
try {
return clazz.getConstructor();
} catch (NoSuchMethodException e) {
return null;
}
}
for (Constructor> constructor : clazz.getConstructors()) {
Class>[] parameters = constructor.getParameterTypes();
if (parameters.length == numberOfParameters) {
return constructor;
}
}
return null;
}
/**
* Finds the best Method match for a given method name and types against a collection of methods.
*
* @param methods The collections of methods to choose from
* @param methodName The name of the method we want to match
* @param types The types that we want to pass to the method.
* @return Null if there is no match, otherwise the Method which bests fits the given method name and types
*/
public static Method bestMatchingMethod(Collection methods, String methodName, Class[] types) {
if (methods == null || Strings.isNullOrEmpty(methodName) || types == null) {
return null;
}
for (Method method : methods) {
if (method.getName().equals(methodName) && typesMatch(method.getParameterTypes(), types)) {
return method;
}
}
return null;
}
/**
* Gets the types for an array of objects
*
* @param args The arguments to get types for
* @return A 0 sized array if the array is null, otherwise an array of class equalling the classes of the given args
* or Object.cass if the arg is null.
*/
public static Class[] getTypes(Object... args) {
if (args.length == 0) {
return new Class[0];
}
Class[] types = new Class[args.length];
for (int i = 0; i < args.length; i++) {
types[i] = args[i] == null ? Object.class : args[i].getClass();
}
return types;
}
/**
* Determines if two methods have the same signature.
*
* @param m1 Method 1
* @param m2 Method 2
* @return True if they have the same signature, false if not
*/
public static boolean methodsEqual(Method m1, Method m2) {
return m1.getName().equals(m2.getName()) && typesMatch(m1.getParameterTypes(), m2.getParameterTypes());
}
public static boolean hasField(Class> clazz, String fieldName) {
if (clazz == null) {
return false;
}
return ClassDescriptorCache.getInstance()
.getClassDescriptor(clazz)
.getFields(false)
.stream()
.anyMatch(f -> f.getName().equals(fieldName));
}
public static boolean hasDeclaredField(Class> clazz, String fieldName) {
if (clazz == null) {
return false;
}
try {
clazz.getDeclaredField(fieldName);
} catch (NoSuchFieldException e) {
return false;
}
return true;
}
/**
* Determines if the two given arrays of class are compatible with one another.
*
* @param c1 array 1
* @param c2 array 2
* @return True if all classes are the same or those c1 are assignable from c2, otherwise false
*/
@SuppressWarnings("unchecked")
public static boolean typesMatch(Class>[] c1, Class[] c2) {
if (c1.length != c2.length) {
return false;
}
for (int i = 0; i < c1.length; i++) {
if (!inSameHierarchy(c1[i], c2[i]) && !isConvertible(c1[i], c2[i])) {
return false;
}
}
return true;
}
private static boolean isConvertible(Class> c1, Class> c2) {
return Val.class.isAssignableFrom(c1) || Val.class.isAssignableFrom(c2);
}
private static boolean inSameHierarchy(Class> c1, Class> c2) {
return Primitives.wrap(c1).isAssignableFrom(c2) || Primitives.wrap(c2).isAssignableFrom(c1);
}
private enum IsInterface implements Predicate> {
INSTANCE;
@Override
public boolean apply(Class> input) {
return input != null && input.isInterface();
}
}
}// END OF CLASS ReflectionUtils