/*
* -----------------------------------------------------------------------
* Copyright © 2013-2016 Meno Hochschild, <http://www.menodata.de/>
* -----------------------------------------------------------------------
* This file (IntervalCollection.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.io.Serializable;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.NoSuchElementException;
/**
* <p>Represents a sorted list of intervals. </p>
*
* <p>Any instance can first be achieved by calling one of the static
* {@code onXYZAxis()}-methods and then be filled with any count of
* typed intervals via {@code plus(...)}-methods. All intervals are
* stored with closed start if they have finite start. </p>
*
* @param <T> generic type characterizing the associated time axis
* @author Meno Hochschild
* @serial exclude
* @since 2.0
* @doctags.concurrency {immutable}
* @see DateInterval#comparator()
* @see ClockInterval#comparator()
* @see TimestampInterval#comparator()
* @see MomentInterval#comparator()
*/
/*[deutsch]
* <p>Repräsentiert eine sortierte Liste von Intervallen. </p>
*
* <p>Zuerst kann eine Instanz mit Hilfe von statischen Fabrikmethoden
* wie {@code onXYZAxis()} erhalten und dann mit einer beliebigen Zahl
* von typisierten Intervallen gefüllt werden - via {@code plus(...)}
* -Methoden. Alle Intervalle werden so gespeichert, daß sie den
* Start inklusive haben, wenn dieser endlich ist. </p>
*
* @param <T> generic type characterizing the associated time axis
* @author Meno Hochschild
* @serial exclude
* @since 2.0
* @doctags.concurrency {immutable}
* @see DateInterval#comparator()
* @see ClockInterval#comparator()
* @see TimestampInterval#comparator()
* @see MomentInterval#comparator()
*/
public abstract class IntervalCollection<T>
implements Serializable {
//~ Instanzvariablen --------------------------------------------------
private transient final List<ChronoInterval<T>> intervals;
//~ Konstruktoren -----------------------------------------------------
/**
* <p>For subclasses only. </p>
*/
IntervalCollection() {
super();
this.intervals = Collections.emptyList();
}
/**
* <p>For subclasses only. </p>
*
* @param intervals sorted list of intervals
*/
IntervalCollection(List<ChronoInterval<T>> intervals) {
super();
this.intervals = Collections.unmodifiableList(intervals);
}
//~ Methoden ----------------------------------------------------------
/**
* <p>Yields an empty instance on the date axis. </p>
*
* @return empty {@code IntervalCollection} for date intervals
* @since 2.0
*/
/*[deutsch]
* <p>Liefert eine leere Instanz auf der Datumsachse. </p>
*
* @return empty {@code IntervalCollection} for date intervals
* @since 2.0
*/
public static IntervalCollection<PlainDate> onDateAxis() {
return DateWindows.EMPTY;
}
/**
* <p>Yields an empty instance on the walltime axis. </p>
*
* @return empty {@code IntervalCollection} for clock intervals
* @since 2.0
*/
/*[deutsch]
* <p>Liefert eine leere Instanz auf der Uhrzeitachse. </p>
*
* @return empty {@code IntervalCollection} for clock intervals
* @since 2.0
*/
public static IntervalCollection<PlainTime> onClockAxis() {
return ClockWindows.EMPTY;
}
/**
* <p>Yields an empty instance on the timestamp axis. </p>
*
* @return empty {@code IntervalCollection} for timestamp intervals
* @since 2.0
*/
/*[deutsch]
* <p>Liefert eine leere Instanz auf der Zeitstempelachse. </p>
*
* @return empty {@code IntervalCollection} for timestamp intervals
* @since 2.0
*/
public static IntervalCollection<PlainTimestamp> onTimestampAxis() {
return TimestampWindows.EMPTY;
}
/**
* <p>Yields an empty instance on the UTC-axis. </p>
*
* @return empty {@code IntervalCollection} for moment intervals
* @since 2.0
*/
/*[deutsch]
* <p>Liefert eine leere Instanz auf der UTC-Achse. </p>
*
* @return empty {@code IntervalCollection} for moment intervals
* @since 2.0
*/
public static IntervalCollection<Moment> onMomentAxis() {
return MomentWindows.EMPTY;
}
/**
* <p>Yields an empty instance for intervals with the component type {@code java.util.Date}. </p>
*
* @return empty {@code IntervalCollection} for old {@code java.util.Date}-intervals
* @since 3.25/4.21
*/
/*[deutsch]
* <p>Liefert eine leere Instanz für Intervalle mit dem Komponententyp {@code java.util.Date}. </p>
*
* @return empty {@code IntervalCollection} for old {@code java.util.Date}-intervals
* @since 3.25/4.21
*/
public static IntervalCollection<Date> onTraditionalTimeLine() {
return onTimeLine(SimpleInterval.onTraditionalTimeLine().getTimeLine());
}
/**
* <p>Yields an empty instance for intervals with the component type {@code java.time.Instant}. </p>
*
* @return empty {@code IntervalCollection} for {@code java.time.Instant}-intervals
* @since 4.21
*/
/*[deutsch]
* <p>Liefert eine leere Instanz für Intervalle mit dem Komponententyp {@code java.time.Instant}. </p>
*
* @return empty {@code IntervalCollection} for {@code java.time.Instant}-intervals
* @since 4.21
*/
public static IntervalCollection<Instant> onInstantTimeLine() {
return onTimeLine(SimpleInterval.onInstantTimeLine().getTimeLine());
}
/**
* <p>Yields an empty instance for intervals on given timeline. </p>
*
* @return empty generic {@code IntervalCollection}
* @since 3.25/4.21
*/
/*[deutsch]
* <p>Liefert eine leere Instanz für Intervalle auf dem angegebenen Zeitstrahl. </p>
*
* @return empty generic {@code IntervalCollection}
* @since 3.25/4.21
*/
@SuppressWarnings("unchecked")
public static <T> IntervalCollection<T> onTimeLine(TimeLine<T> timeLine) {
if (timeLine.equals(PlainDate.axis())) {
return (IntervalCollection<T>) onDateAxis();
} else if (timeLine.equals(PlainTime.axis())) {
return (IntervalCollection<T>) onClockAxis();
} else if (timeLine.equals(PlainTimestamp.axis())) {
return (IntervalCollection<T>) onTimestampAxis();
} else if (timeLine.equals(Moment.axis())) {
return (IntervalCollection<T>) onMomentAxis();
}
return new GenericWindows<>(timeLine, Collections.emptyList());
}
/**
* <p>Returns all appended intervals. </p>
*
* <p>Note that all contained finite intervals have each a closed start. </p>
*
* @return unmodifiable list of intervals sorted by start and then by length
* @since 2.0
*/
/*[deutsch]
* <p>Liefert alle hinzugefügten Intervalle. </p>
*
* <p>Hinweis: Alle enthaltenen endlichen Intervalle haben einen geschlossenen Start. </p>
*
* @return unmodifiable list of intervals sorted by start and then by length
* @since 2.0
*/
public List<ChronoInterval<T>> getIntervals() {
return this.intervals;
}
/**
* <p>Gives an answer if this instance contains no intervals. </p>
*
* @return {@code true} if there are no intervals else {@code false}
* @since 2.0
*/
/*[deutsch]
* <p>Gibt eine Antwort, ob diese Instanz keine Intervalle enthält. </p>
*
* @return {@code true} if there are no intervals else {@code false}
* @since 2.0
*/
public boolean isEmpty() {
return this.intervals.isEmpty();
}
/**
* <p>Queries if there is no intersection of intervals. </p>
*
* @return {@code true} if there is no intersection else {@code false}
* @since 3.24/4.20
*/
/*[deutsch]
* <p>Ermittelt, ob keine Überschneidung von Intervallen existiert. </p>
*
* @return {@code true} if there is no intersection else {@code false}
* @since 3.24/4.20
*/
public boolean isDisjunct() {
for (int i = 0, n = this.intervals.size() - 1; i < n; i++) {
ChronoInterval<T> current = this.intervals.get(i);
ChronoInterval<T> next = this.intervals.get(i + 1);
if (current.getEnd().isInfinite() || next.getStart().isInfinite()) {
return false;
} else if (current.getEnd().isOpen()) {
if (this.isAfter(current.getEnd().getTemporal(), next.getStart().getTemporal())) {
return false;
}
} else if (!this.isBefore(current.getEnd().getTemporal(), next.getStart().getTemporal())) {
return false;
}
}
return true;
}
/**
* <p>Queries if any interval of this collection contains given temporal. </p>
*
* @param temporal time point to be queried
* @return {@code true} if given time point belongs to any interval of this collection else {@code false}
* @since 3.24/4.20
*/
/*[deutsch]
* <p>Fragt ab, ob irgendein Intervall dieser Menge den angegebenen Zeitpunkt enthält. </p>
*
* @param temporal time point to be queried
* @return {@code true} if given time point belongs to any interval of this collection else {@code false}
* @since 3.24/4.20
*/
public boolean contains(T temporal) {
for (ChronoInterval<T> interval : this.intervals) {
if (interval.contains(temporal)) {
return true;
} else if (interval.isAfter(temporal)) {
break;
}
}
return false;
}
/**
* <p>Returns the overall minimum of this interval collection. </p>
*
* <p>The minimum is always inclusive, if finite. </p>
*
* @return lower limit of this instance or {@code null} if infinite
* @throws NoSuchElementException if there are no intervals
* @since 2.0
* @see #isEmpty()
*/
/*[deutsch]
* <p>Liefert das totale Minimum dieser Intervall-Menge. </p>
*
* <p>Das Minimum ist immer inklusive, wenn endlich. </p>
*
* @return lower limit of this instance or {@code null} if infinite
* @throws NoSuchElementException if there are no intervals
* @since 2.0
* @see #isEmpty()
*/
public T getMinimum() {
if (this.isEmpty()) {
throw new NoSuchElementException(
"Empty time windows have no minimum.");
}
return this.intervals.get(0).getStart().getTemporal();
}
/**
* <p>Returns the overall maximum of this interval collection. </p>
*
* <p>The maximum is always inclusive, if finite. </p>
*
* @return upper limit of this instance or {@code null} if infinite
* @throws NoSuchElementException if there are no intervals
* @since 2.0
* @see #isEmpty()
*/
/*[deutsch]
* <p>Liefert das totale Maximum dieser Intervall-Menge. </p>
*
* <p>Das Maximum ist immer inklusive, wenn endlich. </p>
*
* @return upper limit of this instance or {@code null} if infinite
* @throws NoSuchElementException if there are no intervals
* @since 2.0
* @see #isEmpty()
*/
public T getMaximum() {
if (this.isEmpty()) {
throw new NoSuchElementException(
"Empty time windows have no maximum.");
}
int n = this.intervals.size();
Boundary<T> upper = this.intervals.get(n - 1).getEnd();
T max = upper.getTemporal();
if (upper.isInfinite()) {
return null;
}
if (this.isCalendrical()) {
if (upper.isOpen()) {
max = this.getTimeLine().stepBackwards(max);
}
for (int i = n - 2; i >= 0; i--) {
Boundary<T> test = this.intervals.get(i).getEnd();
T candidate = test.getTemporal();
if (test.isInfinite()) {
return null;
} else if (test.isOpen()) {
candidate = this.getTimeLine().stepBackwards(candidate);
}
if (this.isAfter(candidate, max)) {
max = candidate;
}
}
} else {
T last = null;
if (upper.isClosed()) {
T next = this.getTimeLine().stepForward(max);
if (next == null) {
last = max;
} else {
max = next;
}
}
for (int i = n - 2; i >= 0; i--) {
Boundary<T> test = this.intervals.get(i).getEnd();
T candidate = test.getTemporal();
if (test.isInfinite()) {
return null;
} else if (last != null) {
continue;
} else if (test.isClosed()) {
T next = this.getTimeLine().stepForward(candidate);
if (next == null) {
last = candidate;
continue;
} else {
candidate = next;
}
}
if (this.isAfter(candidate, max)) {
max = candidate;
}
}
if (last != null) {
max = last;
} else {
max = this.getTimeLine().stepBackwards(max);
}
}
return max;
}
/**
* <p>Yields the full min-max-range of this instance. </p>
*
* @return minimum range interval spanning over all enclosed intervals
* @since 3.7/4.5
*/
/*[deutsch]
* <p>Liefert den vollen min-max-Bereich dieser Instanz. </p>
*
* @return minimum range interval spanning over all enclosed intervals
* @since 3.7/4.5
*/
public ChronoInterval<T> getRange() {
Boundary<T> start = Boundary.infinitePast();
Boundary<T> end = Boundary.infiniteFuture();
T min = this.getMinimum();
T max = this.getMaximum();
if (min != null) {
start = Boundary.ofClosed(min);
}
if (max != null) {
if (this.isCalendrical()) {
end = Boundary.ofClosed(max);
} else {
T max2 = this.getTimeLine().stepForward(max);
if (max2 != null) {
end = Boundary.ofOpen(max2);
} else {
end = Boundary.ofClosed(max);
}
}
}
return this.newInterval(start, end);
}
/**
* <p>Adds the given interval to this interval collection. </p>
*
* @param interval the new interval to be added
* @return new IntervalCollection-instance containing a sum of
* the own intervals and the given one while this instance
* remains unaffected
* @throws IllegalArgumentException if given interval is finite and has
* open start which cannot be adjusted to one with closed start
* @since 2.0
*/
/*[deutsch]
* <p>Fügt das angegebene Intervall hinzu. </p>
*
* @param interval the new interval to be added
* @return new IntervalCollection-instance containing a sum of
* the own intervals and the given one while this instance
* remains unaffected
* @throws IllegalArgumentException if given interval is finite and has
* open start which cannot be adjusted to one with closed start
* @since 2.0
*/
public IntervalCollection<T> plus(ChronoInterval<T> interval) {
List<ChronoInterval<T>> windows = new ArrayList<>(this.intervals);
windows.add(this.adjust(interval));
Collections.sort(windows, this.getComparator());
return this.create(windows);
}
/**
* <p>Adds the given intervals to this interval collection. </p>
*
* @param intervals the new intervals to be added
* @return new IntervalCollection-instance containing a sum of
* the own intervals and the given one while this instance
* remains unaffected
* @throws IllegalArgumentException if given list contains a finite
* interval with open start which cannot be adjusted to one
* with closed start
* @since 2.0
*/
/*[deutsch]
* <p>Fügt die angegebenen Intervalle hinzu. </p>
*
* @param intervals the new intervals to be added
* @return new IntervalCollection-instance containing a sum of
* the own intervals and the given one while this instance
* remains unaffected
* @throws IllegalArgumentException if given list contains a finite
* interval with open start which cannot be adjusted to one
* with closed start
* @since 2.0
*/
public IntervalCollection<T> plus(List<? extends ChronoInterval<T>> intervals) {
if (intervals.isEmpty()) {
return this;
}
List<ChronoInterval<T>> windows = new ArrayList<>(this.intervals);
for (ChronoInterval<T> i : intervals) {
windows.add(this.adjust(i));
}
Collections.sort(windows, this.getComparator());
return this.create(windows);
}
/**
* <p>Equivalent to {@code plus(other.getIntervals())}. </p>
*
* @param other another interval collection whose intervals are to be added to this instance
* @return new interval collection containing the intervals of this instance and the argument
* @throws IllegalArgumentException if given collection contains a finite
* interval with open start which cannot be adjusted to one with closed start
* @since 3.7/4.5
*/
/*[deutsch]
* <p>Äquivalent zu {@code plus(other.getIntervals())}. </p>
*
* @param other another interval collection whose intervals are to be added to this instance
* @return new interval collection containing the intervals of this instance and the argument
* @throws IllegalArgumentException if given collection contains a finite
* interval with open start which cannot be adjusted to one with closed start
* @since 3.7/4.5
*/
public IntervalCollection<T> plus(IntervalCollection<T> other) {
if (this == other) {
return this;
}
return this.plus(other.getIntervals());
}
/**
* <p>Subtracts all timepoints of given interval from this interval
* collection. </p>
*
* @param interval other interval to be subtracted from this
* @return new interval collection containing all timepoints of
* this instance excluding those of given interval
* @throws IllegalArgumentException if given interval is finite and has
* open start which cannot be adjusted to one with closed start
* @since 2.2
*/
/*[deutsch]
* <p>Subtrahiert alle im angegebenen Zeitintervall enthaltenen
* Zeitpunkte von dieser Intervallmenge. </p>
*
* @param interval other interval to be subtracted from this
* @return new interval collection containing all timepoints of
* this instance excluding those of given interval
* @throws IllegalArgumentException if given interval is finite and has
* open start which cannot be adjusted to one with closed start
* @since 2.2
*/
public IntervalCollection<T> minus(ChronoInterval<T> interval) {
if (
this.isEmpty()
|| interval.isEmpty()
) {
return this;
}
ChronoInterval<T> minuend = this.intervals.get(0);
ChronoInterval<T> iv = this.adjust(interval);
if (
!minuend.getStart().isInfinite()
&& iv.isBefore(minuend.getStart().getTemporal())
) {
return this;
}
List<ChronoInterval<T>> parts = new ArrayList<>();
IntervalCollection<T> subtrahend = this.create(Collections.singletonList(iv));
IntervalCollection<T> diff = subtrahend.withComplement(minuend);
if (!diff.isEmpty()) {
parts.addAll(diff.intervals);
}
for (int i = 1, n = this.intervals.size(); i < n; i++) {
minuend = this.intervals.get(i);
if (
!minuend.getStart().isInfinite()
&& iv.isBefore(minuend.getStart().getTemporal())
) {
parts.add(minuend);
for (int j = i + 1; j < n; j++) {
parts.add(this.intervals.get(j));
}
break; // short cut
} else {
diff = subtrahend.withComplement(minuend);
if (!diff.isEmpty()) {
parts.addAll(diff.intervals);
}
}
}
Collections.sort(parts, this.getComparator());
return this.create(parts);
}
/**
* <p>Subtracts all timepoints of given intervals from this interval
* collection. </p>
*
* @param intervals list of intervals to be subtracted
* @return new interval collection containing all timepoints of
* this instance excluding those of given intervals
* @throws IllegalArgumentException if given list contains a finite
* interval with open start which cannot be adjusted to one
* with closed start
* @since 2.2
*/
/*[deutsch]
* <p>Subtrahiert alle in den angegebenen Zeitintervallen enthaltenen
* Zeitpunkte von dieser Intervallmenge. </p>
*
* @param intervals list of intervals to be subtracted
* @return new interval collection containing all timepoints of
* this instance excluding those of given intervals
* @throws IllegalArgumentException if given list contains a finite
* interval with open start which cannot be adjusted to one
* with closed start
* @since 2.2
*/
public IntervalCollection<T> minus(List<? extends ChronoInterval<T>> intervals) {
if (
this.isEmpty()
|| intervals.isEmpty()
) {
return this;
}
List<ChronoInterval<T>> parts = new ArrayList<>();
List<ChronoInterval<T>> list = new ArrayList<>();
for (ChronoInterval<T> i : intervals) {
list.add(this.adjust(i));
}
Collections.sort(list, this.getComparator());
IntervalCollection<T> subtrahend = this.create(list);
for (int i = 0, n = this.intervals.size(); i < n; i++) {
ChronoInterval<T> minuend = this.intervals.get(i);
IntervalCollection<T> diff = subtrahend.withComplement(minuend);
if (!diff.isEmpty()) {
parts.addAll(diff.intervals);
}
}
Collections.sort(parts, this.getComparator());
return this.create(parts);
}
/**
* <p>Equivalent to {@code minus(other.getIntervals())}. </p>
*
* @param other another interval collection whose intervals are to be subtracted from this instance
* @return new interval collection containing all timepoints of this instance excluding those of argument
* @throws IllegalArgumentException if given collection contains a finite
* interval with open start which cannot be adjusted to one with closed start
* @since 3.7/4.5
*/
/*[deutsch]
* <p>Äquivalent zu {@code minus(other.getIntervals())}. </p>
*
* @param other another interval collection whose intervals are to be subtracted from this instance
* @return new interval collection containing all timepoints of this instance excluding those of argument
* @throws IllegalArgumentException if given collection contains a finite
* interval with open start which cannot be adjusted to one with closed start
* @since 3.7/4.5
*/
public IntervalCollection<T> minus(IntervalCollection<T> other) {
if (this == other) {
List<ChronoInterval<T>> zero = Collections.emptyList();
return this.create(zero);
}
return this.minus(other.getIntervals());
}
/**
* <p>Determines a filtered version of this interval collection within
* given range. </p>
*
* @param timeWindow time window filter
* @return new interval collection containing only timepoints within
* given range
* @throws IllegalArgumentException if given window is finite and has
* open start which cannot be adjusted to one with closed start
* @since 2.1
*/
/*[deutsch]
* <p>Bestimmt eine gefilterte Version dieser Intervallmenge
* innerhalb der angegebenen Grenzen. </p>
*
* @param timeWindow time window filter
* @return new interval collection containing only timepoints within
* given range
* @throws IllegalArgumentException if given window is finite and has
* open start which cannot be adjusted to one with closed start
* @since 2.1
*/
public IntervalCollection<T> withTimeWindow(ChronoInterval<T> timeWindow) {
return this.withFilter(this.adjust(timeWindow));
}
/**
* <p>Determines the complement of this interval collection within
* given range. </p>
*
* @param timeWindow time window filter
* @return new interval collection containing all timepoints within
* given range which do not belong to this instance
* @throws IllegalArgumentException if given window is finite and has
* open start which cannot be adjusted to one with closed start
* @since 2.1
*/
/*[deutsch]
* <p>Bestimmt die Komplementärmenge zu dieser Intervallmenge
* innerhalb der angegebenen Grenzen. </p>
*
* @param timeWindow time window filter
* @return new interval collection containing all timepoints within
* given range which do not belong to this instance
* @throws IllegalArgumentException if given window is finite and has
* open start which cannot be adjusted to one with closed start
* @since 2.1
*/
public IntervalCollection<T> withComplement(ChronoInterval<T> timeWindow) {
ChronoInterval<T> window = this.adjust(timeWindow);
IntervalCollection<T> coll = this.withFilter(window);
if (coll.isEmpty()) {
return this.create(Collections.singletonList(window));
}
Boundary<T> lower = window.getStart();
Boundary<T> upper = window.getEnd();
List<ChronoInterval<T>> gaps = new ArrayList<>();
// left edge
T min = coll.getMinimum();
if (min != null) {
if (lower.isInfinite()) {
this.addLeft(gaps, min);
} else {
T s = lower.getTemporal();
if (lower.isOpen()) {
s = this.getTimeLine().stepBackwards(s);
if (s == null) {
this.addLeft(gaps, min);
} else {
this.addLeft(gaps, s, min);
}
} else {
this.addLeft(gaps, s, min);
}
}
}
// inner gaps
gaps.addAll(coll.withGaps().getIntervals());
// right edge
T max = coll.getMaximum();
if (max != null) {
T s = this.getTimeLine().stepForward(max);
if (s != null) {
Boundary<T> bs = Boundary.ofClosed(s);
Boundary<T> be;
if (upper.isInfinite()) {
be = upper;
gaps.add(this.newInterval(bs, be));
} else if (this.isCalendrical()) {
if (upper.isClosed()) {
be = upper;
} else {
T e = upper.getTemporal();
e = this.getTimeLine().stepBackwards(e);
be = Boundary.ofClosed(e);
}
if (!this.isAfter(s, be.getTemporal())) {
gaps.add(this.newInterval(bs, be));
}
} else {
if (upper.isOpen()) {
be = upper;
} else {
T e = upper.getTemporal();
e = this.getTimeLine().stepForward(e);
if (e == null) {
be = Boundary.infiniteFuture();
} else {
be = Boundary.ofOpen(e);
}
}
if (this.isBefore(s, be.getTemporal())) {
gaps.add(this.newInterval(bs, be));
}
}
}
}
return this.create(gaps);
}
/**
* <p>Searches for all gaps with time points which are not covered by any
* interval of this instance. </p>
*
* <p><img src="doc-files/withGaps.jpg" alt="withGaps"></p>
*
* @return new interval collection containing the inner gaps between
* the own intervals while this instance remains unaffected
* @since 2.0
*/
/*[deutsch]
* <p>Sucht die Lücken mit allen Zeitpunkten, die nicht zu irgendeinem
* Intervall dieser Instanz gehören. </p>
*
* <p><img src="doc-files/withGaps.jpg" alt="withGaps"></p>
*
* @return new interval collection containing the inner gaps between
* the own intervals while this instance remains unaffected
* @since 2.0
*/
public IntervalCollection<T> withGaps() {
int len = this.intervals.size();
if (len == 0) {
return this;
} else if (len == 1) {
List<ChronoInterval<T>> zero = Collections.emptyList();
return this.create(zero);
}
List<ChronoInterval<T>> gaps = new ArrayList<>();
T previous = null;
for (int i = 0, n = len - 1; i < n; i++) {
ChronoInterval<T> current = this.intervals.get(i);
if (current.getEnd().isInfinite()) {
break;
}
T gapStart = current.getEnd().getTemporal();
if (current.getEnd().isClosed()) {
gapStart = this.getTimeLine().stepForward(gapStart);
if (gapStart == null) {
break;
}
}
if ((previous == null) || this.isAfter(gapStart, previous)) {
previous = gapStart;
} else {
gapStart = previous;
}
T gapEnd = this.intervals.get(i + 1).getStart().getTemporal();
if ((gapEnd == null) || !this.isAfter(gapEnd, gapStart)) {
continue;
}
IntervalEdge edge = IntervalEdge.OPEN;
if (this.isCalendrical()) {
edge = IntervalEdge.CLOSED;
gapEnd = this.getTimeLine().stepBackwards(gapEnd);
if (gapEnd == null) {
continue;
}
}
Boundary<T> s = Boundary.ofClosed(gapStart);
Boundary<T> e = Boundary.of(edge, gapEnd);
gaps.add(this.newInterval(s, e));
}
return this.create(gaps);
}
/**
* <p>Combines all intervals to disjunct blocks which neither overlap nor meet each other. </p>
*
* <p>Any overlapping or abutting intervals will be merged to one block. If the interval boundaries
* are still to be kept then consider {@link #withSplits()} instead. </p>
*
* <p><img src="doc-files/withBlocks.jpg" alt="withBlocks"></p>
*
* @return new interval collection containing disjunct merged blocks
* while this instance remains unaffected
* @since 2.0
*/
/*[deutsch]
* <p>Kombiniert alle Intervalle zu disjunkten Blöcken, die sich
* weder überlappen noch berühren. </p>
*
* <p>Alle Intervalle, die sich überlappen oder sich berühren, werden zu jeweils
* einem Block verschmolzen. Wenn die Intervallgrenzen noch erhalten bleiben sollen, dann
* ist {@link #withSplits()} wahrscheinlich die sinnvollere Methode. </p>
*
* <p><img src="doc-files/withBlocks.jpg" alt="withBlocks"></p>
*
* @return new interval collection containing disjunct merged blocks
* while this instance remains unaffected
* @since 2.0
*/
public IntervalCollection<T> withBlocks() {
if (this.intervals.size() < 2) {
return this;
}
Boundary<T> s;
Boundary<T> e;
boolean calendrical = this.isCalendrical();
IntervalEdge edge = (
calendrical
? IntervalEdge.CLOSED
: IntervalEdge.OPEN);
List<ChronoInterval<T>> gaps = this.withGaps().intervals;
List<ChronoInterval<T>> blocks = new ArrayList<>();
T start = this.getMinimum();
for (int i = 0, n = gaps.size(); i < n; i++) {
T end = gaps.get(i).getStart().getTemporal();
if (calendrical) {
end = this.getTimeLine().stepBackwards(end);
}
s = this.createStartBoundary(start);
e = Boundary.of(edge, end);
blocks.add(this.newInterval(s, e));
Boundary<T> b = gaps.get(i).getEnd();
start = b.getTemporal();
if (b.isClosed()) {
start = this.getTimeLine().stepForward(start);
}
}
T max = this.getMaximum();
s = this.createStartBoundary(start);
if ((max != null) && !calendrical) {
max = this.getTimeLine().stepForward(max);
}
if (max == null) {
e = Boundary.infiniteFuture();
} else {
e = Boundary.of(edge, max);
}
blocks.add(this.newInterval(s, e));
return this.create(blocks);
}
/**
* <p>Combines all intervals to disjunct blocks which never overlap but still might meet each other. </p>
*
* <p>Similar to {@link #withBlocks()} but all boundaries will be temporally conserved. </p>
*
* <p><img src="doc-files/withSplits.jpg" alt="withSplits"></p>
*
* @return new interval collection containing disjunct splitted sections
* while this instance remains unaffected
* @since 3.24/4.20
*/
/*[deutsch]
* <p>Kombiniert alle Intervalle zu disjunkten Blöcken, die sich
* nicht überlappen, aber noch sich berühren können. </p>
*
* <p>Ähnlich wie {@link #withBlocks()}, aber alle Intervallgrenzen werden zeitlich beibehalten. </p>
*
* <p><img src="doc-files/withSplits.jpg" alt="withSplits"></p>
*
* @return new interval collection containing disjunct splitted sections
* while this instance remains unaffected
* @since 3.24/4.20
*/
public IntervalCollection<T> withSplits() {
if (this.isDisjunct()) {
return this;
}
List<Boundary<T>> dividers = new ArrayList<>(); // always finite
Boundary<T> infinitePast = null;
Boundary<T> infiniteFuture = null;
for (ChronoInterval<T> interval : this.intervals) {
Boundary<T> start = interval.getStart();
Boundary<T> end = interval.getEnd();
if (start.isInfinite()) {
infinitePast = Boundary.infinitePast();
} else {
int index = this.searchFiniteBoundary(dividers, start);
if (index < 0) {
dividers.add(-index - 1, start);
}
}
if (end.isInfinite()) {
infiniteFuture = Boundary.infiniteFuture();
} else {
if (end.isClosed()) {
T time = this.getTimeLine().stepForward(end.getTemporal());
if (time == null) {
infiniteFuture = Boundary.infiniteFuture();
continue;
}
end = Boundary.ofClosed(time);
} else {
end = Boundary.ofClosed(end.getTemporal());
}
int index = this.searchFiniteBoundary(dividers, end);
if (index < 0) {
dividers.add(-index - 1, end);
}
}
}
List<ChronoInterval<T>> splitted = new ArrayList<>();
Boundary<T> start = infinitePast;
for (Boundary<T> divider : dividers) {
T time = divider.getTemporal();
boolean nextInterval = this.contains(time);
if (start != null) {
if (this.isCalendrical()) {
time = this.getTimeLine().stepBackwards(time);
if (time != null) {
splitted.add(this.newInterval(start, Boundary.ofClosed(time)));
}
} else {
splitted.add(this.newInterval(start, Boundary.ofOpen(time)));
}
}
if (nextInterval) {
start = divider;
} else {
start = null;
}
}
if ((start != null) && (infiniteFuture != null)) {
splitted.add(this.newInterval(start, infiniteFuture));
}
return this.create(splitted);
}
/**
* <p>Determines the intersection of all contained intervals. </p>
*
* <p>Note: This instance remains unaffected as specified for immutable
* classes. </p>
*
* <p><img src="doc-files/withIntersection.jpg" alt="withIntersection"></p>
*
* @return new interval collection containing the intersection interval,
* maybe empty (if there is no intersection)
* @since 2.0
*/
/*[deutsch]
* <p>Ermittelt die Schnittmenge aller enthaltenen Intervalle. </p>
*
* <p>Hinweis: Diese Instanz bleibt unverändert, weil die Klasse
* <i>immutable</i> (unveränderlich) ist. </p>
*
* <p><img src="doc-files/withIntersection.jpg" alt="withIntersection"></p>
*
* @return new interval collection containing the intersection interval,
* maybe empty (if there is no intersection)
* @since 2.0
*/
public IntervalCollection<T> withIntersection() {
int len = this.intervals.size();
if (len < 2) {
return this;
} else {
return this.create(this.intersect(this.intervals));
}
}
/**
* <p>Equivalent to {@code plus(other).withBlocks()}. </p>
*
* <p>Note: Before version 3.7/4.5 the behaviour was just giving an unmerged collection. </p>
*
* @param other another interval collection whose intervals are to be added to this instance
* @return new merged interval collection with disjunct blocks
* @since 2.0
*/
/*[deutsch]
* <p>Äquivalent zu {@code plus(other).withBlocks()}. </p>
*
* <p>Hinweis: Vor der Version 3.7/4.5 war das Verhalten so, daß nur eine Intervallmenge ohne
* notwendig disjunkte Blöcke geliefert wurde. </p>
*
* @param other another interval collection whose intervals are to be added to this instance
* @return new merged interval collection with disjunct blocks
* @since 2.0
*/
public IntervalCollection<T> union(IntervalCollection<T> other) {
return this.plus(other).withBlocks();
}
/**
* <p>Determines the intersection. </p>
*
* @param other another interval collection
* @return new interval collection with disjunct blocks containing all time points in both interval collections
* @since 3.8/4.5
*/
/*[deutsch]
* <p>Ermittelt die gemeinsame Schnittmenge. </p>
*
* @param other another interval collection
* @return new interval collection with disjunct blocks containing all time points in both interval collections
* @since 3.8/4.5
*/
public IntervalCollection<T> intersect(IntervalCollection<T> other) {
if (this.isEmpty() || other.isEmpty()) {
List<ChronoInterval<T>> zero = Collections.emptyList();
return this.create(zero);
}
List<ChronoInterval<T>> list = new ArrayList<>();
for (ChronoInterval<T> a : this.intervals) {
for (ChronoInterval<T> b : other.intervals) {
List<ChronoInterval<T>> candidates = new ArrayList<>(2);
candidates.add(a);
candidates.add(b);
Collections.sort(candidates, this.getComparator());
candidates = this.intersect(candidates);
if (!candidates.isEmpty()) {
list.addAll(candidates);
}
}
}
Collections.sort(list, this.getComparator());
return this.create(list).withBlocks();
}
/**
* <p>Determines the difference which holds all time points either in this <i>xor</i> the other collection. </p>
*
* @param other another interval collection
* @return new interval collection with disjunct blocks containing all time points which are in only one
* of both interval collections
* @since 3.8/4.5
*/
/*[deutsch]
* <p>Ermittelt die Differenz, die alle Zeitpunkte entweder in dieser oder in der anderen enthält. </p>
*
* @param other another interval collection
* @return new interval collection with disjunct blocks containing all time points which are in only one
* of both interval collections
* @since 3.8/4.5
*/
public IntervalCollection<T> xor(IntervalCollection<T> other) {
if (this.isEmpty()) {
return other;
} else if (other.isEmpty()) {
return this;
}
T min1 = this.getMinimum();
T max1 = this.getMaximum();
T min2 = other.getMinimum();
T max2 = other.getMaximum();
T min;
T max;
if ((min1 == null) || (min2 == null)) {
min = null;
} else {
min = (this.isAfter(min1, min2) ? min2 : min1);
}
if ((max1 == null) || (max2 == null)) {
max = null;
} else {
max = (this.isBefore(max1, max2) ? max2 : max1);
}
Boundary<T> start = this.createStartBoundary(min);
Boundary<T> end;
if (max == null) {
end = Boundary.infiniteFuture();
} else if (this.isCalendrical()) {
end = Boundary.ofClosed(max);
} else {
max = this.getTimeLine().stepForward(max);
if (max == null) {
end = Boundary.infiniteFuture();
} else {
end = Boundary.ofOpen(max);
}
}
ChronoInterval<T> window = this.newInterval(start, end);
List<ChronoInterval<T>> list = new ArrayList<>();
IntervalCollection<T> ic1 = this.withComplement(window).intersect(other);
IntervalCollection<T> ic2 = other.withComplement(window).intersect(this);
list.addAll(ic1.getIntervals());
list.addAll(ic2.getIntervals());
Collections.sort(list, this.getComparator());
return this.create(list).withBlocks();
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
} else if (obj instanceof IntervalCollection) {
IntervalCollection<?> that = IntervalCollection.class.cast(obj);
return (
this.getTimeLine().equals(that.getTimeLine())
&& this.intervals.equals(that.intervals)
);
}
return false;
}
@Override
public int hashCode() {
return this.intervals.hashCode();
}
/**
* <p>For debugging purposes. </p>
*
* @return String
*/
/*[deutsch]
* <p>Für Debugging-Zwecke. </p>
*
* @return String
*/
@Override
public String toString() {
int n = this.intervals.size();
StringBuilder sb = new StringBuilder(n * 30);
sb.append('{');
for (int i = 0; i < n; i++) {
sb.append(this.intervals.get(i));
if (i < n - 1) {
sb.append(',');
}
}
return sb.append('}').toString();
}
// Anzahl der internen Intervalle
int getSize() {
return this.intervals.size();
}
/**
* <p>Definiert ein Vergleichsobjekt zum Sortieren der Intervalle
* zuerst nach dem Start und dann nach dem Ende. </p>
*
* @return Comparator for intervals
*/
abstract Comparator<ChronoInterval<T>> getComparator();
/**
* <p>Erzeugt eine neue geänderte Kopie dieser Instanz. </p>
*
* @param intervals new sorted list of intervals
* @return IntervalCollection
*/
abstract IntervalCollection<T> create(List<ChronoInterval<T>> intervals);
/**
* <p>Liefert die zugehörige Zeitachse. </p>
*
* @return TimeLine
*/
abstract TimeLine<T> getTimeLine();
boolean isAfter(T t1, T t2) {
return (this.getTimeLine().compare(t1, t2) > 0);
}
boolean isBefore(T t1, T t2) {
return (this.getTimeLine().compare(t1, t2) < 0);
}
/**
* <p>Erzeugt ein Intervall zwischen den angegebenen Grenzen. </p>
*
* @return new interval
*/
abstract ChronoInterval<T> newInterval(
Boundary<T> start,
Boundary<T> end
);
/**
* <p>Kalendarische Intervalle sind bevorzugt geschlossen und müssen
* diese Methode so überschreiben, daß sie {@code true}
* zurückgeben. </p>
*
* @return boolean
*/
boolean isCalendrical() {
return false;
}
private ChronoInterval<T> adjust(ChronoInterval<T> interval) {
Boundary<T> start = interval.getStart();
if (start.isOpen() && !start.isInfinite()) {
T s = this.getTimeLine().stepForward(start.getTemporal());
if (s == null) {
throw new IllegalArgumentException(
"Interval start with open maximum: " + interval);
} else {
start = Boundary.ofClosed(s);
return this.newInterval(start, interval.getEnd());
}
}
return interval;
}
private IntervalCollection<T> withFilter(ChronoInterval<T> window) {
Boundary<T> lower = window.getStart();
Boundary<T> upper = window.getEnd();
if (
this.isEmpty()
|| (lower.isInfinite() && upper.isInfinite())
) {
return this;
}
List<ChronoInterval<T>> parts = new ArrayList<>();
for (ChronoInterval<T> interval : this.intervals) {
if (
interval.isFinite()
&& window.contains(interval.getStart().getTemporal())
&& window.contains(interval.getEnd().getTemporal())
) {
parts.add(interval);
continue;
}
List<ChronoInterval<T>> pair = new ArrayList<>(2);
pair.add(window);
pair.add(interval);
Collections.sort(pair, this.getComparator());
IntervalCollection<T> is = this.create(pair).withIntersection();
if (!is.isEmpty()) {
parts.add(is.getIntervals().get(0));
}
}
return this.create(parts);
}
private Boundary<T> createStartBoundary(T start) {
if (start == null) {
return Boundary.infinitePast();
} else {
return Boundary.ofClosed(start);
}
}
private void addLeft(
List<ChronoInterval<T>> gaps,
T min
) {
T e = this.getTimeLine().stepBackwards(min);
if (e != null) {
Boundary<T> be;
if (this.isCalendrical()) {
be = Boundary.ofClosed(e);
} else {
be = Boundary.ofOpen(min);
}
Boundary<T> bs = Boundary.infinitePast();
gaps.add(this.newInterval(bs, be));
}
}
private void addLeft(
List<ChronoInterval<T>> gaps,
T start,
T min
) {
if (this.isBefore(start, min)) {
Boundary<T> be;
if (this.isCalendrical()) {
be = Boundary.ofClosed(this.getTimeLine().stepBackwards(min));
} else {
be = Boundary.ofOpen(min);
}
Boundary<T> bs = Boundary.ofClosed(start);
gaps.add(this.newInterval(bs, be));
}
}
private List<ChronoInterval<T>> intersect(List<ChronoInterval<T>> components) {
int len = components.size();
if (len < 2) {
return components;
}
T latestStart = components.get(len - 1).getStart().getTemporal();
T earliestEnd = null;
for (int i = 0; i < len; i++) {
Boundary<T> b = components.get(i).getEnd();
T candidate = b.getTemporal();
if (b.isInfinite()) {
continue;
} else if (this.isCalendrical()) {
if (b.isOpen()) {
candidate = this.getTimeLine().stepBackwards(candidate);
}
} else if (b.isClosed()) {
candidate = this.getTimeLine().stepForward(candidate);
if (candidate == null) {
continue;
}
}
if ((earliestEnd == null) || this.isBefore(candidate, earliestEnd)) {
earliestEnd = candidate;
}
}
Boundary<T> s = null;
Boundary<T> e = null;
if (earliestEnd == null) {
s = this.createStartBoundary(latestStart);
e = Boundary.infiniteFuture();
} else if (this.isCalendrical()) {
if (!this.isBefore(earliestEnd, latestStart)) {
s = this.createStartBoundary(latestStart);
e = Boundary.ofClosed(earliestEnd);
}
} else if (this.isAfter(earliestEnd, latestStart)) {
s = this.createStartBoundary(latestStart);
e = Boundary.ofOpen(earliestEnd);
}
if ((s == null) || (e == null)) {
return Collections.emptyList();
} else {
ChronoInterval<T> interval = this.newInterval(s, e);
return Collections.singletonList(interval);
}
}
private int searchFiniteBoundary(
List<Boundary<T>> list,
Boundary<T> key
) {
int low = 0;
int high = list.size()-1;
while (low <= high) {
int mid = (low + high) >>> 1;
Boundary<T> midVal = list.get(mid);
T t1 = midVal.getTemporal();
T t2 = key.getTemporal();
int cmp = this.getTimeLine().compare(t1, t2);
if (cmp < 0) {
low = mid + 1;
} else if (cmp > 0) {
high = mid - 1;
} else {
return mid; // key found
}
}
return -(low + 1); // key not found
}
}