Package org.moyrax.reflect

Source Code of org.moyrax.reflect.ClassResource

package org.moyrax.reflect;

import java.io.IOException;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.GenericDeclaration;
import java.lang.reflect.TypeVariable;
import java.util.HashMap;
import java.util.List;

import org.apache.commons.lang.NotImplementedException;
import org.apache.commons.lang.Validate;
import org.objectweb.asm.ClassAdapter;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.AnnotationNode;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.MethodNode;

import sun.reflect.annotation.AnnotationParser;
import sun.reflect.annotation.AnnotationType;

/**
* This class reads a {@link Class} resource and parses all the fields and
* methods. It allows to retrieve information without loading the class into
* memory, so it makes possible to instrument the resource before mapping it
* to the JVM cache.
*
* The class also support the basic Java Reflection, and will use it if
* available instead of ASM.
*
* @author Matias Mirabelli <lumen.night@gmail.com>
* @since 1.2
*/
public class ClassResource extends ClassAdapter implements Serializable,
GenericDeclaration, java.lang.reflect.Type, AnnotatedElement {

  /** Default id for serialization. */
  private static final long serialVersionUID = -8120951650118655721L;

  /** Internal cache types. */
  private enum CacheType {
    ANNOTATIONS,
    FIELDS,
    METHODS
  }

  /** {@link ClassReader} to parse the script class. */
  private ClassReader reader;

  /**
   * Indicates if the class is already parsed. Default is <code>false</code>.
   */
  private boolean transformed;

  /**
   * {@link ClassLoader} used to locate resources and related classes.
   */
  private ClassLoader classLoader;

  /** This cache contains the loaded fields and methods in order to keep a
     unique instance of each object in the class. */
  private HashMap<CacheType, HashMap<String, Object>> cache = new
  HashMap<CacheType, HashMap<String, Object>>();

  /**
   * Creates a new {@link ClassResource} to read the specified class.
   *
   * @param klass Class to read. It cannot be null.
   *
   * @throws IOException If the class is not found or it cannot be readed.
   */
  public ClassResource (final Class<?> klass) throws IOException {
    this(klass, Thread.currentThread().getContextClassLoader());
  }

  /**
   * Creates a new {@link ClassResource} to read the specified class.
   *
   * @param klass Class to read. It cannot be null.
   *
   * @throws IOException If the class is not found or it cannot be readed.
   */
  public ClassResource (final Class<?> klass, final ClassLoader classLoader)
  throws IOException {

    this(klass.getName(), classLoader);
  }

  /**
   * Creates a new {@link ClassResource} to read the specified class.
   *
   * @param name Class to read. It cannot be null or empty.
   *
   * @throws IOException If the class is not found or it cannot be readed.
   */
  public ClassResource (final String name) throws IOException {
    this(name, Thread.currentThread().getContextClassLoader());
  }

  /**
   * Creates a new {@link ClassResource} to read the specified class. Also uses
   * the given {@link ClassLoader} to locate the class.
   *
   * @param name Class to read. It cannot be null or empty.
   * @param classLoader The {@link ClassLoader} used to search for the class. It
   *    cannot be null.
   *
   * @throws IOException If the class is not found or it cannot be readed.
   */
  public ClassResource (final String name,
      final ClassLoader theClassLoader) throws IOException {

    this(new ClassReader(theClassLoader.getResourceAsStream(
        name.replace(".", "/") + ".class")));

    this.classLoader = theClassLoader;
  }

  /**
   * This constructor is used to keep the {@link ClassReader} and initializes
   * the proper visitor.
   *
   * @param classReader {@link ClassReader} related to the working class. It
   *    shouldn't be null.
   */
  private ClassResource (final ClassReader classReader) {
    super(new ClassNode());

    this.reader = classReader;

    /* Initializes the caches. */
    cache.put(CacheType.ANNOTATIONS, new HashMap<String, Object>());
    cache.put(CacheType.FIELDS, new HashMap<String, Object>());
    cache.put(CacheType.METHODS, new HashMap<String, Object>());
  }

  /**
   * If the class or interface represented by this Class object is a member of
   * another class, returns the Class object representing the class in which
   * it was declared. This method returns null if this class or interface is
   * not a member of any other class. If this Class object represents an array
   * class, a primitive type, or void,then this method returns null.
   *
   * @return The declaring class for this class.
   */
  public Class<?> getDeclaringClass() {
    Class<?> result;

    try {
      result = classLoader.loadClass(getNode().name.replace("/", "."));
    } catch (ClassNotFoundException ex) {
      throw new IllegalStateException("Cannot load the class "
          + getNode().name, ex);
    }

    return result;
  }

  /**
   * Returns the name of the entity (class, interface, array class, primitive
   * type, or void) represented by this Class object, as a {@link String}.
   *
   * @return The class name.
   */
  public String getName() {
    return getNode().name.replace("/", ".");
  }

  /**
   * Returns the name of the entity (class, interface, array class, primitive
   * type, or void) represented by this Class object, as a {@link String}.
   *
   * @return The class name.
   */
  public String getCanonicalName() {
    return getNode().name.replace("/", ".").replace("$", ".") + "[]";
  }

  /**
   * Determines if the class contains the specified annotation.
   *
   * @param className Name of the annotation's class. It cannot be null or
   *    empty.
   *
   * @return Returns <code>true</code> if the class contains the given
   *    annotation, <code>false</code> otherwise.
   */
  public boolean isAnnotationPresent(final String className) {
    return findAnnotation(className) != null;
  }

  /**
   * {@inheritDoc}
   */
  public boolean isAnnotationPresent(final Class<? extends Annotation> annotationClass) {
    return isAnnotationPresent(annotationClass.getName());
  }

  /**
   * {@inheritDoc}
   */
  public Annotation[] getDeclaredAnnotations() {
    return getAnnotations();
  }

  /**
   * Returns this element's annotation for the specified type if such an
   * annotation is present, otherwise returns <code>null</code>.
   *
   * @param annotationClass Annotation's class.
   */
  @SuppressWarnings("unchecked")
  public <A extends Annotation> A getAnnotation(final Class<A> annotationClass) {
    return (A)buildAnnotation(findAnnotation(annotationClass.getName()));
  }

  /**
   * Returns this element's annotation for the specified type if such an
   * annotation is present, otherwise returns <code>null</code>.
   *
   * @param className Annotation's class name.
   */
  public Annotation getAnnotation(final String className) {
    return buildAnnotation(findAnnotation(className));
  }

  /**
   * {@inheritDoc}
   */
  public Annotation[] getAnnotations() {
    return getAnnotations(getNode().visibleAnnotations);
  }

  /**
   * Returns the methods which has this class.
   */
  public Method[] getMethods() {
    ClassNode node = getNode();

    if (node.methods == null) {
      return new Method[0];
    }

    Method[] result = new Method[node.methods.size()];

    for (int i = 0, j = node.methods.size(); i < j; i++) {
      result[i] = buildMethod((MethodNode)node.methods.get(i));
    }

    return result;
  }

  /**
   * Converts a list of {@link AnnotationNode} to the related
   * {@link Annotation}s objects.
   *
   * @param annotations List of annotations. If it's null, the method will
   *    return an empty array.
   */
  private Annotation[] getAnnotations(final List<?> annotations) {
    if (annotations == null) {
      return new Annotation[0];
    }

    Annotation[] result = new Annotation[annotations.size()];

    for (int i = 0, j = annotations.size(); i < j; i++) {
      result[i] = buildAnnotation((AnnotationNode)annotations.get(i));
    }

    return result;
  }

  /**
   * @return Returns the byte array which contains the code of the transformed
   * class.
   */
  private ClassNode getNode() {
    if (!transformed) {
      reader.accept(this, 8);

      transformed = true;
    }

    return (ClassNode)cv;
  }

  /**
   * Searches for an annotation from its name.
   *
   * @param name Annotation to search for. It cannot be null or empty.
   *
   * @return Returns the {@link AnnotationNode} representing the required
   *   annotation, or <code>null</code> if it's not found.
   */
  private AnnotationNode findAnnotation(final String name) {
    Validate.notEmpty(name, "The annotation name cannot be null or empty.");

    ClassNode node = getNode();

    if (node.visibleAnnotations != null) {
      for (int i = 0, j = node.visibleAnnotations.size(); i < j; i++) {
        AnnotationNode ann = (AnnotationNode)node.visibleAnnotations.get(i);
        String descriptor = "L" + name.replace(".", "/") + ";";

        if (ann.desc.equals(descriptor)) {
          return ann;
        }
      }
    }

    return null;
  }

  /**
   * Creates a Java {@link Annotation} from the related {@link AnnotationNode}.
   *
   * @param node Annotation information. It cannot be null.
   *
   * @return Returns the new {@link Annotation}.
   */
  @SuppressWarnings("unchecked")
  private Annotation buildAnnotation(final AnnotationNode node) {
    Validate.notNull(node, "The annotation node cannot be null.");

    if (!cache.get(CacheType.ANNOTATIONS).containsKey(node.desc)) {
      try {
        /* Extracts the annotation's class. */
        String className = Type.getType(node.desc).getClassName();

        /* Tries to get the annotation class using the current class loader. */
        Class<? extends Annotation> klass =
          (Class<? extends Annotation>)this.getClass()
          .getClassLoader().loadClass(className);

        HashMap<String, Object> values = new HashMap<String, Object>();

        values = (HashMap<String, Object>)AnnotationType
            .getInstance(klass).memberDefaults();

        /* Retrieves all values for this annotation. */
        if (node.values != null) {
          for (int i = 0, j = node.values.size(); i < j; i += 2) {
            String name = (String)node.values.get(i);
            Object value = node.values.get(i + 1);

            if (value.getClass().equals(Type.class)) {
              value = TypeResolver.resolve((Type)value);
            }

            values.put(name, value);
          }
        }

        /* Creates a proxy for the annotation. */
        Object proxy = AnnotationParser.annotationForMap(klass, values);

        /* Puts the annotation into the cache. */
        cache.get(CacheType.ANNOTATIONS).put(node.desc, proxy);
      } catch (ClassNotFoundException ex) {
        throw new IllegalArgumentException("The annotation cannot be"
            + " found.", ex);
      }
    }

    return (Annotation)cache.get(CacheType.ANNOTATIONS).get(node.desc);
  }

  /**
   * Creates a Java {@link Annotation} from the related {@link AnnotationNode}.
   *
   * @param node Annotation information. It cannot be null.
   *
   * @return Returns the new {@link Annotation}.
   */
  @SuppressWarnings("unchecked")
  private Method buildMethod(final MethodNode node) {
    Validate.notNull(node, "The annotation node cannot be null.");

    if (!cache.get(CacheType.METHODS).containsKey(node.desc + node.name)) {
      String declaringClass = getNode().name;
      String[] exceptions = (String[])node.exceptions.toArray(new String[] {});
      Annotation[] annotations = getAnnotations(node.visibleAnnotations);
      Type[] parameters = Type.getArgumentTypes(node.desc);
      Type returnType = Type.getReturnType(node.desc);
      Annotation[][] parameterAnnotations = new Annotation[0][];

      if (node.visibleParameterAnnotations != null) {
        parameterAnnotations =
          new Annotation[node.visibleParameterAnnotations.length][];

        for (int i = 0, j = parameterAnnotations.length; i < j; i++) {
          parameterAnnotations[i] = getAnnotations(
              node.visibleParameterAnnotations[i]);
        }
      }

      /* Puts the annotation into the cache. */
      cache.get(CacheType.METHODS).put(node.desc + node.name, new Method(
          declaringClass, exceptions, node.name, node.access, parameters,
          returnType, annotations, parameterAnnotations, node.desc
      ));
    }

    return (Method)cache.get(CacheType.METHODS).get(node.desc + node.name);
  }

  /**
   * {@inheritDoc}
   */
  public TypeVariable<?>[] getTypeParameters() {
    throw new NotImplementedException("This method is still not implemented.");
  }
}
TOP

Related Classes of org.moyrax.reflect.ClassResource

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.