//Dstl (c) Crown Copyright 2017
package uk.gov.dstl.baleen.uima.utils.select;
import static java.util.stream.Collectors.toList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;
import uk.gov.dstl.baleen.uima.utils.SelectorPath;
/**
* A hierarchy of nodes with an index of items.
*/
public class ItemHierarchy<T> {
/** the root node of all structure, and as such has no structure itself */
private final Node<T> root;
/** the internal index of the items to the node that contains it */
private Map<T, Node<T>> index;
/**
* Constructor accepting the root node.
*
* @param root the root
*/
public ItemHierarchy(Node<T> root) {
this.root = root;
}
/**
* Get the root node of this hierarchy
*
* @return the root node
*/
public Node<T> getRoot() {
return root;
}
/**
* Get the sibling index of the given structure
*
* @param a the item
* @return 0 if no siblings or a 1 based index of position in siblings
*/
public int getSiblingIndex(T a) {
return getIndex().get(a).getSiblingIndex();
}
/**
* Get the children of the given structure
*
* @param a the item
* @return a list of the children
*/
public List<T> getChildren(T a) {
return getIndex().get(a).getChildren().stream().map(Node::getItem).collect(Collectors.toList());
}
/**
* Get the next item in the hierarchy, if it exists
*
* @param a the item
* @return optional of the next item
*/
public Optional<T> getNext(T a) {
Node<T> node = getIndex().get(a);
return getNext(node).map(Node::getItem);
}
/**
* Get the previous item in the hierarchy, if it exists
*
* @param a the item
* @return optional of the next item
*/
public Optional<T> getPrevious(T a) {
Node<T> node = getIndex().get(a);
return getPrevious(node).map(Node::getItem);
}
/**
* Internal get for the previous node
*
* @param node
* @return optional of the previous node
*/
private Optional<Node<T>> getPrevious(Node<T> node) {
Optional<Node<T>> parent = getParent(node);
if (parent.isPresent()) {
ListIterator<Node<T>> iterator = parent.get().getChildren().listIterator();
while (iterator.hasNext()) {
if (node.equals(iterator.next())) {
iterator.previous();
if (iterator.hasPrevious()) {
return Optional.of(iterator.previous());
} else {
return getPrevious(parent.get());
}
}
}
}
return Optional.empty();
}
/**
* Get the parent of the given item, if there is one.
*
* @param item the structure
* @return optional of the parent node
*/
public Optional<T> getParent(T item) {
Node<T> node = getIndex().get(item);
return getParent(node).map(Node::getItem);
}
/**
* Get the parent item of the given type.
*
* @param item the item
* @param type of the parent
* @return optional of the parent item
*/
public <U extends T> Optional<U> getParent(T item, Class<U> type) {
return getToType(item, type, this::getParent);
}
/**
* Get the next item of the given type.
*
* @param item the item
* @param type of the next
* @return optional of the next item
*/
public <U extends T> Optional<U> getNext(T item, Class<U> type) {
return getToType(item, type, this::getNext);
}
/**
* Get the previous item of the given type.
*
* @param item the item
* @param type of the previous
* @return optional of the previous item
*/
public <U extends T> Optional<U> getPrevious(T item, Class<U> type) {
return getToType(item, type, this::getPrevious);
}
/**
* Get the next item in the hierarchy using the given traversal function with item of the given
* type, if it exists.
*
* @param a the item
* @param type the type of the 'next' item
* @return optional of the 'next' item
*/
@SuppressWarnings("unchecked")
public <U extends T> Optional<U> getToType(T a, Class<U> type,
Function<Node<T>, Optional<Node<T>>> function) {
Node<T> node = getIndex().get(a);
Optional<Node<T>> current = function.apply(node);
while (current.isPresent() && type.isInstance(current.get().getItem())) {
current = function.apply(current.get());
}
return (Optional<U>) current.map(Node::getItem);
}
/**
* Internal get for the parent of the given node, if there is one.
*
* @param node the node
* @return optional of the parent
*/
private Optional<Node<T>> getParent(Node<T> node) {
return Optional.ofNullable(node.getParent());
}
/**
* Internal get for the previous node
*
* @param node
* @return optional of the previous node
*/
private Optional<Node<T>> getNext(Node<T> node) {
Optional<Node<T>> parent = getParent(node);
if (parent.isPresent()) {
Iterator<Node<T>> iterator = parent.get().getChildren().iterator();
while (iterator.hasNext()) {
if (node.equals(iterator.next())) {
if (iterator.hasNext()) {
return Optional.of(iterator.next());
} else {
return getNext(parent.get());
}
}
}
}
return Optional.empty();
}
/**
* Lazily create the index of the nodes.
*
* @return the index
*/
protected Map<T, Node<T>> getIndex() {
if (index == null) {
index = index(root);
}
return index;
}
/**
* Internal method to create the index map
*
* @param root the root node
* @return the index
*/
private Map<T, Node<T>> index(Node<T> root) {
Map<T, Node<T>> indexing = new HashMap<>();
index(indexing, root);
return indexing;
}
/**
* Internal recursive method to build the index
*
* @param indexing the index being built
* @param node the current node
*/
private void index(Map<T, Node<T>> indexing, Node<T> node) {
for (Node<T> child : node.getChildren()) {
indexing.put(child.getItem(), child);
index(indexing, child);
}
}
/**
* Get the selector path of the given item
*
* @param a the item
* @return the selector path in this hierarchy for the given item
*/
public SelectorPath getSelectorPath(T a) {
Node<T> node = getIndex().get(a);
if (node == null) {
return null;
}
return new SelectorPath(node.toPath());
}
/**
* Get the selector path of the given item
*
* @param a the item
* @return the selector path in this hierarchy for the given item
*/
public SelectorPath getSelectorPath(T a, Class<? extends T> type) {
Node<T> node = getIndex().get(a);
if (node == null) {
return null;
}
return new SelectorPath(node.toPath(type));
}
/**
* The path for the given item
* <p>
* That is a list of the items from the root to the given item following the parent child
* relation.
*
* @param s the item
* @return the path
*/
public List<T> getPath(T s) {
LinkedList<T> path = new LinkedList<>();
Node<T> parent = getIndex().get(s);
do {
path.addFirst(parent.getItem());
parent = parent.getParent();
} while (parent.hasParent());
return path;
}
/**
* The path for the given item filter to the given item types
* <p>
* That is a list of the items from the root to the given item following the parent child
* relation.
*
* @param s the item
* @param type the type to filter to
* @return the path
*/
public List<T> getPath(T s, Class<? extends T> type) {
return getPath(s).stream().filter(type::isInstance).collect(toList());
}
/**
* Find elements that match the {@link Selector} CSS style query, with the root as the starting
* context.
* <p>
* See the query syntax documentation in {@link Selector}.
*
* @param query a {@link Selector} CSS-like query
* @return nodes that match the query (empty if none match)
* @see Selector
*/
public Nodes<T> select(String query) {
return getRoot().select(query);
}
}