/*
* -----------------------------------------------------------------------
* Copyright © 2013-2016 Meno Hochschild, <http://www.menodata.de/>
* -----------------------------------------------------------------------
* This file (IntervalTree.java) is part of project Time4J.
*
* Time4J 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 2.1 of the License, or
* (at your option) any later version.
*
* Time4J 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 Time4J. If not, see <http://www.gnu.org/licenses/>.
* -----------------------------------------------------------------------
*/
package net.time4j.range;
import net.time4j.Moment;
import net.time4j.PlainDate;
import net.time4j.PlainTime;
import net.time4j.PlainTimestamp;
import net.time4j.engine.TimeLine;
import java.time.Instant;
import java.util.AbstractCollection;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
/**
* <p>Represents an augmented interval tree holding intervals for easy and quick search. </p>
*
* <p>The semantics of augmented interval trees (AVL-trees) is described for example
* in <a href="https://en.wikipedia.org/wiki/Interval_tree">Wikipedia</a>. Empty intervals
* are never stored. An interval tree is also like a read-only collection of intervals. </p>
*
* @param <T> the temporal type of time points in intervals
* @param <I> the type of intervals stored in the tree
* @author Meno Hochschild
* @since 3.25/4.21
*/
/*[deutsch]
* <p>Repräsentiert einen angereicherten AVL-Baum, der Intervalle zum einfachen und schnellen
* Suchen speichert. </p>
*
* <p>Die Semantik von angereicherten AVL-Bämen ist zum Beispiel auf
* <a href="https://en.wikipedia.org/wiki/Interval_tree">Wikipedia</a> beschrieben.
* Leere Intervalle werden nie gespeichert. Ein Intervallbaum verhält sich auch
* wie eine Nur-Lese-Collection von Intervallen. </p>
*
* @param <T> the temporal type of time points in intervals
* @param <I> the type of intervals stored in the tree
* @author Meno Hochschild
* @since 3.25/4.21
*/
public class IntervalTree<T, I extends ChronoInterval<T>>
extends AbstractCollection<I> {
//~ Instanzvariablen --------------------------------------------------
private final Node<T, I> root;
private final int size;
private final TimeLine<T> timeLine;
// optimization of iterator()
private volatile List<I> intervals = null;
//~ Konstruktoren -----------------------------------------------------
private IntervalTree(
Collection<I> intervals,
TimeLine<T> timeLine
) {
super();
if (timeLine == null) {
throw new NullPointerException("Missing timeline.");
}
Node<T, I> r = null;
int count = 0;
for (I interval : intervals) {
if (!interval.isEmpty()) {
r = insert(r, interval, timeLine);
count = Math.incrementExact(count);
}
}
this.root = r;
this.size = count;
this.timeLine = timeLine;
}
//~ Methoden ----------------------------------------------------------
/**
* <p>Creates an interval tree on the date axis filled with given date intervals. </p>
*
* @param <I> the type of intervals stored in the tree
* @param intervals collection of date intervals
* @return new interval tree
* @throws ArithmeticException if the count of intervals overflows an int
*/
/*[deutsch]
* <p>Erzeugt einen Intervallbaum auf der Datumsachse gefüllt mit den angegebenen Datumsintervallen. </p>
*
* @param <I> the type of intervals stored in the tree
* @param intervals collection of date intervals
* @return new interval tree
* @throws ArithmeticException if the count of intervals overflows an int
*/
public static <I extends ChronoInterval<PlainDate>> IntervalTree<PlainDate, I> onDateAxis(
Collection<I> intervals
) {
return IntervalTree.onTimeLine(PlainDate.axis(), intervals);
}
/**
* <p>Creates an interval tree on the clock axis filled with given clock intervals. </p>
*
* @param <I> the type of intervals stored in the tree
* @param intervals collection of clock intervals
* @return new interval tree
* @throws ArithmeticException if the count of intervals overflows an int
*/
/*[deutsch]
* <p>Erzeugt einen Intervallbaum auf der Uhrzeitachse gefüllt mit den angegebenen Uhrzeitintervallen. </p>
*
* @param <I> the type of intervals stored in the tree
* @param intervals collection of clock intervals
* @return new interval tree
* @throws ArithmeticException if the count of intervals overflows an int
*/
public static <I extends ChronoInterval<PlainTime>> IntervalTree<PlainTime, I> onClockAxis(
Collection<I> intervals
) {
return IntervalTree.onTimeLine(PlainTime.axis(), intervals);
}
/**
* <p>Creates an interval tree on the timestamp axis filled with given timestamp intervals. </p>
*
* @param <I> the type of intervals stored in the tree
* @param intervals collection of timestamp intervals
* @return new interval tree
* @throws ArithmeticException if the count of intervals overflows an int
*/
/*[deutsch]
* <p>Erzeugt einen Intervallbaum auf der kombinierten Datum-Zeit-Achse gefüllt mit den
* angegebenen Zeitstempelintervallen. </p>
*
* @param <I> the type of intervals stored in the tree
* @param intervals collection of timestamp intervals
* @return new interval tree
* @throws ArithmeticException if the count of intervals overflows an int
*/
public static <I extends ChronoInterval<PlainTimestamp>> IntervalTree<PlainTimestamp, I> onTimestampAxis(
Collection<I> intervals
) {
return IntervalTree.onTimeLine(PlainTimestamp.axis(), intervals);
}
/**
* <p>Creates an interval tree on the moment axis (UTC) filled with given moment intervals. </p>
*
* @param <I> the type of intervals stored in the tree
* @param intervals collection of moment intervals
* @return new interval tree
* @throws ArithmeticException if the count of intervals overflows an int
*/
/*[deutsch]
* <p>Erzeugt einen Intervallbaum auf der Momentachse (UTC) gefüllt mit den angegebenen Momentintervallen. </p>
*
* @param <I> the type of intervals stored in the tree
* @param intervals collection of moment intervals
* @return new interval tree
* @throws ArithmeticException if the count of intervals overflows an int
*/
public static <I extends ChronoInterval<Moment>> IntervalTree<Moment, I> onMomentAxis(
Collection<I> intervals
) {
return IntervalTree.onTimeLine(Moment.axis(), intervals);
}
/**
* <p>Creates an interval tree for the legacy type {@code java.util.Date} filled with given simple intervals. </p>
*
* @param intervals collection of simple intervals
* @return new interval tree
* @throws ArithmeticException if the count of intervals overflows an int
*/
/*[deutsch]
* <p>Erzeugt einen Intervallbaum für den Type {@code java.util.Date} gefüllt mit den angegebenen
* vereinfachten Intervallen. </p>
*
* @param intervals collection of simple intervals
* @return new interval tree
* @throws ArithmeticException if the count of intervals overflows an int
*/
public static IntervalTree<Date, SimpleInterval<Date>> onTraditionalTimeLine(
Collection<SimpleInterval<Date>> intervals
) {
return IntervalTree.onTimeLine(SimpleInterval.onTraditionalTimeLine().getTimeLine(), intervals);
}
/**
* <p>Creates an interval tree for the type {@code java.time.Instant} filled with given simple intervals. </p>
*
* @param intervals collection of simple intervals
* @return new interval tree
* @throws ArithmeticException if the count of intervals overflows an int
*/
/*[deutsch]
* <p>Erzeugt einen Intervallbaum für den Type {@code java.time.Instant} gefüllt mit den angegebenen
* vereinfachten Intervallen. </p>
*
* @param intervals collection of simple intervals
* @return new interval tree
* @throws ArithmeticException if the count of intervals overflows an int
*/
public static IntervalTree<Instant, SimpleInterval<Instant>> onInstantTimeLine(
Collection<SimpleInterval<Instant>> intervals
) {
return IntervalTree.onTimeLine(SimpleInterval.onInstantTimeLine().getTimeLine(), intervals);
}
/**
* <p>Creates an interval tree on a timeline filled with given intervals. </p>
*
* @param <I> the type of intervals stored in the tree
* @param timeLine the underlying timeline
* @param intervals collection of moment intervals
* @return new interval tree
* @throws ArithmeticException if the count of intervals overflows an int
*/
/*[deutsch]
* <p>Erzeugt einen Intervallbaum auf einem Zeitstrahl gefüllt mit den angegebenen Momentintervallen. </p>
*
* @param <I> the type of intervals stored in the tree
* @param timeLine the underlying timeline
* @param intervals collection of moment intervals
* @return new interval tree
* @throws ArithmeticException if the count of intervals overflows an int
*/
public static <T, I extends ChronoInterval<T>> IntervalTree<T, I> onTimeLine(
TimeLine<T> timeLine,
Collection<I> intervals
) {
return new IntervalTree<>(intervals, timeLine);
}
/**
* <p>Checks if this tree contains no intervals. </p>
*
* @return {@code true} if empty else {@code false}
*/
/*[deutsch]
* <p>Ermittelt, ob dieser Baum leer ist. </p>
*
* @return {@code true} if empty else {@code false}
*/
@Override
public boolean isEmpty() {
return (this.root == null);
}
/**
* <p>Collects all stored intervals into a new list and then obtains an iterator for this list. </p>
*
* <p>This method is only useful if users really want to iterate over <strong>all</strong> stored
* intervals. Otherwise a customized {@code Visitor}-implementation is more flexible. </p>
*
* @return an {@code Iterator} which is read-only
*/
/*[deutsch]
* <p>Sammelt alle gespeicherten Intervalle in eine neue Liste und liefert dann einen {@code Iterator}
* für diese Liste. </p>
*
* <p>Diese Methode ist nur sinnvoll, wenn Anwender alle gespeicherten Intervalle brauchen. Ansonsten
* ist eine benutzerdefinierte {@code Visitor}-Implementierung flexibler. </p>
*
* @return an {@code Iterator} which is read-only
*/
@Override
public Iterator<I> iterator() {
List<I> i = this.intervals;
if (i == null) {
Collector collector = new Collector();
this.accept(collector);
i = Collections.unmodifiableList(collector.visited);
this.intervals = i;
}
return i.iterator();
}
/**
* <p>Obtains the count of stored intervals. </p>
*
* @return int
*/
/*[deutsch]
* <p>Ermittelt die Anzahl der gespeicherten Intervalle. </p>
*
* @return int
*/
@Override
public int size() {
return this.size;
}
/**
* <p>Obtains a list of all stored intervals which intersect given point in time. </p>
*
* @param timepoint the point in time to be checked
* @return unmodifiable list of all stored intervals which contain given point in time, maybe empty
*/
/*[deutsch]
* <p>Liefert eine Liste aller gespeicherten Intervalle, die den angegebenen Suchzeitpunkt enthalten. </p>
*
* @param timepoint the point in time to be checked
* @return unmodifiable list of all stored intervals which contain given point in time, maybe empty
*/
public List<I> findIntersections(T timepoint) {
List<I> found = new ArrayList<>();
findIntersections(timepoint, this.timeLine.stepForward(timepoint), this.root, found);
return Collections.unmodifiableList(found);
}
/**
* <p>Obtains a list of all stored intervals which intersect given search interval. </p>
*
* @param interval the search interval
* @return unmodifiable list of all stored intervals which intersect the search interval, maybe empty
*/
/*[deutsch]
* <p>Liefert eine Liste aller gespeicherten Intervalle, die sich mit dem angegebenen Suchintervall
* überschneiden. </p>
*
* @param interval the search interval
* @return unmodifiable list of all stored intervals which intersect the search interval, maybe empty
*/
public List<I> findIntersections(ChronoInterval<T> interval) {
// trivial case
if (interval.isEmpty()) {
return Collections.emptyList();
}
// make search interval half-open
T low = interval.getStart().getTemporal();
T high = interval.getEnd().getTemporal();
if ((low != null) && interval.getStart().isOpen()) {
low = this.timeLine.stepForward(low);
}
if ((high != null) && interval.getEnd().isClosed()) {
high = this.timeLine.stepForward(high);
}
// collect recursively
List<I> found = new ArrayList<>();
findIntersections(low, high, this.root, found);
return Collections.unmodifiableList(found);
}
/**
* <p>Queries if given interval is stored in this tree. </p>
*
* @param interval the interval to be checked
* @return boolean
*/
/*[deutsch]
* <p>Ermittelt, ob das angegebene Intervall in diesem Baum gespeichert ist. </p>
*
* @param interval the interval to be checked
* @return boolean
*/
public boolean contains(ChronoInterval<T> interval) {
// trivial case
if (interval.isEmpty()) {
return false;
}
T low = interval.getStart().getTemporal();
if ((low != null) && interval.getStart().isOpen()) {
low = this.timeLine.stepForward(low);
}
List<ChronoInterval<T>> found = new ArrayList<>();
this.findByEquals(interval, low, found, this.root);
return !found.isEmpty();
}
/**
* <p>Accepts given interval tree visitor. </p>
*
* <p>All nodes will be visited in ascending order, first sorted by start then by end. </p>
*
* <p>Example: </p>
*
* <pre>
* DateInterval i1 = DateInterval.between(PlainDate.of(2014, 2, 28), PlainDate.of(2014, 5, 31));
* DateInterval i2 = DateInterval.between(PlainDate.of(2014, 5, 31), PlainDate.of(2014, 6, 1));
* DateInterval i3 = DateInterval.between(PlainDate.of(2014, 6, 15), PlainDate.of(2014, 6, 30));
* IntervalTree<PlainDate, DateInterval> tree = IntervalTree.onDateAxis(Arrays.asList(i3, i1, i2));
*
* tree.accept(
* (interval) -> {
* System.out.println(interval);
* return false;
* }
* );
*
* // output:
* [2014-02-28/2014-05-31]
* [2014-05-31/2014-06-01]
* [2014-06-15/2014-06-30]
* </pre>
*
* @param visitor the interval tree visitor
*/
/*[deutsch]
* <p>Nimmt den angegebenen Baumbesucher an. </p>
*
* <p>Alle Knoten werden in aufsteigender Reihenfolge besucht, zuerst sortiert nach dem Start, dann
* nach dem Ende eines Intervalls. </p>
*
* <p>Beispiel: </p>
*
* <pre>
* DateInterval i1 = DateInterval.between(PlainDate.of(2014, 2, 28), PlainDate.of(2014, 5, 31));
* DateInterval i2 = DateInterval.between(PlainDate.of(2014, 5, 31), PlainDate.of(2014, 6, 1));
* DateInterval i3 = DateInterval.between(PlainDate.of(2014, 6, 15), PlainDate.of(2014, 6, 30));
* IntervalTree<PlainDate, DateInterval> tree = IntervalTree.onDateAxis(Arrays.asList(i3, i1, i2));
*
* tree.accept(
* (interval) -> {
* System.out.println(interval);
* return false;
* }
* );
*
* // Ausgabe:
* [2014-02-28/2014-05-31]
* [2014-05-31/2014-06-01]
* [2014-06-15/2014-06-30]
* </pre>
*
* @param visitor the interval tree visitor
*/
public void accept(Visitor<I> visitor) {
accept(visitor, this.root);
}
private static <T, I extends ChronoInterval<T>> Node<T, I> insert(
Node<T, I> node,
I interval,
TimeLine<T> timeLine
) {
if (node == null) {
return new Node<>(interval);
}
if (compareAtStart(node.interval.getStart(), interval.getStart(), timeLine) > 0) {
node.left = insert(node.left, interval, timeLine);
} else {
node.right = insert(node.right, interval, timeLine);
}
node.height = Math.max(getHeight(node.left), getHeight(node.right)) + 1;
node.max = findMax(node, timeLine);
int balance = getBalance(node);
if (balance < -1) {
if (getBalance(node.right) > 0) {
node.right = rightRotate(node.right, timeLine);
}
return leftRotate(node, timeLine);
} else if (balance > 1) {
if (getBalance(node.left) < 0) {
node.left = leftRotate(node.left, timeLine);
}
return rightRotate(node, timeLine);
}
return node;
}
private static <T, I extends ChronoInterval<T>> Node<T, I> leftRotate(
Node<T, I> n,
TimeLine<T> timeLine
) {
Node<T, I> r = n.right;
n.right = r.left;
r.left = n;
n.height = Math.max(getHeight(n.left), getHeight(n.right)) + 1;
r.height = Math.max(getHeight(r.left), getHeight(r.right)) + 1;
n.max = findMax(n, timeLine);
r.max = findMax(r, timeLine);
return r;
}
private static <T, I extends ChronoInterval<T>> Node<T, I> rightRotate(
Node<T, I> n,
TimeLine<T> timeLine
) {
Node<T, I> r = n.left;
n.left = r.right;
r.right = n;
n.height = Math.max(getHeight(n.left), getHeight(n.right)) + 1;
r.height = Math.max(getHeight(r.left), getHeight(r.right)) + 1;
n.max = findMax(n, timeLine);
r.max = findMax(r, timeLine);
return r;
}
private static int getHeight(Node<?, ?> node) {
return ((node == null) ? 0 : node.height);
}
private static <T, I extends ChronoInterval<T>> Boundary<T> findMax(
Node<T, I> n,
TimeLine<T> timeLine
) {
if ((n.left == null) && (n.right == null)) {
return n.max;
} else if (n.left == null) {
if (compareAtEnd(n.right.max, n.max, timeLine) > 0) {
return n.right.max;
} else {
return n.max;
}
} else if (n.right == null) {
if (compareAtEnd(n.left.max, n.max, timeLine) > 0) {
return n.left.max;
} else {
return n.max;
}
}
Boundary<T> maximized;
if (compareAtEnd(n.left.max, n.right.max, timeLine) < 0) {
maximized = n.right.max;
} else {
maximized = n.left.max;
}
if (compareAtEnd(n.max, maximized, timeLine) > 0) {
maximized = n.max;
}
return maximized;
}
private static <T> int compareAtStart(
Boundary<T> b1,
Boundary<T> b2,
TimeLine<T> timeLine
) {
if (b1.isInfinite()) {
return ((b2.isInfinite() ? 0 : -1));
} else if (b2.isInfinite()) {
return 1;
}
T t1 = b1.getTemporal();
T t2 = b2.getTemporal();
if (b1.getEdge() != b2.getEdge()) {
if (b1.isOpen() && b2.isClosed()) {
t2 = timeLine.stepBackwards(t2);
if (t2 == null) {
return 1;
}
} else if (b1.isClosed() && b2.isOpen()) {
t1 = timeLine.stepBackwards(t1);
if (t1 == null) {
return -1;
}
}
}
return timeLine.compare(t1, t2);
}
private static <T> int compareAtEnd(
Boundary<T> b1,
Boundary<T> b2,
TimeLine<T> timeLine
) {
if (b1.isInfinite()) {
return ((b2.isInfinite() ? 0 : 1));
} else if (b2.isInfinite()) {
return -1;
}
T t1 = b1.getTemporal();
T t2 = b2.getTemporal();
if (b1.getEdge() != b2.getEdge()) {
if (b1.isOpen() && b2.isClosed()) {
t2 = timeLine.stepForward(t2);
if (t2 == null) {
return -1;
}
} else if (b1.isClosed() && b2.isOpen()) {
t1 = timeLine.stepForward(t1);
if (t1 == null) {
return 1;
}
}
}
return timeLine.compare(t1, t2);
}
private static int getBalance(Node<?, ?> node) {
if (node == null) {
return 0;
}
return getHeight(node.left) - getHeight(node.right);
}
private void findIntersections(
T low, // inclusive if not null
T high, // exclusive if not null
Node<T, I> node,
List<I> found
) {
if (node == null) {
return;
}
// If the node's max interval is before the search interval, no children will match (short-cut)
if (low != null) {
if (node.max.isOpen()) {
if (this.timeLine.compare(node.max.getTemporal(), low) <= 0) {
return;
}
} else if (this.timeLine.compare(node.max.getTemporal(), low) < 0) {
return;
}
}
// left children
findIntersections(low, high, node.left, found);
// check: (start < high)
T start = node.interval.getStart().getTemporal();
boolean c1 = (start == null || high == null);
if (!c1) {
if (node.interval.getStart().isClosed()) {
c1 = (this.timeLine.compare(start, high) < 0);
} else {
T startClosed = this.timeLine.stepForward(start);
c1 = ((startClosed != null) && (this.timeLine.compare(startClosed, high) < 0));
}
}
if (c1) {
// check: (end > low)
T end = node.interval.getEnd().getTemporal();
boolean c2 = (end == null || low == null);
if (!c2) {
if (node.interval.getEnd().isOpen()) {
c2 = (this.timeLine.compare(low, end) < 0);
} else {
c2 = (this.timeLine.compare(low, end) <= 0);
}
}
if (c2) {
found.add(node.interval);
}
} else {
return; // short-cut: start >= high (interval nodes are primarily sorted by start)
}
// right children
findIntersections(low, high, node.right, found);
}
private boolean findByEquals(
ChronoInterval<T> interval,
T low,
List<ChronoInterval<T>> found,
Node<T, I> node
) {
if (node == null) {
return false;
}
if (this.findByEquals(interval, low, found, node.left)) {
return true;
}
if (interval.equals(node.interval)) {
found.add(interval);
return true; // cancel search
}
// short-cut (all further stored intervals are after given search interval)
if (
((low != null) && node.interval.isAfter(low))
|| (interval.getStart().isInfinite() && !node.interval.getStart().isInfinite())
) {
return true; // cancel search
}
return this.findByEquals(interval, low, found, node.right);
}
private static <T, I extends ChronoInterval<T>> boolean accept(
Visitor<I> visitor,
Node<T, I> node
) {
if (node == null) {
return false;
}
if (accept(visitor, node.left)) {
return true;
}
if (visitor.visited(node.interval)) {
return true;
}
return accept(visitor, node.right);
}
//~ Innere Klassen ----------------------------------------------------
/**
* <p>Callback interface for tree traversal according to the visitor pattern design. </p>
*
* @param <I> the type of visited intervals
*/
/*[deutsch]
* <p>Callback-Interface zum Abwandern eines Intervallbaums entsprechend dem Besucher-Entwurfsmuster. </p>
*
* @param <I> the type of visited intervals
*/
@FunctionalInterface
public interface Visitor<I> {
//~ Methoden ------------------------------------------------------
/**
* <p>Called recursively during traversal of tree for every interval node. </p>
*
* @param interval visited interval
* @return {@code true} if further traversal shall be cancelled else {@code false}
*/
/*[deutsch]
* <p>Wird während des Abgehens des Baums rekursiv für jeden Intervallknoten aufgerufen. </p>
*
* @param interval visited interval
* @return {@code true} if further traversal shall be cancelled else {@code false}
*/
boolean visited(I interval);
}
private static class Node<T, I extends ChronoInterval<T>> {
//~ Instanzvariablen ----------------------------------------------
private final I interval;
// tree organization
Node<T, I> left = null;
Node<T, I> right = null;
int height;
Boundary<T> max;
//~ Konstruktoren -------------------------------------------------
Node(I interval) {
super();
this.interval = interval;
this.height = 1;
this.max = interval.getEnd();
}
}
private class Collector
implements Visitor<I> {
//~ Instanzvariablen ----------------------------------------------
private List<I> visited = new ArrayList<>(IntervalTree.this.size());
//~ Methoden ------------------------------------------------------
@Override
public boolean visited(I interval) {
this.visited.add(interval);
return false;
}
}
}