ReflectionUtils.java

  1. /*
  2.  * *************************************************************************************************************************************************************
  3.  *
  4.  * TheseFoolishThings: Miscellaneous utilities
  5.  * http://tidalwave.it/projects/thesefoolishthings
  6.  *
  7.  * Copyright (C) 2009 - 2024 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 the License.
  12.  * 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 an "AS IS" BASIS, WITHOUT WARRANTIES OR
  17.  * CONDITIONS OF ANY KIND, either express or implied.  See the License for the specific language governing permissions and limitations under the License.
  18.  *
  19.  * *************************************************************************************************************************************************************
  20.  *
  21.  * git clone https://bitbucket.org/tidalwave/thesefoolishthings-src
  22.  * git clone https://github.com/tidalwave-it/thesefoolishthings-src
  23.  *
  24.  * *************************************************************************************************************************************************************
  25.  */
  26. package it.tidalwave.util;

  27. import java.lang.annotation.Annotation;
  28. import java.lang.reflect.Array;
  29. import java.lang.reflect.Field;
  30. import java.lang.reflect.GenericArrayType;
  31. import java.lang.reflect.InvocationTargetException;
  32. import java.lang.reflect.ParameterizedType;
  33. import java.lang.reflect.Type;
  34. import java.lang.reflect.TypeVariable;
  35. import javax.annotation.CheckForNull;
  36. import javax.annotation.Nonnull;
  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.     private static final List<String> INJECT_CLASS_NAMES = List.of("javax.inject.Inject", "jakarta.inject.Inject");

  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.     @Nonnull
  67.     public static <T> List<Class<?>> getTypeArguments (@Nonnull final Class<T> baseClass,
  68.                                                        @Nonnull final Class<? extends T> childClass)
  69.       {
  70.         final Map<Type, Type> resolvedTypes = new HashMap<>();
  71.         Type type = childClass;

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

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

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

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

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

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

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

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

  117.         return typeArgumentsAsClasses;
  118.       }

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

  136.             if (constructors.length > 1)
  137.               {
  138.                 throw new RuntimeException("Multiple constructors in " + type);
  139.               }

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

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

  149.     /***********************************************************************************************************************************************************
  150.      * Performs dependency injection to an object by means of field introspection.
  151.      *
  152.      * @param object  the object
  153.      * @param beans   the bag of objects to instantiate
  154.      * @since         3.2-ALPHA-17
  155.      **********************************************************************************************************************************************************/
  156.     public static void injectDependencies (@Nonnull final Object object, @Nonnull final Map<Class<?>, Object> beans)
  157.       {
  158.         for (final var field : object.getClass().getDeclaredFields())
  159.           {
  160.             if (hasInjectAnnotation(field))
  161.               {
  162.                 field.setAccessible(true);
  163.                 final var type = field.getType();
  164.                 final var dependency = beans.get(type);

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

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

  180.     /***********************************************************************************************************************************************************
  181.      * Returns the class literal associated to the given type.
  182.      *
  183.      * @param   type    the type to inspect
  184.      * @return          the class literal; it might be {@code null} if fails
  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.     /***********************************************************************************************************************************************************
  215.      *
  216.      **********************************************************************************************************************************************************/
  217.     private static boolean hasInjectAnnotation (@Nonnull final Field field)
  218.       {
  219.         final var classLoader = Thread.currentThread().getContextClassLoader();

  220.         for (final var className : INJECT_CLASS_NAMES)
  221.           {
  222.             try
  223.               {
  224.                 @SuppressWarnings("unchecked")
  225.                 final var clazz = (Class<? extends Annotation>)classLoader.loadClass(className);

  226.                 if (field.getAnnotation(clazz) != null)
  227.                   {
  228.                     return true;
  229.                   }
  230.               }
  231.             catch (ClassNotFoundException ignored)
  232.               {
  233.                 // try next
  234.               }
  235.           }

  236.         return false;
  237.       }
  238.   }