package org.javabuilders.util;
import java.util.Map;
import java.util.Set;
import org.javabuilders.Builder;
import org.javabuilders.BuilderConfig;
import org.javabuilders.ChildrenCardinalityException;
import org.javabuilders.Node;
import org.javabuilders.TypeDefinition;
/**
* Util methods for validating children cardinality
* @author Jacek Furmankiewicz
*
*/
public class ChildrenCardinalityUtils {
/**
* Checks if the child cardinality is correct in the current node
* @param config Config
* @param current Current node
*/
public static void checkChildrenCardinality(BuilderConfig config, Node current) {
if (!current.getKey().equals(Builder.CONTENT)) {
Object obj = current.getMainObject();
Map<Class<?>,int[]> childrenCardinalities = TypeDefinition.getChildrenCardinality(config, obj.getClass());
//check that no unexpected types are found
checkChildrenForUnexpectedTypes(current, current.getChildNodes(), childrenCardinalities.keySet());
checkChildrenForUnexpectedTypes(current, current.getContentNodes(), childrenCardinalities.keySet());
//check that the expected types are of the correct cardinality
for(Class<?> type : childrenCardinalities.keySet()) {
int[] limits = childrenCardinalities.get(type);
//if 1 type defined only and can be only one instance, it should not be a list, but a single value
if (childrenCardinalities.size() == 1 && limits[1] == 1) {
//single value
//check that only no other types are there
Set<Node> set = current.getChildNodes();
if (set.size() < limits[0]) {
throw new ChildrenCardinalityException("At least {0} child of type {1} is required: {2}",limits[0],type, current.getProperties());
} else if (set.size() > limits[1]) {
throw new ChildrenCardinalityException("No more than {0} child of type {1} is allowed: {2}",limits[1],type, current.getProperties());
}
//make sure no list either
set = current.getContentNodes(Object.class);
if (set.size() > 0) {
throw new ChildrenCardinalityException("No child nodes are allowed: {0}", current.getProperties());
}
} else {
//make sure none as child nodes
Set<Node> set = current.getChildNodes(type);
if (set != null && set.size() > 0) {
for(Node node : set) {
//the Content node comes back, but we should ignore it
if (!node.getKey().equals(Builder.CONTENT)) {
throw new ChildrenCardinalityException("{0}: expected a list of type {1}, but found it as a child node instead.\n{1}",
current.getMainObject().getClass().getSimpleName(),
type, current.getProperties());
}
}
}
//list
set = current.getContentNodes(type);
if (set.size() < limits[0]) {
throw new ChildrenCardinalityException("At least {0} children of type {1} are required: {2}",limits[0],type, current.getProperties());
} else if (set.size() > limits[1]) {
if (limits[1] == 0 && Object.class.equals(type)) {
throw new ChildrenCardinalityException("No children are allowed under {0}: {1}", obj.getClass().getSimpleName(), current.getProperties());
} else {
throw new ChildrenCardinalityException("No more than {0} children of type {1} are allowed under {2}: {3}",limits[1],type,
obj.getClass().getSimpleName(), current.getProperties());
}
}
}
}
}
}
//checks if any unexpected types are present
private static void checkChildrenForUnexpectedTypes(Node current, Set<Node> nodes, Set<Class<?>> allowedTypes) {
if (nodes != null) {
for(Node node : nodes) {
if (!node.getKey().equals(Builder.CONTENT)) {
boolean assignable = false;
for(Class<?> type : allowedTypes) {
if (type.isAssignableFrom(node.getMainObject().getClass())) {
assignable = true;
break;
}
}
if (!assignable) {
throw new ChildrenCardinalityException("{0}: Found unexpected type {1}.\nNot in list of allowed types {2}.\n{3}",
current.getMainObject().getClass().getSimpleName(),
node.getMainObject().getClass().getSimpleName(),
allowedTypes, current.getProperties());
}
}
}
}
}
}