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

org.spf4j.base.Reflections Maven / Gradle / Ivy

Go to download

A continuously growing collection of utilities to measure performance, get better diagnostics, improve performance, or do things more reliably, faster that other open source libraries...

The newest version!
/*
 * Copyright (c) 2001-2017, Zoltan Farkas All Rights Reserved.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * Additionally licensed with:
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * 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 CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.spf4j.base;

import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.lang.reflect.Type;
import java.net.URL;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.jar.JarInputStream;
import java.util.jar.Manifest;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.ParametersAreNonnullByDefault;
import org.spf4j.concurrent.UnboundedLoadingCache;

@ParametersAreNonnullByDefault
@SuppressFBWarnings("PMB_POSSIBLE_MEMORY_BLOAT") // PRIMITIVES is private.
public final class Reflections {

  private static final BiMap, Class> PRIMITIVE_MAP = HashBiMap.create(12);
  private static final Map> PRIMITIVES = new HashMap<>(12);

  private static final MethodHandle PARAMETER_TYPES_METHOD_FIELD_GET;
  private static final MethodHandle PARAMETER_TYPES_CONSTR_FIELD_GET;
  private static final MethodHandle FIND_CLASS;

  static {
    initPrimitiveMap();
    Field mPtField = AccessController.doPrivileged((PrivilegedAction) () -> {
      Field f;
      try {
        f = Method.class.getDeclaredField("parameterTypes");
        f.setAccessible(true);
      } catch (NoSuchFieldException | SecurityException ex) {
        Logger.getLogger(Reflections.class.getName()).log(Level.FINE,
                "Para type stealing from Method not supported", ex);
        f = null;
      }
      return f;
    });
    Field cPtField = AccessController.doPrivileged((PrivilegedAction) () -> {
      Field f;
      try {
        f = Constructor.class.getDeclaredField("parameterTypes");
        f.setAccessible(true);
      } catch (NoSuchFieldException | SecurityException ex) {
        Logger.getLogger(Reflections.class.getName()).log(Level.FINE,
                "Para type stealing from Constructor not supported", ex);
        f = null;
      }
      return f;
    });

    MethodHandles.Lookup lookup = MethodHandles.lookup();
    if (mPtField != null) {
      try {
        PARAMETER_TYPES_METHOD_FIELD_GET = lookup.unreflectGetter(mPtField);
      } catch (IllegalAccessException ex) {
        throw new ExceptionInInitializerError(ex);
      }
    } else {
      PARAMETER_TYPES_METHOD_FIELD_GET = null;
    }
    if (cPtField != null) {
      try {
        PARAMETER_TYPES_CONSTR_FIELD_GET = lookup.unreflectGetter(cPtField);
      } catch (IllegalAccessException ex) {
        throw new ExceptionInInitializerError(ex);
      }
    } else {
      PARAMETER_TYPES_CONSTR_FIELD_GET = null;
    }

    final Method fc = AccessController.doPrivileged((PrivilegedAction) () -> {
      Method m;
      try {
        m = ClassLoader.class.getDeclaredMethod("findLoadedClass", String.class);
        m.setAccessible(true);
      } catch (NoSuchMethodException | SecurityException ex) {
        throw new ExceptionInInitializerError(ex);
      }
      return m;
    });
    try {
      FIND_CLASS = lookup.unreflect(fc);
    } catch (IllegalAccessException ex) {
      throw new ExceptionInInitializerError(ex);
    }

  }

  @SuppressFBWarnings("RV_RETURN_VALUE_IGNORED")
  private static void initPrimitiveMap() {
    PRIMITIVE_MAP.put(boolean.class, Boolean.class);
    PRIMITIVE_MAP.put(byte.class, Byte.class);
    PRIMITIVE_MAP.put(char.class, Character.class);
    PRIMITIVE_MAP.put(short.class, Short.class);
    PRIMITIVE_MAP.put(int.class, Integer.class);
    PRIMITIVE_MAP.put(long.class, Long.class);
    PRIMITIVE_MAP.put(float.class, Float.class);
    PRIMITIVE_MAP.put(double.class, Double.class);
    PRIMITIVE_MAP.put(void.class, Void.class);
    for (Class clasz : PRIMITIVE_MAP.keySet()) {
      PRIMITIVES.put(clasz.getName(), clasz);
    }
  }


  private Reflections() { }



  /**
   * Optimized alternative of Method.getParameterTypes that steals the parameterTypeArry from method
   * instead of copying it.
   *
   * Here is the benchmark comparison for a 5 arg method:
   *
   * Benchmark                                       Mode  Cnt           Score          Error  Units
   * ReflectionsBenchmark.normalGetTypes            thrpt   10   227015245.933 ±  2725143.765  ops/s
   * ReflectionsBenchmark.optimizedGetTypes         thrpt   10  1159407471.306 ± 21204385.301  ops/s
   *
   * @param m
   * @return
   */
  public static Class[] getParameterTypes(final Method m) {
    if (PARAMETER_TYPES_METHOD_FIELD_GET != null) {
      try {
        return (Class[]) PARAMETER_TYPES_METHOD_FIELD_GET.invokeExact(m);
      } catch (Error | RuntimeException ex) {
        throw ex;
      } catch (Throwable ex) {
        throw new UncheckedExecutionException(ex);
      }
    } else {
      return m.getParameterTypes();
    }
  }


  public static Class[] getParameterTypes(final Constructor m) {
    if (PARAMETER_TYPES_CONSTR_FIELD_GET != null) {
      try {
        return (Class[]) PARAMETER_TYPES_CONSTR_FIELD_GET.invokeExact(m);
      } catch (Error | RuntimeException ex) {
        throw ex;
      } catch (Throwable ex) {
        throw new UncheckedExecutionException(ex);
      }
    } else {
      return m.getParameterTypes();
    }
  }

  public static Class primitiveToWrapper(final Class clasz) {
    if (clasz.isPrimitive()) {
      return PRIMITIVE_MAP.get(clasz);
    } else {
      return clasz;
    }
  }

  @Nonnull
  public static Type primitiveToWrapper(final Type type) {
    if (type instanceof Class && ((Class) type).isPrimitive()) {
      return PRIMITIVE_MAP.get((Class) type);
    } else {
      return type;
    }
  }

  public static Class wrapperToPrimitive(final Class clasz) {
    if (clasz.isPrimitive()) {
      return clasz;
    } else {
      return PRIMITIVE_MAP.inverse().get(clasz);
    }
  }

  public static boolean isWrappableOrWrapper(final Class clasz) {
    return (PRIMITIVE_MAP.containsKey(clasz) || PRIMITIVE_MAP.containsValue(clasz));
  }


  public static Object getAnnotationAttribute(@Nonnull final Annotation annot, @Nonnull final String attributeName) {
    for (Method method : annot.annotationType().getDeclaredMethods()) {
      if (method.getName().equals(attributeName)) {
        try {
          return method.invoke(annot);
        } catch (IllegalAccessException | InvocationTargetException ex) {
          throw new UncheckedExecutionException(ex);
        }
      }
    }
    throw new IllegalArgumentException(attributeName + " attribute is not present on annotation " + annot);
  }

  /**
   * Equivalent to Class.getDeclaredMethod which returns a null instead of throwing an exception.
   * returned method is also made accessible.
   *
   * @param c
   * @param methodName
   * @param paramTypes
   * @return
   */
  @Nullable
  @SuppressFBWarnings(value = "ES_COMPARING_STRINGS_WITH_EQ", justification = "comparing interned strings")
  public static Method getDeclaredMethod(final Class c,
          final String methodName,
          final Class... paramTypes) {
    String internedName = methodName.intern();
    for (Method m : c.getDeclaredMethods()) {
      if (m.getName() == internedName
              && Arrays.equals(paramTypes, getParameterTypes(m))) {
        if (!m.isAccessible()) {
          AccessController.doPrivileged(new PrivilegedAction() {
            @Override
            public Void run() {
               m.setAccessible(true);
               return null;
            }
          });
        }
        return m;
      }
    }
    return null;
  }

  /**
   * Equivalent to Class.getDeclaredMethod which returns a null instead of throwing an exception.
   * returned method is also made accessible.
   *
   * @param c
   * @param methodName
   * @param paramTypes
   * @return
   */
  @Nullable
  @SuppressFBWarnings(value = "ES_COMPARING_STRINGS_WITH_EQ", justification = "comparing interned strings")
  public static Method getMethod(final Class c,
          final String methodName,
          final Class... paramTypes) {
    String internedName = methodName.intern();
    for (Method m : c.getMethods()) {
      if (m.getName() == internedName
              && Arrays.equals(paramTypes, getParameterTypes(m))) {
        return m;
      }
    }
    return null;
  }

  /**
   * Equivalent to Class.getDeclaredConstructor, only that it does not throw an exception if no constructor,
   * it returns null instead, also makes all constructors accessible..
   */
  @Nullable
  public static Constructor getConstructor(final Class c, final Class... paramTypes) {
    for (Constructor cons : c.getDeclaredConstructors()) {
      if (Arrays.equals(getParameterTypes(cons), paramTypes)) {
        AccessController.doPrivileged((PrivilegedAction) () -> {
          cons.setAccessible(true);
          return null;
        });
        return cons;
      }
    }
    return null;
  }

  /**
   * Method lookup utility that looks up a method declaration that is compatible. (taking in consideration boxed
   * primitives and varargs)
   *
   * @param c
   * @param methodName
   * @param paramTypes
   * @return
   */
  @Nullable
  public static Method getCompatibleMethod(final Class c,
          final String methodName,
          final Class... paramTypes) {
    Method[] methods = c.getMethods();
    for (Method m : methods) {

      if (!methodName.equals(m.getName())) {
        continue;
      }

      Class[] actualTypes = getParameterTypes(m);
      if (actualTypes.length > paramTypes.length) {
        continue;
      }

      boolean found = true;
      int last = actualTypes.length - 1;
      for (int j = 0; j < actualTypes.length; j++) {
        final Class actType = actualTypes[j];
        final Class paramType = paramTypes[j];
        found = canAssign(actType, paramType);
        if (!found && j == last) {
          if (actType.isArray()) {
            boolean matchvararg = true;
            Class varargType = actType.getComponentType();
            for (int k = j; k < paramTypes.length; k++) {
              if (!canAssign(varargType, paramTypes[k])) {
                matchvararg = false;
                break;
              }
            }
            found = matchvararg;
          }
        } else if (j == last && actualTypes.length < paramTypes.length) {
          found = false;
        }
        if (!found) {
          break;
        }
      }

      if (found) {
        return m;
      }
    }
    return null;
  }

  @Nullable
  public static MethodHandle getCompatibleMethodHandle(final Class c,
          final String methodName,
          final Class... paramTypes) {
    Method compatibleMethod = getCompatibleMethod(c, methodName, paramTypes);
    if (compatibleMethod == null) {
      return null;
    } else {
      try {
        return MethodHandles.lookup().unreflect(compatibleMethod);
      } catch (IllegalAccessException ex) {
        throw new RuntimeException(ex);
      }
    }
  }


  public static Object invoke(final Method m, final Object object, final Object[] parameters)
          throws IllegalAccessException, InvocationTargetException {
    int np = parameters.length;
    if (np > 0) {
      Class[] actTypes = getParameterTypes(m);
      Class lastParamClass = actTypes[actTypes.length - 1];
      if (Reflections.canAssign(lastParamClass, parameters[np - 1].getClass())) {
        return m.invoke(object, parameters);
      } else if (lastParamClass.isArray()) {
        int lidx = actTypes.length - 1;
        int l = np - lidx;
        Object array = Array.newInstance(lastParamClass.getComponentType(), l);
        for (int k = 0; k < l; k++) {
          Array.set(array, k, parameters[lidx + k]);
        }
        Object[] newParams = new Object[actTypes.length];
        System.arraycopy(parameters, 0, newParams, 0, lidx);
        newParams[lidx] = array;
        return m.invoke(object, newParams);
      } else {
        throw new IllegalStateException();
      }
    } else {
      return m.invoke(object);
    }
  }

  public static boolean canAssign(final Class to, final Class from) {
    boolean found = true;
    if (!to.isAssignableFrom(from)) {
      if (to.isPrimitive()) {
        Class boxed = PRIMITIVE_MAP.get(to);
        found = boxed.equals(from);
      } else if (from.isPrimitive()) {
        found = PRIMITIVE_MAP.get(from).equals(to);
      } else {
        found = false;
      }
    }
    return found;
  }

  static final class MethodDesc {

    @Nonnull
    private final Class clasz;
    @Nonnull
    private final String name;
    @Nonnull
    private final Class[] paramTypes;

    MethodDesc(final Class clasz, final String name, final Class[] paramTypes) {
      this.clasz = clasz;
      this.name = name;
      this.paramTypes = paramTypes;
    }

    public Class getClasz() {
      return clasz;
    }

    public String getName() {
      return name;
    }

    public Class[] getParamTypes() {
      return paramTypes;
    }

    @Override
    public int hashCode() {
      return 47 * this.clasz.hashCode() + this.name.hashCode();
    }

    @Override
    public boolean equals(final Object obj) {
      if (obj == null) {
        return false;
      }
      if (obj instanceof MethodDesc) {
        final MethodDesc other = (MethodDesc) obj;
        if (!this.clasz.equals(other.clasz)) {
          return false;
        }
        if (!this.name.equals(other.name)) {
          return false;
        }
        return (Arrays.equals(this.paramTypes, other.paramTypes));
      } else {
        return false;
      }
    }

    @Override
    public String toString() {
      return "MethodDesc{" + "clasz=" + clasz + ','
              + " name=" + name + ", paramTypes=" + Arrays.toString(paramTypes) + '}';
    }

  }

  private static final LoadingCache> CACHE_FAST
          = new UnboundedLoadingCache<>(64,
                  new CacheLoader>() {
            @Override
            public Optional load(final MethodDesc k) {
              final Method m = getCompatibleMethod(k.getClasz(), k.getName(), k.getParamTypes());
              if (m == null) {
                return Optional.empty();
              }
              AccessController.doPrivileged(new PrivilegedAction() {
                @Override
                public Object run() {
                  m.setAccessible(true);
                  return null; // nothing to return
                }
              });
              return Optional.of(m);
            }
          });

  @Nullable
  public static Method getCompatibleMethodCached(final Class c,
          final String methodName,
          final Class... paramTypes) {
    return CACHE_FAST.getUnchecked(new MethodDesc(c, methodName, paramTypes)).orElse(null);
  }

  private static final LoadingCache CACHE_FAST_MH
          = new UnboundedLoadingCache<>(64,
                  new CacheLoader() {
            @Override
            public MethodHandle load(final MethodDesc k) {
              return getCompatibleMethodHandle(k.getClasz(), k.getName(), k.getParamTypes());
            }
          });

  public static MethodHandle getCompatibleMethodHandleCached(final Class c,
          final String methodName,
          final Class... paramTypes) {
    return CACHE_FAST_MH.getUnchecked(new MethodDesc(c, methodName, paramTypes));
  }


  /**
   * Get the manifest of a jar file.
   *
   * @param jarUrl
   * @return
   * @throws IOException
   */
  @Nullable
  @SuppressFBWarnings({ "NP_LOAD_OF_KNOWN_NULL_VALUE", "URLCONNECTION_SSRF_FD" })
  public static Manifest getManifest(@Nonnull final URL jarUrl) throws IOException {
    try (JarInputStream jis = new JarInputStream(jarUrl.openStream())) {
      return jis.getManifest();
    }
  }

  /**
   * create a proxy instance that will proxy interface methods to static methods on the target class.
   * @param 
   * @param clasz
   * @param target
   * @return
   */
  public static  T implementStatic(final Class clasz, final Class target) {

    Method[] methods = clasz.getMethods();
    final Map map = new HashMap<>(methods.length);
    for (Method from : methods) {
      Method to = getCompatibleMethodCached(target, from.getName(), getParameterTypes(from));
      if (to == null || !Modifier.isStatic(to.getModifiers())) {
        throw new IllegalArgumentException("Cannot map from " + clasz + " to " + target);
      }
      map.put(from, to);
    }

    return (T) Proxy.newProxyInstance(clasz.getClassLoader(), new Class[]{clasz},
            (final Object proxy, final Method method, final Object[] args) -> map.get(method).invoke(null, args));
  }

  public static  T implement(final Class clasz, final Object target) {

    Method[] methods = clasz.getMethods();
    final Map map = new HashMap<>(methods.length);
    Class aClass = target.getClass();
    for (Method from : methods) {
      Method to = getCompatibleMethodCached(aClass, from.getName(), getParameterTypes(from));
      if (to == null) {
        throw new IllegalArgumentException("Cannot map from " + clasz + " to " + target);
      }
      map.put(from, to);
    }


    return (T) Proxy.newProxyInstance(clasz.getClassLoader(), new Class[]{clasz},
            (final Object proxy, final Method method, final Object[] args) ->
                    map.get(method).invoke(target, args));
  }


  @Nullable
  public static Class getLoadedClass(final ClassLoader cl, final String className) {
    try {
      return (Class) FIND_CLASS.invoke(cl, className);
    } catch (RuntimeException | Error ex) {
      throw ex;
    } catch (Throwable ex) {
      throw new UncheckedExecutionException(ex);
    }

  }

  /**
   * utility method that will work for primittives as well.
   * @param name the class name
   * @return
   * @throws ClassNotFoundException
   */
  public static Class forName(final String name) throws ClassNotFoundException {
    Class primitive = PRIMITIVES.get(name);
    if (primitive == null) {
      return Class.forName(name);
    } else {
      return primitive;
    }
  }

  /**
   * utility method that will work for primitives as well.
   * @param name the class name
   * @param loader the class loader
   * @return
   * @throws ClassNotFoundException
   */
  public static Class forName(final String name, final ClassLoader loader) throws ClassNotFoundException {
    Class primitive = PRIMITIVES.get(name);
    if (primitive == null) {
      return Class.forName(name, true, loader);
    } else {
      return primitive;
    }
  }

  public static List getImplementedGenericInterfaces(final Class clasz) {
    Type[] genericInterfaces = clasz.getGenericInterfaces();
    Class superclass = clasz.getSuperclass();
    if (superclass == null) {
      return Arrays.asList(genericInterfaces);
    } else {
      List result = new ArrayList<>(genericInterfaces.length);
      for (Type type : genericInterfaces) {
        result.add(type);
      }
      result.addAll(getImplementedGenericInterfaces(superclass));
      return result;
    }
  }


  /**
   * Annotation access with extended inheritance semantics.
   * Class level annotations are inherited to methods.
   * superclass and implemented interface annotations are inherited.
   * package level annotations are inherited to class and methods as well.
   * @param 
   * @param annotationClass
   * @param element
   * @return
   */
  @Nullable
  @SuppressFBWarnings("ITC_INHERITANCE_TYPE_CHECKING")
  public static  A getInheritedAnnotation(
          final Class annotationClass, final AnnotatedElement element) {
    A annotation = element.getAnnotation(annotationClass);
    if (annotation == null) {
      if (element instanceof Method) {
        Method method = (Method) element;
        annotation = getOverriddenAnnotation(annotationClass,  method);
        if (annotation != null) {
          return annotation;
        }
        Class declaringClass = method.getDeclaringClass();
        annotation = getOverriddenAnnotation(annotationClass, declaringClass);
        if (annotation == null) {
          Package aPackage = declaringClass.getPackage();
          if (aPackage != null) {
            annotation = aPackage.getAnnotation(annotationClass);
          }
        }
      } else if (element instanceof Class) {
        Class declaringClass = (Class) element;
        annotation = getOverriddenAnnotation(annotationClass, declaringClass);
        if (annotation == null) {
          Package aPackage = declaringClass.getPackage();
          if (aPackage != null) {
            annotation = aPackage.getAnnotation(annotationClass);
          }
        }
      }
    }
    return annotation;
  }

  @Nullable
  private static  A getOverriddenAnnotation(
          final Class annotationClass, final Class clasz) {
    A annotation = clasz.getAnnotation(annotationClass);
    if (annotation != null) {
      return annotation;
    }
    final Class superclass = clasz.getSuperclass();
    if (superclass != null) {
      annotation
              = getOverriddenAnnotation(annotationClass, superclass);
      if (annotation != null) {
        return annotation;
      }
    }
    for (final Class intf : clasz.getInterfaces()) {
      annotation = getOverriddenAnnotation(annotationClass, intf);
      if (annotation != null) {
        return annotation;
      }
    }
    return null;
  }

  @Nullable
  private static  A getOverriddenAnnotation(
          final Class annotationClass, final Method method) {
    final Class methodClass = method.getDeclaringClass();
    final String methodName = method.getName();
    final Class[] params = method.getParameterTypes();
    final Class superclass = methodClass.getSuperclass();
    if (superclass != null) {
      final A annotation
              = getOverriddenMethodAnnotationFrom(annotationClass, superclass, methodName, params);
      if (annotation != null) {
        return annotation;
      }
    }
    for (final Class intf : methodClass.getInterfaces()) {
      final A annotation = getOverriddenMethodAnnotationFrom(annotationClass, intf, methodName, params);
      if (annotation != null) {
        return annotation;
      }
    }

    return null;
  }

  @Nullable
  private static  A getOverriddenMethodAnnotationFrom(
          final Class annotationClass, final Class searchClass,
          final String methodName, final Class[] params) {
      final Method method = getMethod(searchClass, methodName, params);
      if (method == null) {
        return null;
      }
      final A annotation = method.getAnnotation(annotationClass);
      if (annotation != null) {
        return annotation;
      }
      return getOverriddenAnnotation(annotationClass, method);
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy