Package net.sourceforge.javautil.common.reflection.cache

Source Code of net.sourceforge.javautil.common.reflection.cache.ClassDescriptor

package net.sourceforge.javautil.common.reflection.cache;

import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import net.sourceforge.javautil.common.ReflectionUtil;
import net.sourceforge.javautil.common.exception.ThrowableManagerRegistry;
import net.sourceforge.javautil.common.reflection.ReflectionContext;
import net.sourceforge.javautil.common.reflection.IReflectionManager;
import net.sourceforge.javautil.common.reflection.cache.ClassMethodAbstract.ArgumentMatch;

/**
* A class descriptor containing information on what public properties
* and methods are available on a particular class. Also providing
* many helper classes and methods for dealing with common reflection
* operations.
*
* @author elponderador
* @author $Author: ponderator $
* @version $Id: ClassDescriptor.java 2722 2011-01-16 05:38:59Z ponderator $
*/
public class ClassDescriptor<T> {
 
  protected final ClassCache cache;
  protected final Class<T> clazz;
 
  protected final List<ClassConstructor<T>> constructors = new ArrayList<ClassConstructor<T>>();
  protected final Map<String, List<ClassMethod>> methods = new LinkedHashMap<String, List<ClassMethod>>();
  protected final Map<String, ClassProperty> properties = new LinkedHashMap<String, ClassProperty>();
  protected final Map<String, List<ClassField>> fields = new LinkedHashMap<String, List<ClassField>>();
 
  protected List<ClassMethod> getters = new ArrayList<ClassMethod>();
 
  protected Boolean initializedFields;
  protected Boolean initializedProperties;
  protected Boolean initializedMethods;
  protected Boolean initializedConstructors;

  /**
   * All the description information will be loaded at instantiation time.
   *
   * @param clazz The class for which to create a descriptor
   */
  public ClassDescriptor(ClassCache cache, Class<T> clazz) {
    this.clazz = clazz;
    this.cache = cache;
  }
 
  /**
   * @return The class this descriptor is for
   */
  public Class<T> getDescribedClass () { return this.clazz; }
 
  /**
   * @return An array, possibly empty, of publicly accessible properties
   */
  public String[] getPropertyNames () {
    this.detectProperties();
    String[] names = new String[properties.size()];
    Iterator<String> keys = this.properties.keySet().iterator();
    for (int i=0; keys.hasNext(); i++) { names[i] = keys.next(); }
    return names;
  }

  /**
   * @return An array, possibly empty, of publicly accessible fields
   */
  public String[] getFieldNames () {
    this.detectFields();
    String[] names = new String[fields.size()];
    Iterator<String> keys = this.fields.keySet().iterator();
    for (int i=0; keys.hasNext(); i++) { names[i] = keys.next(); }
    return names;
  }
 
  /**
   * @param <A> The type of annotation
   * @param clazz The annotation class
   * @return The annotation if present on the class, otherwise null
   */
  public <A extends Annotation> A getAnnotation (Class<A> clazz) {
    return this.clazz.getAnnotation(clazz);
  }
 
  /**
   * @return True if this describes an array, otherwise false
   */
  public boolean isArray () { return this.clazz.isArray(); }
 
  /**
   * @param name The name of a property
   * @return True if the class does have this public property, otherwise false
   */
  public boolean hasProperty (String name) {
    this.detectProperties();
    return this.properties.containsKey(name);
  }
 
  /**
   * @param name The name of a method
   * @return True if the class does have this public method, otherwise false
   */
  public boolean hasMethod (String name) {
    this.detectMethods();
    return this.methods.containsKey(name);
  }
 
  /**
   * @return The class cache where this descriptor resides
   */
  public ClassCache getCache() { return cache; }
 
  /**
   * @return True if this descriptor describes one of the primitive types, a boxed primitive or a {@link String}
   */
  public boolean isBasicType () {
    return clazz.isPrimitive() ||
      clazz == Integer.class ||
      clazz == Long.class ||
      clazz == Short.class ||
      clazz == Double.class ||
      clazz == Float.class ||
      clazz == Boolean.class ||
      clazz == Byte.class ||
      clazz == Character.class ||
      clazz == String.class;
  }
 
  /**
   * @return The component type of the array if this describes an array, otherwise null
   */
  public ClassDescriptor getComponentTypeDescriptor () {
    return clazz.isArray() ? cache.getDescriptor(clazz.getComponentType()) : null;
  }

  /**
   * The key of the map will be the name of the method, and the collection
   * will be one or more methods with different signatures.
   *
   * @return All the public methods
   */
  public Map<String, List<ClassMethod>> getMethods () {
    this.detectMethods();
    return new LinkedHashMap<String, List<ClassMethod>>(this.methods);
  }
 
  /**
   * @param name The name of the method
   * @return An array of methods with the same name, but different signatures, or an empty array if no such method exists
   */
  public ClassMethod[] getMethods (String name) {
    this.detectMethods();
    List<ClassMethod> methods = this.methods.get(name);
    return methods == null ? new ClassMethod[0] : methods.toArray(new ClassMethod[methods.size()]);
  }
 
  /**
   * @param name The name of the field
   * @return The field corresponding to the name, or null if no such field exists
   */
  public ClassField getField (String name) {
    this.detectFields();
    return this.fields.containsKey(name) ? this.fields.get(name).get(0) : null;
  }
 
  /**
   * The key will be the name of the field and the value the wrapper for
   * accessing it.
   *
   * @return A map of all public fields
   */
  public Map<String, List<ClassField>> getFields () {
    this.detectFields();
    return new LinkedHashMap<String, List<ClassField>>(this.fields);
  }
 
  public int getFieldCount () {
    this.detectFields();
    return this.fields.size();
  }
 
  /**
   * @param type The type of annotation to search for
   * @return The first field found that has the annotation declared on it
   */
  public ClassField getField (Class<? extends Annotation> type) {
    this.detectFields();
    for (String name : fields.keySet()) {
      for (ClassField field : fields.get(name)) {
        if (field.getAnnotation(type) != null) return field;
      }
    }
    return null;
  }
 
  /**
   * @param type The type of annotation to search for
   * @return An array, possibly empty, of fields that have the annotation declared on them
   */
  public ClassField[] getFields (Class<? extends Annotation> type) {
    this.detectFields();
   
    List<ClassField> fields = new ArrayList<ClassField>();
   
    for (String name : this.fields.keySet()) {
      for (ClassField field : this.fields.get(name)) {
        if (field.field.getAnnotation(type) != null) fields.add(field);
      }
    }
   
    return fields.toArray(new ClassField[fields.size()]);
  }
 
  /**
   * The key will be the Java Bean property name, and the object will
   * allow for access to, setting and getting on the property.
   *
   * @return All the public properties
   */
  public Map<String, ClassProperty> getProperties () {
    this.detectProperties();
    return new LinkedHashMap<String, ClassProperty>(this.properties);
  }
 
  /**
   * @param instance The instance for which to get properties
   * @return A map of property names<->instance values
   */
  public Map<String, Object> getProperties (Object instance) {
    this.detectProperties();
    Map<String, Object> values = new LinkedHashMap<String, Object>();
   
    for (String name : this.properties.keySet()) {
      values.put(name, this.properties.get(name).getValue(instance));
    }
   
    return values;
  }
 
  /**
   * @param name The name of the property
   * @return The property corresponding to the name, or null if no such property exists
   */
  public ClassProperty getProperty (String name) {
    this.detectProperties();
    return this.properties.get(name);
  }
 
  /**
   * @param annotation The annotation class to check for
   * @return An array, possibly empty, of methods that have the specified annotation
   */
  public ClassMethod[] getMethods (Class<? extends Annotation> annotation) {
    this.detectMethods();
   
    List<ClassMethod> methods = new ArrayList<ClassMethod>();
   
    for (String name : this.methods.keySet()) {
      List<ClassMethod> nm = this.methods.get(name);
      for (ClassMethod method : nm) {
        if (method.getJavaMember().getAnnotation(annotation) != null)
          methods.add(method);
      }
    }
   
    return methods.toArray(new ClassMethod[methods.size()]);
  }
 
  /**
   * @param annotation The annotation class to check for
   * @return The first method found with the annotation, or null if no method has it
   */
  public ClassMethod getMethod (Class<? extends Annotation> annotation) {
    this.detectMethods();
   
    for (String name : this.methods.keySet()) {
      List<ClassMethod> nm = this.methods.get(name);
      for (ClassMethod method : nm) {
        if (method.getJavaMember().getAnnotation(annotation) != null)
          return method;
      }
    }
   
    return null;
  }

  /**
   * @param annotation The annotation class to check for
   * @return The first property found with this annotation, or null if there are none
   */
  public ClassProperty getProperty (Class<? extends Annotation> annotation) {
    this.detectProperties();
   
    for (String name : this.properties.keySet()) {
      ClassProperty property = this.properties.get(name);
      if (property.getAnnotation(annotation) != null)
        return property;
    }
    return null;
  }
 
  /**
   * @param annotation The annotation class to check for
   * @return An array, possibly empty, of the properties that have the specified annotation
   */
  public ClassProperty[] getProperties (Class<? extends Annotation> annotation) {
    this.detectProperties();
   
    List<ClassProperty> properties = new ArrayList<ClassProperty>();
   
    for (String name : this.properties.keySet()) {
      ClassProperty property = this.properties.get(name);
      if (property.getAnnotation(annotation) != null)
        properties.add(property);
    }
   
    return properties.toArray(new ClassProperty[properties.size()]);
  }
 
  /**
   * @param arguments The arguments to use for the constructor
   * @return A class constructor, or null if it could not be found
   */
  public ClassConstructor<T> getConstructor (Object... arguments) {
    this.detectConstructors();
    return this.getClosestMatchInternal(this.constructors, arguments);
  }
 
  /**
   * @param parameterTypes The parameter types
   * @return A class constructor, or null if none matched
   */
  public ClassConstructor<T> getConstructor (Class<?>... parameterTypes) {
    this.detectConstructors();
    return this.getClosestMatchInternal(this.constructors, parameterTypes);
  }
 
  /**
   * @param annotation The annotation type to look for
   * @return The first constructor that was annotated with this annotation, or null if none were
   */
  public ClassConstructor<T> getConstructor (Class<? extends Annotation> annotation) {
    this.detectConstructors();
   
    for (ClassConstructor<T> constructor : this.constructors) {
      if (constructor.getAnnotation(annotation) != null) return constructor;
    }
   
    return null;
  }
 
  /**
   * @return The constructors publicly available for this descriptor
   */
  public List<ClassConstructor<T>> getConstructors () {
    this.detectConstructors();
    return new ArrayList<ClassConstructor<T>>(this.constructors);
  }
 
  /**
   * Create a new instance of the class.
   *
   * @param arguments The arguments to use for creating the class
   * @return The instance that was created
   */
  public T newInstance (Object... arguments) {
    if (clazz.isArray()) {
      boolean valid = true;
      int[] indices = new int[arguments.length];
      for (int a=0; a<arguments.length; a++) {
        Object arg = arguments[a];
        if (arg != null && arg.getClass() == Integer.class) {
          indices[a] = ((Integer)arg).intValue();
          continue;
        }
        valid = false; break;
      }
      if (!valid || indices.length == 0)
        indices = new int[] { 0 };
     
      return this.createArray(indices);
    }
    this.detectConstructors();
   
    ClassConstructor<T> constructor = this.getClosestMatchInternal(this.constructors, arguments);
   
    if (constructor != null) return constructor.newInstance(arguments);
   
    throw new ClassInstantiationException(this, arguments, "No matching constructor could be found");
  }
 
  protected T createArray (int... indices) {
    return (T) Array.newInstance(clazz.getComponentType(), indices);
  }
 
  /**
   * @param <E> The type of object
   * @param instance The instance of the class, or null if this is a static property
   * @param name The name of the property
   * @return The current value of the property
   */
  public <E extends T> Object getPropertyValue (E instance, String name) {
    this.detectProperties();
   
    if (!this.properties.containsKey(name))
      throw new ClassPropertyNotFoundException(this, name);
   
    return this.properties.get(name).getValue(instance);
  }
 
  /**
   * This is the complement of {@link #deserializeProperties(Object, Map)}.
   *
   * @param instance The instance to serialize properties for
   * @return
   */
  public Map<String, String> serializeProperties (Object instance) {
    this.detectProperties();
   
    Map<String, String> settings = new LinkedHashMap<String, String>();
    Map<String, ClassProperty> properties = getProperties();
    for (String name : properties.keySet()) {
      settings.put(name, ReflectionUtil.coerce(String.class, properties.get(name).getValue(this)));
    }
    return settings;
  }
 
  /**
   * The keys on the map should correspond to properties that really
   * exist and the values should be appropriate values for the respective
   * property.
   *
   * @param instance The instance on which to set the properties
   * @param properties The properties to set on the instance
   */
  public void deserializeProperties (T instance, Map<String, Object> properties) {
    this.detectProperties();
   
    for (String name : properties.keySet()) {
      ClassProperty property = this.getProperty(name);
      if (!property.isWritable())
        throw new ClassPropertyAccessException(this, property, "Cannot set read-only property:" + name);
       
      property.setValue(instance, ReflectionUtil.coerce(property.getType(), properties.get(name)));
    }
  }
 
  /**
   * Set/assign a value to the property.
   *
   * @param <E> The type of object
   * @param instance The instance of the class, or null if this is a static property
   * @param name The name of the property
   * @param value The value to set/assign
   */
  public <E extends T> void setPropertyValue (E instance, String name, Object value) {
    this.detectProperties();

    if (!this.properties.containsKey(name))
      throw new ClassPropertyNotFoundException(this, name);
   
    ClassProperty property = this.properties.get(name);
    property.setValue(instance, ReflectionUtil.coerce(property.getType(), value));
  }
 
  /**
   * @param name The name of the method to invoke
   * @param instance The instance on which to invoke it, or null if this is a static call
   * @param arguments The arguments to pass to the method invocation
   * @return The value returned by the method
   */
  public Object invoke (String name, Object instance, Object... arguments) {
    this.detectMethods();
   
    if (!this.methods.containsKey(name))
      throw new ClassDescriptorException(this, "No such method exists: " + name);
   
    ClassMethod method = this.getClosestMatchInternal(this.methods.get(name), arguments);
   
    if (method == null)
      throw new ClassDescriptorException(this, "No such method exists for arguments provided: " + name);
   
    return method.invoke(instance, arguments);
  }
 
  public String toString () { return "ClassDescriptor[" + clazz + "]"; }
 
  /**
   * @param name The name of the method
   * @param types The parameter types
   * @return The method that most closely matches the types, or null if no such method exists
   */
  public ClassMethod findMethod (String name, Class... types) {
    this.detectMethods();
    return this.getClosestMatch(this.methods.get(name), types);
  }
 
  /**
   * @param name The name of the method
   * @param arguments The arguments for the method
   * @return The method that most closely matches the arguments, or null if no such method exists
   */
  public ClassMethod findMethod (String name, Object... arguments) {
    this.detectMethods();
    return this.getClosestMatch(this.methods.get(name), arguments);
  }
 
  /**
   * @param <M> The type of method
   * @param members The collection to search
   * @param arguments The arguments to search against
   * @return A method/constructor that should work with the arguments, otherwise null
   */
  public <M extends ClassMethodAbstract> M getClosestMatch (Collection<M> members, Object... arguments) {
    this.detectMethods();
    return this.getClosestMatchInternal(members, arguments);
  }
   
  protected <M extends ClassMethodAbstract> M getClosestMatchInternal (Collection<M> members, Object... arguments) {
    if (members == null) return null;
    M match = null;
    synchronized (members) {
      for (M member : members) {
        ArgumentMatch result = member.compareArguments(arguments);
        if (result == ArgumentMatch.EXACT) return member;
        else if (result == ArgumentMatch.FUNCTIONAL && match == null)
          match = member;
      }
      return match;
    }
  }
 
  /**
   * @param <M> The type of method
   * @param members The collection to search
   * @param arguments The argument types to search against
   * @return A method/constructor that should work with the argument types, otherwise null
   */
  public <M extends ClassMethodAbstract> M getClosestMatch (Collection<M> members, Class... arguments) {
    this.detectMethods();
    return this.getClosestMatchInternal(members, arguments);
  }
 
  protected <M extends ClassMethodAbstract> M getClosestMatchInternal (Collection<M> members, Class... arguments) {
    if (members == null) return null;
    M match = null;
    for (M member : members) {
      ArgumentMatch result = member.compareArgumentTypes(arguments);
      if (result == ArgumentMatch.EXACT) return member;
      else if (result == ArgumentMatch.FUNCTIONAL && match == null)
        match = member;
    }
    return match;
  }
 
  protected void detectFields () {
    if (this.initializedFields != null && this.initializedFields) return;
    synchronized (this.fields) {
      if (this.initializedFields == Boolean.TRUE) return;
      this.initializedFields = Boolean.FALSE;
     
      Class sc = clazz;
      IReflectionManager manager = ReflectionContext.getReflectionManager();
      while (sc != null) {
        for (Field field : manager.getDeclaredFields(sc)) {
          if (!this.fields.containsKey(field.getName())) this.fields.put(field.getName(), new ArrayList<ClassField>());
          this.fields.get(field.getName()).add( new ClassField(field, this) );
        }
        sc = sc.getSuperclass();
      }
     
      this.initializedFields = Boolean.TRUE;
    }
  }
 
  protected void detectProperties () {
    if (this.initializedProperties != null && this.initializedProperties) return;
    synchronized (this.properties) {
      if (this.initializedProperties == Boolean.TRUE) return;
      this.initializedProperties = Boolean.FALSE;
     
      this.detectMethods();
     
      try {
        // TODO: detect write only/setter only methods
        for (ClassMethod getter : getters) {
          String name = getter.getName().startsWith("is") ? getter.getName().substring(2) : getter.getName().substring(3);
          String propertyName = name.length() > 1 ?
            name.substring(0, 1).toLowerCase() + name.substring(1) :
            name.toLowerCase();
         
          ClassMethod setterMethod = null;
          String setterName = "set" + name;
         
          if (methods.containsKey(setterName)) {
            setterMethod = this.getClosestMatchInternal(methods.get(setterName), getter.getReturnType());
          }
           
          this.properties.put(propertyName, new ClassProperty(this, propertyName, getter, setterMethod));
        }
       
        this.getters = null;
        this.initializedProperties = Boolean.TRUE;
      } catch (Exception e) {
        this.initializedProperties = null;
        throw ThrowableManagerRegistry.caught(e);
      }
    }
   
  }
 
  protected void detectMethods () {
    if (this.initializedMethods != null && this.initializedMethods) return;
    synchronized (this.methods) {
      if (this.initializedMethods == Boolean.TRUE) return;
      this.initializedMethods = Boolean.FALSE;
     
      try {
        IReflectionManager manager = ReflectionContext.getReflectionManager();
        Set<Class<?>> supers = clazz.isInterface() ? ReflectionUtil.extractInterfaces(clazz) : ReflectionUtil.extractSuperclasses(clazz);
        for (Class<?> sc : supers) {
          for (Method method : manager.getDeclaredMethods(sc)) {
            String name = method.getName();
            ClassMethod cmethod = null;
           
            if (!methods.containsKey(name)) methods.put(name, new ArrayList<ClassMethod>());
            else {
              boolean override = false;
              List<ClassMethod> cms = new ArrayList<ClassMethod>(methods.get(name));
              for (ClassMethod cm : cms) {
                if (cm.getParameterTypes().length == method.getParameterTypes().length) {
                  boolean sameArguments = true;
                  for (int p=0; p<cm.getParameterTypes().length; p++) {
                    if (cm.getParameterTypes()[p] != method.getParameterTypes()[p]) {
                      sameArguments = false;
                      break;
                    }
                  }
                  if (!sameArguments) continue;
                  if (method.getReturnType().isAssignableFrom(cm.getReturnType())) {
                    override = true;
                  } else {
                    methods.get(name).remove(cm);
                  }
                }
              }
              if (override) continue;
            }
           
            methods.get(name).add(cmethod = new ClassMethod(this, method));
     
            if (method.getDeclaringClass() != Object.class &&
                (
                  (name.startsWith("get") && name.length() > 3) ||
                  (name.startsWith("is") && name.length() > 2)
                )
                && method.getParameterTypes().length == 0) {
              getters.add( cmethod );
              cmethod.propertyMethod = true;
            }
          }
          this.initializedMethods = Boolean.TRUE;
         
          sc = sc.getSuperclass();
        }
      } catch (Exception e) {
        this.initializedMethods = null;
        throw ThrowableManagerRegistry.caught(e);
      }
    }
  }
 
  /**
   * Initialize the property, constructor and method wrappers
   */
  protected void detectConstructors () {
    if (this.initializedConstructors != null && this.initializedConstructors == Boolean.TRUE) return;
    synchronized (this.constructors) {
      if (this.initializedConstructors == Boolean.TRUE) return;
      this.initializedConstructors = Boolean.FALSE;
     
      try {
        for (Constructor constructor : ReflectionContext.getReflectionManager().getDeclaredConstructors(clazz)) {
          this.constructors.add(new ClassConstructor<T>(this, constructor));
        }
        this.initializedConstructors = Boolean.TRUE;
      } catch (Exception e) {
        this.initializedConstructors = null;
        throw ThrowableManagerRegistry.caught(e);
      }
    }
  }
 
}
TOP

Related Classes of net.sourceforge.javautil.common.reflection.cache.ClassDescriptor

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.