content: package alien4cloud.utils;
  
  import java.beans.BeanInfo;
  import java.beans.IntrospectionException;
  import java.beans.Introspector;
  import java.beans.PropertyDescriptor;
  import java.lang.annotation.Annotation;
  import java.lang.reflect.Field;
  import java.lang.reflect.InvocationTargetException;
  import java.lang.reflect.Modifier;
  import java.lang.reflect.ParameterizedType;
  import java.lang.reflect.Type;
  import java.lang.reflect.TypeVariable;
  import java.util.HashMap;
  import java.util.LinkedList;
  import java.util.List;
  import java.util.Map;
  import java.util.Set;
  
  import org.springframework.beans.BeanWrapper;
  import org.springframework.beans.BeanWrapperImpl;
  import org.springframework.beans.BeansException;
  import org.springframework.util.ReflectionUtils;
  
  import com.google.common.collect.Maps;
  import com.google.common.collect.Sets;
  
  import alien4cloud.exception.InvalidArgumentException;
  import lombok.extern.slf4j.Slf4j;
  
  /**
   * Utility class to help working with java reflection.
   */
  @Slf4j
  public final class ReflectionUtil {
  
      private ReflectionUtil() {
      }
  
      /**
       * Get the Class of the generic argument of a generic class based on it's implementation class.
       *
       * @param implementationClass The implementation class that actually is a sub-class of generic class.
       * @param genericClass The generic class for which to get the argument type.
       * @param index The index of the argument.
       * @return null if the type cannot be found, the generic type when found.
       */
      public static <T> Class<?> getGenericArgumentType(Class<? extends T> implementationClass, Class<T> genericClass, int index) {
          if (implementationClass.isAssignableFrom(genericClass)) {
              return null;
          }
  
          Type[] types = getGenericArgumentTypes(implementationClass, genericClass, implementationClass.getTypeParameters());
          if (types != null) {
              return (Class<?>) types[index];
          }
  
          return null;
      }
  
      private static <T> Type[] getGenericArgumentTypes(Class<? extends T> currentClass, Class<T> genericClass, Type... resolvedTypes) {
          Map<String, Type> resolvedTypesByName = new HashMap<String, Type>();
          for (int i = 0; i < resolvedTypes.length; i++) {
              TypeVariable<?> typeVariable = (TypeVariable<?>) currentClass.getTypeParameters()[i];
              resolvedTypesByName.put(typeVariable.getName(), resolvedTypes[i]);
          }
  
          Set<Type> directGenericInterfaces = getDirectGenericInterfaces(currentClass, genericClass);
          Type[] types = null;
          for (Type genericInterface : directGenericInterfaces) {
              if (genericInterface instanceof Class) {
                  types = getGenericArgumentTypes((Class<? extends T>) genericInterface, genericClass);
              }
              if (types == null && genericInterface instanceof ParameterizedType) {
                  ParameterizedType parameterizedType = (ParameterizedType) genericInterface;
                  List<Type> nextResolvedTypes = new LinkedList<Type>();
                  for (Type t : parameterizedType.getActualTypeArguments()) {
                      if (t instanceof TypeVariable<?>) {
                          Type resolvedType = resolvedTypesByName.get(((TypeVariable<?>) t).getName());
                          nextResolvedTypes.add(resolvedType != null ? resolvedType : t);
                      } else {
                          nextResolvedTypes.add(t);
                      }
                  }
  
                  types = getGenericArgumentTypes((Class<? extends T>) parameterizedType.getRawType(), genericClass,
                          nextResolvedTypes.toArray(new Type[nextResolvedTypes.size()]));
              }
              if (types != null) {
                  return types;
              }
          }
  
          return resolvedTypes;
      }
  
      /**
       * Get all direct generic interfaces that can be assigned from the expected class.
       * 
       * @param clazz The class from which to get generic.
       * @param expectedClazz The class from which .
       * @return a Set that contains all direct generic interfaces matching the expectedClazz from clazz.
       */
      private static Set<Type> getDirectGenericInterfaces(Class<?> clazz, Class<?> expectedClazz) {
          Set<Type> superTypes = Sets.newHashSet();
          if (clazz.getGenericSuperclass() != null && isAssignableFrom(clazz.getGenericSuperclass(), expectedClazz)) {
              superTypes.add(clazz.getGenericSuperclass());
          }
          for (Type genericInterface : clazz.getGenericInterfaces()) {
              if (isAssignableFrom(genericInterface, expectedClazz)) {
                  superTypes.add(genericInterface);
              }
          }
          return superTypes;
      }
  
      private static boolean isAssignableFrom(Type type, Class<?> clazz) {
          if (type instanceof Class) {
              return clazz.isAssignableFrom((Class<?>) type);
          }
          if (type instanceof ParameterizedType) {
              Type rawType = ((ParameterizedType) type).getRawType();
              return rawType instanceof Class && clazz.isAssignableFrom((Class<?>) rawType);
          }
          return false;
      }
  
      /**
       * Try getting an annotation from a property, fall back to field with the same name if not found
       *
       * @param clazz the parent class
       * @param annotationClass the annotation type to search
       * @param property the property to search for annotation
       * @param <T> generic annotation type
       * @return annotation
       */
      public static <T extends Annotation> T getAnnotation(Class<?> clazz, final Class<T> annotationClass, final PropertyDescriptor property) {
          T annotationType = property.getReadMethod().getAnnotation(annotationClass);
          if (annotationType == null) {
              // Search on the setter
              annotationType = property.getWriteMethod().getAnnotation(annotationClass);
          }
          try {
              final Map<Class<?>, T> foundFields = Maps.newHashMap();
              if (annotationType == null) {
                  // Search in the field declaration
                  ReflectionUtils.doWithFields(clazz, new ReflectionUtils.FieldCallback() {
                      @Override
                      public void doWith(final Field field) throws IllegalAccessException {
                          foundFields.put(field.getDeclaringClass(), field.getAnnotation(annotationClass));
  
                      }
                  }, new ReflectionUtils.FieldFilter() {
                      @Override
                      public boolean matches(final Field field) {
                          // no static fields please
                          return !Modifier.isStatic(field.getModifiers())
                                  && (field.getName().equals(property.getName()) || field.getName().equals(property.getReadMethod().getName()));
                      }
                  });
                  if (!foundFields.isEmpty()) {
                      annotationType = foundFields.values().iterator().next();
                  }
              }
          } catch (IllegalArgumentException e) {
              if (log.isDebugEnabled()) {
                  log.debug("Reflection error", e);
              }
          }
          return annotationType;
      }
  
      /**
       * Get properties descriptors
       *
       * @param clazz the class to get descriptors from
       * @return properties descriptors
       */
      public static PropertyDescriptor[] getPropertyDescriptors(Class<?> clazz) {
          try {
              BeanInfo beanInfo = Introspector.getBeanInfo(clazz);
              return beanInfo.getPropertyDescriptors();
          } catch (IntrospectionException e) {
              throw new InvalidArgumentException("Cannot get descriptors of class [" + clazz.getName() + "]", e);
          }
      }
  
      /**
       * Merge object from an object to another. Failsafe : resist to invalid property.
       * 
       * @param from source of the update
       * @param to target of the update
       * @param ignoreNullValue indicate if we should merge null value
       * @param ignores properties names that should be ignored
       */
      public static void mergeObject(Object from, Object to, boolean ignoreNullValue, Set<String> ignores) {
          try {
              Map<String, Object> settablePropertiesMap = Maps.newHashMap();
              PropertyDescriptor[] propertyDescriptors = getPropertyDescriptors(from.getClass());
              for (PropertyDescriptor property : propertyDescriptors) {
                  if (property.getReadMethod() == null || property.getWriteMethod() == null) {
                      continue;
                  }
                  Object value = property.getReadMethod().invoke(from);
                  if ((value != null || !ignoreNullValue) && !ignores.contains(property.getName())) {
                      settablePropertiesMap.put(property.getName(), value);
                  }
              }
              for (Map.Entry<String, Object> settableProperty : settablePropertiesMap.entrySet()) {
                  // Set new values
                  String propertyName = settableProperty.getKey();
                  Object propertyValue = settableProperty.getValue();
                  setPropertyValue(to, propertyName, propertyValue);
              }
          } catch (IllegalAccessException | InvocationTargetException | BeansException e) {
              throw new InvalidArgumentException("Cannot merge object", e);
          }
      }
  
      /**
       * Merge object from an object to another. Failsafe : resist to invalid property.
       *
       * @param from source of the update
       * @param to target of the update
       * @param ignores properties names that should be ignored
       */
      public static void mergeObject(Object from, Object to, String... ignores) {
          mergeObject(from, to, true, Sets.newHashSet(ignores));
      }
  
      /**
       * Get property's value of an object
       *
       * @param object the object to get property from
       * @param property the name of the property
       * @return the value of the property
       */
      public static Object getPropertyValue(Object object, String property) {
          BeanWrapper wrapper = new BeanWrapperImpl(object);
          return wrapper.getPropertyValue(property);
      }
  
      /**
       * Set property's value of an object
       *
       * @param object the object to set property
       * @param property the name of the property
       * @param value new value to set
       */
      public static void setPropertyValue(Object object, String property, Object value) {
          BeanWrapper wrapper = new BeanWrapperImpl(object);
          if (wrapper.isWritableProperty(property)) {
              wrapper.setPropertyValue(property, value);
          }
      }
  
      /**
       * Recursive getDeclaredField that allows looking for parent types fields.
       * 
       * @param fieldName The name of the field to get.
       * @param clazz The class in which to search.
       */
      public static Field getDeclaredField(Class<?> clazz, String fieldName) throws NoSuchFieldException {
          try {
              return clazz.getDeclaredField(fieldName);
          } catch (NoSuchFieldException e) {
              if (Object.class.equals(clazz.getSuperclass())) {
                  throw e;
              }
              return getDeclaredField(clazz.getSuperclass(), fieldName);
          }
      }
  
      /**
       * Recursive getDeclaredField that allows looking for parent types fields for the first field that holds the given annotation.
       *
       * @param annotationClass The annotation we are looking for.
       * @param clazz The class in which to search.
       */
      public static Field getDeclaredField(Class<?> clazz, Class<? extends Annotation> annotationClass) {
          Field[] fields = clazz.getDeclaredFields();
          for (Field field : fields) {
              if (field.isAnnotationPresent(annotationClass)) {
                  return field;
              }
          }
          if (Object.class.equals(clazz.getSuperclass())) {
              return null;
          }
          return getDeclaredField(clazz.getSuperclass(), annotationClass);
      }
  
      /**
       * Return true if the given class is a primitive type, a primitive wrapper or a String.
       * 
       * @param clazz The class to check.
       * @return True if the class is String, a primitive type or a primitive Wrapper type.
       */
      public static boolean isPrimitiveOrWrapperOrString(Class<?> clazz) {
          return clazz.isPrimitive() || clazz.equals(String.class) || clazz.equals(Boolean.class) || clazz.equals(Integer.class) || clazz.equals(Long.class)
                  || clazz.equals(Float.class) || clazz.equals(Double.class) || clazz.equals(Character.class) || clazz.equals(Byte.class)
                  || clazz.equals(Short.class);
      }
  
  }
  