package jfun.yan.xml.nut;
import java.beans.BeanInfo;
import java.beans.IndexedPropertyDescriptor;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import jfun.yan.Binder;
import jfun.yan.Component;
import jfun.yan.xml.ConfigurationException;
/**
* Utility class to run introspection on a {@link Nut} class
* to get NutDescriptor.
* <p>
* @author Ben Yu
* Nov 9, 2005 11:34:52 PM
*/
public class NutIntrospector implements java.io.Serializable {
private final HashMap cache = new HashMap();
/**
* Get a NutDescriptor for a Nut class.
* @param type the class.
* @return the NutDescriptor object.
* @throws IntrospectionException when introspection fails.
*/
public NutDescriptor getNutDescriptor(final Class type)
throws IntrospectionException{
NutDescriptor desc = (NutDescriptor)cache.get(type);
if(desc == null){
if(!Nut.class.isAssignableFrom(type)){
throw new IllegalArgumentException("only Nut class is introspectable.");
}
/*
try{
type.getConstructor(null);
}
catch(NoSuchMethodException e){
throw new IllegalArgumentException("default constructor not present in class "+type);
}*/
desc = new NutDescriptor(type);
cache.put(type, desc);
final Evaluator evaluator = getEvaluator(type);
desc.setEvaluator(evaluator);
desc.setPropertyDescriptors(getProperties(type));
populateSub(type, desc);
}
return desc;
}
private Evaluator getEvaluator(final Class type){
if(ComponentNut.class.isAssignableFrom(type)){
return new Evaluator(){
public Object eval(Object obj)
throws Exception{
final ComponentNut nut = (ComponentNut)obj;
return nut.eval();
}
public Class getType(){
return Component.class;
}
};
}
else if(BinderNut.class.isAssignableFrom(type)){
return new Evaluator(){
public Object eval(Object obj) {
final BinderNut nut = (BinderNut)obj;
try{
return nut.eval();
}
catch(Exception e){
throw new ConfigurationException(e, nut.getTagLocation());
}
}
public Class getType(){
return Binder.class;
}
};
}
else{
try{
final Method mtd = type.getMethod("eval", null);
final Class rtype = mtd.getReturnType();
return new Evaluator(){
public Object eval(Object obj){
final Nut nut = (Nut)obj;
try{
final Object r = mtd.invoke(nut, null);
if(void.class.equals(rtype)){
return nut;
}
else return r;
}
catch(InvocationTargetException e){
throw new ConfigurationException(e.getTargetException(), nut.getTagLocation());
}
catch(Exception e){
throw new ConfigurationException(e, nut.getTagLocation());
}
}
public Class getType(){
return (void.class.equals(rtype)?type:rtype);
}
};
}
catch(NoSuchMethodException e){
return new Evaluator(){
public Object eval(Object nut){
return nut;
}
public Class getType(){
return type;
}
};
}
}
}
private Map getProperties(Class type)
throws IntrospectionException{
final BeanInfo info = Introspector.getBeanInfo(type);
final PropertyDescriptor[] pdescs = info.getPropertyDescriptors();
final HashMap ret = new HashMap();
for(int i=0; i<pdescs.length; i++){
final PropertyDescriptor desc = pdescs[i];
if(desc.getWriteMethod()!=null && !(desc instanceof IndexedPropertyDescriptor)){
ret.put(desc.getName(), desc);
}
}
return ret;
}
//gets the only "set" method with one array parameter.
//if more than one is found, use the most restrictive one.
private final void populateSub(Class type, NutDescriptor desc)
throws IntrospectionException{
final HashMap subs= new HashMap();
final Method[] mtds = type.getMethods();
final ArrayList anonymous_adders = new ArrayList();
Class etype = null;
Method r = null;
for(int i=0; i<mtds.length; i++){
final Method mtd = mtds[i];
final String mname = mtd.getName();
if(mname.equals("set")){
final Class[] ptypes = mtd.getParameterTypes();
if(ptypes.length != 1){
continue;
}
final Class ptype = ptypes[0];
if(!ptype.isArray()){
continue;
}
final Class etype2 = ptype.getComponentType();
if(etype != null){
if(etype.isAssignableFrom(etype2)){
//we find a more restrictive version
etype = etype2;
r = mtd;
}
}
else{
r = mtd;
etype = etype2;
}
}
else if(mname.startsWith(ADDER)){
final Class[] ptypes = mtd.getParameterTypes();
if(ptypes.length!=1){
continue;
}
Class ptype = ptypes[0];
final String elem_name = mname.substring(3).toLowerCase(Locale.US);
if(subs.containsKey(elem_name)){
throw new IllegalArgumentException("duplicate adder: "+mname);
}
if(elem_name.length()==0){
/*
if(!ptype.isAssignableFrom(String.class)){
//add() with non-string parameter.
continue;
}*/
anonymous_adders.add(new Method1(mtd, ptype));
}
else{
if(!Nut.class.isAssignableFrom(ptype))
continue;
desc.putAdder(elem_name, mtd);
if(elem_name.length()>0){
subs.put(elem_name, getNutDescriptor(ptype));
}
}
}
}
if(r!=null){
desc.setCollectionDescriptor(
new NutDescriptor.CollectionDescriptor(etype, r)
);
}
else{
desc.setRegularDescriptor(
new NutDescriptor.RegularDescriptor(subs)
);
}
final Method1[] anonymous = new Method1[anonymous_adders.size()];
anonymous_adders.toArray(anonymous);
desc.setAdderSuite(new MethodSuite(ADDER, anonymous));
}
private static final String ADDER = "add";
}