/**
* 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 org.apache.aries.blueprint.utils;
import java.lang.ref.WeakReference;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
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.security.AccessControlContext;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import org.apache.aries.blueprint.container.GenericType;
import org.apache.aries.blueprint.di.ExecutionContext;
import org.apache.aries.blueprint.services.ExtendedBlueprintContainer;
import org.osgi.framework.BundleReference;
import org.osgi.service.blueprint.container.ComponentDefinitionException;
/**
* TODO: javadoc
*
* @version $Rev$, $Date$
*/
public class ReflectionUtils {
private static Map<Class<?>, WeakReference<Method[]>> publicMethods = Collections.synchronizedMap(new WeakHashMap<Class<?>, WeakReference<Method[]>>());
private static Map<Class<?>, PropertyDescriptor[][]> beanInfos = Collections.synchronizedMap(new WeakHashMap<Class<?>, PropertyDescriptor[][]>());
public static boolean hasDefaultConstructor(Class type) {
if (!Modifier.isPublic(type.getModifiers())) {
return false;
}
if (Modifier.isAbstract(type.getModifiers())) {
return false;
}
Constructor[] constructors = type.getConstructors();
for (Constructor constructor : constructors) {
if (Modifier.isPublic(constructor.getModifiers()) &&
constructor.getParameterTypes().length == 0) {
return true;
}
}
return false;
}
public static Set<String> getImplementedInterfaces(Set<String> classes, Class clazz) {
if (clazz != null && clazz != Object.class) {
for (Class itf : clazz.getInterfaces()) {
if (Modifier.isPublic(itf.getModifiers())) {
classes.add(itf.getName());
}
getImplementedInterfaces(classes, itf);
}
getImplementedInterfaces(classes, clazz.getSuperclass());
}
return classes;
}
public static Set<Class<?>> getImplementedInterfacesAsClasses(Set<Class<?>> classes, Class<?> clazz) {
if (clazz != null && clazz != Object.class) {
for (Class<?> itf : clazz.getInterfaces()) {
if (Modifier.isPublic(itf.getModifiers())) {
classes.add(itf);
}
getImplementedInterfacesAsClasses(classes, itf);
}
getImplementedInterfacesAsClasses(classes, clazz.getSuperclass());
}
return classes;
}
public static Set<String> getSuperClasses(Set<String> classes, Class clazz) {
if (clazz != null && clazz != Object.class) {
if (Modifier.isPublic(clazz.getModifiers())) {
classes.add(clazz.getName());
}
getSuperClasses(classes, clazz.getSuperclass());
}
return classes;
}
public static Method getLifecycleMethod(Class clazz, String name) {
if (name != null) {
for (Method method : getPublicMethods(clazz)) {
if (method.getName().equals(name)
&& method.getParameterTypes().length == 0
&& Void.TYPE.equals(method.getReturnType())) {
return method;
}
}
}
return null;
}
public static Method[] getPublicMethods(Class clazz) {
WeakReference<Method[]> ref = publicMethods.get(clazz);
Method[] methods = ref != null ? ref.get() : null;
if (methods == null) {
ArrayList<Method> array = new ArrayList<Method>();
doGetPublicMethods(clazz, array);
methods = array.toArray(new Method[array.size()]);
publicMethods.put(clazz, new WeakReference<Method[]>(methods));
}
return methods;
}
private static void doGetPublicMethods(Class clazz, ArrayList<Method> methods) {
Class parent = clazz.getSuperclass();
if (parent != null) {
doGetPublicMethods(parent, methods);
}
for (Class interf : clazz.getInterfaces()) {
doGetPublicMethods(interf, methods);
}
if (Modifier.isPublic(clazz.getModifiers())) {
for (Method mth : clazz.getMethods()) {
removeByNameAndSignature(methods, mth);
methods.add(mth);
}
}
}
private static void removeByNameAndSignature(ArrayList<Method> methods, Method toRemove) {
for (int i = 0; i < methods.size(); i++) {
Method m = methods.get(i);
if (m != null &&
m.getReturnType() == toRemove.getReturnType() &&
m.getName() == toRemove.getName() &&
arrayContentsEq(m.getParameterTypes(),
toRemove.getParameterTypes())) {
methods.remove(i--);
}
}
}
private static boolean arrayContentsEq(Object[] a1, Object[] a2) {
if (a1 == null) {
return a2 == null || a2.length == 0;
}
if (a2 == null) {
return a1.length == 0;
}
if (a1.length != a2.length) {
return false;
}
for (int i = 0; i < a1.length; i++) {
if (a1[i] != a2[i]) {
return false;
}
}
return true;
}
public static List<Method> findCompatibleMethods(Class clazz, String name, Class[] paramTypes) {
List<Method> methods = new ArrayList<Method>();
for (Method method : getPublicMethods(clazz)) {
Class[] methodParams = method.getParameterTypes();
if (name.equals(method.getName()) && Void.TYPE.equals(method.getReturnType()) && methodParams.length == paramTypes.length && !method.isBridge()) {
boolean assignable = true;
for (int i = 0; i < paramTypes.length && assignable; i++) {
assignable &= paramTypes[i] == null || methodParams[i].isAssignableFrom(paramTypes[i]);
}
if (assignable) {
methods.add(method);
}
}
}
return methods;
}
public static PropertyDescriptor[] getPropertyDescriptors(Class clazz, boolean allowFieldInjection) {
PropertyDescriptor[][] properties = beanInfos.get(clazz);
int index = allowFieldInjection ? 0 : 1;
if (properties == null) {
properties = new PropertyDescriptor[2][];
beanInfos.put(clazz, properties);
}
if (properties[index] == null) {
Set<String> propertyNames = new HashSet<String>();
Map<String,Method> getters = new HashMap<String, Method>();
Map<String,List<Method>> setters = new HashMap<String, List<Method>>();
Set<String> illegalProperties = new HashSet<String>();
for (Method method : getPublicMethods(clazz)) {
if (Modifier.isStatic(method.getModifiers()) || method.isBridge()) continue;
String name = method.getName();
Class<?> argTypes[] = method.getParameterTypes();
Class<?> resultType = method.getReturnType();
if (name.length() > 3 && name.startsWith("set") && resultType == Void.TYPE && argTypes.length == 1) {
name = decapitalize(name.substring(3));
if (!!!setters.containsKey(name)) setters.put(name, new ArrayList<Method>());
setters.get(name).add(method);
propertyNames.add(name);
} else if (name.length() > 3 && name.startsWith("get") && resultType != Void.TYPE && argTypes.length == 0) {
name = decapitalize(name.substring(3));
if (getters.containsKey(name)) illegalProperties.add(name);
else propertyNames.add(name);
getters.put(name, method);
} else if (name.length() > 2 && name.startsWith("is") && argTypes.length == 0 && resultType == boolean.class) {
name = decapitalize(name.substring(2));
if (getters.containsKey(name)) illegalProperties.add(name);
else propertyNames.add(name);
getters.put(name, method);
}
}
Map<String, PropertyDescriptor> props = new HashMap<String, PropertyDescriptor>();
for (String propName : propertyNames) {
props.put(propName,
new MethodPropertyDescriptor(propName, getters.get(propName), setters.get(propName)));
}
if (allowFieldInjection) {
for (Class cl = clazz; cl != null && cl != Object.class; cl = cl.getSuperclass()) {
for (Field field : cl.getDeclaredFields()) {
if (!!!Modifier.isStatic(field.getModifiers())) {
String name = decapitalize(field.getName());
PropertyDescriptor desc = props.get(name);
if (desc == null) {
props.put(name, new FieldPropertyDescriptor(name, field));
} else if (desc instanceof MethodPropertyDescriptor) {
props.put(name,
new JointPropertyDescriptor((MethodPropertyDescriptor) desc,
new FieldPropertyDescriptor(name, field)));
} else {
illegalProperties.add(name);
}
}
}
}
}
List<PropertyDescriptor> result = new ArrayList<PropertyDescriptor>();
for (PropertyDescriptor prop : props.values()) {
if (!!!illegalProperties.contains(prop.getName())) result.add(prop);
}
properties[index] = result.toArray(new PropertyDescriptor[result.size()]);
}
return properties[index];
}
private static String decapitalize(String name) {
if (name == null || name.length() == 0) {
return name;
}
if (name.length() > 1 && Character.isUpperCase(name.charAt(1)) &&
Character.isUpperCase(name.charAt(0))) {
return name;
}
char chars[] = name.toCharArray();
chars[0] = Character.toLowerCase(chars[0]);
return new String(chars);
}
public static Object invoke(AccessControlContext acc, final Method method, final Object instance, final Object... args) throws Exception {
if (acc == null) {
return method.invoke(instance, args);
} else {
try {
return AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
public Object run() throws Exception {
return method.invoke(instance, args);
}
}, acc);
} catch (PrivilegedActionException e) {
throw e.getException();
}
}
}
public static Object newInstance(AccessControlContext acc, final Class clazz) throws Exception {
if (acc == null) {
return clazz.newInstance();
} else {
try {
return AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
public Object run() throws Exception {
return clazz.newInstance();
}
}, acc);
} catch (PrivilegedActionException e) {
throw e.getException();
}
}
}
public static Object newInstance(AccessControlContext acc, final Constructor constructor, final Object... args) throws Exception {
if (acc == null) {
return constructor.newInstance(args);
} else {
try {
return AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
public Object run() throws Exception {
return constructor.newInstance(args);
}
}, acc);
} catch (PrivilegedActionException e) {
throw e.getException();
}
}
}
public static abstract class PropertyDescriptor {
private final String name;
public PropertyDescriptor(String name) {
this.name = name;
}
public String getName() {
return name;
}
public abstract boolean allowsGet();
public abstract boolean allowsSet();
protected abstract Object internalGet(ExtendedBlueprintContainer container, Object instance) throws Exception;
protected abstract void internalSet(ExtendedBlueprintContainer container, Object instance, Object value) throws Exception;
public Object get(final Object instance, final ExtendedBlueprintContainer container) throws Exception {
if (container.getAccessControlContext() == null) {
return internalGet(container, instance);
} else {
try {
return AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
public Object run() throws Exception {
return internalGet(container, instance);
}
}, container.getAccessControlContext());
} catch (PrivilegedActionException e) {
throw e.getException();
}
}
}
public void set(final Object instance, final Object value, final ExtendedBlueprintContainer container) throws Exception {
if (container.getAccessControlContext() == null) {
internalSet(container, instance, value);
} else {
try {
AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
public Object run() throws Exception {
internalSet(container, instance, value);
return null;
}
}, container.getAccessControlContext());
} catch (PrivilegedActionException e) {
throw e.getException();
}
}
}
protected Object convert(Object obj, Type type) throws Exception {
return ExecutionContext.Holder.getContext().convert(obj, new GenericType(type));
}
}
private static class JointPropertyDescriptor extends PropertyDescriptor {
private final MethodPropertyDescriptor mpd;
private final FieldPropertyDescriptor fpd;
public JointPropertyDescriptor(MethodPropertyDescriptor mpd, FieldPropertyDescriptor fpd) {
super(mpd.getName());
this.mpd = mpd;
this.fpd = fpd;
}
@Override
public boolean allowsGet() {
return mpd.allowsGet() || fpd.allowsGet();
}
@Override
public boolean allowsSet() {
return mpd.allowsSet() || fpd.allowsSet();
}
@Override
protected Object internalGet(ExtendedBlueprintContainer container, Object instance) throws Exception {
if (mpd.allowsGet()) return mpd.internalGet(container, instance);
else if (fpd.allowsGet()) return fpd.internalGet(container, instance);
else throw new UnsupportedOperationException();
}
@Override
protected void internalSet(ExtendedBlueprintContainer container, Object instance, Object value) throws Exception {
if (mpd.allowsSet()) mpd.internalSet(container, instance, value);
else if (fpd.allowsSet()) fpd.internalSet(container, instance, value);
else throw new UnsupportedOperationException();
}
}
private static class FieldPropertyDescriptor extends PropertyDescriptor {
// instead of holding on to the java.lang.reflect.Field objects we retrieve it every time. The reason is that PropertyDescriptors are
// used as values in a WeakHashMap with the class corresponding to the field as the key
private final String fieldName;
private final WeakReference<Class<?>> declaringClass;
public FieldPropertyDescriptor(String name, Field field) {
super(name);
this.fieldName = field.getName();
this.declaringClass = new WeakReference(field.getDeclaringClass());
}
public boolean allowsGet() {
return true;
}
public boolean allowsSet() {
return true;
}
private Field getField(ExtendedBlueprintContainer container) throws ClassNotFoundException, NoSuchFieldException {
if (declaringClass.get() == null) throw new ClassNotFoundException("Declaring class was garbage collected");
return declaringClass.get().getDeclaredField(fieldName);
}
protected Object internalGet(final ExtendedBlueprintContainer container, final Object instance) throws Exception {
if (useContainersPermission(container)) {
try {
return AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
public Object run() throws Exception {
return doInternalGet(container, instance);
}
});
} catch (PrivilegedActionException pae) {
Exception e = pae.getException();
if (e instanceof IllegalAccessException) throw (IllegalAccessException) e;
else throw (RuntimeException) e;
}
} else {
return doInternalGet(container, instance);
}
}
private Object doInternalGet(ExtendedBlueprintContainer container, Object instance) throws Exception {
Field field = getField(container);
boolean isAccessible = field.isAccessible();
field.setAccessible(true);
try {
return field.get(instance);
} finally {
field.setAccessible(isAccessible);
}
}
protected void internalSet(final ExtendedBlueprintContainer container, final Object instance, final Object value) throws Exception {
try {
Boolean wasSet = AccessController.doPrivileged(new PrivilegedExceptionAction<Boolean>() {
public Boolean run() throws Exception {
if (useContainersPermission(container)) {
doInternalSet(container, instance, value);
return Boolean.TRUE;
}
return Boolean.FALSE;
}
});
if(!!!wasSet) {
doInternalSet(container, instance, value);
}
} catch (PrivilegedActionException pae) {
throw pae.getException();
}
}
private void doInternalSet(ExtendedBlueprintContainer container, Object instance, Object value) throws Exception {
Field field = getField(container);
final Object convertedValue = convert(value, field.getGenericType());
boolean isAccessible = field.isAccessible();
field.setAccessible(true);
try {
field.set(instance, convertedValue);
} finally {
field.setAccessible(isAccessible);
}
}
/**
* Determine whether the field access (in particular the call to {@link Field#setAccessible(boolean)} should be done with the Blueprint extender's
* permissions, rather than the joint (more restrictive) permissions of the extender plus the Blueprint bundle.
*
* We currently only allow this for classes that originate from inside the Blueprint bundle. Otherwise this would open a potential security hole.
* @param container
* @return
*/
private boolean useContainersPermission(ExtendedBlueprintContainer container) throws ClassNotFoundException {
if (declaringClass.get() == null) throw new ClassNotFoundException("Declaring class was garbage collected");
ClassLoader loader = declaringClass.get().getClassLoader();
if (loader == null) return false;
if (loader instanceof BundleReference) {
BundleReference ref = (BundleReference) loader;
return ref.getBundle().equals(container.getBundleContext().getBundle());
}
return false;
}
}
private static class MethodDescriptor {
private final String methodName;
private final WeakReference<Class<?>> declaringClass;
private final List<WeakReference<Class<?>>> argClasses;
public MethodDescriptor(Method method) {
methodName = method.getName();
declaringClass = new WeakReference<Class<?>>(method.getDeclaringClass());
List<WeakReference<Class<?>>> accumulator = new ArrayList<WeakReference<Class<?>>>();
for (Class<?> c : method.getParameterTypes()) {
accumulator.add(new WeakReference<Class<?>>(c));
}
argClasses = Collections.unmodifiableList(accumulator);
}
public Method getMethod(ExtendedBlueprintContainer container) throws ClassNotFoundException, NoSuchMethodException {
Class<?>[] argumentClasses = new Class<?>[argClasses.size()];
for (int i=0; i<argClasses.size(); i++) {
argumentClasses[i] = argClasses.get(i).get();
if (argumentClasses[i] == null) throw new ClassNotFoundException("Argument class was garbage collected");
}
if (declaringClass.get() == null) throw new ClassNotFoundException("Declaring class was garbage collected");
return declaringClass.get().getMethod(methodName, argumentClasses);
}
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append(declaringClass.get()).append(".").append(methodName).append("(");
boolean first = true;
for (WeakReference<Class<?>> wcl : argClasses) {
if (!!!first) builder.append(",");
else first = false;
builder.append(wcl.get());
}
builder.append(")");
return builder.toString();
}
}
private static class MethodPropertyDescriptor extends PropertyDescriptor {
// instead of holding on to the java.lang.reflect.Method objects we retrieve it every time. The reason is that PropertyDescriptors are
// used as values in a WeakHashMap with the class corresponding to the methods as the key
private final MethodDescriptor getter;
private final Collection<MethodDescriptor> setters;
private MethodPropertyDescriptor(String name, Method getter, Collection<Method> setters) {
super(name);
this.getter = (getter != null) ? new MethodDescriptor(getter) : null;
if (setters != null) {
Collection<MethodDescriptor> accumulator = new ArrayList<MethodDescriptor>();
for (Method s : setters) accumulator.add(new MethodDescriptor(s));
this.setters = Collections.unmodifiableCollection(accumulator);
} else {
this.setters = Collections.emptyList();
}
}
public boolean allowsGet() {
return getter != null;
}
public boolean allowsSet() {
return !!!setters.isEmpty();
}
protected Object internalGet(ExtendedBlueprintContainer container, Object instance)
throws Exception {
if (getter != null) {
return getter.getMethod(container).invoke(instance);
} else {
throw new UnsupportedOperationException();
}
}
protected void internalSet(ExtendedBlueprintContainer container, Object instance, Object value) throws Exception {
Method setterMethod = findSetter(container, value);
if (setterMethod != null) {
setterMethod.invoke(instance, convert(value, resolveParameterType(instance.getClass(), setterMethod)));
} else {
throw new ComponentDefinitionException(
"No converter available to convert value "+value+" into a form applicable for the " +
"setters of property "+getName());
}
}
private Type resolveParameterType(Class<?> impl, Method setterMethod) {
Type type = setterMethod.getGenericParameterTypes()[0];
Class<?> declaringClass = setterMethod.getDeclaringClass();
TypeVariable<?>[] declaredVariables = declaringClass.getTypeParameters();
if (TypeVariable.class.isInstance(type)) {
// e.g.: "T extends Serializable"
TypeVariable variable = TypeVariable.class.cast(type);
int index = 0;
for (; index < declaredVariables.length; index++) {
// find the class declaration index...
if (variable == declaredVariables[index]) {
break;
}
}
if (index >= declaredVariables.length) {
// not found - now what...
return type;
}
// navigate from the implementation type up to the declaring super
// class to find the real generic type...
Class<?> c = impl;
while (c != null && c != declaringClass) {
Type sup = c.getGenericSuperclass();
if (sup != null && ParameterizedType.class.isInstance(sup)) {
ParameterizedType pt = ParameterizedType.class.cast(sup);
if (declaringClass == pt.getRawType()) {
Type t = pt.getActualTypeArguments()[index];
return t;
}
}
c = c.getSuperclass();
}
return type;
} else {
// not a generic type...
return type;
}
}
private Method findSetter(ExtendedBlueprintContainer container, Object value) throws Exception {
Class<?> valueType = (value == null) ? null : value.getClass();
Method getterMethod = (getter != null) ? getter.getMethod(container) : null;
Collection<Method> setterMethods = getSetters(container);
Method result = findMethodByClass(getterMethod, setterMethods, valueType);
if (result == null) result = findMethodWithConversion(setterMethods, value);
return result;
}
private Collection<Method> getSetters(ExtendedBlueprintContainer container) throws Exception {
Collection<Method> result = new ArrayList<Method>();
for (MethodDescriptor md : setters) result.add(md.getMethod(container));
return result;
}
private Method findMethodByClass(Method getterMethod, Collection<Method> setterMethods, Class<?> arg)
throws ComponentDefinitionException {
Method result = null;
if (!hasSameTypeSetter(getterMethod, setterMethods)) {
throw new ComponentDefinitionException(
"At least one Setter method has to match the type of the Getter method for property "
+ getName());
}
if (setterMethods.size() == 1) {
return setterMethods.iterator().next();
}
for (Method m : setterMethods) {
Class<?> paramType = m.getParameterTypes()[0];
if ((arg == null && Object.class.isAssignableFrom(paramType))
|| (arg != null && paramType.isAssignableFrom(arg))) {
// pick the method that has the more specific parameter if
// any
if (result != null) {
Class<?> oldParamType = result.getParameterTypes()[0];
if (paramType.isAssignableFrom(oldParamType)) {
// do nothing, result is correct
} else if (oldParamType.isAssignableFrom(paramType)) {
result = m;
} else {
throw new ComponentDefinitionException(
"Ambiguous setter method for property "
+ getName()
+ ". More than one method matches the parameter type "
+ arg);
}
} else {
result = m;
}
}
}
return result;
}
// ensure there is a setter that matches the type of the getter
private boolean hasSameTypeSetter(Method getterMethod, Collection<Method> setterMethods) {
if (getterMethod == null) {
return true;
}
Iterator<Method> it = setterMethods.iterator();
while (it.hasNext()) {
Method m = it.next();
if (m.getParameterTypes()[0].equals(getterMethod.getReturnType())) {
return true;
}
}
return false;
}
private Method findMethodWithConversion(Collection<Method> setterMethods, Object value) throws Exception {
ExecutionContext ctx = ExecutionContext.Holder.getContext();
List<Method> matchingMethods = new ArrayList<Method>();
for (Method m : setterMethods) {
Type paramType = m.getGenericParameterTypes()[0];
if (ctx.canConvert(value, new GenericType(paramType))) matchingMethods.add(m);
}
if (matchingMethods.isEmpty()) return null;
else if (matchingMethods.size() == 1) return matchingMethods.get(0);
else throw new ComponentDefinitionException(
"Ambiguous setter method for property "+ getName() +
". More than one method matches the parameter "+value+" after applying conversion.");
}
public String toString() {
return "PropertyDescriptor <name: "+getName()+", getter: "+getter+", setter: "+setters;
}
}
public static Throwable getRealCause(Throwable t) {
if (t instanceof InvocationTargetException && t.getCause() != null) {
return t.getCause();
}
return t;
}
}