package net.sourceforge.javautil.common.reflection;
import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import net.sourceforge.javautil.common.reflection.cache.ClassCache;
import net.sourceforge.javautil.common.reflection.cache.ClassDescriptor;
import net.sourceforge.javautil.common.reflection.cache.ClassMethod;
import net.sourceforge.javautil.common.reflection.cache.ClassProperty;
import net.sourceforge.javautil.common.visitor.IVisitable;
/**
* A helper/utility for {@link ClassDescriptor}'s to hold onto a single instance
* and manipulate it via reflection.
*
* @author elponderador
* @author $Author: ponderator $
* @version $Id: ObjectManager.java 2624 2010-11-24 13:56:23Z ponderator $
*/
public class ObjectManager<T> implements IVisitable<ObjectVisitorBase> {
/**
* @param <T> The type/class of object
* @param cache The cache to use
* @param object The non-null object instance
* @return A manager for manipulating the object
*/
public static <T> ObjectManager<T> getManager (T object) {
if (object == null) throw new IllegalArgumentException("Null objects not allowed for management");
return new ObjectManager<T>(ClassCache.getFor((Class<T>)object.getClass()), object);
}
/**
* The parent of this manager, or null if it does not have one
*/
protected final ObjectManager parent;
/**
* The descriptor for the class of the inestnace
*/
protected final ClassDescriptor<T> descriptor;
/**
* The instance to be managed, or null if it has not been instantiated
*/
protected T instance;
/**
* This assumes null instance and no parent.
*
* @see #ObjectManager(ClassDescriptor, ObjectManager, Object)
*/
public ObjectManager(ClassDescriptor<T> descriptor) {
this(descriptor, null, null);
}
/**
* This assumes no parent.
*
* @see #ObjectManager(ClassDescriptor, ObjectManager, Object)
*/
public ObjectManager(ClassDescriptor<T> descriptor, T instance) {
this(descriptor, null, instance);
}
/**
* This assumes null instance.
*
* @see #ObjectManager(ClassDescriptor, ObjectManager, Object)
*/
public ObjectManager(ClassDescriptor<T> descriptor, ObjectManager parent) {
this(descriptor, parent, null);
}
/**
* @param descriptor The {@link #descriptor}
* @param instance The {@link #instance}
*/
public ObjectManager(ClassDescriptor<T> descriptor, ObjectManager parent, T instance) {
this.descriptor = descriptor;
this.instance = instance;
this.parent = parent;
}
/**
* @param visitor The visitor that is interested in visiting this object and its properties
* @return The visitor for chaining
*/
public <V extends ObjectVisitorBase> V accept (V visitor) { this.accept(visitor, new ObjectVisitorContext(this, instance)); return visitor; }
/**
* The recursive method for visiting the object and its properties. Null values will NOT be visited.
* This will respect {@link Collection}'s, arrays and {@link Map}'s and iterate over their values
* and call the visitor for each one.
*
* @param visitor The visitor to call
* @param instance The instance in question, or null if the property was null
* @param property The property related to the instance, or null if the root
* @return The response
*/
protected void accept (ObjectVisitorBase visitor, ObjectVisitorContext ctx) {
visitor.visit(ctx);
if (ctx.isContinue()) {
ClassDescriptor descriptor = ClassCache.getFor(instance.getClass());
Map<String, ClassProperty> properties = descriptor.getProperties();
for (String name : properties.keySet()) {
ClassProperty op = properties.get(name);
Object value = op.getValue(instance);
if (value == null) continue;
if (op.getType().isArray()) {
for (int i=0; i<Array.getLength(value); i++) {
Object avalue = Array.get(value, i);
if (avalue != null) accept(visitor, ctx.setVisited(avalue, op));
if (ctx.isAborted()) break;
}
} else if (value instanceof Collection) {
Iterator it = ((Collection)value).iterator();
while (it.hasNext()) {
Object ivalue = it.next();
if (ivalue != null) accept(visitor, ctx.setVisited(ivalue, op));
if (ctx.isAborted()) break;
}
} else if (value instanceof Map) {
Iterator keys = ((Map)value).keySet().iterator();
while (keys.hasNext()) {
Object mvalue = ((Map)value).get(keys.next());
if (mvalue != null) accept(visitor, ctx.setVisited(mvalue, op));
if (ctx.isAborted()) break;
}
} else accept(visitor, ctx.setVisited(value, op));
if (ctx.isAborted()) break;
}
}
}
/**
* @return The {@link #instance}
*/
public T getInstance() { return instance; }
public void setInstance(T instance) { this.instance = instance; }
/**
* @return The {@link #parent}
*/
public ObjectManager getParent() { return parent; }
/**
* @return The {@link #descriptor}
*/
public ClassDescriptor<T> getDescriptor() { return descriptor; }
/**
* @param name The name of a property
* @return True if the property exists, otherwise false
*/
public boolean hasProperty (String name) { return descriptor.hasProperty(name); }
/**
* @param name The name of a method
* @return True if the method exists, otherwise false
*/
public boolean hasMethod (String name) { return descriptor.hasMethod(name); }
/**
* @param name The name of the property
* @param value The value to set/assign to the property
*/
public void setProperty (String name, Object value) {
descriptor.setPropertyValue(instance, name, value);
}
/**
* @param name The name of the property
* @return The current value of the property
*/
public Object getProperty (String name) {
return descriptor.getPropertyValue(instance, name);
}
/**
* @param name The name of the property
* @return A descriptor for the class/type of the property
*/
public ClassDescriptor getPropertyDescriptor (String name) {
return descriptor.getProperty(name).getTypeDescriptor();
}
/**
* @param name The name of the property
* @return An instance of the object manager for the current value of the property, if null a manager for the descriptor
*/
public ObjectManager getPropertyManager (String name) {
Object value = this.getProperty(name);
return value == null ?
new ObjectManager(this.getPropertyDescriptor(name), this) :
new ObjectManager(descriptor.getCache().getDescriptor( value.getClass() ), this, value);
}
/**
* This will match the arguments passed and see they are valid values for the method being invoked. Otherwise
* the method must have no arguments, in any other case an exception will be thrown.
*
* @param <V> The type of value expected
* @param annotationType The annotation type for locating the method
* @param defaultValue The default value to return if the method is not found
* @param optionalArguments The optional arguments to pass to the method
* @return The value from the invocation of the method if found, otherwise the defaultValue
*/
public <V> V invokeAnnotated (Class<? extends Annotation> annotationType, V defaultValue, Object... optionalArguments) {
ClassMethod method = this.descriptor.getMethod(annotationType);
if (method != null) {
switch (method.compareArguments(optionalArguments)) {
case EXACT: case FUNCTIONAL:
return (V) method.invoke(instance, optionalArguments);
default:
if (method.getParameterTypes().length == 0) {
return (V) method.invoke(instance);
} else {
throw new ReflectionException("Parameters required for method: " + method.getJavaMember());
}
}
} else {
return defaultValue;
}
}
/**
* @param name The name of the method to invoke
* @param arguments The arguments to pass to the invocation
* @return The value returned by the method invocation
*/
public Object invoke (String name, Object... arguments) {
return this.descriptor.invoke(name, instance, arguments);
}
}