content: /**
   * 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) {
          return getPropertyDescriptors(clazz, allowFieldInjection, false);
      }
  
      public static PropertyDescriptor[] getPropertyDescriptors(Class clazz, boolean allowFieldInjection, boolean allowNonStandardSetters) {
          PropertyDescriptor[][] properties = beanInfos.get(clazz);
          int index = (allowFieldInjection ? 0 : 2) + (allowNonStandardSetters ? 0 : 1);
          
          if (properties == null) {
              properties = new PropertyDescriptor[4][];
              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));
                      Method getter = getters.get(name);
                      if (getter == null) {
                          propertyNames.add(name);
                          getters.put(name, method);
                      } else if (!getter.getName().startsWith("is")
                              || getter.getReturnType() != boolean.class
                              || resultType != boolean.class) {
                          illegalProperties.add(name);
                      }
                  } else if (name.length() > 2 && name.startsWith("is") && argTypes.length == 0 && resultType == boolean.class) {
                      name = decapitalize(name.substring(2));
                      Method getter = getters.get(name);
                      if (getter == null) {
                          propertyNames.add(name);
                          getters.put(name, method);
                      } else if (!getter.getName().startsWith("get") || getter.getReturnType() != boolean.class) {
                          illegalProperties.add(name);
                      } else {
                          getters.put(name, method);
                      }
                  }
              }
  
              if (allowNonStandardSetters) {
                  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.startsWith("get") && resultType != Void.TYPE && argTypes.length == 0 && !getters.containsKey(name)) {
                          getters.put(name, method);
                          propertyNames.add(name);
                      } else if (!name.startsWith("set") && resultType == clazz && argTypes.length == 1) {
                          if (!setters.containsKey(name)) {
                              setters.put(name, new ArrayList<Method>());
                          }
                          setters.get(name).add(method);
                          propertyNames.add(name);
                      }
                  }
              }
  
              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();
              Method matchingMethod = null;
              for (Method m : setterMethods) {
                  Type paramType = m.getGenericParameterTypes()[0];
                  if (ctx.canConvert(value, new GenericType(paramType))) {
                      if (matchingMethod != null) {
                          Class<?> p1Class = matchingMethod.getParameterTypes()[0];
                          Class<?> p2Class = m.getParameterTypes()[0];
                          if(!p2Class.equals(p1Class) && p1Class.isAssignableFrom(p2Class)) {
                              // Keep method whose parameter type is the closest to value type
                              matchingMethod = m;
                          } else if(p2Class.equals(p1Class) || !p2Class.isAssignableFrom(p1Class)) {
                              throw new ComponentDefinitionException(
                                  "Ambiguous setter method for property "
                                  + getName() + ". More than one method matches the parameter "
                                  + value + " after applying conversion.");
                          }
                      } else {
                          matchingMethod = m;
                      }
                  }
              }
              return matchingMethod;
          }
          
          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;
      }
  
  }
  