/*****************************************************************************
* Copyright (C) Codehaus.org. All rights reserved. *
* ------------------------------------------------------------------------- *
* The software in this package is published under the terms of the BSD *
* style license a copy of which has been included with this distribution in *
* the LICENSE.txt file. *
*****************************************************************************/
/*
* Created on Apr 7, 2005
*
* Author Michelle Lei
* ZBS
*/
package jfun.yan.etc;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.util.HashMap;
import jfun.yan.Component;
import jfun.yan.Components;
import jfun.yan.Creator;
import jfun.yan.PropertyBinder;
/**
* Helper functions for bean component including customizations
* of bean property resolution strategy.
* The default strategy is byType.
* byName, byDisplayName and byQualifiedName are provided in this class directly.
* More flexible policies can be implemented by
* using the {@link Beans#resolveBy(jfun.yan.Component, FromProperty)} method.
* <p>
* Codehaus.org.
*
* @author Michelle Lei
*
*/
public class Beans {
private static PropertyDescriptor[] getBeanProperties(Class c){
try{
return Introspector.getBeanInfo(c).getPropertyDescriptors();
}
catch(IntrospectionException e){
throw new IllegalArgumentException(e.getMessage());
}
}
/**
* Customize the resolution mechanism for java bean properties
* using the result from a FromProperty object.
*
* @param cc the component.
* @param type the type of the java bean.
* @param pn the FromProperty object.
* @return the new Component.
*/
public static Component resolveBy(final Component cc, final Class type,
final FromProperty pn){
final PropertyDescriptor[] props = getBeanProperties(type);
//Component r = cc;
final java.util.HashMap hmap = new java.util.HashMap();
for(int i=0; i<props.length; i++){
final PropertyDescriptor prop = props[i];
if(prop.getWriteMethod() == null) continue;
final String name = prop.getName();
hmap.put(name, Components.useKey(pn.fromProperty(type, prop)));
//r = r.withProperty(name, Components.useKey(pn.fromProperty(type, prop)))
//;
}
return cc.withProperties(hmap);
//return r;
}
/**
* Customize the resolution mechanism for java bean properties
* using the result from a FromProperty object.
*
* @param cc the component.
* @param pn the FromProperty object.
* @return the new Component.
*/
public static Component resolveBy(final Component cc, final FromProperty pn){
final Class type = cc.getType();
if(type==null || !cc.isConcrete()){
return cc.bind(new jfun.yan.Binder(){
public Creator bind(Object obj)
throws Exception{
if(obj==null) return Components.value(null);
else return resolveBy(Components.value(obj), obj.getClass(), pn);
}
});
}
else{
return resolveBy(cc, type, pn);
}
}
/**
* resolve java bean properties by name instead of the default "byType"
* policy.
*
* @param cc the component.
* @return the new Component.
*/
public static Component byName(final Component cc){
/*return resolveBy(cc, new FromProperty(){
public Object fromProperty(Class c, PropertyDescriptor prop){
return prop.getName();
}
});*/
//we use withProperties because it is easier.
//and we get more testing coverage.
return cc.bindProperties(new jfun.yan.PropertyBinder(){
public Creator bind(Class component_type, Object key, Class type){
return Components.useKey(key);
}
});
}
/**
* resolve java bean properties by type.
*
* @param cc the component.
* @return the new Component.
*/
public static Component byType(final Component cc){
return cc.bindProperties(new jfun.yan.PropertyBinder(){
public Creator bind(Class component_type, Object key, Class type){
return Components.useType(type);
}
});
}
/**
* resolve java bean properties by display name instead of the default "byType"
* policy.
*
* @param cc the component.
* @return the new Component.
*/
public static Component byDisplayName(final Component cc){
return resolveBy(cc, new FromProperty(){
public Object fromProperty(Class c, PropertyDescriptor prop){
return prop.getDisplayName();
}
});
}
/**
* resolve java bean properties by fully qualified name
* (class_name.property_name) instead of the default "byType"
* policy.
*
* @param cc the component.
* @return the new Component.
*/
public static Component byQualifiedName(final Component cc){
return resolveBy(cc, new FromProperty(){
public Object fromProperty(Class c, PropertyDescriptor prop){
return jfun.util.Misc.getTypeName(c)+'.'+prop.getName();
}
});
}
/**
* Create a Component that makes the parameters mandatory properties
* and all java bean setters optional.
* <br>
* For a class whose constructor has two parameters,
* the two parameters will be made two mandatory properties under the provided
* property names; all the java bean setters will be made optional properties.
* <br>
* This way, mandatory properties can be expressed as parameters and optional
* properties can be represented as bean properties.
* Yet, configuration consistently uses named properties, avoiding confusion
* caused by position based parameters.
* @param cc the component that expects some mandatory parameters
* (such as a constructor or a static factory method)
* @param mandatory_params the property names to name the parameters.
* @return the new Component object that expects mandatory properties for the
* parameters and optioinal properties for the java bean properties.
* @throws IntrospectionException
* When Java Bean Introspection fails.
*/
public static Component beanComponent(final Component cc,
final String[] mandatory_params)
throws IntrospectionException{
if(mandatory_params==null || mandatory_params.length==0)
return cc.bean().optionalProperties();
final HashMap params = new HashMap();
for(int i=0;i<mandatory_params.length;i++){
final String name = mandatory_params[i];
params.put(name, name);
}
return cc.fromProperties(mandatory_params).bean()
.bindProperties(new PropertyBinder(){
public Creator bind(Class component_type, Object key, Class type){
final Component p = Components.useProperty(component_type, key, type);
if(params.containsKey(key)){
return p;
}
else return p.optional();
}
});
}
}