package com.laytonsmith.persistence;
import com.laytonsmith.PureUtilities.Pair;
import com.laytonsmith.core.GenericTreeNode;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* This class represents a data source model. The underlying model is just a map
* of values or maps, but this class abstracts accessing and storing the data.
* If a value in a model can be more efficiently lazily loaded, this class
* perhaps should not be used, but for non event driven models, it should
* suffice for many cases. If a data source inherently has a better way to store
* the data, then it may choose to not use this class. Note: Not all data
* sources can store a key in both namespace.value and namespace.value.other, in
* that case, to make namespace.value's actual value, it should be stored as
* namespace.value._
*
*/
public final class DataSourceModel {
private final GenericTreeNode<Pair<String, String>> tree = new GenericTreeNode<>();
public DataSourceModel(Map<String, Object> model) {
//We have to do a depth first traversal here to get all the keys
if (model != null) {
build(model, tree);
}
}
/**
* This constructor assumes that the key is fully specified in dot notation.
*
* @param list
*/
public DataSourceModel(List<Pair<String, String>> list) {
for (Pair<String, String> pair : list) {
String[] key = pair.getKey().split("\\.");
set(key, pair.getValue());
}
}
private void build(Object node, GenericTreeNode<Pair<String, String>> treeNode) {
if (node instanceof Map) {
//We need to iterate through all the keys, creating children as we go
for (String key : ((Map<String, Object>) node).keySet()) {
if (key.equals("_")) {
//Special case, this is a reserved key
build(((Map<String, Object>) node).get(key), treeNode);
} else {
GenericTreeNode<Pair<String, String>> newNode =
new GenericTreeNode<>(new Pair<String, String>(key, null));
treeNode.addChild(newNode);
build(((Map<String, Object>) node).get(key), newNode);
}
}
} else {
//This is the node we want to put the data in
treeNode.data.setValue(node==null?null:node.toString());
}
}
public Map<String, Object> toMap() {
Map<String, Object> map = new HashMap<>();
for (GenericTreeNode<Pair<String, String>> child : tree.getChildren()) {
decompose(map, child);
}
return map;
}
public List<Pair<String[], String>> toList() {
List<Pair<String[], String>> list = new ArrayList<>();
//TODO
return list;
}
private void decompose(Map<String, Object> node, GenericTreeNode<Pair<String, String>> treeNode) {
if (treeNode.hasChildren()) {
//If it's not a leaf node, we need to add a new child to the map.
//However, if the data isn't null, we need to add the data now as a _ key
Map<String, Object> map = new HashMap<>();
if (treeNode.getData().getValue() != null) {
map.put("_", treeNode.getData().getValue());
}
node.put(treeNode.getData().getKey(), map);
for (GenericTreeNode<Pair<String, String>> child : treeNode.getChildren()) {
decompose(map, child);
}
} else {
//It's a leaf node, so we just put the data in the map and call it a day
node.put(treeNode.getData().getKey(), treeNode.getData().getValue());
}
}
public String get(String[] key) {
return getValue(new ArrayList<>(Arrays.asList(key)), tree);
}
public void set(String[] key, String value) {
setValue(new ArrayList<>(Arrays.asList(key)), tree, value);
}
public void clearKey(String[] key) {
set(key, null);
}
private String getValue(List<String> keys, GenericTreeNode<Pair<String, String>> treeNode) {
String value = null;
if (!keys.isEmpty()) {
String key = keys.get(0);
keys.remove(0);
for (GenericTreeNode<Pair<String, String>> child : treeNode.getChildren()) {
if (child.getData().getKey().equals(key)) {
return getValue(keys, child);
}
}
} else {
value = treeNode.getData().getValue();
}
return value;
}
private void setValue(List<String> keys, GenericTreeNode<Pair<String, String>> treeNode, String value) {
if (keys.isEmpty()) {
treeNode.getData().setValue(value);
} else {
GenericTreeNode<Pair<String, String>> found = null;
String key = keys.get(0);
keys.remove(0);
for (GenericTreeNode<Pair<String, String>> child : treeNode.getChildren()) {
if (child.getData().getKey().equals(key)) {
found = child;
break;
}
}
if (found == null) {
found = new GenericTreeNode<>(new Pair<String, String>(key, null));
treeNode.addChild(found);
}
if (value == null) {
if (keys.isEmpty()) {
//We need to remove this node.
//TODO: This fails to remove the parent
for (int i = 0; i < treeNode.getNumberOfChildren(); i++) {
GenericTreeNode<Pair<String, String>> node = treeNode.getChildAt(i);
if (node.getData().getKey().equals(key)) {
treeNode.removeChildAt(i);
break;
}
}
return;
}
}
setValue(keys, found, value);
}
}
public Set<String[]> keySet() {
Set<String[]> keys = new HashSet<>();
for (GenericTreeNode child : tree.getChildren()) {
traverse(child, new ArrayList<String>(), keys);
}
return keys;
}
private void traverse(GenericTreeNode<Pair<String, String>> treeNode, List<String> ongoingKey, Set<String[]> keys) {
if (treeNode.hasChildren()) {
ongoingKey.add(treeNode.getData().getKey());
if (treeNode.getData().getValue() != null) {
//Data and children
keys.add(ongoingKey.toArray(new String[ongoingKey.size()]));
}
for (GenericTreeNode<Pair<String, String>> child : treeNode.getChildren()) {
//recurse down now
traverse(child, ongoingKey, keys);
}
ongoingKey.remove(ongoingKey.size() - 1);
} else {
//This is it, we're done here, so we can put the key in the list now, then pop off the last element in the ongoingKey
ongoingKey.add(treeNode.getData().getKey());
keys.add(ongoingKey.toArray(new String[ongoingKey.size()]));
ongoingKey.remove(ongoingKey.size() - 1);
}
}
}