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

org.helenus.commons.lang3.reflect.ReflectionUtils Maven / Gradle / Ivy

Go to download

JPA-like syntax for annotating POJO classes for persistence via Cassandra's Java driver - Common Utilities

There is a newer version: 3.0.4
Show newest version
/*
 * Copyright (C) 2015-2015 The Helenus Driver Project Authors.
 *
 * 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.helenus.commons.lang3.reflect;

import java.lang.annotation.Annotation;
import java.lang.annotation.Repeatable;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;

import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLDecoder;

import java.security.AccessController;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Deque;
import java.util.Enumeration;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;
import java.util.stream.Stream;

import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.PackageElement;
import javax.lang.model.type.ArrayType;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;

import org.helenus.annotation.Keyable;

/**
 * The ReflectionUtils class provides reflection utilities.
 *
 * @copyright 2015-2015 The Helenus Driver Project Authors
 *
 * @author  The Helenus Driver Project Authors
 * @version 1 - Jan 15, 2015 - paouelle - Creation
 *
 * @since 1.0
 */
public class ReflectionUtils {
  /**
   * Reflection factory for obtaining constructors used to instantiate blank
   * object whether or not a default constructor was defined by the user.
   *
   * @author paouelle
   */
  @SuppressWarnings("all")
  private static final sun.reflect.ReflectionFactory reflFactory
    = (sun.reflect.ReflectionFactory)AccessController.doPrivileged(
        new sun.reflect.ReflectionFactory.GetReflectionFactoryAction()
      );

  /**
   * Finds the first annotation from a given class' and interface's hierarchy of
   * the specified annotation.
   *
   * @author paouelle
   *
   * @param  the type of annotation to search for
   *
   * @param  clazz the class from which to search
   * @param  annotationClass the annotation to search for
   * @return the first annotation in clazz's hierarchy or
   *         null if none is found
   * @throws NullPointerException if clazz or
   *         annotationClass is null
   */
  public static  A findFirstAnnotation(
    Class clazz, Class annotationClass
  ) {
    org.apache.commons.lang3.Validate.notNull(clazz, "invalid null class");
    org.apache.commons.lang3.Validate.notNull(
      annotationClass, "invalid null annotation class"
    );
    Class c = clazz;

    do {
      final A[] as = c.getDeclaredAnnotationsByType(annotationClass);

      if (as.length > 0) {
        return as[0];
      }
      for (final Class i: c.getInterfaces()) {
        final A a = ReflectionUtils.findFirstAnnotation(i, annotationClass);

        if (a != null) {
          return a;
        }
      }
      c = c.getSuperclass();
    } while (c != null);
    return null;
  }

  /**
   * Finds the first class from a given class' hierarchy that is annotated
   * with the specified annotation.
   *
   * @author paouelle
   *
   * @param  the type of the class to start searching from
   *
   * @param  clazz the class from which to search
   * @param  annotationClass the annotation to search for
   * @return the first class in clazz's hierarchy annotated with
   *         the specified annotation or null if none is found
   * @throws NullPointerException if clazz or
   *         annotationClass is null
   */
  public static  Class findFirstClassAnnotatedWith(
    Class clazz, Class annotationClass
  ) {
    org.apache.commons.lang3.Validate.notNull(clazz, "invalid null class");
    org.apache.commons.lang3.Validate.notNull(
      annotationClass, "invalid null annotation class"
    );
    Class c = clazz;

    do {
      if (c.getDeclaredAnnotationsByType(annotationClass).length > 0) {
        return c;
      }
      c = c.getSuperclass();
    } while (c != null);
    return null;
  }

  /**
   * Finds the first class from a given class' hierarchy that is not annotated
   * with the specified annotation.
   *
   * @author paouelle
   *
   * @param  the type of the class to start searching from
   *
   * @param  clazz the class from which to search
   * @param  annotationClass the annotation to search for
   * @return the first class in clazz's hierarchy not annotated
   *         with the specified annotation or null if none found
   * @throws NullPointerException if clazz or
   *         annotationClass is null
   */
  public static  Class findFirstClassNotAnnotatedWith(
    Class clazz, Class annotationClass
  ) {
    org.apache.commons.lang3.Validate.notNull(clazz, "invalid null class");
    org.apache.commons.lang3.Validate.notNull(
      annotationClass, "invalid null annotation class"
    );
    Class c = clazz;

    while ((c != null)
           && (c.getDeclaredAnnotationsByType(annotationClass).length > 0)) {
      c = c.getSuperclass();
    }
    return c;
  }

  /**
   * Finds the last class from a given class' hierarchy that is annotated
   * with the specified annotation.
   *
   * @author paouelle
   *
   * @param  the type of the class to start searching from
   *
   * @param  clazz the class from which to search
   * @param  annotationClass the annotation to search for
   * @return the last class in clazz's hierarchy annotated with
   *         the specified annotation or null if none found
   * @throws NullPointerException if clazz or
   *         annotationClass is null
   */
  public static  Class findLastClassAnnotatedWith(
    Class clazz, Class annotationClass
  ) {
    org.apache.commons.lang3.Validate.notNull(clazz, "invalid null class");
    org.apache.commons.lang3.Validate.notNull(
      annotationClass, "invalid null annotation class"
    );
    Class p = null;
    Class c = clazz;

    while ((c != null)
        && (c.getDeclaredAnnotationsByType(annotationClass).length > 0)) {
      p = c;
      c = c.getSuperclass();
    }
    return p;
  }

  /**
   * Gets the serialization constructor for the given class based on the given
   * base class. The specified base class is expected to have a default constructor
   * to use for initializing the hierarchy from that point on. The constructor
   * can have any visibility (i.e. public, protected, package private, or private).
   * 

* Note: The returned constructor results in having a proper object of * the given class instantiated without calling any of the constructors from * its class or super classes. The fields of all classes in the hierarchy will * not be initialized; thus set using default Java initialization (i.e. * null, 0, 0L, false, ...). This is how Java creates new * serialized objects before calling the readObject() method of * each of the subclasses. * * @author paouelle * * @param the class for which to get the serialization constructor * * @param clazz the class for which to get the serialization constructor * @param bclass the base class to start the initialization from in the * returned serialization constructor * @return the non-null serialization constructor for the given * class * @throws NullPointerException if clazz or bclass * is null * @throws IllegalArgumentException if bclass is not a base class * for clazz * @throws SecurityException if the request is denied */ public static Constructor getSerializationConstructorFromBaseClass( Class clazz, Class bclass ) { org.apache.commons.lang3.Validate.notNull(clazz, "invalid null class"); org.apache.commons.lang3.Validate.notNull(bclass, "invalid null base class"); org.apache.commons.lang3.Validate.isTrue( bclass.isAssignableFrom(clazz), bclass.getName() + " is not a superclass of " + clazz.getName() ); final Constructor ctor; try { // find the default ctor for the base class ctor = bclass.getDeclaredConstructor(); ctor.setAccessible(true); } catch (NoSuchMethodException e) { throw new IllegalStateException( "missing default constructor for base class: " + bclass.getName(), e ); } @SuppressWarnings({"restriction", "unchecked"}) final Constructor nc = (Constructor)ReflectionUtils.reflFactory.newConstructorForSerialization( clazz, ctor ); nc.setAccessible(true); return nc; } /** * Gets the serialization constructor for the given class based on the given * annotation. The annotation is used to find the first class in its hierarchy * that is not annotated with the annotation and expect a default constructor * to use for initializing the hierarchy from that point on. The constructor * can have any visibility (i.e. public, protected, package private, or private). *

* Note: The returned constructor results in having a proper object of * the given class instantiated without calling any of the constructors from * its class or super classes. The fields of all classes in the hierarchy will * not be initialized; thus set using default Java initialization (i.e. * null, 0, 0L, false, ...). This is how Java creates new * serialized objects before calling the readObject() method of * each of the subclasses. * * @author paouelle * * @param the class for which to get the serialization constructor * * @param clazz the class for which to get the serialization constructor * @param annotationClass the annotation to search with * @return the non-null serialization constructor for the given * class * @throws NullPointerException if clazz or * annotationClass is null * @throws IllegalStateException if unable to find a default constructor for * the first base class not annotated with annotationClass * @throws SecurityException if the request is denied */ public static Constructor getSerializationConstructorFromAnnotation( Class clazz, Class annotationClass ) { org.apache.commons.lang3.Validate.notNull(clazz, "invalid null class"); org.apache.commons.lang3.Validate.notNull( annotationClass, "invalid null annotation class" ); final Class bclass = ReflectionUtils.findFirstClassNotAnnotatedWith( clazz, annotationClass ); return ReflectionUtils.getSerializationConstructorFromBaseClass(clazz, bclass); } /** * Gets the declared members of the given type from the specified class. * * @author paouelle * * @param the type of member * * @param type the type of members to retrieve * @param clazz the class from which to retrieve the members * @return the non-null members of the given type from the * specified class * @throws NullPointerException if type or * clazz is null * @throws IllegalArgumentException if type is not * {@link Field}, {@link Method}, or {@link Constructor} */ @SuppressWarnings("unchecked") public static T[] getDeclaredMembers( Class type, Class clazz ) { org.apache.commons.lang3.Validate.notNull(type, "invalid null member type"); org.apache.commons.lang3.Validate.notNull(clazz, "invalid null class"); if (type == Field.class) { return (T[])clazz.getDeclaredFields(); } else if (type == Method.class) { return (T[])clazz.getDeclaredMethods(); } else if (type == Constructor.class) { return (T[])clazz.getDeclaredConstructors(); } else { throw new IllegalArgumentException( "invalid member class: " + type.getName() ); } } /** * Gets all members of a given type (up the super class hierarchy) annotated * with the specified annotation. * * @author paouelle * * @param the type of members to retrieve (either {@link Field}, * {@link Method}, or {@link Constructor}) * * @param type the type of members to retrieve * @param clazz the class from which to find all annotated members * @param annotation the annotation for which to find all members * @param up true to look up the class hierarchy; * false to only look at the specified class level * @return a list in the provided order for all annotated members * @throws NullPointerException if type, * clazz or annotation is null * @throws IllegalArgumentException if type is not * {@link Field}, {@link Method}, or {@link Constructor} */ public static List getAllMembersAnnotatedWith( Class type, Class clazz, Class annotation, boolean up ) { org.apache.commons.lang3.Validate.notNull(type, "invalid null member type"); org.apache.commons.lang3.Validate.notNull(clazz, "invalid null class"); org.apache.commons.lang3.Validate.notNull(annotation, "invalid null annotation class"); final LinkedList> classes = new LinkedList<>(); if (up) { while (clazz != null) { classes.push(clazz); clazz = clazz.getSuperclass(); } } else { classes.push(clazz); } final List members = new ArrayList<>(12); while (!classes.isEmpty()) { clazz = classes.pop(); for (final T m: ReflectionUtils.getDeclaredMembers(type, clazz)) { if ((m instanceof AnnotatedElement) && ((AnnotatedElement)m).getAnnotationsByType(annotation).length > 0) { members.add(m); } } } return members; } /** * Gets all fields (up the super class hierarchy) annotated with the specified * annotation. * * @author paouelle * * @param clazz the class from which to find all annotated fields * @param annotation the annotation for which to find all fields * @param up true to look up the class hierarchy; * false to only look at the specified class level * @return a list in the provided order for all annotated fields * @throws NullPointerException if clazz or * annotation is null */ public static List getAllFieldsAnnotatedWith( Class clazz, Class annotation, boolean up ) { return ReflectionUtils.getAllMembersAnnotatedWith( Field.class, clazz, annotation, up ); } /** * Gets all methods (up the super class hierarchy) annotated with the specified * annotation. * * @author paouelle * * @param clazz the class from which to find all annotated methods * @param annotation the annotation for which to find all methods * @param up true to look up the class hierarchy; * false to only look at the specified class level * @return a list in the provided order for all annotated methods * @throws NullPointerException if clazz or * annotation is null */ public static List getAllMethodsAnnotatedWith( Class clazz, Class annotation, boolean up ) { return ReflectionUtils.getAllMembersAnnotatedWith( Method.class, clazz, annotation, up ); } /** * Gets all members of a given type (up the super class hierarchy) annotated * with the specified annotation. * * @author paouelle * * @param the type of members to retrieve (either {@link Field}, * {@link Method}, or {@link Constructor}) * @param the type of annotation to search for * * @param type the type of members to retrieve * @param clazz the class from which to find all annotated members * @param annotation the annotation for which to find all members * @param up true to look up the class hierarchy; * false to only look at the specified class level * @return a non-null map in the provided order for all annotated * members with their found annotations * @throws NullPointerException if type, * clazz or annotation is null * @throws IllegalArgumentException if type is not * {@link Field}, {@link Method}, or {@link Constructor} */ public static Map getAllAnnotationsForMembersAnnotatedWith( Class type, Class clazz, Class annotation, boolean up ) { org.apache.commons.lang3.Validate.notNull(type, "invalid null member type"); org.apache.commons.lang3.Validate.notNull(clazz, "invalid null class"); org.apache.commons.lang3.Validate.notNull(annotation, "invalid null annotation class"); final LinkedList> classes = new LinkedList<>(); if (up) { while (clazz != null) { classes.push(clazz); clazz = clazz.getSuperclass(); } } else { classes.push(clazz); } final Map members = new LinkedHashMap<>(12); while (!classes.isEmpty()) { clazz = classes.pop(); for (final T m: ReflectionUtils.getDeclaredMembers(type, clazz)) { if (m instanceof AnnotatedElement) { final A[] as = ((AnnotatedElement)m).getAnnotationsByType(annotation); if (as.length > 0) { members.put(m, as); } } } } return members; } /** * Gets all fields of a given type (up the super class hierarchy) annotated * with the specified annotation. * * @author paouelle * * @param the type of annotation to search for * * @param clazz the class from which to find all annotated fields * @param annotation the annotation for which to find all fields * @param up true to look up the class hierarchy; * false to only look at the specified class level * @return a non-null map in the provided order for all annotated * fields with their found annotations * @throws NullPointerException if clazz or * annotation is null */ public static Map getAllAnnotationsForFieldsAnnotatedWith( Class clazz, Class annotation, boolean up ) { return ReflectionUtils.getAllAnnotationsForMembersAnnotatedWith( Field.class, clazz, annotation, up ); } /** * Gets all methods of a given type (up the super class hierarchy) annotated * with the specified annotation. * * @author paouelle * * @param the type of annotation to search for * * @param clazz the class from which to find all annotated methods * @param annotation the annotation for which to find all methods * @param up true to look up the class hierarchy; * false to only look at the specified class level * @return a non-null map in the provided order for all annotated * methods with their found annotations * @throws NullPointerException if clazz or * annotation is null */ public static Map getAllAnnotationsForMethodsAnnotatedWith( Class clazz, Class annotation, boolean up ) { return ReflectionUtils.getAllAnnotationsForMembersAnnotatedWith( Method.class, clazz, annotation, up ); } /** * Gets annotations that are associated with the specified element. If there * are no annotations associated with the element, the return value is an * empty map. The difference between this method and * {@link AnnotatedElement#getAnnotationsByType(Class)} is that this method * works on annotation classes that are annotated with the {@link Keyable} * annotation in order to determine the key of each annotation. For this to * work, the annotated class must defined a key element as defined in the * {@link Keyable} annotation of the same return type as specified in * keyClass. If its argument is a repeatable annotation type * (JLS 9.6), this method, attempts to find one or more annotations of that * type by "looking through" a container annotation. The caller of this method * is free to modify the returned map; it will have no effect on the maps * returned to other callers. *

* This method will look for both the specified annotation and if the * annotation is annotated with {@link Repeatable}, it will also look for this * containing annotation which must have a value() * element with an array type of the specified annotation. The resulting * array will always start with the annotation itself if found followed by * all of those provided by the containing annotation. * * @author paouelle * * @param the type of the keys to find * @param the type of the annotation to query for and return if present * * @param keyClass the class of the keys to find and return * @param annotationClass the type of annotations to retrieve * @param annotatedElement the element from which to retrieve the annotations * @return a non-null ordered map of annotations associated with the * given element properly keyed as defined in each annotation (may be * empty if none found) * @throws NullPointerException if annotatedElement, * annotationClass or keyClass is * null * @throws IllegalArgumentException if annotationClass is not * annotated with {@link Keyable} or if the containing annotation * doesn't define a value() element returning an array * of type annotationClass or if annotationClass * doesn't define an element named as specified in its {@link Keyable} * annotation that returns a value of the same class as keyClass * or again if a duplicated keyed annotation is found */ @SuppressWarnings("unchecked") public static Map getAnnotationsByType( Class keyClass, Class annotationClass, AnnotatedElement annotatedElement ) { org.apache.commons.lang3.Validate.notNull( annotationClass, "invalid null annotation class" ); org.apache.commons.lang3.Validate.notNull( annotatedElement, "invalid null annotation element" ); org.apache.commons.lang3.Validate.notNull( keyClass, "invalid null key class" ); final Keyable k = annotationClass.getAnnotation(Keyable.class); org.apache.commons.lang3.Validate.isTrue( k != null, "annotation @%s not annotated with @Keyable", annotationClass.getName() ); final Method km; try { km = annotationClass.getMethod(k.value()); } catch (NoSuchMethodException e) { throw new IllegalArgumentException( "annotation key element @" + annotationClass.getName() + "." + k.value() + "() not found", e ); } org.apache.commons.lang3.Validate.isTrue( keyClass.isAssignableFrom(km.getReturnType()), "annotation key element @%s.%s() doesn't return class: %s", annotationClass.getName(), k.value(), keyClass.getName() ); final T[] as = annotatedElement.getAnnotationsByType(annotationClass); if (as.length == 0) { return Collections.emptyMap(); } final Map map = new LinkedHashMap<>(as.length); for (T a: as) { final K ak; try { ak = (K)km.invoke(a); } catch (IllegalAccessException e) { // not expected throw new IllegalStateException(e); } catch (InvocationTargetException e) { final Throwable t = e.getTargetException(); if (t instanceof Error) { throw (Error)t; } else if (t instanceof RuntimeException) { throw (RuntimeException)t; } else { // not expected throw new IllegalStateException(e); } } org.apache.commons.lang3.Validate.isTrue( map.put(ak, a) == null, "duplicate key '%s' found in annotation @%s", ak, annotationClass.getName() ); } return map; } /** * Gets all declared members of a given type (up the super class hierarchy). * * @author paouelle * * @param the type of members to retrieve (either {@link Field}, * {@link Method}, or {@link Constructor}) * * @param type the type of members to retrieve * @param clazz the class from which to find all declared members * @param up true to look up the class hierarchy; * false to only look at the specified class level * @return a list in the provided order for all declared members * @throws NullPointerException if type or * clazz is null * @throws IllegalArgumentException if type is not * {@link Field}, {@link Method}, or {@link Constructor} */ public static List getAllDeclaredMembers( Class type, Class clazz, boolean up ) { org.apache.commons.lang3.Validate.notNull(type, "invalid null member type"); org.apache.commons.lang3.Validate.notNull(clazz, "invalid null class"); final LinkedList> classes = new LinkedList<>(); if (up) { while (clazz != null) { classes.push(clazz); clazz = clazz.getSuperclass(); } } else { classes.push(clazz); } final List members = new ArrayList<>(12); while (!classes.isEmpty()) { clazz = classes.pop(); for (final T m: ReflectionUtils.getDeclaredMembers(type, clazz)) { members.add(m); } } return members; } /** * Gets all declared fields (up the super class hierarchy). * * @author paouelle * * @param clazz the class from which to find all declared fields * @param up true to look up the class hierarchy; * false to only look at the specified class level * @return a list in the provided order for all declared fields * @throws NullPointerException if clazz is null */ public static List getAllDeclaredFields( Class clazz, boolean up ) { return ReflectionUtils.getAllDeclaredMembers(Field.class, clazz, up); } /** * Gets all declared methods (up the super class hierarchy). * * @author paouelle * * @param clazz the class from which to find all declared methods * @param up true to look up the class hierarchy; * false to only look at the specified class level * @return a list in the provided order for all declared methods * @throws NullPointerException if clazz is null */ public static List getAllDeclaredMethods( Class clazz, boolean up ) { return ReflectionUtils.getAllDeclaredMembers(Method.class, clazz, up); } /** * Gets the raw class from the provided type. * * @author paouelle * * @param t the type for which to get the raw class * @return the raw class for the given type */ public static Class getRawClass(Type t) { Type tt = t; while (!(tt instanceof Class)) { if (tt instanceof ParameterizedType) { tt = ((ParameterizedType)t).getRawType(); } else { throw new IllegalStateException("there should be a raw class for: " + t); } } return (Class)tt; } /** * Gets the hierarchy of a given class. * * @author paouelle * * @param the type of the class to get the hierarchy for * * @param clazz the class to get the hierarchy for * @return a list of all classes in the given class's hierarchy starting with * the specified class */ public static List> getClassHierarchy(Class clazz) { org.apache.commons.lang3.Validate.notNull(clazz, "invalid null class"); final LinkedList> classes = new LinkedList<>(); while (clazz != null) { classes.push(clazz); clazz = clazz.getSuperclass(); } return classes; } /** * Finds all classes defined in a given package using the current classloader. * * @author paouelle * * @param pkg the package from which to find all defined classes * @return the non-null collection of all classes defined in the * given package * @throws NullPointerException if pkg is null */ public static Collection> findClasses(String pkg) { return ReflectionUtils.findClasses( pkg, Thread.currentThread().getContextClassLoader() ); } /** * Finds all classes defined in a given package. * * @author paouelle * * @param pkg the package from which to find all defined classes * @param cl the classloader to find the classes with * @return the non-null collection of all classes defined in the * given package * @throws NullPointerException if pkg or cl is * null */ public static Collection> findClasses(String pkg, ClassLoader cl) { org.apache.commons.lang3.Validate.notNull(pkg, "invalid null pkg"); final String scannedPath = pkg.replace('.', File.separatorChar); final Enumeration resources; try { resources = cl.getResources(scannedPath); } catch (IOException e) { throw new IllegalArgumentException( "Unable to get resources from path '" + scannedPath + "'. Are you sure the given '" + pkg + "' package exists?", e ); } final List> classes = new LinkedList<>(); while (resources.hasMoreElements()) { final URL url = resources.nextElement(); if ("jar".equals(url.getProtocol())) { ReflectionUtils.findClassesFromJar(classes, url, scannedPath, cl); } else if ("file".equals(url.getProtocol())) { final File file = new File(url.getFile()); ReflectionUtils.findClassesFromFile(classes, file, pkg, cl); } else { throw new IllegalArgumentException("package is provided by an unknown url: " + url); } } return classes; } /** * Find all classes from a given jar file and add them to the provided * list. * * @author paouelle * * @param classes the non-null collection of classes where to add new * found classes * @param url the non-null url for the jar where to find classes * @param resource the non-null resource being scanned that * corresponds to given package * @param cl the classloader to find the classes with */ private static void findClassesFromJar( Collection> classes, URL url, String resource, ClassLoader cl ) { try { final JarURLConnection conn = (JarURLConnection)url.openConnection(); final URL jurl = conn.getJarFileURL(); try ( final JarInputStream jar = new JarInputStream(jurl.openStream()); ) { while (true) { final JarEntry entry = jar.getNextJarEntry(); if (entry == null) { break; } final String name = entry.getName(); if (name.endsWith(".class") && name.startsWith(resource)) { final String cname = name.substring( 0, name.length() - 6 // 6 for .class ).replace(File.separatorChar, '.'); try { classes.add(cl.loadClass(cname)); } catch (ClassNotFoundException e) { // ignore it } } } } } catch (IOException e) { throw new IllegalArgumentException("unable to find classes in package", e); } } /** * Find all classes from a given file or directory and add them to the provided * list. * * @author paouelle * * @param classes the non-null collection of classes where to add new * found classes * @param file the non-null file or directory from which to find * classes * @param resource the non-null resource being scanned that * corresponds to given file or directory * @param cl the classloader to find the classes with */ private static void findClassesFromFile( Collection> classes, File file, String resource, ClassLoader cl ) { if (file.isDirectory()) { for (File f: file.listFiles()) { ReflectionUtils.findClassesFromFile( classes, f, resource + "." + f.getName(), cl ); } } else if (resource.endsWith(".class")) { final String cname = resource.substring(0, resource.length() - 6); // 6 for .class try { classes.add(cl.loadClass(cname)); } catch (ClassNotFoundException e) { // ignore it } } } /** * Finds all resources defined in a given package using the current classloader. * * @author paouelle * * @param pkg the package from which to find all defined resources * @return the non-null collection of all resources defined in the * given package * @throws NullPointerException if pkg is null */ public static Collection findResources(String pkg) { return ReflectionUtils.findResources( pkg, Thread.currentThread().getContextClassLoader() ); } /** * Finds all resources defined in a given package. * * @author paouelle * * @param pkg the package from which to find all defined resources * @param cl the classloader to find the resources with * @return the non-null collection of all resources defined in the * given package * @throws NullPointerException if pkg or cl is * null */ public static Collection findResources(String pkg, ClassLoader cl) { org.apache.commons.lang3.Validate.notNull(pkg, "invalid null pkg"); final String scannedPath = pkg.replace('.', File.separatorChar); final Enumeration resources; try { resources = cl.getResources(scannedPath); } catch (IOException e) { throw new IllegalArgumentException( "Unable to get resources from path '" + scannedPath + "'. Are you sure the given '" + pkg + "' package exists?", e ); } final List urls = new LinkedList<>(); try { while (resources.hasMoreElements()) { final URL url = resources.nextElement(); if ("jar".equals(url.getProtocol())) { ReflectionUtils.findResourcesFromJar(urls, url, scannedPath, cl); } else if ("file".equals(url.getProtocol())) { final File file = new File(URLDecoder.decode(url.getFile(), "UTF-8")); ReflectionUtils.findResourcesFromFile(urls, file, scannedPath, cl); } else { throw new IllegalArgumentException("package is provided by an unknown url: " + url); } } } catch (UnsupportedEncodingException e) { // ignore as it should never happen that utf-8 is not supported!!!! throw new InternalError(e); } return urls; } /** * Find all resources from a given jar file and add them to the provided * list. * * @author paouelle * * @param urls the non-null collection of resources where to add new * found resources * @param url the non-null url for the jar where to find resources * @param resource the non-null resource being scanned that * corresponds to given package * @param cl the classloader to find the resources with */ private static void findResourcesFromJar( Collection urls, URL url, String resource, ClassLoader cl ) { try { final JarURLConnection conn = (JarURLConnection)url.openConnection(); final URL jurl = conn.getJarFileURL(); try ( final JarInputStream jar = new JarInputStream(jurl.openStream()); ) { while (true) { final JarEntry entry = jar.getNextJarEntry(); if (entry == null) { break; } final String name = entry.getName(); if (name.startsWith(resource)) { final URL u = cl.getResource(name); if (u != null) { urls.add(u); } } } } } catch (IOException e) { throw new IllegalArgumentException("unable to find resources in package", e); } } /** * Find all resources from a given file or directory and add them to the provided * list. * * @author paouelle * * @param urls the non-null collection of resources where to add new * found resources * @param file the non-null file or directory from which to find * resources * @param resource the non-null resource being scanned that * corresponds to given file or directory * @param cl the classloader to find the resources with */ private static void findResourcesFromFile( Collection urls, File file, String resource, ClassLoader cl ) { if (file.isDirectory()) { for (File f: file.listFiles()) { ReflectionUtils.findResourcesFromFile( urls, f, resource + File.separatorChar + f.getName(), cl ); } } else { final URL u = cl.getResource(resource); if (u != null) { urls.add(u); } } } /** * Checks if the specified class is assignment compatible (i.e. is a subclass of) * any of the classes provided. * * @author paouelle * * @param classes the set of classes to check if clazz is * assignment compatible with * @param clazz the class to be checking * @return true if clazz is a subclass of at least * one of the specified classes */ public final static boolean isAssignableFrom(Class[] classes, Class clazz) { for (final Class c: classes) { if (c.isAssignableFrom(clazz)) { return true; } } return false; } /** * Gets a class object corresponding to a given declared type. * * @author paouelle * * @param type the declared type for which to get a class object * @return the corresponding non-null class object * @throws ClassNotFoundException if the class is not found */ public static Class classFor(DeclaredType type) throws ClassNotFoundException { // recurse the class hierarchy to build it manually final StringBuilder sb = new StringBuilder(40); final Deque es = new LinkedList<>(); for (Element e = type.asElement(); e != null; e = e.getEnclosingElement()) { es.push(e); } Element previous = null; for (final Element e: es) { if (previous != null) { if (previous.getKind().isClass() || previous.getKind().isInterface()) { sb.append('$'); } else { // should be a package sb.append('.'); } } if (e instanceof PackageElement) { // easy as well! sb.append(((PackageElement)e).getQualifiedName()); } else if (e.getKind() == ElementKind.PACKAGE) { // oh well! rely on toString() :-( sb.append(e); } else { // anything else, deal with its simple name only sb.append(e.getSimpleName()); } previous = e; } return Class.forName(sb.toString()); } /** * Gets a primitive class for the specified primitive type kind. * * @author paouelle * * @param kind the primitive type kind for which to get a class * @return the corresponding non-null class * @throws ClassNotFoundException if an invalid primitive kind is specified */ private static Class primitiveClassFor(TypeKind kind) throws ClassNotFoundException { switch (kind) { case BYTE: return Byte.TYPE; case CHAR: return Character.TYPE; case SHORT: return Short.TYPE; case INT: return Integer.TYPE; case LONG: return Long.TYPE; case FLOAT: return Float.TYPE; case DOUBLE: return Double.TYPE; case BOOLEAN: return Boolean.TYPE; default: throw new ClassNotFoundException("unknown primitive kind: " + kind); } } /** * Gets a primitive array class for the specified primitive component type kind. * * @author paouelle * * @param kind the primitive component type kind for which to get a class * @return the corresponding non-null class * @throws ClassNotFoundException if an invalid primitive component kind is specified */ private static Class primitiveArrayClassFor(TypeKind kind) throws ClassNotFoundException { switch (kind) { case BYTE: return byte[].class; case CHAR: return char[].class; case SHORT: return short[].class; case INT: return int[].class; case LONG: return long[].class; case FLOAT: return float[].class; case DOUBLE: return double[].class; case BOOLEAN: return boolean[].class; default: throw new ClassNotFoundException("unknown primitive array element kind: " + kind); } } /** * Gets a class object corresponding to a given type mirror. * * @author paouelle * * @param tm the type mirror for which to get a class object * @return the corresponding non-null class object * @throws ClassNotFoundException if the class is not found */ public static Class classFor(TypeMirror tm) throws ClassNotFoundException { if (tm instanceof ArrayType) { final ArrayType array = (ArrayType)tm; final TypeMirror compType = array.getComponentType(); if (compType.getKind().isPrimitive()) { return ReflectionUtils.primitiveArrayClassFor(compType.getKind()); } else if (compType instanceof DeclaredType) { return Array.newInstance( ReflectionUtils.classFor((DeclaredType)compType), 0 ).getClass(); } // else - oh! well, rely on toString() return Array.newInstance(Class.forName(compType.toString()), 0).getClass(); } else if (tm.getKind().isPrimitive()) { return ReflectionUtils.primitiveClassFor(tm.getKind()); } else if (tm.getKind() == TypeKind.VOID) { return Void.TYPE; } else if (tm instanceof DeclaredType) { return ReflectionUtils.classFor((DeclaredType)tm); } // else - oh! well, rely on toString() return Class.forName(tm.toString()); } /** * Gets annotations of a specific type that are associated with the * element. *

* If there are no annotations associated with this element, the return * value is an empty stream. * * @param the type of the annotation to query for and return if present * * @param annotatedElement the element to retrieve the annotations from * @param annotationClass the type of annotations to retrieve * @return a stream of the element's annotations for the specified annotation * type if present on this element, else an empty stream * @throws NullPointerException if the given element or annotation class are * null * * @see https://blog.retep.org/2009/02/13/getting-class-values-from-annotations-in-an-annotationprocessor/ */ @SuppressWarnings("unchecked") public static Stream annotationsByType( Element annotatedElement, Class annotationClass ) { final String aname = annotationClass.getName(); return annotatedElement.getAnnotationMirrors().stream() .filter(am -> aname.equals(am.getAnnotationType().toString())) .map(am -> (T)java.lang.reflect.Proxy.newProxyInstance( annotationClass.getClassLoader(), new Class[] { annotationClass }, new AnnotationProxy(am) )); } /** * Gets annotations which are themselves annotated with a given type of * annotation that are present on the element. *

* If there are no annotations present on the element, the return * value is an empty stream. *

* For example, one could find all annotations annotated with the @Tag * annotation, which in the following examples would return @ThisOne and * @ThisOneToo annotations but not @NotThisOne: *

   *   @Tag
   *   public @interface ThisOne {
   *      String value();
   *   }
   *
   *   @Tag
   *   public @interface ThisOneToo {
   *      String value();
   *   }
   *
   *   public @interface NotThisOne {
   *      String value();
   *   }
   * 
* * @param annotatedElement the element to retrieve the annotations from * @param annotationClass the type of annotations to retrieve annotations * annotated with * @return a stream of the element's annotations annotated with the specified * annotation type if present on this element, else an empty stream * @throws NullPointerException if the given element or annotation class are * null * * @see https://blog.retep.org/2009/02/13/getting-class-values-from-annotations-in-an-annotationprocessor/ */ public static Stream annotationsAnnotatedWith( Element annotatedElement, Class annotationClass ) { return annotatedElement.getAnnotationMirrors().stream() .map(am -> { try { final Class aclass = ReflectionUtils.classFor(am.getAnnotationType()); if (!aclass.isAnnotationPresent(annotationClass)) { return null; } return (Annotation)java.lang.reflect.Proxy.newProxyInstance( aclass.getClassLoader(), new Class[] { aclass }, new AnnotationProxy(am) ); } catch (ClassNotFoundException e) { // ignore it as it will most likely be an annotation defined within // the current compilation unit which means we do not have a class for them anyway return null; } }) .filter(a -> a != null); } /** * Gets annotations that are present on the element. *

* If there are no annotations present on the element, the return * value is an empty stream. * * @param annotatedElement the element to retrieve the annotations from * @return a stream of annotations present on the element * @throws NullPointerException if the given element is null * * @see https://blog.retep.org/2009/02/13/getting-class-values-from-annotations-in-an-annotationprocessor/ */ public static Stream annotations(Element annotatedElement) { return annotatedElement.getAnnotationMirrors().stream() .map(am -> { try { final Class aclass = ReflectionUtils.classFor(am.getAnnotationType()); return (Annotation)java.lang.reflect.Proxy.newProxyInstance( aclass.getClassLoader(), new Class[] { aclass }, new AnnotationProxy(am) ); } catch (ClassNotFoundException e) { // ignore it as it will most likely be an annotation defined within // the current compilation unit which means we do not have a class for them anyway return null; } }) .filter(a -> a != null); } /** * Gets annotations which are themselves annotated with a given type of * annotation that are present on the element. *

* If there are no annotations present on the element, the return * value is an array of length 0. *

* For example, one could find all annotations annotated with the @Tag * annotation, which in the following examples would return @ThisOne and * @ThisOneToo annotations but not @NotThisOne: *

   *   @Tag
   *   public @interface ThisOne {
   *      String value();
   *   }
   *
   *   @Tag
   *   public @interface ThisOneToo {
   *      String value();
   *   }
   *
   *   public @interface NotThisOne {
   *      String value();
   *   }
   * 
* * @param annotatedElement the element to retrieve the annotations from * @param annotationClass the type of annotations to retrieve annotations * annotated with * @return an array of the element's annotations annotated with the specified * annotation type if present on this element, else an array of length * 0 * @throws NullPointerException if the given element or annotation class are * null * * @see https://blog.retep.org/2009/02/13/getting-class-values-from-annotations-in-an-annotationprocessor/ */ public static Annotation[] getAnnotationsAnnotatedWith( Element annotatedElement, Class annotationClass ) { return ReflectionUtils.annotationsAnnotatedWith(annotatedElement, annotationClass) .toArray(Annotation[]::new); } /** * Gets annotations that are present on the element. *

* If there are no annotations present on the element, the return * value is an array of length 0. *

* The caller of this method is free to modify the returned array; it will * have no effect on the arrays returned to other callers. * * @param annotatedElement the element to retrieve the annotations from * @return an array of annotations present on the element, else an array of * length 0 * @throws NullPointerException if the given element is null * * @see https://blog.retep.org/2009/02/13/getting-class-values-from-annotations-in-an-annotationprocessor/ */ public static Annotation[] getAnnotations(Element annotatedElement) { return ReflectionUtils.annotations(annotatedElement) .toArray(Annotation[]::new); } /** * Gets annotations of a specific that are associated with the element. *

* If there are no annotations associated with this element, the return * value is an array of length 0. *

* The difference between this method and * {@link ReflectionUtils#getAnnotation(Element, Class)} is that this method * detects if its argument is a repeatable annotation type (JLS 9.6), * and if so, attempts to find one or more annotations of that type by * "looking through" a container annotation. *

* The caller of this method is free to modify the returned array; it will * have no effect on the arrays returned to other callers. *

* * @param the type of the annotation to query for and return if present * * @param annotatedElement the element to retrieve the annotations from * @param annotationClass the type of annotations to retrieve * @return an array of the element's annotations for the specified annotation * type if present on this element, else an empty array * @throws NullPointerException if the given element or annotation class are * null * * @see https://blog.retep.org/2009/02/13/getting-class-values-from-annotations-in-an-annotationprocessor/ */ @SuppressWarnings("unchecked") public static T[] getAnnotationsByType( Element annotatedElement, Class annotationClass ) { return ReflectionUtils.annotationsByType(annotatedElement, annotationClass) .toArray(size -> (T[])Array.newInstance(annotationClass, size)); } /** * Gets the specified element's annotation for the specified type if such an * annotation is present, else null where classes * referenced from the annotation are properly resolved and will not generate * a compile-time mirror exception. *

* This method is useful when dealing with annotations in a processing * environment where Javac does not load classes in the normal manner. In fact * it doesn’t at all for classes that are in the source – it’s all contained * within a model. * * @author paouelle * * @param the type of the annotation to query for and return if present * * @param annotatedElement the element to retrieve the annotation from * @param annotationClass the type of annotation to retrieve * @return the element's annotation for the specified annotation type if * present on this element, else null * @throws NullPointerException if the given element or annotation class are * null * * @see https://blog.retep.org/2009/02/13/getting-class-values-from-annotations-in-an-annotationprocessor/ */ public static T getAnnotation( Element annotatedElement, Class annotationClass ) { return ReflectionUtils.annotationsByType(annotatedElement, annotationClass) .findFirst() .orElse(null); } /** * Prevents instantiation of a new class object. * * @author paouelle * * @throws IllegalStateException always thrown */ private ReflectionUtils() { throw new IllegalStateException("invalid constructor called"); } } /** * The AnnotationProxy class provides a definition for a dynamic * proxy used when dynamically creating annotation instances to go around the * annotation processor issues with annotations that contains classes as returned * types for its elements. * * @copyright 2015-2016 The Helenus Driver Project Authors * * @author The Helenus Driver Project Authors * @version 1 - Jul 7, 2016 - paouelle - Creation * * @since 1.0 */ class AnnotationProxy implements InvocationHandler { /** * Holds the annotation mirror this object is a proxy for. * * @author paouelle */ private final AnnotationMirror am; /** * Instantiates a new AnnotationProxy object. * * @author paouelle * * @param am the annotation mirror for which we are creating a proxy */ AnnotationProxy(AnnotationMirror am) { this.am = am; } /** * {@inheritDoc} * * @author paouelle * * @see java.lang.reflect.InvocationHandler#invoke(java.lang.Object, java.lang.reflect.Method, java.lang.Object[]) */ @Override public java.lang.Object invoke( java.lang.Object proxy, Method method, java.lang.Object[] args ) throws Throwable { final Class rclass = method.getReturnType(); final String name = method.getName(); //System.err.print(">>> M: " + method.getDeclaringClass().getSimpleName() + "." + name); for (final Map.Entry e: am.getElementValues().entrySet()) { if (name.equals(e.getKey().getSimpleName().toString())) { final AnnotationValue eav = e.getValue(); if (Class.class.isAssignableFrom(rclass)) { //System.err.println(" -> class, eav: " + eav.getValue().getClass() + ", eav str: " + eav.getValue().toString()); if (eav.getValue() instanceof DeclaredType) { return ReflectionUtils.classFor((DeclaredType)eav.getValue()); } // oh well! really on toString and cross your fingers :-( return Class.forName(eav.getValue().toString()); } else if (rclass.isArray() && (eav.getValue() instanceof List)) { // why couldn't the damn compiler just return arrays!!!!! final List list = List.class.cast(eav.getValue()); final Object array = Array.newInstance(rclass.getComponentType(), list.size()); int i = 0; for (final Object o: list) { final Object av = ((AnnotationValue)o).getValue(); if (Class.class.isAssignableFrom(rclass.getComponentType())) { // special case since classes are not returned here!!!! if (av instanceof DeclaredType) { Array.set(array, i++, ReflectionUtils.classFor((DeclaredType)av)); } else { // oh well! really on toString and cross your fingers :-( Array.set(array, i++, Class.forName(av.toString())); } } else { Array.set(array, i++, av); } } return array; } //System.err.println(" -> ?, eav: " + eav.getValue().getClass() + ", eav str: " + eav.getValue()); return eav.getValue(); } } // fallback to default defined in annotation class //System.err.println(" -> default: " + method.getDefaultValue()); return method.getDefaultValue(); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy