All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.senzing.reflect.ReflectionUtilities Maven / Gradle / Ivy

The newest version!
package com.senzing.reflect;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;

/**
 * Provides utilities for Java reflection.
 */
public class ReflectionUtilities {
  /**
   * An unmodifiable {@link Map} of primitive types to their
   * corresponding promoted object types.
   */
  private static final Map PROMOTED_TYPE_MAP;

  /**
   * An unmodifiable {@link Map} of promoted object types to their
   * corresponding primitive types.
   */
  private static final Map PRIMITIVE_TYPE_MAP;

  static {
    Map primitiveMap = new LinkedHashMap<>();
    Map promotedMap = new LinkedHashMap<>();
    try {
      promotedMap.put(void.class, Void.class);
      promotedMap.put(boolean.class, Boolean.class);
      promotedMap.put(char.class, Character.class);
      promotedMap.put(byte.class, Byte.class);
      promotedMap.put(short.class, Short.class);
      promotedMap.put(int.class, Integer.class);
      promotedMap.put(long.class, Long.class);
      promotedMap.put(float.class, Float.class);
      promotedMap.put(double.class, Double.class);

      promotedMap.forEach((primitive, promoted) -> {
        primitiveMap.put(promoted, primitive);
      });

      promotedMap.keySet().forEach((primitive) -> {
        primitiveMap.put(primitive, primitive);
      });

      primitiveMap.keySet().forEach(keyType -> {
        if (!keyType.isPrimitive()) {
          promotedMap.put(keyType, keyType);
        }
      });

    } finally {
      PROMOTED_TYPE_MAP   = Collections.unmodifiableMap(promotedMap);
      PRIMITIVE_TYPE_MAP  = Collections.unmodifiableMap(primitiveMap);
    }
  }

  /**
   * Provides a synchronized handler that synchronizes on the proxy for
   * every method that is invoked.
   */
  private static class SynchronizedHandler implements InvocationHandler {
    /**
     * The target object for invoking the method.
     */
    private Object target;

    /**
     * The object to synchronize on.
     */
    private Object monitor = null;

    /**
     * Constructs with the specified target object on which to invoke the
     * methods.  The constructed instance will synchronize on the proxy object
     * that is passed to {@link #invoke(Object, Method, Object[]))}.
     *
     * @param target The target object to invoke the methods on.
     */
    public SynchronizedHandler(Object target) {
      this(target, null);
    }

    /**
     * Constructs with the specified target object on which to invoke the
     * methods and the specified object to synchronize on, or null if
     * the constructed instance should synchronize on the proxy object that is
     * passed to {@link #invoke(Object, Method, Object[]))}.
     *
     * @param target  The target object to invoke the methods on.
     * @param monitor The object to synchronize on, or null if the
     *                constructed instance should synchronize on the proxy
     *                object that is passed to {@link
     *                #invoke(Object, Method, Object[]))}.
     */
    public SynchronizedHandler(Object target, Object monitor) {
      this.target = target;
      this.monitor = monitor;
    }

    /**
     * Overridden to synchronize on the specified proxy object before calling
     * the specified {@link Method} on the underlying target object with the
     * specified arguments.
     *
     * @param proxy  The proxy object to synchronize on.
     * @param method The {@link Method} to invoke on the underlying target
     *               object.
     * @param args   The arguments to invoke the method with.
     * @return The result from invoking the method.
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) {
      final Object monitor = (this.monitor == null) ? proxy : this.monitor;
      synchronized (monitor) {
        try {
          return method.invoke(this.target, args);

        } catch (IllegalAccessException | InvocationTargetException e) {
          throw new RuntimeException(e);
        }
      }
    }
  }

  /**
   * Gets the primitive type associated with the specified promoted object
   * type for that primitive type.  If a type is specified that does not
   * correspond to a primitive type then null is returned.  If
   * the specified type is itself {@linkplain Class#isPrimitive() primitive}
   * then the specified type is returned.
   *
   * @param promotedType The promoted type for which the primitive type is
   *                     being requested.
   *
   * @return The primitive type corresponding to the specified promoted type,
   *         or null if the specified type does not have a
   *         corresponding primitive type.
   */
  public static Class getPrimitiveType(Class promotedType) {
    return PRIMITIVE_TYPE_MAP.get(promotedType);
  }

  /**
   * Gets the promoted object type associated with the specified primitive type.
   * If a type is specified that is not primitive then null is
   * returned.
   *
   * @param primitiveType The primitive type for which the promoted object type
   *                      is being requested.
   *
   * @return The primitive type corresponding to the specified promoted type,
   *         or null if the specified type does not have a
   *         corresponding primitive type.
   */
  public static Class getPromotedType(Class primitiveType) {
    return PROMOTED_TYPE_MAP.get(primitiveType);
  }

  /**
   * Converts a {@link Number} value to the target number type that must have
   * a corresponding primitive {@link Number} type (e.g.: {@link
   * java.math.BigInteger} and {@link java.math.BigDecimal} are not allowed
   * here).  This helps with converting between numeric types when trying to
   * invoke methods by reflection.  If the specified value is null
   * then null is returned.
   *
   * @param value The value to be converted.
   * @param numType The {@link Class} indicating the type of primitive number
   *                to convert to which must be associated with a primitive
   *                numeric type.
   * @return The converted value or null if the specified value
   *         was null.
   * @throws NullPointerException If the specified numeric type is
   *                              null.
   * @throws IllegalArgumentException If an illegal numeric type is specified.
   */
  public static Number convertPrimitiveNumber(Number value, Class numType)
  {
    // verify the target number type is valid
    Objects.requireNonNull(numType, "Number type cannot be null");

    if (numType.isPrimitive()) numType = getPromotedType(numType);

    if (!Number.class.isAssignableFrom(numType)
        || (getPrimitiveType(numType) == null))
    {
      throw new IllegalArgumentException(
          "The specified target number type must extend java.lang.Number and "
              + "have a corresponding primitive numeric type: "
              +  numType.getName());
    }

    // if a null value was specified then return null
    if (value == null) return null;

    numType = getPromotedType(numType);

    // switch on the target type name
    switch (numType.getName()) {
      case "java.lang.Byte":
        return value.byteValue();
      case "java.lang.Short":
        return value.shortValue();
      case "java.lang.Integer":
        return value.intValue();
      case "java.lang.Long":
        return value.longValue();
      case "java.lang.Float":
        return value.floatValue();
      case "java.lang.Double":
        return value.doubleValue();
      default:
        throw new IllegalStateException(
            "Unable to convert to target number type: " + numType);
    }
  }

  /**
   * Creates a synchronized proxy wrapper for the specified {@link Object}
   * using the specified proxy interface class.  The returned instance will
   * synchronize on the returned proxy object before invoking any method
   * on the specified target object.
   *
   * @param proxyInterface The interface that the returned synchronized proxy
   *                       will implement.
   * @param targetObject The target object to wrap with the proxy.
   *
   * @return The synchronized proxy wrapper.
   * @param  The interface for the return value.
   * @param  The type of the target object that implements the
   *            specified interface <I>
   */
  public static  I synchronizedProxy(Class  proxyInterface,
                                                    T         targetObject)
  {
    return synchronizedProxy(proxyInterface, targetObject, null);
  }

  /**
   * Creates a synchronized proxy wrapper for the specified {@link Object}
   * using the specified proxy interface class and specified monitor object to
   * synchronize on.  If the specified monitor object is null then
   * the returned instance will synchronize on the returned proxy object before
   * invoking any method on the specified target object.
   *
   * @param proxyInterface The interface that the returned synchronized proxy
   *                       will implement.
   * @param targetObject The target object to wrap with the proxy.
   * @param monitor The monitor object to synchronize on.
   *
   * @return The synchronized proxy wrapper.
   * @param  The interface for the return value.
   * @param  The type of the target object that implements the
   *            specified interface <I>
   */
  @SuppressWarnings("unchecked")
  public static  I synchronizedProxy(Class  proxyInterface,
                                                    T         targetObject,
                                                    Object    monitor)
  {
    // check the parameters
    Objects.requireNonNull(
        proxyInterface, "The proxy interface cannot be null.");
    Objects.requireNonNull(
        targetObject, "The specified target object cannot be null.");
    if (!proxyInterface.isInterface()) {
      throw new IllegalArgumentException(
          "The specified proxy class is not an interface: "
              + proxyInterface.getName());
    }
    if (!proxyInterface.isAssignableFrom(targetObject.getClass())) {
      throw new IllegalArgumentException(
          "The specified target object does not implement the specified proxy "
          + "interface.  proxyInterface=[ " + proxyInterface.getName() + " ], "
          + "targetObjectClass=[ " + targetObject.getClass().getName() + " ]");
    }
    ClassLoader         classLoader = targetObject.getClass().getClassLoader();
    Class[]             interfaces  = { proxyInterface };
    SynchronizedHandler handler     = new SynchronizedHandler(targetObject,
                                                              monitor);

    return (I) Proxy.newProxyInstance(classLoader, interfaces, handler);
  }
}