/* * ----------------------------------------------------------------------- * Copyright © 2013-2016 Meno Hochschild, <http://www.menodata.de/> * ----------------------------------------------------------------------- * This file (Boundary.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.engine.Temporal; import java.io.IOException; import java.io.InvalidObjectException; import java.io.ObjectInputStream; import java.io.Serializable; /** * <p>Represents an interval boundary, either the lower one or the * upper one. </p> * * @param <T> generic temporal type * @author Meno Hochschild * @since 2.0 * @doctags.concurrency {immutable} */ /*[deutsch] * <p>Repräsentiert eine Intervallgrenze, die entweder die untere * oder die obere Grenze eines Intervalls ist. </p> * * @param <T> generic temporal type * @author Meno Hochschild * @since 2.0 * @doctags.concurrency {immutable} */ public final class Boundary<T> implements Serializable { //~ Statische Felder/Initialisierungen -------------------------------- private static final int NORMAL = 0; private static final int PAST = 1; private static final int FUTURE = 2; @SuppressWarnings("rawtypes") private static final Boundary INFINITE_PAST = new Boundary(false); @SuppressWarnings("rawtypes") private static final Boundary INFINITE_FUTURE = new Boundary(true); private static final long serialVersionUID = -8193246842948266154L; //~ Instanzvariablen -------------------------------------------------- private transient final int mode; private transient final IntervalEdge edge; private transient final T temporal; //~ Konstruktoren ----------------------------------------------------- private Boundary(boolean future) { super(); this.mode = (future ? FUTURE : PAST); this.edge = IntervalEdge.OPEN; this.temporal = null; } private Boundary( IntervalEdge edge, T temporal ) { super(); if (edge == null) { throw new NullPointerException("Missing boundary type."); } else if (temporal == null) { throw new NullPointerException("Missing boundary time."); } this.mode = NORMAL; this.edge = edge; this.temporal = temporal; } //~ Methoden ---------------------------------------------------------- /** * <p>Defines a symbolic open boundary for the infinite past. </p> * * @param <T> generic temporal type * @return open boundary in infinite past without defined time * @since 2.0 */ /*[deutsch] * <p>Definiert eine symbolische offene Grenze für die unbegrenzte * Vergangenheit. </p> * * @param <T> generic temporal type * @return open boundary in infinite past without defined time * @since 2.0 */ @SuppressWarnings("unchecked") public static <T> Boundary<T> infinitePast() { return (Boundary<T>) INFINITE_PAST; } /** * <p>Defines a symbolic open boundary for the infinite future. </p> * * @param <T> generic temporal type * @return open boundary in infinite future without defined time * @since 2.0 */ /*[deutsch] * <p>Definiert eine symbolische offene Grenze für die unbegrenzte * Zukunft. </p> * * @param <T> generic temporal type * @return open boundary in infinite future without defined time * @since 2.0 */ @SuppressWarnings("unchecked") public static <T> Boundary<T> infiniteFuture() { return (Boundary<T>) INFINITE_FUTURE; } /** * <p>Defines a finite open boundary for the specified time * point. </p> * * @param <T> generic temporal type * @param temporal time of the boundary * @return finite open boundary * @since 2.0 * @see #infinitePast() * @see #infiniteFuture() * @see #isOpen() */ /*[deutsch] * <p>Definiert eine feste offene Grenze für den angegebenen * Zeitpunkt. </p> * * @param <T> generic temporal type * @param temporal time of the boundary * @return finite open boundary * @since 2.0 * @see #infinitePast() * @see #infiniteFuture() * @see #isOpen() */ public static <T> Boundary<T> ofOpen(T temporal) { return new Boundary<>(IntervalEdge.OPEN, temporal); } /** * <p>Defines a finite closed boundary for the specified * time point. </p> * * @param <T> generic temporal type * @param temporal time of the boundary * @return finite closed boundary * @since 2.0 * @see #infinitePast() * @see #infiniteFuture() * @see #isClosed() */ /*[deutsch] * <p>Definiert eine feste geschlossene Grenze für den * angegebenen Zeitpunkt. </p> * * @param <T> generic temporal type * @param temporal time of the boundary * @return finite closed boundary * @since 2.0 * @see #infinitePast() * @see #infiniteFuture() * @see #isClosed() */ public static <T> Boundary<T> ofClosed(T temporal) { return new Boundary<>(IntervalEdge.CLOSED, temporal); } /** * <p>Definiert eine feste Grenze für den angegebenen * Zeitpunkt. </p> * * @param <T> generic temporal type * @param edge boundary type * @param temporal time of the boundary * @return finite boundary * @since 2.0 * @see #infinitePast() * @see #infiniteFuture() * @see #isOpen() * @see #isClosed() */ static <T> Boundary<T> of( IntervalEdge edge, T temporal ) { return new Boundary<>(edge, temporal); } /** * <p>Yields the time point of this interval boundary. </p> * * @return time point or {@code null} if infinite * @since 2.0 * @see #isInfinite() */ /*[deutsch] * <p>Liefert den Zeitpunkt dieser Intervallgrenze. </p> * * @return time point or {@code null} if infinite * @since 2.0 * @see #isInfinite() */ public T getTemporal() { return this.temporal; } /** * <p>Determines if this boundary is open. </p> * * <p>If open then the associated time point does not belong to a given * interval with this boundary. </p> * * @return {@code true} if open else {@code false} * @since 2.0 * @see #ofOpen(Object) ofOpen(T) */ /*[deutsch] * <p>Gibt an, ob diese Intervalgrenze offen ist. </p> * * <p>Wenn offen, dann gehört die assoziierte Zeit dieser Grenze * nicht zu einem Intervall mit dieser Grenze. </p> * * @return {@code true} if open else {@code false} * @since 2.0 * @see #ofOpen(Object) ofOpen(T) */ public boolean isOpen() { return (this.edge == IntervalEdge.OPEN); } /** * <p>Determines if this boundary is closed. </p> * * <p>If closed then the associated time point belongs to a given * interval with this boundary. </p> * * @return {@code true} if closed else {@code false} * @since 2.0 * @see #ofClosed(Object) ofClosed(T) */ /*[deutsch] * <p>Gibt an, ob diese Intervalgrenze geschlossen ist. </p> * * <p>Wenn geschlossen, dann gehört die assoziierte Zeit dieser Grenze * zu einem Intervall mit dieser Grenze. </p> * * @return {@code true} if closed else {@code false} * @since 2.0 * @see #ofClosed(Object) ofClosed(T) */ public boolean isClosed() { return (this.edge == IntervalEdge.CLOSED); } /** * <p>Determines if this boundary is infinite. </p> * * <p>If infinite then the associated time point is not defined. </p> * * @return {@code true} if infinite else {@code false} * @since 2.0 * @see #getTemporal() */ /*[deutsch] * <p>Gibt an, ob diese Intervalgrenze unbegrenzt ist. </p> * * <p>Wenn unbegrenzt, dann hat diese Intervallgrenze keine definierte * Zeit. </p> * * @return {@code true} if infinite else {@code false} * @since 2.0 * @see #getTemporal() */ public boolean isInfinite() { return (this.temporal == null); } @Override public boolean equals(Object obj) { if (this == obj) { return true; } else if (obj instanceof Boundary) { Boundary<?> that = Boundary.class.cast(obj); if ( (this.edge != that.edge) || (this.mode != that.mode) ) { return false; } else if (this.temporal == null) { return (that.temporal == null); } else { return this.temporal.equals(that.temporal); } } else { return false; } } @Override public int hashCode() { int hash = 37 + (this.temporal != null ? this.temporal.hashCode() : 0); hash = 83 * hash + 5 * this.mode; return 83 * hash + this.edge.hashCode(); } /** * <p>Yields a descriptive string. </p> * * @return String (either "(-∞)", "(+∞)" * or "(temporal)" for finite open boundary or * "[temporal]" for finite closed boundary) */ /*[deutsch] * <p>Liefert eine Beschreibung. </p> * * @return String (either "(-∞)", "(+∞)" * or "(temporal)" for finite open boundary or * "[temporal]" for finite closed boundary) */ @Override public String toString() { if (this.mode == PAST) { return "(-\u221E)"; } else if (this.mode == FUTURE) { return "(+\u221E)"; } else { boolean open = this.isOpen(); StringBuilder sb = new StringBuilder(); sb.append(open ? '(' : '['); sb.append(this.temporal); sb.append(open ? ')' : ']'); return sb.toString(); } } /** * <p>Liefert den Intervallrand. </p> * * @return IntervalEdge * @since 2.0 */ IntervalEdge getEdge() { return this.edge; } /** * <p>Rein temporaler Vergleich ohne Ansehen der open/closed-Bedingung. </p> * * @param <T> generic temporal type * @param start starting boundary * @param end ending boundary * @return {@code true} if start is after end else {@code false} * @since 2.0 */ static <T extends Temporal<? super T>> boolean isAfter( Boundary<T> start, Boundary<T> end ) { if (start.mode == PAST) { return false; } else if (start.mode == FUTURE) { return (end.mode != FUTURE); } else if (end.mode == PAST) { return true; } else if (end.mode == FUTURE) { return false; } else { return start.temporal.isAfter(end.temporal); } } /** * <p>Rein temporaler Vergleich ohne Ansehen der open/closed-Bedingung. </p> * * @param <T> generic temporal type * @param start starting boundary * @param end ending boundary * @return {@code true} if start is simultaneous to end else {@code false} * @since 2.0 */ static <T extends Temporal<? super T>> boolean isSimultaneous( Boundary<T> start, Boundary<T> end ) { if (start.mode == PAST) { return (end.mode == PAST); } else if (start.mode == FUTURE) { return (end.mode == FUTURE); } else if (end.mode == PAST) { return false; } else if (end.mode == FUTURE) { return false; } else { return start.temporal.isSimultaneous(end.temporal); } } /** * @serialData Uses * <a href="../../../serialized-form.html#net.time4j.range.SPX"> * a dedicated serialization form</a> as proxy. The format * is bit-compressed. The first byte contains in the six most * significant bits the type-ID {@code 57}. The lowest bit is * {@code 1} if this instance is infinite past. The bit (2) * will be set if this instance is infinite future. After this * header byte and in case of finite boundary, one byte * follows describing the open/closed-state. Finally the * bytes for the temporal follow. * * Schematic algorithm: * * <pre> int header = 57; header <<= 2; if (this == Boundary.infinitePast()) { header |= 1; out.writeByte(header); } else if (this == Boundary.infiniteFuture()) { header |= 2; out.writeByte(header); } else { out.writeByte(header); out.writeByte(isOpen() ? 1 : 0); out.writeObject(getTemporal()); } </pre> * * @return replacement object in serialization graph */ private Object writeReplace() { return new SPX(this, SPX.BOUNDARY_TYPE); } /** * @serialData Blocks because a serialization proxy is required. * @param in object input stream * @throws InvalidObjectException (always) */ private void readObject(ObjectInputStream in) throws IOException { throw new InvalidObjectException("Serialization proxy required."); } }