ReflectionUtils.java

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

  28. import java.lang.reflect.Array;
  29. import java.lang.reflect.GenericArrayType;
  30. import java.lang.reflect.InvocationTargetException;
  31. import java.lang.reflect.ParameterizedType;
  32. import java.lang.reflect.Type;
  33. import java.lang.reflect.TypeVariable;
  34. import javax.annotation.CheckForNull;
  35. import javax.annotation.Nonnull;
  36. import javax.inject.Inject;
  37. import java.util.ArrayList;
  38. import java.util.Arrays;
  39. import java.util.HashMap;
  40. import java.util.List;
  41. import java.util.Map;
  42. import lombok.extern.slf4j.Slf4j;
  43. import static java.util.Objects.requireNonNull;
  44. import static java.util.stream.Collectors.*;
  45. import static it.tidalwave.util.ShortNames.*;

  46. /***********************************************************************************************************************
  47.  *
  48.  * Adapted from <a href="http://www.artima.com/weblogs/viewpost.jsp?thread=208860">this article</a>
  49.  *
  50.  * @author Ian Robertson
  51.  * @author Fabrizio Giudici
  52.  *
  53.  **********************************************************************************************************************/
  54. @Slf4j
  55. public class ReflectionUtils
  56.   {
  57.     /*******************************************************************************************************************
  58.      *
  59.      * Get the actual type arguments a subclass has used to extend a generic base class.
  60.      *
  61.      * @param   <T>           the static type of the base class
  62.      * @param   baseClass     the base class
  63.      * @param   childClass    the subclass
  64.      * @return                a list of the raw classes for the actual type arguments.
  65.      *
  66.      ******************************************************************************************************************/
  67.     @Nonnull
  68.     public static <T> List<Class<?>> getTypeArguments (@Nonnull final Class<T> baseClass,
  69.                                                        @Nonnull final Class<? extends T> childClass)
  70.       {
  71.         final Map<Type, Type> resolvedTypes = new HashMap<>();
  72.         Type type = childClass;

  73.         // start walking up the inheritance hierarchy until we hit baseClass
  74.         while (!getClass(type).equals(baseClass))
  75.           {
  76.             if (type instanceof Class<?>)
  77.               {
  78.                 // there is no useful information for us in raw types, so just keep going.
  79.                 type = ((Class<?>)type).getGenericSuperclass();
  80.               }
  81.             else
  82.               {
  83.                 final var parameterizedType = (ParameterizedType) type;
  84.                 final var rawType = (Class<?>) parameterizedType.getRawType();
  85.                 final var actualTypeArguments = parameterizedType.getActualTypeArguments();
  86.                 final TypeVariable<?>[] typeParameters = rawType.getTypeParameters();

  87.                 for (var i = 0; i < actualTypeArguments.length; i++)
  88.                   {
  89.                     resolvedTypes.put(typeParameters[i], actualTypeArguments[i]);
  90.                   }

  91.                 if (!rawType.equals(baseClass))
  92.                   {
  93.                     type = rawType.getGenericSuperclass();
  94.                   }
  95.               }
  96.           }

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

  100.         if (type instanceof Class)
  101.           {
  102.             actualTypeArguments = ((Class<?>)type).getTypeParameters();
  103.           }
  104.         else
  105.           {
  106.             actualTypeArguments = ((ParameterizedType)type).getActualTypeArguments();
  107.           }

  108.         final var typeArgumentsAsClasses = new ArrayList<Class<?>>();

  109.         // resolve types by chasing down type variables.
  110.         for (var baseType : actualTypeArguments)
  111.           {
  112.             while (resolvedTypes.containsKey(baseType))
  113.               {
  114.                 baseType = resolvedTypes.get(baseType);
  115.               }

  116.             typeArgumentsAsClasses.add(getClass(baseType));
  117.           }

  118.         return typeArgumentsAsClasses;
  119.       }

  120.     /*******************************************************************************************************************
  121.      *
  122.      * Instantiates an object of the given class performing dependency injections through the constructor.
  123.      *
  124.      * @param <T>     the generic type of the object to instantiate
  125.      * @param type    the dynamic type of the object to instantiate; it is expected to have a single constructor
  126.      * @param beans   the pool of objects to instantiate
  127.      * @return        the new instance
  128.      * @throws        RuntimeException if something fails
  129.      * @since         3.2-ALPHA-17
  130.      *
  131.      ******************************************************************************************************************/
  132.     public static <T> T instantiateWithDependencies (@Nonnull final Class<? extends T> type,
  133.                                                      @Nonnull final Map<Class<?>, Object> beans)
  134.       {
  135.         try
  136.           {
  137.             log.debug("instantiateWithDependencies({}, {})", shortName(type), shortIds(beans.values()));
  138.             final var constructors = type.getConstructors();

  139.             if (constructors.length > 1)
  140.               {
  141.                 throw new RuntimeException("Multiple constructors in " + type);
  142.               }

  143.             final var parameters = Arrays.stream(constructors[0].getParameterTypes()).map(beans::get).collect(toList());

  144.             log.trace(">>>> ctor arguments: {}", shortIds(parameters));
  145.             return type.cast(constructors[0].newInstance(parameters.toArray()));
  146.           }
  147.         catch (InstantiationException | IllegalAccessException | InvocationTargetException e)
  148.           {
  149.             throw new RuntimeException(e);
  150.           }
  151.       }

  152.     /*******************************************************************************************************************
  153.      *
  154.      * @since         3.2-ALPHA-17
  155.      *
  156.      ******************************************************************************************************************/
  157.     public static void injectDependencies (@Nonnull final Object object, @Nonnull final Map<Class<?>, Object> beans)
  158.       {
  159.         for (final var field : object.getClass().getDeclaredFields())
  160.           {
  161.             if (field.getAnnotation(Inject.class) != null)
  162.               {
  163.                 field.setAccessible(true);
  164.                 final var type = field.getType();
  165.                 final var dependency = beans.get(type);

  166.                 if (dependency == null)
  167.                   {
  168.                     throw new RuntimeException("Can't inject " + object + "." + field.getName());
  169.                   }

  170.                 try
  171.                   {
  172.                     field.set(object, dependency);
  173.                   }
  174.                 catch (IllegalArgumentException | IllegalAccessException e)
  175.                   {
  176.                     throw new RuntimeException(e);
  177.                   }
  178.               }
  179.           }
  180.       }

  181.     /*******************************************************************************************************************
  182.      *
  183.      *
  184.      *
  185.      ******************************************************************************************************************/
  186.     @CheckForNull
  187.     public static Class<?> getClass (@Nonnull final Type type)
  188.       {
  189.         requireNonNull(type, "type");

  190.         if (type instanceof Class<?>)
  191.           {
  192.             return (Class<?>)type;
  193.           }
  194.         else if (type instanceof ParameterizedType)
  195.           {
  196.             return getClass(((ParameterizedType)type).getRawType());
  197.           }
  198.         else if (type instanceof GenericArrayType)
  199.           {
  200.             final var componentType = ((GenericArrayType)type).getGenericComponentType();
  201.             final var componentClass = getClass(componentType);

  202.             if (componentClass == null)
  203.               {
  204.                 return null;
  205.               }

  206.             return Array.newInstance(componentClass, 0).getClass();
  207.           }
  208.         else
  209.           {
  210. //            throw new IllegalArgumentException(type.toString());
  211.             return null;
  212.           }
  213.       }
  214.   }