/*
* Copyright (c) [2016] [ <ether.camp> ]
* This file is part of the ethereumJ library.
*
* The ethereumJ library is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* The ethereumJ library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with the ethereumJ library. If not, see <http://www.gnu.org/licenses/>.
*/
package org.ethereum.net.swarm;
import java.util.*;
/**
* Trie (or prefix tree) structure, which stores Nodes with String keys in highly branched structure
* for quick search by string prefixes.
* E.g. if there is a single node 'aaa' and the node 'aab' is added then the tree will look like:
* aa
* / \
* a b
* Where leaf nodes correspond to added elements
*
* @param <P> Subclass of TrieNode
*/
public abstract class StringTrie<P extends StringTrie.TrieNode<P>> {
/**
* Tree Node
*/
public static abstract class TrieNode<N extends TrieNode<N>> {
N parent;
Map<String, N> children = new LinkedHashMap<>();
String path; // path relative to parent
/**
* Create a root node (null parent and empty path)
*/
protected TrieNode() {
this(null, "");
}
public TrieNode(N parent, String relPath) {
this.parent = parent;
this.path = relPath;
}
public String getRelativePath() {
return path;
}
/**
* Calculates absolute path relative to the root node
*/
public String getAbsolutePath() {
return (parent != null ? parent.getAbsolutePath() : "") + path;
}
public N getParent() {
return parent;
}
/**
* Finds the descendant which has longest common path prefix with the passed path
*/
N getMostSuitableChild(String relPath) {
N n = loadChildren().get(getKey(relPath));
if (n == null) return (N) this;
if (relPath.startsWith(n.getRelativePath())) {
return n.getMostSuitableChild(relPath.substring(n.getRelativePath().length()));
} else {
return (N) this;
}
}
/**
* @return the direct child which has the key the same as the relPath key
* null if no such child
*/
N getChild(String relPath) {
return loadChildren().get(getKey(relPath));
}
/**
* Add a new descendant with specified path
* @param relPath the path relative to this node
* @return added node wich path is [relPath] relative to this node
*/
N add(String relPath) {
return addChild(relPath);
}
N addChild(String relPath) {
N child = getChild(relPath);
if (child == null) {
N newNode = createNode((N) this, relPath);
putChild(newNode);
return newNode;
} else {
if (!child.isLeaf() && relPath.startsWith(child.getRelativePath())) {
return child.addChild(relPath.substring(child.getRelativePath().length()));
}
if (child.isLeaf() && relPath.equals(child.getRelativePath())) {
return child;
}
String commonPrefix = Util.getCommonPrefix(relPath, child.getRelativePath());
N newSubRoot = createNode((N) this, commonPrefix);
child.path = child.path.substring(commonPrefix.length());
child.parent = newSubRoot;
N newNode = createNode(newSubRoot, relPath.substring(commonPrefix.length()));
newSubRoot.putChild(child);
newSubRoot.putChild(newNode);
this.putChild(newSubRoot);
newSubRoot.nodeChanged();
this.nodeChanged();
child.nodeChanged();
return newNode;
}
}
/**
* Deletes current leaf node, rebalancing the tree if needed
* @throws RuntimeException if this node is not leaf
*/
void delete() {
if (!isLeaf()) throw new RuntimeException("Can't delete non-leaf entry: " + this);
N parent = getParent();
parent.loadChildren().remove(getKey(getRelativePath()));
if (parent.loadChildren().size() == 1 && parent.parent != null) {
Map<String, N> c = parent.loadChildren();
N singleChild = c.values().iterator().next();
singleChild.path = parent.path + singleChild.path;
singleChild.parent = parent.parent;
parent.parent.loadChildren().remove(getKey(parent.path));
parent.parent.putChild(singleChild);
parent.parent.nodeChanged();
singleChild.nodeChanged();
}
}
void putChild(N n) {
loadChildren().put(getKey(n.path), n);
}
/**
* Returns the children if any. Doesn't cause any lazy loading
*/
public Collection<N> getChildren() {
return children.values();
}
/**
* Returns the children after loading (if required)
*/
protected Map<String, N> loadChildren() {
return children;
}
public boolean isLeaf() {
return loadChildren().isEmpty();
}
/**
* Calculates the key for the string prefix.
* The longer the key the more children remain on the same tree level
* I.e. if the key is the first character of the path (e.g. with ASCII only chars),
* the max number of children in a single node is 128.
* @return Key corresponding to this path
*/
protected String getKey(String path) {
return path.length() > 0 ? path.substring(0,1) : "";
}
/**
* Subclass should create the instance of its own class
* normally the implementation should invoke TrieNode(parent, path) superconstructor.
*/
protected abstract N createNode(N parent, String path);
/**
* The node is notified on changes (either its path or direct children changed)
*/
protected void nodeChanged() {}
}
P rootNode;
public StringTrie(P rootNode) {
this.rootNode = rootNode;
}
public P get(String path) {
return rootNode.getMostSuitableChild(path);
}
public P add(String path) {
return add(rootNode, path);
}
public P add(P parent, String path) {
return parent.addChild(path);
}
public P delete(String path) {
P p = get(path);
if (path.equals(p.getAbsolutePath()) && p.isLeaf()) {
p.delete();
return p;
} else {
return null;
}
}
/**
* @return Pre-order walk tree elements iterator
*/
// public Iterator<P> iterator() {
// return new Iterator<P>() {
// P curNode = rootNode;
// Stack<Iterator<P>> childIndices = new Stack<>();
//
// @Override
// public boolean hasNext() {
// return curNode != null;
// }
//
// @Override
// public P next() {
// P ret = curNode;
// if (!curNode.getChildren().isEmpty()) {
// Iterator<P> it = curNode.getChildren().iterator();
// childIndices.push(it);
// curNode = it.next();
// } else {
// curNode = null;
// while(curNode != null && !childIndices.isEmpty()) {
// Iterator<P> peek = childIndices.peek();
// if (peek.hasNext()) {
// curNode = peek.next();
// } else {
// childIndices.pop();
// }
// }
// }
// return ret;
// }
// };
// }
//
// /**
// * @return Pre-order walk tree non-leaf elements iterator
// */
// public Iterator<P> nonLeafIterator() {
// return new Iterator<P>() {
// Iterator<P> leafIt = iterator();
// P cur = findNext();
//
// @Override
// public boolean hasNext() {
// return cur != null;
// }
//
// @Override
// public P next() {
// P ret = cur;
// cur = findNext();
// return ret;
// }
//
// private P findNext() {
// P ret = null;
// while(leafIt.hasNext() && (ret = leafIt.next()).isLeaf());
// return ret;
// }
// };
// }
// protected abstract P createNode(P parent, String path);
//
// protected void nodeChanged(P node) {}
}