ReflectionUtils.java

  1. /*
  2.  * #%L
  3.  * *********************************************************************************************************************
  4.  *
  5.  * SteelBlue
  6.  * http://steelblue.tidalwave.it - git clone git@bitbucket.org:tidalwave/steelblue-src.git
  7.  * %%
  8.  * Copyright (C) 2015 - 2015 Tidalwave s.a.s. (http://tidalwave.it)
  9.  * %%
  10.  *
  11.  * *********************************************************************************************************************
  12.  *
  13.  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
  14.  * the License. You may obtain a copy of the License at
  15.  *
  16.  *     http://www.apache.org/licenses/LICENSE-2.0
  17.  *
  18.  * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
  19.  * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the
  20.  * specific language governing permissions and limitations under the License.
  21.  *
  22.  * *********************************************************************************************************************
  23.  *
  24.  *
  25.  *
  26.  * *********************************************************************************************************************
  27.  * #L%
  28.  */
  29. package it.tidalwave.role.ui.javafx.impl.util;

  30. import lombok.experimental.UtilityClass;
  31. import lombok.extern.slf4j.Slf4j;
  32. import javax.annotation.CheckForNull;
  33. import javax.annotation.Nonnull;
  34. import java.lang.reflect.Array;
  35. import java.lang.reflect.Constructor;
  36. import java.lang.reflect.Field;
  37. import java.lang.reflect.GenericArrayType;
  38. import java.lang.reflect.InvocationTargetException;
  39. import java.lang.reflect.ParameterizedType;
  40. import java.lang.reflect.Type;
  41. import java.lang.reflect.TypeVariable;
  42. import java.util.ArrayList;
  43. import java.util.Arrays;
  44. import java.util.HashMap;
  45. import java.util.List;
  46. import java.util.Map;
  47. import javax.inject.Inject;
  48. import static java.util.stream.Collectors.toList;
  49. import static it.tidalwave.role.spi.impl.LogUtil.*;

  50. /***********************************************************************************************************************
  51.  *
  52.  * Just slightly adapted from http://www.artima.com/weblogs/viewpost.jsp?thread=208860
  53.  *
  54.  * TODO: Consider incorporating this in TFT.
  55.  *
  56.  * @author Fabrizio Giudici
  57.  * @author based on code of Ian Robertson
  58.  *
  59.  **********************************************************************************************************************/
  60. @Slf4j @UtilityClass
  61. public class ReflectionUtils
  62.   {
  63.     /*******************************************************************************************************************
  64.      *
  65.      *
  66.      *
  67.      ******************************************************************************************************************/
  68.     public static void injectDependencies (@Nonnull final Object object, @Nonnull final Map<Class<?>, Object> beans)
  69.       {
  70.         for (final Field field : object.getClass().getDeclaredFields())
  71.           {
  72.             if (field.getAnnotation(Inject.class) != null)
  73.               {
  74.                 field.setAccessible(true);
  75.                 final Class<?> type = field.getType();
  76.                 final Object dependency = beans.get(type);

  77.                 if (dependency == null)
  78.                   {
  79.                     throw new RuntimeException("Can't inject " + object + "." + field.getName());
  80.                   }

  81.                 try
  82.                   {
  83.                     field.set(object, dependency);
  84.                   }
  85.                 catch (IllegalArgumentException | IllegalAccessException e)
  86.                   {
  87.                     throw new RuntimeException(e);
  88.                   }
  89.               }
  90.           }
  91.       }

  92.     /*******************************************************************************************************************
  93.      *
  94.      * Instantiates an object of the given class performing dependency injections through the constructor.
  95.      *
  96.      * @param <T>     the generic type of the object to instantiate
  97.      * @param type    the dynamic type of the object to instantiate; it is expected to have a single constructor
  98.      * @param beans   the pool of objects to instantiate
  99.      * @return        the new instance
  100.      * @throws        RuntimeException if something fails
  101.      *
  102.      ******************************************************************************************************************/
  103.     public static <T> T instantiateWithDependencies (@Nonnull final Class<T> type,
  104.                                                      @Nonnull final Map<Class<?>, Object> beans)
  105.       {
  106.         try
  107.           {
  108.             log.debug("instantiateWithDependencies({}, {})", shortName(type), shortIds(beans.values()));
  109.             final Constructor<?>[] constructors = type.getConstructors();

  110.             if (constructors.length > 1)
  111.               {
  112.                 throw new RuntimeException("Multiple constructors in " + type);
  113.               }

  114.             final List<Object> parameters =
  115.                     Arrays.stream(constructors[0].getParameterTypes()).map(beans::get).collect(toList());

  116.             log.trace(">>>> ctor arguments: {}", shortIds(parameters));
  117.             return type.cast(constructors[0].newInstance(parameters.toArray()));
  118.           }
  119.         catch (InstantiationException | IllegalAccessException | InvocationTargetException e)
  120.           {
  121.             throw new RuntimeException(e);
  122.           }
  123.       }

  124.     /*******************************************************************************************************************
  125.      *
  126.      * Get the actual type arguments a child class has used to extend a generic base class.
  127.      *
  128.      * @param baseClass the base class
  129.      * @param childClass the child class
  130.      * @return a list of the raw classes for the actual type arguments.
  131.      *
  132.      ******************************************************************************************************************/
  133.     public static <T> List<Class<?>> getTypeArguments (@Nonnull final Class<T> baseClass,
  134.                                                        @Nonnull final Class<? extends T> childClass)
  135.       {
  136.         final Map<Type, Type> resolvedTypes = new HashMap<>();
  137.         Type type = childClass;

  138.         // start walking up the inheritance hierarchy until we hit baseClass
  139.         while (!getClass(type).equals(baseClass))
  140.           {
  141.             if (type instanceof Class<?>)
  142.               {
  143.                 // there is no useful information for us in raw types, so just keep going.
  144.                 type = ((Class<?>)type).getGenericSuperclass();
  145.               }
  146.             else
  147.               {
  148.                 final ParameterizedType parameterizedType = (ParameterizedType) type;
  149.                 final Class<?> rawType = (Class) parameterizedType.getRawType();
  150.                 final Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
  151.                 final TypeVariable<?>[] typeParameters = rawType.getTypeParameters();

  152.                 for (int i = 0; i < actualTypeArguments.length; i++)
  153.                   {
  154.                     resolvedTypes.put(typeParameters[i], actualTypeArguments[i]);
  155.                   }

  156.                 if (!rawType.equals(baseClass))
  157.                   {
  158.                     type = rawType.getGenericSuperclass();
  159.                   }
  160.               }
  161.           }

  162.         // finally, for each actual type argument provided to baseClass, determine (if possible)
  163.         // the raw class for that type argument.
  164.         final Type[] actualTypeArguments;

  165.         if (type instanceof Class)
  166.           {
  167.             actualTypeArguments = ((Class)type).getTypeParameters();
  168.           }
  169.         else
  170.           {
  171.             actualTypeArguments = ((ParameterizedType)type).getActualTypeArguments();
  172.           }

  173.         final List<Class<?>> typeArgumentsAsClasses = new ArrayList<>();
  174.         // resolve types by chasing down type variables.
  175.         for (Type baseType : actualTypeArguments)
  176.           {
  177.             while (resolvedTypes.containsKey(baseType))
  178.               {
  179.                 baseType = resolvedTypes.get(baseType);
  180.               }

  181.             typeArgumentsAsClasses.add(getClass(baseType));
  182.           }

  183.         return typeArgumentsAsClasses;
  184.       }

  185.     /*******************************************************************************************************************
  186.      *
  187.      *
  188.      *
  189.      ******************************************************************************************************************/
  190.     @CheckForNull
  191.     public static Class<?> getClass (@Nonnull final Type type)
  192.       {
  193.         if (type instanceof Class<?>)
  194.           {
  195.             return (Class<?>)type;
  196.           }
  197.         else if (type instanceof ParameterizedType)
  198.           {
  199.             return getClass(((ParameterizedType)type).getRawType());
  200.           }
  201.         else if (type instanceof GenericArrayType)
  202.           {
  203.             final Type componentType = ((GenericArrayType)type).getGenericComponentType();
  204.             final Class<?> componentClass = getClass(componentType);

  205.             if (componentClass == null)
  206.               {
  207.                 return null;
  208.               }

  209.             return Array.newInstance(componentClass, 0).getClass();
  210.           }
  211.         else
  212.           {
  213.             return null;
  214.           }
  215.       }
  216.   }