package org.activityinfo.model.formTree;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.activityinfo.model.form.FormClass;
import org.activityinfo.model.form.FormField;
import org.activityinfo.model.resource.ResourceId;
import org.activityinfo.model.type.FieldType;
import org.activityinfo.model.type.FieldTypeClass;
import org.activityinfo.model.type.ReferenceType;
import java.util.*;
/**
* Contains a tree of fields based on references to other {@code FormClasses}
*/
public class FormTree {
public class Node {
private Node parent;
private FormField field;
private FieldPath path;
private FormClass formClass;
private List<Node> children = Lists.newArrayList();
private int depth;
public boolean isRoot() {
return parent == null;
}
public boolean isReference() {
return field.getType() instanceof ReferenceType;
}
public Node addChild(FormClass declaringClass, FormField field) {
assert isReference() : "only reference fields can have children";
FormTree.Node childNode = new FormTree.Node();
childNode.parent = this;
childNode.field = field;
childNode.path = new FieldPath(this.path, field.getId());
childNode.formClass = declaringClass;
children.add(childNode);
nodeMap.put(childNode.path, childNode);
if (childNode.parent != null) {
childNode.depth = childNode.parent.depth + 1;
}
return childNode;
}
/**
*
* @return the fields that are defined on the classes in this Field's range.
*/
public List<Node> getChildren() {
return children;
}
public FieldPath getPath() {
return path;
}
public FormField getField() {
return field;
}
/**
*
* @return the form class which has defined this form
*/
public FormClass getDefiningFormClass() {
return formClass;
}
public ResourceId getFieldId() {
return field.getId();
}
/**
*
* @return for Reference fields, the range of this field
*/
public Set<ResourceId> getRange() {
if(field.getType() instanceof ReferenceType) {
return ((ReferenceType) field.getType()).getRange();
} else {
return Collections.emptySet();
}
}
public FieldType getType() {
return field.getType();
}
public FieldTypeClass getTypeClass() {
return field.getType().getTypeClass();
}
public Node getParent() {
return parent;
}
/**
*
* @return a readable path for this node for debugging
*/
public String debugPath() {
StringBuilder path = new StringBuilder();
path.append(toString(this.getField().getLabel(), this.getDefiningFormClass()));
Node parent = this.parent;
while(parent != null) {
path.insert(0, toString(parent.getField().getLabel(), parent.getDefiningFormClass()) + ".");
parent = parent.parent;
}
return path.toString();
}
@Override
public String toString() {
return toString(field.getLabel(), this.getDefiningFormClass()) + ":" + field.getType();
}
private String toString(String label, FormClass definingFormClass) {
String field = "[";
if(definingFormClass != null && definingFormClass.getLabel() != null) {
field += definingFormClass.getLabel() + ":";
}
field += label;
field += "]";
return field;
}
public Node findDescendant(FieldPath relativePath) {
FieldPath path = new FieldPath(getPath(), relativePath);
return findDescendantByAbsolutePath(path);
}
private Node findDescendantByAbsolutePath(FieldPath path) {
if(this.path.equals(path)) {
return this;
} else {
for(Node child : children) {
Node descendant = child.findDescendantByAbsolutePath(path);
if(descendant != null) {
return descendant;
}
}
return null;
}
}
public boolean isLeaf() {
return children.isEmpty();
}
public int getDepth() {
return depth;
}
}
public enum SearchOrder {
DEPTH_FIRST,
BREADTH_FIRST
}
private List<Node> rootFields = Lists.newArrayList();
private Map<FieldPath, Node> nodeMap = Maps.newHashMap();
public FormTree() {
}
public Node addRootField(FormClass declaringClass, FormField field) {
Node node = new Node();
node.formClass = declaringClass;
node.field = field;
node.path = new FieldPath(field.getId());
rootFields.add(node);
nodeMap.put(node.path, node);
return node;
}
public List<Node> getRootFields() {
return rootFields;
}
public List<FieldPath> getRootPaths() {
List<FieldPath> paths = Lists.newArrayList();
for (Node node : rootFields) {
paths.add(node.getPath());
}
return paths;
}
public Map<ResourceId, FormClass> getRootFormClasses() {
Map<ResourceId, FormClass> map = Maps.newHashMap();
for(Node node : rootFields) {
map.put(node.getDefiningFormClass().getId(), node.getDefiningFormClass());
}
return map;
}
public Node getNodeByPath(FieldPath path) {
Node node = nodeMap.get(path);
if (node == null) {
throw new IllegalArgumentException();
}
return node;
}
public Node getRootField(ResourceId fieldId) {
return nodeMap.get(new FieldPath(fieldId));
}
private void findLeaves(List<Node> leaves, Iterable<Node> children) {
for(Node child : children) {
if(child.isLeaf()) {
leaves.add(child);
} else {
findLeaves(leaves, child.getChildren());
}
}
}
public List<Node> getLeaves() {
List<Node> leaves = Lists.newArrayList();
findLeaves(leaves, rootFields);
return leaves;
}
public List<FieldPath> search(SearchOrder order, Predicate<? super Node> descendPredicate,
Predicate<? super Node> matchPredicate) {
List<FieldPath> paths = Lists.newArrayList();
search(paths, rootFields, order, descendPredicate, matchPredicate);
return paths;
}
private void search(List<FieldPath> paths,
Iterable<Node> childNodes,
SearchOrder searchOrder,
Predicate<? super Node> descendPredicate,
Predicate<? super Node> matchPredicate) {
for(Node child : childNodes) {
if (searchOrder == SearchOrder.BREADTH_FIRST && matchPredicate.apply(child)) {
paths.add(child.path);
}
if(!child.getChildren().isEmpty() & descendPredicate.apply(child)) {
search(paths, child.getChildren(), searchOrder, descendPredicate, matchPredicate);
}
if (searchOrder == SearchOrder.DEPTH_FIRST && matchPredicate.apply(child)) {
paths.add(child.path);
}
}
}
public static Predicate<Node> isDataTypeProperty() {
return new Predicate<FormTree.Node>() {
@Override
public boolean apply(Node input) {
return input.field.getType() instanceof ReferenceType;
}
};
}
public static Predicate<Node> isReference() {
return Predicates.not(isDataTypeProperty());
}
public static Predicate<Node> pathIn(final Collection<FieldPath> paths) {
return new Predicate<FormTree.Node>() {
@Override
public boolean apply(Node input) {
return paths.contains(input.path);
}
};
}
public static Predicate<Node> pathNotIn(final Collection<FieldPath> paths) {
return Predicates.not(pathIn(paths));
}
@Override
public String toString() {
StringBuilder s = new StringBuilder();
for(Node node : getLeaves()) {
s.append(node.debugPath()).append("\n");
}
return s.toString();
}
}