package org.openlca.ilcd.util;
import java.lang.reflect.Field;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Deque;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.datatype.XMLGregorianCalendar;
import org.openlca.ilcd.commons.IDataSet;
import org.openlca.ilcd.commons.LangString;
import org.openlca.ilcd.commons.Ref;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class RefTree {
public final Node root;
public static class Node {
public String field;
public Ref ref;
public List<Node> childs = new ArrayList<>();
}
public RefTree() {
root = new Node();
}
public List<Ref> getRefs() {
Deque<Node> next = new ArrayDeque<>();
next.add(root);
HashSet<String> handled = new HashSet<>();
List<Ref> list = new ArrayList<>();
while (!next.isEmpty()) {
Node node = next.poll();
if (node.ref != null) {
boolean add = handled.add(key(node.ref));
if (add)
list.add(node.ref);
}
next.addAll(node.childs);
}
return list;
}
// we use this key function and not the equals-function of Ref because
// we want also collect the same reference in different versions.
private String key(Ref ref) {
if (ref == null)
return "/";
return "/" + ref.type + "/" + ref.uuid + "/" + ref.version;
}
public static RefTree create(IDataSet ds) {
RefTree tree = new RefTree();
if (ds == null) {
tree.root.field = "none";
return tree;
}
tree.root.field = ds.getClass().getSimpleName();
tree.root.childs.addAll(fetchChilds(ds));
return tree;
}
private static List<Node> fetchChilds(Object obj) {
if (obj == null)
return Collections.emptyList();
List<Node> nodes = new ArrayList<>();
try {
for (Field field : obj.getClass().getDeclaredFields()) {
if (!follow(field.getType()))
continue;
field.setAccessible(true);
Object val = field.get(obj);
if (val == null)
continue;
if (val instanceof Ref) {
addNode(field, (Ref) val, nodes);
continue;
}
if (val instanceof Collection) {
Collection<?> c = (Collection<?>) val;
followCollection(field, c, nodes);
continue;
}
if (val instanceof Map) {
Map<?, ?> m = (Map<?, ?>) val;
followCollection(field, m.values(), nodes);
continue;
}
if (Object[].class.isAssignableFrom(val.getClass())) {
Object[] array = (Object[]) val;
followCollection(field, Arrays.asList(array), nodes);
continue;
}
collectChilds(field, val, nodes);
}
} catch (Exception e) {
Logger log = LoggerFactory.getLogger(RefTree.class);
log.error("failed to create RefTree", e);
}
return nodes;
}
private static void followCollection(Field field, Collection<?> c,
List<Node> nodes) {
for (Object elem : c) {
if (!follow(elem.getClass()))
break;
if (elem instanceof Ref) {
addNode(field, (Ref) elem, nodes);
continue;
}
collectChilds(field, elem, nodes);
}
}
private static void addNode(Field field, Ref ref, List<Node> nodes) {
Node node = node(field);
node.ref = ref;
nodes.add(node);
}
private static void collectChilds(Field field, Object val,
List<Node> nodes) {
List<Node> childs = fetchChilds(val);
if (childs.isEmpty())
return;
Node node = node(field);
node.childs.addAll(childs);
nodes.add(node);
}
private static Node node(Field field) {
Node n = new Node();
n.field = field.getName();
if (!field.isAnnotationPresent(XmlElement.class))
return n;
XmlElement xe = field.getAnnotation(XmlElement.class);
if (xe == null || xe.name().equals("##default"))
return n;
n.field = xe.name();
return n;
}
private static boolean follow(Class<?> clazz) {
if (clazz == null)
return false;
if (!Object.class.isAssignableFrom(clazz))
return false;
if (Boolean.class.isAssignableFrom(clazz))
return false;
if (Number.class.isAssignableFrom(clazz))
return false;
if (String.class.isAssignableFrom(clazz))
return false;
if (Character.class.isAssignableFrom(clazz))
return false;
if (LangString.class.isAssignableFrom(clazz))
return false;
if (Enum.class.isAssignableFrom(clazz))
return false;
if (XMLGregorianCalendar.class.isAssignableFrom(clazz))
return false;
else
return true;
}
}