package org.jboss.processFlow.console.binding;
import java.beans.BeanInfo;
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.List;
import java.util.Map;
import org.apache.commons.collections.FactoryUtils;
import org.apache.commons.collections.ListUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.collections.TransformerUtils;
/**
* The abstrace dataBinder that apply type conversion for primmitive types and data binding for the complex types
*
* @author tanxu
* @date Feb 16, 2012
* @since
*/
public abstract class AbstractDataBinder implements IDataBinder {
static HashMap<String, Class> primitiveTypes = new HashMap<String, Class>();
protected String taskName;
protected BindingContext bindingContext;
static {
primitiveTypes.put("integer", Integer.class);
primitiveTypes.put("long", Long.class);
primitiveTypes.put("string", String.class);
primitiveTypes.put("int", Integer.class);
}
public AbstractDataBinder() {
}
@Override
public void setTaskName(String taskName) {
this.taskName = taskName;
}
@Override
public void setBindingContext(BindingContext bindingContext) {
this.bindingContext = bindingContext;
}
@Override
public void bind(Map<String, Object> sourceContext) throws Exception {
BeanDefinition beanDefinition = bindingContext.getBeanDefinition(taskName);
if (beanDefinition == null)
return;
List<PropertyDefinition> propDefList = beanDefinition.getPropertyDefinitions();
for (PropertyDefinition propDef : propDefList) {
if (isPrimitiveType(propDef.getValueType())) {
// just do the type conversion for primitive type entryies
Object newValue = doConvert(getType(propDef.getValueType()), sourceContext.get(propDef.getProperty()));
// update the value
sourceContext.put(propDef.getProperty(), newValue);
}
else {
Map<String, Object> target = new HashMap<String, Object>();
if (isList(propDef.getValueType())) {
Class type = getGenericTypeOfList(propDef.getValueType());
// construct a lazy list that will add non-existing elements when it indexed
// since normal data binding framework won't create the new element for the target list
List obj = ListUtils.lazyList(new ArrayList(), FactoryUtils.instantiateFactory(type));
target.put(propDef.getProperty(), obj);
}
else if (isMap(propDef.getValueType())) {
// construct a lazy map that will add non-existing elements when it indexed
// since normal data binding framework won't create the new entry for the target map
// XXX need pay attention to the key type and value type?
Map obj = MapUtils.lazyMap(new HashMap(), TransformerUtils.stringValueTransformer());
target.put(propDef.getProperty(), obj);
}
Object targetObj = renderTargetIfNeeded(target, sourceContext);
Map<String, Object> context = renderContextIfNeeded(targetObj, sourceContext);
Map<String, Object> newValues = doBind(targetObj, context);
// update the values with new binded ones
sourceContext.putAll(newValues);
}
}
}
/**
* @param valueType
* @return
* @throws ClassNotFoundException
*/
private Class getGenericTypeOfList(String valueType) throws ClassNotFoundException {
int beginIdx = valueType.indexOf('<');
if (beginIdx <= 0)
return String.class;
int endIdx = valueType.indexOf('>');
if (endIdx <= 0)
throw new IllegalArgumentException("invalid value type, generic type should be closed" + valueType);
String className = valueType.substring(beginIdx + 1, endIdx);
return Class.forName(className);
}
/**
* @param valueType
* @return
*/
private boolean isMap(String valueType) {
return valueType.toLowerCase().startsWith("map");
}
/**
* @param valueType
* @return
*/
private boolean isList(String valueType) {
return valueType.toLowerCase().startsWith("list");
}
/**
* @param property
* @return
*/
private Class getType(String type) {
return primitiveTypes.get(type.toLowerCase());
}
/**
* @param type
* @return
*/
private boolean isPrimitiveType(String type) {
return primitiveTypes.containsKey(type);
}
/**
* @param property
* @return <code>true</code> if it's navigatiable property for the EL
*/
protected boolean isNavigatiableProperty(String property) {
int dotIdx = property.indexOf('.'); // normal property path
if (dotIdx >= 0)
return true;
int bracketIdx = property.indexOf('['); // indexed property path
return bracketIdx >= 0;
}
protected static final Object[] NULL_ARG = new Object[] {};
/**
* Get the bean properties as <code>java.uti.Map</code>
*
* @param bean
* @return
* @throws IntrospectionException
* @throws IllegalArgumentException
* @throws IllegalAccessException
* @throws InvocationTargetException
*/
protected Map getProperties(Object bean) throws IntrospectionException, IllegalArgumentException,
IllegalAccessException, InvocationTargetException {
Map<String, Object> properties = new HashMap<String, Object>();
BeanInfo bi = Introspector.getBeanInfo(bean.getClass());
PropertyDescriptor[] props = bi.getPropertyDescriptors();
for (int i = 0; i < props.length; i++) {
Method getter = props[i].getReadMethod();
if (getter == null)
continue;
String name = props[i].getName();
Object result = getter.invoke(bean, NULL_ARG);
properties.put(name, result);
}
return properties;
}
/**
* Convert the <code>source</code> to the <code>targetType</code>. <br/>
* It mainly used to convert string to java primitive type
*
* @param targetType
* @param source
* @return
*/
abstract protected Object doConvert(Class targetType, Object source);
/**
* Binding the values to complex object <code>target</code>.
*
* @param target
* @param sourceContext
* @throws Exception
*/
abstract protected Map<String, Object> doBind(Object target, Map<String, Object> sourceContext) throws Exception;
/**
* This is useful if you want to do pre-process on the <code>sourceContext</code>, for example, convert the
* key/propertyName, etc.
*
* @param target
* @param sourceContext
* @return
*/
abstract protected Map<String, Object> renderContextIfNeeded(Object target, Map<String, Object> sourceContext);
/**
* The is useful if you want to do pre-process on the <code>target</code> object, for example, wrap a map as a
* InternalMapBean for Spring since Spring data binding don't take a Map as a Bean
*
* @param target
* @param sourceContext
* @return
*/
abstract protected Object renderTargetIfNeeded(Object target, Map<String, Object> sourceContext);
}