| /** |
| * 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.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.Type; |
| 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.osgi.service.blueprint.container.ComponentDefinitionException; |
| |
| /** |
| * TODO: javadoc |
| * |
| * @version $Rev: 990095 $, $Date: 2010-08-27 11:56:22 +0100 (Fri, 27 Aug 2010) $ |
| */ |
| public class ReflectionUtils { |
| |
| // TODO: MLK: PropertyDescriptor holds a reference to Method which holds a reference to the Class itself |
| 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<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) { |
| try { |
| Method method = clazz.getMethod(name); |
| if (Void.TYPE.equals(method.getReturnType())) { |
| return method; |
| } |
| } catch (NoSuchMethodException e) { |
| // fall thru |
| } |
| } |
| return null; |
| } |
| |
| public static List<Method> findCompatibleMethods(Class clazz, String name, Class[] paramTypes) { |
| List<Method> methods = new ArrayList<Method>(); |
| for (Method method : clazz.getMethods()) { |
| 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 : clazz.getMethods()) { |
| 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 (Field field : clazz.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(Object instance) throws Exception; |
| protected abstract void internalSet(Object instance, Object value) throws Exception; |
| |
| public Object get(final Object instance, AccessControlContext acc) throws Exception { |
| if (acc == null) { |
| return internalGet(instance); |
| } else { |
| try { |
| return AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() { |
| public Object run() throws Exception { |
| return internalGet(instance); |
| } |
| }, acc); |
| } catch (PrivilegedActionException e) { |
| throw e.getException(); |
| } |
| } |
| } |
| |
| public void set(final Object instance, final Object value, AccessControlContext acc) throws Exception { |
| if (acc == null) { |
| internalSet(instance, value); |
| } else { |
| try { |
| AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() { |
| public Object run() throws Exception { |
| internalSet(instance, value); |
| return null; |
| } |
| }, acc); |
| } 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(Object instance) throws Exception { |
| if (mpd.allowsGet()) return mpd.internalGet(instance); |
| else if (fpd.allowsGet()) return fpd.internalGet(instance); |
| else throw new UnsupportedOperationException(); |
| } |
| |
| @Override |
| protected void internalSet(Object instance, Object value) throws Exception { |
| if (mpd.allowsSet()) mpd.internalSet(instance, value); |
| else if (fpd.allowsSet()) fpd.internalSet(instance, value); |
| else throw new UnsupportedOperationException(); |
| } |
| } |
| |
| private static class FieldPropertyDescriptor extends PropertyDescriptor { |
| private final Field field; |
| |
| public FieldPropertyDescriptor(String name, Field field) { |
| super(name); |
| this.field = field; |
| } |
| |
| public boolean allowsGet() { |
| return true; |
| } |
| |
| public boolean allowsSet() { |
| return true; |
| } |
| |
| protected Object internalGet(Object instance) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException { |
| field.setAccessible(true); |
| return field.get(instance); |
| } |
| |
| protected void internalSet(Object instance, Object value) throws Exception { |
| field.setAccessible(true); |
| field.set(instance, convert(value, field.getGenericType())); |
| } |
| } |
| |
| private static class MethodPropertyDescriptor extends PropertyDescriptor { |
| private final Method getter; |
| private final Collection<Method> setters; |
| |
| private MethodPropertyDescriptor(String name, Method getter, Collection<Method> setters) { |
| super(name); |
| this.getter = getter; |
| this.setters = (setters != null) ? setters : Collections.<Method>emptyList(); |
| } |
| |
| public boolean allowsGet() { |
| return getter != null; |
| } |
| |
| public boolean allowsSet() { |
| return !!!setters.isEmpty(); |
| } |
| |
| protected Object internalGet(Object instance) |
| throws IllegalArgumentException, IllegalAccessException, InvocationTargetException { |
| if (getter != null) { |
| return getter.invoke(instance); |
| } else { |
| throw new UnsupportedOperationException(); |
| } |
| } |
| |
| protected void internalSet(Object instance, Object value) throws Exception { |
| |
| Method setterMethod = findSetter(value); |
| |
| if (setterMethod != null) { |
| setterMethod.invoke(instance, convert(value, setterMethod.getGenericParameterTypes()[0])); |
| } else { |
| throw new ComponentDefinitionException( |
| "No converter available to convert value "+value+" into a form applicable for the " + |
| "setters of property "+getName()); |
| } |
| } |
| |
| private Method findSetter(Object value) { |
| Class<?> valueType = (value == null) ? null : value.getClass(); |
| |
| Method result = findMethodByClass(valueType); |
| |
| if (result == null) result = findMethodWithConversion(value); |
| |
| return result; |
| } |
| |
| private Method findMethodByClass(Class<?> arg) |
| throws ComponentDefinitionException { |
| Method result = null; |
| |
| if (!hasSameTypeSetter()) { |
| throw new ComponentDefinitionException( |
| "At least one Setter method has to match the type of the Getter method for property " |
| + getName()); |
| } |
| |
| if (setters.size() == 1) { |
| return setters.iterator().next(); |
| } |
| |
| for (Method m : setters) { |
| 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() { |
| if (getter == null) { |
| return true; |
| } |
| Iterator<Method> it = setters.iterator(); |
| while (it.hasNext()) { |
| Method m = it.next(); |
| if (m.getParameterTypes()[0].equals(getter.getReturnType())) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private Method findMethodWithConversion(Object value) throws ComponentDefinitionException { |
| ExecutionContext ctx = ExecutionContext.Holder.getContext(); |
| List<Method> matchingMethods = new ArrayList<Method>(); |
| for (Method m : setters) { |
| 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; |
| } |
| |
| } |