![JAR search and dependency download from the Maven repository](/logo.png)
cc.liloo.spark.clazz.Reflect Maven / Gradle / Ivy
/**
Apache License V2.0
--------------------------------------------------
Copyright (c) 2011-2013, Lukas Eder, [email protected]
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.
--------------------------------------------------
*/
package cc.liloo.spark.clazz;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* A wrapper for an {@link Object} or {@link Class} upon which reflective calls can be made.
*
* An example of using Reflect
is
*
*
* // Static import all reflection methods to decrease verbosity
* import static org.joor.Reflect.*;
* Wrap an Object / Class / class name with the on() method:
* on("java.lang.String")
* Invoke constructors using the create() method:
* .create("Hello World")
* Invoke methods using the call() method:
* .call("toString")
* Retrieve the wrapped object
*
*
* @author Lukas Eder
*/
public class Reflect {
// ---------------------------------------------------------------------
// Static API used as entrance points to the fluent API
// ---------------------------------------------------------------------
/**
* Wrap a class name.
* This is the same as calling on(Class.forName(name))
* @param name A fully qualified class name
* @return A wrapped class object, to be used for further reflection.
* @throws ReflectException If any reflection exception occurred.
* @see #on(Class)
*/
public static Reflect on(String name) throws ReflectException {
return on(forName(name));
}
/**
* Wrap a class.
* Use this when you want to access static fields and methods on a {@link Class} object, or as a basis for
* constructing objects of that class using {@link #create(Object...)}
* @param clazz The class to be wrapped
* @return A wrapped class object, to be used for further reflection.
*/
public static Reflect on(Class> clazz) {
return new Reflect(clazz);
}
/**
* Wrap an object.
* Use this when you want to access instance fields and methods on any {@link Object}
* @param object The object to be wrapped
* @return A wrapped object, to be used for further reflection.
*/
public static Reflect on(Object object) {
return new Reflect(object);
}
/**
* Conveniently render an {@link AccessibleObject} accessible
* @param Class
* @param accessible The object to render accessible
* @return The argument object rendered accessible
*/
public static T accessible(T accessible) {
if (accessible == null)
return null;
if (!accessible.isAccessible())
accessible.setAccessible(true);
return accessible;
}
// ---------------------------------------------------------------------
// Members
// ---------------------------------------------------------------------
/**
* The wrapped object
*/
private final Object object;
/**
* A flag indicating whether the wrapped object is a {@link Class} (for accessing static fields and methods), or any
* other type of {@link Object} (for accessing instance fields and methods).
*/
private final boolean isClass;
// ---------------------------------------------------------------------
// Constructors
// ---------------------------------------------------------------------
private Reflect(Class> type) {
this.object = type;
this.isClass = true;
}
private Reflect(Object object) {
this.object = object;
this.isClass = false;
}
// ---------------------------------------------------------------------
// Fluent Reflection API
// ---------------------------------------------------------------------
/**
* @param Class
* @return the wrapped object
*/
@SuppressWarnings("unchecked")
public T get() {
return (T) object;
}
/**
* Set a field value.
*
* This is roughly equivalent to {@link Field#set(Object, Object)}. If the wrapped object is a {@link Class}, then
* this will set a value to a static member field. If the wrapped object is any other {@link Object}, then this will
* set a value to an instance member field.
*
* @param name The field name
* @param value The new field value
* @return The same wrapped object, to be used for further reflection.
* @throws ReflectException If any reflection exception occurred.
*/
public Reflect set(String name, Object value) throws ReflectException {
try {
// Try setting a public field
Field field = type().getField(name);
field.set(object, unwrap(value));
return this;
} catch (Exception e1) {
// Try again, setting a non-public field
try {
accessible(type().getDeclaredField(name)).set(object, unwrap(value));
return this;
} catch (Exception e2) {
throw new ReflectException(e2);
}
}
}
/**
* Get a field value.
*
* This is roughly equivalent to {@link Field#get(Object)}. If the wrapped object is a {@link Class}, then this will
* get a value from a static member field. If the wrapped object is any other {@link Object}, then this will get a
* value from an instance member field.
*
* If you want to "navigate" to a wrapped version of the field, use {@link #field(String)} instead.
*
* @param Class
* @param name The field name
* @return The field value
* @throws ReflectException If any reflection exception occurred.
* @see #field(String)
*/
public T get(String name) throws ReflectException {
return field(name). get();
}
public Field getDeclaredField(Class> clazz, String name) throws NoSuchFieldException {
Field field = null;
while (clazz != Object.class) {
try {
field = clazz.getDeclaredField(name);
if (field != null) break;
} catch (Exception e) {
clazz = clazz.getSuperclass();
}
}
if (field == null) throw new NoSuchFieldException("name is not found");
return field;
}
/**
* Get a wrapped field.
*
* This is roughly equivalent to {@link Field#get(Object)}. If the wrapped object is a {@link Class}, then this will
* wrap a static member field. If the wrapped object is any other {@link Object}, then this wrap an instance member
* field.
*
* @param name The field name
* @return The wrapped field
* @throws ReflectException If any reflection exception occurred.
*/
public Reflect field(String name) throws ReflectException {
try {
// Try getting a public field
Field field = type().getField(name);
return on(field.get(object));
} catch (Exception e1) {
// Try again, getting a non-public field
try {
return on(accessible(getDeclaredField(type(), name)).get(object));
} catch (Exception e2) {
throw new ReflectException(e2);
}
}
}
/**
* Get a Map containing field names and wrapped values for the fields' values.
*
* If the wrapped object is a {@link Class}, then this will return static fields. If the wrapped object is any other
* {@link Object}, then this will return instance fields.
*
* These two calls are equivalent
*
* on(object).field("myField");
* on(object).fields().get("myField");
*
*
* @return A map containing field names and wrapped values.
*/
public Map fields() {
Map result = new LinkedHashMap();
for (Field field : type().getFields()) {
if (!isClass ^ Modifier.isStatic(field.getModifiers())) {
String name = field.getName();
result.put(name, field(name));
}
}
return result;
}
/**
* Call a method by its name.
*
* This is a convenience method for calling call(name, new Object[0])
*
* @param name The method name
* @return The wrapped method result or the same wrapped object if the method returns void
, to be used for further reflection.
* @throws ReflectException If any reflection exception occurred.
* @see #call(String, Object...)
*/
public Reflect call(String name) throws ReflectException {
return call(name, new Object[0]);
}
/**
* Call a method by its name.
*
* This is roughly equivalent to {@link Method#invoke(Object, Object...)}. If the wrapped object is a {@link Class},
* then this will invoke a static method. If the wrapped object is any other {@link Object}, then this will invoke
* an instance method.
*
* Just like {@link Method#invoke(Object, Object...)}, this will try to wrap primitive types or unwrap primitive
* type wrappers if applicable. If several methods are applicable, by that rule, the first one encountered is
* called. i.e. when calling
* on(...).call("method", 1, 1);
*
The first of the following methods will be called:
* public void method(int param1, Integer param2);
* public void method(Integer param1, int param2);
* public void method(Number param1, Number param2);
* public void method(Number param1, Object param2);
* public void method(int param1, Object param2);
*
*
* The best matching method is searched for with the following strategy:
*
* - public method with exact signature match in class hierarchy
* - non-public method with exact signature match on declaring class
* - public method with similar signature in class hierarchy
* - non-public method with similar signature on declaring class
*
*
* @param name The method name
* @param args The method arguments
* @return The wrapped method result or the same wrapped object if the method returns void
, to be used for further reflection.
* @throws ReflectException If any reflection exception occurred.
*/
public Reflect call(String name, Object... args) throws ReflectException {
Class>[] types = types(args);
// Try invoking the "canonical" method, i.e. the one with exact matching argument types
try {
Method method = exactMethod(name, types);
return on(method, object, args);
}
// If there is no exact match, try to find a method that has a "similar" signature if primitive argument types are converted to their wrappers
catch (NoSuchMethodException e) {
try {
Method method = similarMethod(name, types);
return on(method, object, args);
} catch (NoSuchMethodException e1) {
throw new ReflectException(e1);
}
}
}
/**
* Searches a method with the exact same signature as desired.
*
* If a public method is found in the class hierarchy, this method is returned. Otherwise a private method with the
* exact same signature is returned. If no exact match could be found, we let the {@code NoSuchMethodException} pass
* through.
*/
private Method exactMethod(String name, Class>[] types) throws NoSuchMethodException {
final Class> type = type();
// first priority: find a public method with exact signature match in class hierarchy
try {
return type.getMethod(name, types);
}
// second priority: find a private method with exact signature match on declaring class
catch (NoSuchMethodException e) {
return type.getDeclaredMethod(name, types);
}
}
/**
* Searches a method with a similar signature as desired using
* {@link #isSimilarSignature(java.lang.reflect.Method, String, Class[])}.
*
* First public methods are searched in the class hierarchy, then private methods on the declaring class. If a
* method could be found, it is returned, otherwise a {@code NoSuchMethodException} is thrown.
*/
private Method similarMethod(String name, Class>[] types) throws NoSuchMethodException {
final Class> type = type();
// first priority: find a public method with a "similar" signature in class hierarchy similar interpreted in when primitive argument types are converted to their wrappers
for (Method method : type.getMethods()) {
if (isSimilarSignature(method, name, types)) {
return method;
}
}
// second priority: find a non-public method with a "similar" signature on declaring class
for (Method method : type.getDeclaredMethods()) {
if (isSimilarSignature(method, name, types)) {
return method;
}
}
throw new NoSuchMethodException("No similar method " + name + " with params " + Arrays.toString(types) + " could be found on type " + type() + ".");
}
/**
* Determines if a method has a "similar" signature, especially if wrapping primitive argument types would result in an exactly matching signature.
*/
private boolean isSimilarSignature(Method possiblyMatchingMethod, String desiredMethodName, Class>[] desiredParamTypes) {
return possiblyMatchingMethod.getName().equals(desiredMethodName) && match(possiblyMatchingMethod.getParameterTypes(), desiredParamTypes);
}
/**
* Call a constructor.
*
* This is a convenience method for calling create(new Object[0])
*
* @return The wrapped new object, to be used for further reflection.
* @throws ReflectException If any reflection exception occurred.
* @see #create(Object...)
*/
public Reflect create() throws ReflectException {
return create(new Object[0]);
}
/**
* Call a constructor.
*
* This is roughly equivalent to {@link Constructor#newInstance(Object...)}. If the wrapped object is a
* {@link Class}, then this will create a new object of that class. If the wrapped object is any other
* {@link Object}, then this will create a new object of the same type.
*
* Just like {@link Constructor#newInstance(Object...)}, this will try to wrap primitive types or unwrap primitive
* type wrappers if applicable. If several constructors are applicable, by that rule, the first one encountered is
* called. i.e. when calling
*
*
* on(C.class).create(1, 1);
*
*
The first of the following constructors will be applied:
*
*
* public C(int param1, Integer param2);
* public C(Integer param1, int param2);
* public C(Number param1, Number param2);
* public C(Number param1, Object param2);
* public C(int param1, Object param2);
*
*
*
* @param args The constructor arguments
* @return The wrapped new object, to be used for further reflection.
* @throws ReflectException If any reflection exception occurred.
*/
public Reflect create(Object... args) throws ReflectException {
Class>[] types = types(args);
// Try invoking the "canonical" constructor, i.e. the one with exact matching argument types
try {
Constructor> constructor = type().getDeclaredConstructor(types);
return on(constructor, args);
}
// If there is no exact match, try to find one that has a "similar"
// signature if primitive argument types are converted to their wrappers
catch (NoSuchMethodException e) {
for (Constructor> constructor : type().getConstructors()) {
if (match(constructor.getParameterTypes(), types)) {
return on(constructor, args);
}
}
throw new ReflectException(e);
}
}
@SuppressWarnings("unchecked")
/**
* Create a proxy for the wrapped object allowing to typesafely invoke methods on it using a custom interface
* @param proxyType The interface type that is implemented by the proxy
* @return A proxy for the wrapped object
*/
public P as(Class
proxyType) {
final boolean isMap = (object instanceof Map);
final InvocationHandler handler = (proxy, method, args) -> {
String name = method.getName();
try {
// Actual method name matches always come first
return on(object).call(name, args).get();
} catch (ReflectException e) {
// [#14] Simulate POJO behaviour on wrapped map objects
if (isMap) {
Map map = (Map) object;
int length = (args == null ? 0 : args.length);
if (length == 0 && name.startsWith("get")) {
return map.get(property(name.substring(3)));
} else if (length == 0 && name.startsWith("is")) {
return map.get(property(name.substring(2)));
} else if (length == 1 && name.startsWith("set")) {
map.put(property(name.substring(3)), args[0]);
return null;
}
}
throw e;
}
};
return (P) Proxy.newProxyInstance(proxyType.getClassLoader(), new Class[] { proxyType }, handler);
}
/**
* Get the POJO property name of an getter/setter
*/
private static String property(String string) {
int length = string.length();
if (length == 0) return "";
else if (length == 1) return string.toLowerCase();
else return string.substring(0, 1).toLowerCase() + string.substring(1);
}
// ---------------------------------------------------------------------
// Object API
// ---------------------------------------------------------------------
/**
* Check whether two arrays of types match, converting primitive types to their corresponding wrappers.
*/
private boolean match(Class>[] declaredTypes, Class>[] actualTypes) {
if (declaredTypes.length == actualTypes.length) {
for (int i = 0; i < actualTypes.length; i++)
if (!wrapper(declaredTypes[i]).isAssignableFrom(wrapper(actualTypes[i]))) return false;
return true;
} else {
return false;
}
}
/**
* {@inheritDoc}
*/
@Override
public int hashCode() {
return object.hashCode();
}
/**
* {@inheritDoc}
*/
@Override
public boolean equals(Object obj) {
if (obj instanceof Reflect) return object.equals(((Reflect) obj).get());
return false;
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
return object.toString();
}
// ---------------------------------------------------------------------
// Utility methods
// ---------------------------------------------------------------------
/**
* Wrap an object created from a constructor
*/
private static Reflect on(Constructor> constructor, Object... args) throws ReflectException {
try {
return on(accessible(constructor).newInstance(args));
} catch (Exception e) {
throw new ReflectException(e);
}
}
/**
* Wrap an object returned from a method
*/
private static Reflect on(Method method, Object object, Object... args) throws ReflectException {
try {
accessible(method);
if (method.getReturnType() == void.class) {
method.invoke(object, args);
return on(object);
} else {
return on(method.invoke(object, args));
}
} catch (Exception e) {
throw new ReflectException(e);
}
}
/**
* Unwrap an object
*/
private static Object unwrap(Object object) {
if (object instanceof Reflect) return ((Reflect) object).get();
return object;
}
/**
* Get an array of types for an array of objects
*
* @see Object#getClass()
*/
private static Class>[] types(Object... values) {
if (values == null) return new Class[0];
Class>[] result = new Class[values.length];
for (int i = 0; i < values.length; i++) {
Object value = values[i];
result[i] = value == null ? Object.class : value.getClass();
}
return result;
}
/**
* Load a class
* @see Class#forName(String)
*/
private static Class> forName(String name) throws ReflectException {
try {
return Class.forName(name);
} catch (Exception e) {
throw new ReflectException(e);
}
}
/**
* @see Object#getClass()
* @return Get the type of the wrapped object.
*/
public Class> type() {
if (isClass) return (Class>) object;
else return object.getClass();
}
/**
* Get a wrapper type for a primitive type, or the argument type itself, if it is not a primitive type.
* @param type Class type
* @return The wapper Class
*/
public static Class> wrapper(Class> type) {
if (type == null) {
return null;
} else if (type.isPrimitive()) {
if (boolean.class == type) return Boolean.class;
else if (int.class == type) return Integer.class;
else if (long.class == type) return Long.class;
else if (short.class == type) return Short.class;
else if (byte.class == type) return Byte.class;
else if (double.class == type) return Double.class;
else if (float.class == type) return Float.class;
else if (char.class == type) return Character.class;
else if (void.class == type) return Void.class;
}
return type;
}
}