com.yahoo.document.annotation.SpanList Maven / Gradle / Ivy
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.document.annotation;
import com.yahoo.document.serialization.SpanNodeReader;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
/**
* A node in a Span tree that can have child nodes.
*
* @author Einar M R Rosenvinge
*/
public class SpanList extends SpanNode {
public static final byte ID = 2;
private final List children;
private int cachedFrom = Integer.MIN_VALUE; //triggers calculateFrom()
private int cachedTo = Integer.MIN_VALUE; //triggers calculateTo()
/** Creates a new SpanList. */
public SpanList() {
this.children = new LinkedList();
}
public SpanList(SpanNodeReader reader) {
this();
reader.read(this);
}
protected SpanList(List children) {
this.children = children;
}
/**
* Deep-copies a SpanList.
*
* @param other the SpanList to copy.
*/
public SpanList(SpanList other) {
this.children = new LinkedList();
for (SpanNode otherNode : other.children) {
if (otherNode instanceof Span) {
children.add(new Span((Span) otherNode));
} else if (otherNode instanceof AlternateSpanList) {
children.add(new AlternateSpanList((AlternateSpanList) otherNode));
} else if (otherNode instanceof SpanList) {
children.add(new SpanList((SpanList) otherNode));
} else if (otherNode instanceof DummySpanNode) {
children.add(otherNode); //shouldn't really happen
} else {
throw new IllegalStateException("Cannot create copy of " + otherNode + " with class "
+ ((otherNode == null) ? "null" : otherNode.getClass()));
}
}
}
void checkValidity(SpanNode node, List childrenToCheck) {
if (!node.isValid()) {
throw new IllegalStateException("Cannot reuse SpanNode instance " + node + ", is INVALID.");
}
if (node.getParent() != null) {
if (node.getParent() != this) {
throw new IllegalStateException(node + " is already a child of " + node.getParent() + ", cannot be added to " + this);
} else if (node.getParent() == this && childrenToCheck.contains(node)) {
throw new IllegalStateException(node + " is already a child of " + this + ", cannot be added twice to the same parent node.");
}
}
}
/**
* Adds a child node to this SpanList.
*
* @param node the node to add.
* @return this, for call chaining
* @throws IllegalStateException if SpanNode.isValid() returns false.
*/
public SpanList add(SpanNode node) {
checkValidity(node, children());
node.setParent(this);
resetCachedFromAndTo();
children().add(node);
return this;
}
/** Create a span, add it to this list and return it */
public Span span(int from, int length) {
Span span=new Span(from,length);
add(span);
return span;
}
void setInvalid() {
//invalidate ourselves:
super.setInvalid();
//invalidate all our children:
for (SpanNode node : children()) {
node.setInvalid();
}
}
/**
* Moves a child of this SpanList to another SpanList.
*
* @param node the node to move
* @param target the SpanList to add the node to
* @throws IllegalArgumentException if the given node is not a child of this SpanList
*/
public void move(SpanNode node, SpanList target) {
boolean removed = children().remove(node);
if (removed) {
//we found the node
node.setParent(null);
resetCachedFromAndTo();
target.add(node);
} else {
throw new IllegalArgumentException("Node " + node + " is not a child of this SpanList, cannot move.");
}
}
/**
* Moves a child of this SpanList to another SpanList.
*
* @param nodeNum the index of the node to move
* @param target the SpanList to add the node to
* @throws IndexOutOfBoundsException if the given index is out of range
*/
public void move(int nodeNum, SpanList target) {
SpanNode node = children().remove(nodeNum);
if (node != null) {
//we found the node
node.setParent(null);
resetCachedFromAndTo();
target.add(node);
}
}
/**
* Moves a child of this SpanList to another SpanList.
*
* @param node the node to move
* @param target the SpanList to add the node to
* @param targetSubTree the index of the subtree of the given AlternateSpanList to add the node to
* @throws IllegalArgumentException if the given node is not a child of this SpanList
* @throws IndexOutOfBoundsException if the target subtree index is out of range
*/
public void move(SpanNode node, AlternateSpanList target, int targetSubTree) {
if (targetSubTree < 0 || targetSubTree >= target.getNumSubTrees()) {
throw new IndexOutOfBoundsException(target + " has no subtree at index " + targetSubTree);
}
boolean removed = children().remove(node);
if (removed) {
//we found the node
node.setParent(null);
resetCachedFromAndTo();
target.add(targetSubTree, node);
} else {
throw new IllegalArgumentException("Node " + node + " is not a child of this SpanList, cannot move.");
}
}
/**
* Moves a child of this SpanList to another SpanList.
*
* @param nodeNum the index of the node to move
* @param target the SpanList to add the node to
* @param targetSubTree the index of the subtree of the given AlternateSpanList to add the node to
* @throws IndexOutOfBoundsException if the given index is out of range, or the target subtree index is out of range
*/
public void move(int nodeNum, AlternateSpanList target, int targetSubTree) {
if (targetSubTree < 0 || targetSubTree >= target.getNumSubTrees()) {
throw new IndexOutOfBoundsException(target + " has no subtree at index " + targetSubTree);
}
SpanNode node = children().remove(nodeNum);
if (node != null) {
//we found the node
node.setParent(null);
resetCachedFromAndTo();
target.add(targetSubTree, node);
}
}
/**
* Removes and invalidates the given SpanNode from this.
*
* @param node the node to remove.
* @return this, for chaining.
*/
public SpanList remove(SpanNode node) {
boolean removed = children().remove(node);
if (removed) {
node.setParent(null);
resetCachedFromAndTo();
node.setInvalid();
}
return this;
}
/**
* Removes and invalidates the SpanNode at the given index from this.
*
* @param i the index of the node to remove.
* @return this, for chaining.
*/
public SpanList remove(int i) {
SpanNode node = children().remove(i);
if (node != null) {
node.setParent(null);
node.setInvalid();
}
return this;
}
/**
* Returns a modifiable list of the immediate children of this SpanList.
*
* @return a modifiable list of the immediate children of this SpanList.
*/
protected List children() {
return children;
}
/**
* Returns the number of children this SpanList holds.
*
* @return the number of children this SpanList holds.
*/
public int numChildren() {
return children().size();
}
/**
* Traverses all immediate children of this SpanList. The ListIterator returned support all optional operations
* specified in the ListIterator interface.
*
* @return a ListIterator which traverses all immediate children of this SpanNode
* @see java.util.ListIterator
*/
@Override
public ListIterator childIterator() {
return new InvalidatingIterator(this, children().listIterator());
}
/**
* Recursively traverses all children (not only leaf nodes) of this SpanList, in a depth-first fashion.
* The ListIterator only supports iteration forwards, and the optional operations that are implemented are
* remove() and set(). add() is not supported.
*
* @return a ListIterator which recursively traverses all children and their children etc. of this SpanList.
* @see java.util.ListIterator
*/
@Override
public ListIterator childIteratorRecursive() {
return new InvalidatingIterator(this, new RecursiveNodeIterator(children().listIterator()));
}
/** Removes and invalidates all references to child nodes. */
public void clearChildren() {
for (SpanNode node : children()) {
node.setInvalid();
node.setParent(null);
}
children().clear();
resetCachedFromAndTo();
}
/**
* Sorts children by occurrence in the text covered.
*
* @see SpanNode#compareTo(SpanNode)
*/
public void sortChildren() {
Collections.sort(children());
}
/**
* Recursively sorts all children by occurrence in the text covered.
*/
public void sortChildrenRecursive() {
for (SpanNode node : children()) {
if (node instanceof SpanList) {
((SpanList) node).sortChildrenRecursive();
}
Collections.sort(children());
}
}
/**
* Always returns false, even if this node has no children.
*
* @return always false, even if this node has no children
*/
@Override
public boolean isLeafNode() {
return false;
}
private void calculateFrom() {
int smallestFrom = Integer.MAX_VALUE;
for (SpanNode n : children()) {
final int thisFrom = n.getFrom();
if (thisFrom != -1) {
smallestFrom = Math.min(thisFrom, smallestFrom);
}
}
if (smallestFrom == Integer.MAX_VALUE) {
//all children were empty SpanLists which returned -1
smallestFrom = -1;
}
cachedFrom = smallestFrom;
}
/**
* Returns the character index where this {@link SpanNode} starts (inclusive), i.e. the smallest {@link com.yahoo.document.annotation.SpanNode#getFrom()} of all children.
*
* @return the lowest getFrom() of all children, or -1 if this SpanList has no children.
*/
@Override
public int getFrom() {
if (children().isEmpty()) {
return -1;
}
if (cachedFrom == Integer.MIN_VALUE) {
calculateFrom();
}
return cachedFrom;
}
private void calculateTo() {
int greatestTo = Integer.MIN_VALUE;
for (SpanNode n : children()) {
greatestTo = Math.max(n.getTo(), greatestTo);
}
cachedTo = greatestTo;
}
/**
* Returns the character index where this {@link SpanNode} ends (exclusive), i.e. the greatest {@link com.yahoo.document.annotation.SpanNode#getTo()} of all children.
*
* @return the greatest getTo() of all children, or -1 if this SpanList has no children.
*/
@Override
public int getTo() {
if (children().isEmpty()) {
return -1;
}
if (cachedTo == Integer.MIN_VALUE) {
calculateTo();
}
return cachedTo;
}
void resetCachedFromAndTo() {
cachedFrom = Integer.MIN_VALUE;
cachedTo = Integer.MIN_VALUE;
if (getParent() instanceof SpanList) {
((SpanList) getParent()).resetCachedFromAndTo();
}
}
/**
* Returns the length of this span, i.e. getFrom() - getTo().
*
* @return the length of this span
*/
@Override
public int getLength() {
return getTo() - getFrom();
}
/**
* Returns the text that is covered by this SpanNode.
*
* @param text the input text
* @return the text that is covered by this SpanNode.
*/
@Override
public CharSequence getText(CharSequence text) {
if (children().isEmpty()) {
return "";
}
StringBuilder str = new StringBuilder();
for (SpanNode node : children()) {
CharSequence childText = node.getText(text);
if (childText != null) {
str.append(node.getText(text));
}
}
return str;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof SpanList)) return false;
if (!super.equals(o)) return false;
SpanList spanList = (SpanList) o;
if (children() != null ? !children().equals(spanList.children()) : spanList.children() != null) return false;
return true;
}
@Override
public int hashCode() {
int result = super.hashCode();
result = 31 * result + (children() != null ? children().hashCode() : 0);
return result;
}
@Override
public String toString() {
return "SpanList with " + children().size() + " children";
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy