//Dstl (c) Crown Copyright 2017
package uk.gov.dstl.baleen.uima.utils.select;
import static java.util.stream.Collectors.toList;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import org.apache.commons.lang.Validate;
import uk.gov.dstl.baleen.uima.utils.SelectorPart;
/**
* Abstract node implementation
*
*/
public abstract class AbstractNode<T> implements Node<T> {
/** The item */
protected final T item;
/** the parent of this node */
private final AbstractNode<T> parent;
/** the children of this node */
private final List<AbstractNode<T>> children = new ArrayList<>();
/**
* Constructor for the Structure Node.
*
* @param parent the parent node
* @param annotation the structure
*/
protected AbstractNode(AbstractNode<T> parent, T annotation) {
this.parent = parent;
this.item = annotation;
}
/**
* Add child node, to be used during construction of the hierarchy
*
* @param node the child node
*/
protected void addChild(AbstractNode<T> node) {
children.add(node);
}
@Override
public Nodes<T> getChildren() {
return new Nodes<>(children);
}
@Override
public Nodes<T> getChildren(Class<?> type) {
return new Nodes<>(children.stream().filter(c -> type.equals(c.getType())).collect(toList()));
}
@Override
public T getItem() {
return item;
}
@Override
public AbstractNode<T> getParent() {
return parent;
}
@Override
@SuppressWarnings("unchecked")
public Class<? extends T> getType() {
if (item == null) {
return null;
} else {
return (Class<? extends T>) item.getClass();
}
}
@Override
public String getTypeName() {
if (item == null) {
return "Root";
} else {
return getType().getSimpleName();
}
}
@Override
public List<SelectorPart> toPath() {
LinkedList<SelectorPart> path = new LinkedList<>();
toPath(path, Object.class);
return path;
}
@Override
public List<SelectorPart> toPath(Class<? extends T> type) {
LinkedList<SelectorPart> path = new LinkedList<>();
toPath(path, type);
return path;
}
/**
* Internal method to build the path of the annotation
*
* @param path
*/
private void toPath(LinkedList<SelectorPart> path, Class<?> filter) {
if (item != null) {
int index = getTypeIndex() + 1;
if (filter.isInstance(item)) {
path.addFirst(new SelectorPart(item.getClass(), index));
}
if (hasParent()) {
parent.toPath(path, filter);
}
}
}
@Override
public int getTypeIndex() {
if (!hasParent()) {
return 0;
}
Nodes<T> siblings = parent.getChildren(getType());
return siblings.indexOf(this);
}
@Override
public int getSiblingIndex() {
if (!hasParent()) {
return 0;
}
Nodes<T> siblings = parent.getChildren();
return siblings.indexOf(this);
}
@Override
public boolean hasParent() {
return parent != null;
}
@Override
public String toString() {
return "<" + getTypeName() + ">" + text();
}
@Override
public Nodes<T> select(String query) {
return Selector.select(query, this);
}
@Override
public String attr(String attributeKey) {
return attributes().getOrDefault(attributeKey, "");
}
@Override
public boolean hasText() {
return !text().isEmpty();
}
@Override
public boolean is(Evaluator<T> eval) {
return eval.matches(this.root(), this);
}
/**
* Get this node's root node; that is, its topmost ancestor. If this node is the top ancestor,
* returns {@code this}.
*
* @return topmost ancestor.
*/
private AbstractNode<T> root() {
AbstractNode<T> node = this;
while (node.parent != null) {
node = node.parent;
}
return node;
}
@Override
public AbstractNode<T> nextSibling() {
if (parent == null) {
return null;
}
Nodes<T> siblings = parent.getChildren();
Integer index = indexInList(this, siblings);
Validate.notNull(index);
if (siblings.size() > index + 1) {
return (AbstractNode<T>) siblings.get(index + 1);
} else {
return null;
}
}
@Override
public AbstractNode<T> previousSibling() {
if (parent == null) {
return null;
}
Nodes<T> siblings = parent.getChildren();
Integer index = indexInList(this, siblings);
Validate.notNull(index);
if (index > 0) {
return (AbstractNode<T>) siblings.get(index - 1);
} else {
return null;
}
}
@Override
public Nodes<T> getParents() {
Nodes<T> parents = new Nodes<>();
AbstractNode<T> node = this;
while (node.hasParent()) {
node = node.getParent();
parents.add(node);
}
return parents;
}
@Override
public boolean hasAttr(String attributeKey) {
return attributes().containsKey(attributeKey.toLowerCase());
}
@Override
public Nodes<T> getSiblings() {
if (parent == null) {
return new Nodes<>(0);
}
Nodes<T> elements = parent.getChildren();
Nodes<T> siblings = new Nodes<>(elements.size() - 1);
for (Node<T> el : elements) {
if (el != this) {
siblings.add(el);
}
}
return siblings;
}
/**
* Get the index of the search node in the given list.
* <p>
* Search by identity
*
* @param search the node to search for
* @param nodes the node to search in
* @return the index
*/
private Integer indexInList(Node<T> search, List<? extends Node<T>> nodes) {
Validate.notNull(search);
Validate.notNull(nodes);
for (int i = 0; i < nodes.size(); i++) {
Node<T> node = nodes.get(i);
if (node == search) {
return i;
}
}
return null;
}
@Override
public Nodes<T> getAllNodes() {
return Collector.collect(new Evaluator.AllNodes<>(), this);
}
@Override
public boolean hasClass(String className) {
return getClasses().contains(className.toLowerCase());
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (item == null ? 0 : item.hashCode());
return result;
}
@Override
@SuppressWarnings("rawtypes")
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
AbstractNode<?> other = (AbstractNode) obj;
if (item == null) {
if (other.item != null) {
return false;
}
} else if (!item.equals(other.item)) {
return false;
}
return true;
}
}