package org.javabuilders.handler.binding;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.javabuilders.BuildException;
import org.javabuilders.BuildProcess;
import org.javabuilders.Builder;
import org.javabuilders.BuilderConfig;
import org.javabuilders.NamedObjectProperty;
import org.javabuilders.Node;
import org.javabuilders.handler.AbstractTypeHandler;
import org.javabuilders.handler.ITypeChildrenHandler;
import org.javabuilders.util.BuilderUtils;
/**
* Abstract ancestor for binding support implementations
* @author Jacek Furmankiewicz
*
*/
public abstract class AbstractBuilderBindingsHandler extends
AbstractTypeHandler implements ITypeChildrenHandler {
public final static String READ = "read";
public final static String READ_ONCE = "readOnce";
public final static String READ_WRITE = "readWrite";
/* (non-Javadoc)
* @see org.javabuilders.handler.ITypeHandler#createNewInstance(org.javabuilders.BuilderConfig, org.javabuilders.BuildResult, org.javabuilders.Node, java.lang.String, java.util.Map)
*/
public Node createNewInstance(BuilderConfig config, BuildProcess result,
Node parent, String key, Map<String, Object> typeDefinition)
throws BuildException {
BuilderBindings instance = new BuilderBindings();
return useExistingInstance(config, result, parent, key, typeDefinition, instance);
}
/**
* Parses the binding definitions to return them in a format that can easily processed
* by domain-specific implementations
* @param node Current node
* @param process Current build process
* @return List of target/source mappings
*/
@SuppressWarnings("unchecked")
protected Map<NamedObjectProperty,BindingSourceDefinition> getBindingDefinitions(Node node, BuildProcess process) {
Map<NamedObjectProperty,BindingSourceDefinition> defs = new LinkedHashMap<NamedObjectProperty, BindingSourceDefinition>();
//process each definition
List<Object> content = (List<Object>)node.getProperties().get(Builder.CONTENT);
for(Object def : content) {
if (def instanceof Map) {
Map<String,Object> map = (Map<String,Object>)def;
for(String target : map.keySet()) {
//the expression can be a straight string or a List with two items
Object expression = map.get(target);
List<String> fullExpression = null;
if (expression instanceof String) {
//re-create it as if it were a List
fullExpression = new ArrayList<String>();
fullExpression.add((String)expression);
fullExpression.add(READ_WRITE); //the default binding update strategy
} else if (expression instanceof List) {
fullExpression = (List<String>)expression;
if (fullExpression.size() < 1 || fullExpression.size() > 2) {
throw new BuildException("Binding definition expression in invalid format: " + expression + ". Must be a list with two elements, first the expression, second the update strategy");
}
} else {
throw new BuildException("Binding definition expression in invalid format: " + expression);
}
String sourcePathText = fullExpression.get(0);
//check if source paths are valid
List<NamedObjectProperty> sources = BuilderUtils.getParsedPropertyExpression(sourcePathText);
Set<String> uniqueSourceNames = new HashSet<String>();
Object sourceObject = null;
String sourceObjectName = null;
for(NamedObjectProperty source : sources) {
sourceObjectName = source.getName();
sourceObject = process.getByName(sourceObjectName);
if (sourceObject == null) {
throw new BuildException("Binding source path expression does not refer to a known named object: " + source);
} else {
uniqueSourceNames.add(sourceObjectName);
}
}
//source path can only refer to properties from one object, if there are multiple ones
String nameFormat = "%s.";
if (uniqueSourceNames.size() == 1) {
sourcePathText = sourcePathText.replace(String.format(nameFormat,sourceObjectName),"");
} else {
throw new BuildException("Binding source path expression cannot refer to properties of more than 1 unique named object: " +
"{0}.\nRefers to multiple objects: {1}", sourcePathText, uniqueSourceNames);
}
//parse target expression
List<NamedObjectProperty> targets = BuilderUtils.getParsedPropertyExpression(target);
NamedObjectProperty targetProperty = null;
Object targetObject = null;
if (targets.size() == 1) {
targetProperty = targets.get(0);
targetObject = process.getByName(targetProperty.getName());
if (targetObject == null) {
throw new BuildException("Binding target propertyName does not refer to a known named object: " + targetProperty);
}
} else {
throw new BuildException("Unable to parse target binding expresson. It must be in single 'objectName.propertyName' format. Invalid format: " + target);
}
//create the binding source definition
BindingSourceDefinition sourceDef = new BindingSourceDefinition(sourceObject,
sourcePathText, fullExpression.get(1));
defs.put(targetProperty, sourceDef);
}
} else {
throw new BuildException("Binding definition in invalid format: " + def);
}
}
return defs;
}
/* (non-Javadoc)
* @see org.javabuilders.IApplicable#getApplicableClass()
*/
public final Class<?> getApplicableClass() {
return BuilderBindings.class;
}
}