/*
* -----------------------------------------------------------------------
* Copyright © 2013-2016 Meno Hochschild, <http://www.menodata.de/>
* -----------------------------------------------------------------------
* This file (TimeAxis.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.engine;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* <p>A time axis is a dynamic view on a chronology where a system of
* registered time units is used to define a time arithmetic for any
* time points belonging to this time axis respective chronology. </p>
*
* @param <U> generic type of time units
* @param <T> generic type of time context compatible to {@link TimePoint})
* @author Meno Hochschild
*/
/*[deutsch]
* <p>Eine Zeitachse ist eine dynamische Sicht auf eine Chronologie,
* in der mit Hilfe eines Systems von Zeiteinheiten eine Zeitarithmetik auf
* beliebigen Zeitpunkten der Chronologie definiert wird. </p>
*
* @param <U> generic type of time units
* @param <T> generic type of time context compatible to {@link TimePoint})
* @author Meno Hochschild
*/
public final class TimeAxis<U, T extends TimePoint<U, T>>
extends Chronology<T>
implements TimeLine<T> {
//~ Instanzvariablen --------------------------------------------------
private final Class<U> unitType;
private final Map<U, UnitRule<T>> unitRules;
private final Map<U, Double> unitLengths;
private final Map<U, Set<U>> convertibleUnits;
private final Map<ChronoElement<?>, U> baseUnits;
private final T min;
private final T max;
private final CalendarSystem<T> calendarSystem;
private final ChronoElement<T> self;
private final TimeLine<T> timeline;
//~ Konstruktoren -----------------------------------------------------
private TimeAxis(
Class<T> chronoType,
Class<U> unitType,
ChronoMerger<T> merger,
Map<ChronoElement<?>, ElementRule<T, ?>> ruleMap,
Map<U, UnitRule<T>> unitRules,
final Map<U, Double> unitLengths,
Map<U, Set<U>> convertibleUnits,
List<ChronoExtension> extensions,
Map<ChronoElement<?>, U> baseUnits,
T min,
T max,
CalendarSystem<T> calendarSystem, // optional
TimeLine<T> timeline, // optional
boolean useEnumUnits
) {
super(chronoType, merger, ruleMap, extensions);
Map<U, UnitRule<T>> uRules = unitRules;
if (useEnumUnits) {
uRules = new IdentityHashMap<>(unitRules.size());
uRules.putAll(unitRules);
}
this.unitType = unitType;
this.unitRules = uRules;
this.unitLengths = Collections.unmodifiableMap(unitLengths);
this.convertibleUnits = Collections.unmodifiableMap(convertibleUnits);
this.baseUnits = Collections.unmodifiableMap(baseUnits);
this.min = min;
this.max = max;
this.calendarSystem = calendarSystem;
this.self = new SelfElement<>(chronoType, min, max);
if (timeline == null) {
List<U> registeredUnits = new ArrayList<>(unitRules.keySet());
Collections.sort(
registeredUnits,
(unit1, unit2) -> Double.compare(
getLength(unitLengths, unit1),
getLength(unitLengths, unit2)));
U step = registeredUnits.get(0);
this.timeline = new DefaultTimeLine<>(step, min, max);
} else {
this.timeline = timeline;
}
}
//~ Methoden ----------------------------------------------------------
/**
* <p>Returns the type of supported time units. </p>
*
* @return reified type of time unit
*/
/*[deutsch]
* <p>Liefert den Zeiteinheitstyp. </p>
*
* @return reified type of time unit
*/
public Class<U> getUnitType() {
return this.unitType;
}
/**
* <p>Returns all registered time units. </p>
*
* @return unmodifiable set of registered units without duplicates
*/
/*[deutsch]
* <p>Liefert alle registrierten Zeiteinheiten. </p>
*
* @return unmodifiable set of registered units without duplicates
*/
public Set<U> getRegisteredUnits() {
return Collections.unmodifiableSet(this.unitRules.keySet());
}
/**
* <p>Queries if given time unit is registered. </p>
*
* @param unit time unit (optional)
* @return {@code true} if registered else {@code false}
*/
/*[deutsch]
* <p>Ist die angegebene Zeiteinheit registriert? </p>
*
* @param unit time unit (optional)
* @return {@code true} if registered else {@code false}
*/
public boolean isRegistered(U unit) {
return this.unitRules.containsKey(unit);
}
/**
* <p>Queries if given time unit is supported. </p>
*
* <p>A time unit is supported if it is either registered or
* if it defines a suitable rule. </p>
*
* @param unit time unit (optional)
* @return {@code true} if supported else {@code false}
* @see BasicUnit#derive(Chronology)
*/
/*[deutsch]
* <p>Wird die angegebene Zeiteinheit unterstützt? </p>
*
* <p>Unterstützung ist gegeben, wenn die Einheit entweder registriert
* ist oder eine zu dieser Chronologie passende Regel definiert. </p>
*
* @param unit time unit (optional)
* @return {@code true} if supported else {@code false}
* @see BasicUnit#derive(Chronology)
*/
public boolean isSupported(U unit) {
if (this.isRegistered(unit)) {
return true;
} else if (unit instanceof BasicUnit) {
return (BasicUnit.class.cast(unit).derive(this) != null);
} else {
return false;
}
}
/**
* <p>Returns the length of given time unit in seconds as it is
* usual or estimated on this time axis. </p>
*
* <p>Example: In ISO-systems the year has {@code 365.2425 * 86400}
* seconds by default (mean average), in a julian calender
* {@code 365.25 * 86400} seconds however. Daylight-saving-transitions
* or UTC-leapseconds are not counted here. </p>
*
* <p>Note: If given time unit is not registered then Time4J tries
* to interprete the unit as {@code ChronoUnit}. If this fails, too,
* then the length is not calculatable. </p>
*
* @param unit time unit
* @return estimated standard length in seconds or
* {@code Double.NaN} if not calculatable
* @see ChronoUnit
*/
/*[deutsch]
* <p>Liefert die in dieser Chronologie übliche Länge der
* angegebenen Zeiteinheit in Sekunden. </p>
*
* <p>Beispiel: In ISO-Systemen hat das Jahr standardmäßig
* {@code 365.2425 * 86400} Sekunden, in einem julianischen Kalender
* hingegen {@code 365.25 * 86400} Sekunden. DST-Übergänge
* in Zeitzonen oder UTC-Schaltsekunden werden nicht mitgezählt. </p>
*
* <p>Hinweis: Ist die angegebene Zeiteinheit nicht registriert, wird
* versucht, die Zeiteinheit als {@code ChronoUnit} zu interpretieren.
* Schlägt auch das fehl, ist die Länge nicht ermittelbar. </p>
*
* @param unit time unit
* @return estimated standard length in seconds or
* {@code Double.NaN} if not calculatable
* @see ChronoUnit
*/
public double getLength(U unit) {
return getLength(this.unitLengths, unit);
}
/**
* <p>Queries if given time units are convertible. </p>
*
* <p>Convertibility means that there exists a fixed integer factor
* for conversion between the units. Examples for convertible units
* are weeks/days (factor 7) or years/months (factor 12) in ISO-systems.
* Otherwise minutes and seconds will only be convertible with factor
* {@code 60} if there is no UTC-context with possible leap seconds. </p>
*
* <p>If two time units are convertible then the length of a time unit
* ({@code getLength()}) can be used to convert time units by applying
* the rounded quotient of lengths of units. </p>
*
* @param unit1 first time unit
* @param unit2 second time unit
* @return {@code true} if convertible else {@code false}
* @see #getLength(Object) getLength(U)
*/
/*[deutsch]
* <p>Sind die angegebenen Zeiteinheiten ineinander konvertierbar? </p>
*
* <p>Konvertierbarkeit bedeutet, daß immer ein konstanter
* ganzzahliger Faktor zur Umrechnung zwischen den Zeiteinheiten
* angewandt werden kann. Beispiele für konvertierbare Einheiten
* sind in ISO-basierten Kalendersystemen die Paare Wochen/Tage
* (Faktor 7) oder Jahre/Monate (Faktor 12). Andererseits sind Minuten
* und Sekunden zueinander nur dann konvertierbar mit dem Faktor
* {@code 60}, wenn kein UTC-Kontext mit möglichen Schaltsekunden
* vorliegt. </p>
*
* <p>Ist die Konvertierbarkeit gegeben, darf die Länge einer
* Zeiteinheit mittels der Methode {@code getLength()} herangezogen
* werden, um Zeiteinheiten umzurechnen, indem als Faktor der gerundete
* Quotient der Längen der Zeiteinheiten bestimmt wird. </p>
*
* @param unit1 first time unit
* @param unit2 second time unit
* @return {@code true} if convertible else {@code false}
* @see #getLength(Object) getLength(U)
*/
public boolean isConvertible(
U unit1,
U unit2
) {
Set<U> set = this.convertibleUnits.get(unit1);
return ((set != null) && set.contains(unit2));
}
/**
* <p>Queries if given element has a base unit. </p>
*
* @param element chronological element (optional)
* @return {@code true} if given element has a base unit else {@code false}
* @see #getBaseUnit(ChronoElement)
*/
/*[deutsch]
* <p>Ermittelt, ob das angegebene Element eine Basiseinheit hat. </p>
*
* @param element chronological element (optional)
* @return {@code true} if given element has a base unit else {@code false}
* @see #getBaseUnit(ChronoElement)
*/
public boolean hasBaseUnit(ChronoElement<?> element) {
if (element == null) {
return false;
}
boolean found = this.baseUnits.containsKey(element);
if (
!found
&& (element instanceof BasicElement)
) {
ChronoElement<?> parent = ((BasicElement) element).getParent();
found = ((parent != null) && this.baseUnits.containsKey(parent));
}
return found;
}
/**
* <p>Returns the base unit of given element if available. </p>
*
* <p>Only registred elements can have a base unit unless the element
* is a {@code BasicElement} and refers another registered element with
* a base unit. </p>
*
* @param element chronological element
* @return found base unit
* @throws ChronoException if there is no base unit
* @see #hasBaseUnit(ChronoElement)
* @see BasicElement#getParent()
*/
/*[deutsch]
* <p>Liefert die Basiseinheit zum angegebenen Element. </p>
*
* <p>Nur registrierte Elemente können eine Basiseinheit haben, es
* sei denn, das angegebene Element ist ein {@code BasicElement} und
* referenziert ein anderes auf dieser Zeitachse registriertes Element
* mit einer Basiseinheit. </p>
*
* @param element chronological element
* @return found base unit
* @throws ChronoException if there is no base unit
* @see #hasBaseUnit(ChronoElement)
* @see BasicElement#getParent()
*/
public U getBaseUnit(ChronoElement<?> element) {
if (element == null) {
throw new NullPointerException("Missing element.");
}
U baseUnit = this.baseUnits.get(element);
if (
(baseUnit == null)
&& (element instanceof BasicElement)
) {
ChronoElement<?> parent = ((BasicElement) element).getParent();
baseUnit = this.baseUnits.get(parent);
}
if (baseUnit == null) {
throw new ChronoException(
"Base unit not found for: " + element.name());
}
return baseUnit;
}
/**
* <p>Compares time units by ascending precision (that is descending length). </p>
*
* <p>Note: Before release v4.21, the time axis implemented {@code Comparator<U>}, not {@code Comparator<T>}.
* This new method serves as the replacement for the old comparator method. </p>
*
* @return Comparator
* @see #compare(TimePoint, TimePoint)
* @since 3.25/4.21
*/
/*[deutsch]
* <p>Vergleicht Zeiteinheiten nach wachsender Genauigkeit (also abnehmender Länge). </p>
*
* <p>Hinweis: Vor dem Release v4.21 hat diese Klasse das Interface {@code Comparator<U>} und
* nicht {@code Comparator<T>} implementiert. Diese neue Methode dient als Ersatz für die
* alte Vergleichsmethode. </p>
*
* @return Comparator
* @see #compare(TimePoint, TimePoint)
* @since 3.25/4.21
*/
public Comparator<? super U> unitComparator() {
return (unit1, unit2) -> Double.compare(this.getLength(unit2), this.getLength(unit1));
}
/**
* <p>Compares points in time by their temporal position on the timeline. </p>
*
* @param first the first point in comparison
* @param second the second point in comparison
* @return positive, zero or negative number if {@code first} is later, simultaneous or earlier than {@code second}
* @since 3.25/4.21
*/
/*[deutsch]
* <p>Vergleicht Zeitpunkte nach ihrer temporalen Position auf dem Zeitstrahl. </p>
*
* @param first the first point in comparison
* @param second the second point in comparison
* @return positive, zero or negative number if {@code first} is later, simultaneous or earlier than {@code second}
* @since 3.25/4.21
*/
@Override
public int compare(
T first,
T second
) {
return first.compareTo(second);
}
/**
* <p>Yields the minimum of this time axis. </p>
*
* @return earliest possible time point
*/
/*[deutsch]
* <p>Ermittelt das Minimum auf der Zeitachse. </p>
*
* @return earliest possible time point
*/
public T getMinimum() {
return this.min;
}
/**
* <p>Yields the maximum of this time axis. </p>
*
* @return latest possible time point
*/
/*[deutsch]
* <p>Ermittelt das Maximum auf der Zeitachse. </p>
*
* @return latest possible time point
*/
public T getMaximum() {
return this.max;
}
@Override
public boolean hasCalendarSystem() {
return (this.calendarSystem != null);
}
@Override
public CalendarSystem<T> getCalendarSystem() {
if (this.calendarSystem == null) {
return super.getCalendarSystem();
} else {
return this.calendarSystem;
}
}
@Override
public CalendarSystem<T> getCalendarSystem(String variant) {
if (variant.isEmpty()) {
return this.getCalendarSystem();
}
return super.getCalendarSystem(variant);
}
@Override
@Deprecated
public T createFrom(
ChronoEntity<?> entity,
AttributeQuery attributes,
boolean preparsing
) {
if (entity.contains(this.self)) {
return entity.get(this.self);
}
return super.createFrom(entity, attributes, preparsing);
}
@Override
public T createFrom(
ChronoEntity<?> entity,
AttributeQuery attributes,
boolean lenient,
boolean preparsing
) {
if (entity.contains(this.self)) {
return entity.get(this.self);
}
return super.createFrom(entity, attributes, lenient, preparsing);
}
/**
* <p>Yields this time axis as chronological self-referencing
* element. </p>
*
* @return self-referencing element
*/
/*[deutsch]
* <p>Liefert diese Zeitachse als chronologisches Element mit
* Selbstbezug. </p>
*
* @return self-referencing element
*/
public ChronoElement<T> element() {
return this.self;
}
@Override
public T stepForward(T timepoint) {
return this.timeline.stepForward(timepoint);
}
@Override
public T stepBackwards(T timepoint) {
return this.timeline.stepBackwards(timepoint);
}
/**
* <p>Liefert die chronologische Regel zur angegebenen Zeiteinheit. </p>
*
* @param unit time unit
* @return unit rule or {@code null} if not registered
*/
UnitRule<T> getRule(U unit) {
if (unit == null) {
throw new NullPointerException("Missing chronological unit.");
}
UnitRule<T> rule = this.unitRules.get(unit);
if (rule == null) {
if (unit instanceof BasicUnit) {
rule = BasicUnit.class.cast(unit).derive(this);
}
if (rule == null) {
throw new RuleNotFoundException(this, unit);
}
}
return rule;
}
private static <U> double getLength(
Map<U, Double> unitLengths,
U unit
) {
Double length = unitLengths.get(unit);
if (length == null) {
if (unit instanceof ChronoUnit) {
return ChronoUnit.class.cast(unit).getLength();
} else {
return Double.NaN;
}
} else {
return length.doubleValue();
}
}
//~ Innere Klassen ----------------------------------------------------
/**
* <p>Creates a builder for a new time axis respective chronology
* and will only be used during loading a class of type
* {@code TimePoint (T)} in a <i>static initializer</i>. </p>
*
* <p>Instances of this class will be created by the static factory
* methods {@code setUp()}. </p>
*
* @param <U> generic type of time unit
* @param <T> generic type of time context
* @author Meno Hochschild
* @see #setUp(Class,Class,ChronoMerger,TimePoint,TimePoint)
* @see #setUp(Class,Class,ChronoMerger,CalendarSystem)
* @doctags.concurrency {mutable}
*/
/*[deutsch]
* <p>Erzeugt einen Builder für eine neue Zeitachse respektive
* Chronologie und wird ausschließlich beim Laden einer
* {@code TimePoint}-Klasse T in einem <i>static initializer</i>
* benutzt. </p>
*
* <p>Instanzen dieser Klasse werden über die statischen
* {@code setUp()}-Fabrikmethoden erzeugt. </p>
*
* @param <U> generic type of time unit
* @param <T> generic type of time context
* @author Meno Hochschild
* @see #setUp(Class,Class,ChronoMerger,TimePoint,TimePoint)
* @see #setUp(Class,Class,ChronoMerger,CalendarSystem)
* @doctags.concurrency {mutable}
*/
public static final class Builder<U, T extends TimePoint<U, T>>
extends Chronology.Builder<T> {
//~ Instanzvariablen ----------------------------------------------
private final Class<U> unitType;
private final Map<U, UnitRule<T>> unitRules;
private final Map<U, Double> unitLengths;
private final Map<U, Set<U>> convertibleUnits;
private final Map<ChronoElement<?>, U> baseUnits;
private final T min;
private final T max;
private final CalendarSystem<T> calendarSystem;
private TimeLine<T> timeline = null;
private boolean useEnumUnits = true;
//~ Konstruktoren -------------------------------------------------
private Builder(
Class<U> unitType,
Class<T> chronoType,
ChronoMerger<T> merger,
T min,
T max,
CalendarSystem<T> calendarSystem, // optional
TimeLine<T> timeline
) {
super(chronoType, merger);
if (unitType == null) {
throw new NullPointerException("Missing unit type.");
} else if (min == null) {
throw new NullPointerException("Missing minimum of range.");
} else if (max == null) {
throw new NullPointerException("Missing maximum of range.");
} else if (
Calendrical.class.isAssignableFrom(chronoType)
&& (calendarSystem == null)
) {
throw new NullPointerException("Missing calendar system.");
}
this.unitType = unitType;
this.unitRules = new HashMap<>();
this.unitLengths = new HashMap<>();
this.convertibleUnits = new HashMap<>();
this.baseUnits = new HashMap<>();
this.min = min;
this.max = max;
this.calendarSystem = calendarSystem;
this.timeline = timeline;
}
//~ Methoden ------------------------------------------------------
/**
* <p>Creates a builder for building a chronological but
* non-calendrical system. </p>
*
* @param <U> generic type of time unit
* @param <T> generic type of time context
* @param unitType reified type of time units
* @param chronoType reified chronological type
* @param merger generic replacement for static
* creation of time points
* @param min minimum value on time axis
* @param max maximum value on time axis
* @return new {@code Builder} object
*/
/*[deutsch]
* <p>Erzeugt ein Hilfsobjekt zum Bauen eines chronologischen, aber
* nicht kalendarischen Systems. </p>
*
* @param <U> generic type of time unit
* @param <T> generic type of time context
* @param unitType reified type of time units
* @param chronoType reified chronological type
* @param merger generic replacement for static
* creation of time points
* @param min minimum value on time axis
* @param max maximum value on time axis
* @return new {@code Builder} object
*/
public static <U, T extends TimePoint<U, T>> Builder<U, T> setUp(
Class<U> unitType,
Class<T> chronoType,
ChronoMerger<T> merger,
T min,
T max
) {
return new Builder<>(
unitType,
chronoType,
merger,
min,
max,
null,
null);
}
/**
* <p>Creates a builder for building a time axis for
* plain calendrical objects. </p>
*
* @param <U> generic type of time unit
* @param <D> generic type of date context
* @param unitType reified type of time units
* @param chronoType reified chronological type
* @param merger generic replacement for static
* creation of time points
* @param calendarSystem calender system
* @return new {@code Builder} object
*/
/*[deutsch]
* <p>Erzeugt ein Hilfsobjekt zum Bauen einer Zeitachse für
* reine Datumsangaben. </p>
*
* @param <U> generic type of time unit
* @param <D> generic type of date context
* @param unitType reified type of time units
* @param chronoType reified chronological type
* @param merger generic replacement for static
* creation of time points
* @param calendarSystem calender system
* @return new {@code Builder} object
*/
public static <U, D extends Calendrical<U, D>> Builder<U, D> setUp(
Class<U> unitType,
Class<D> chronoType,
ChronoMerger<D> merger,
CalendarSystem<D> calendarSystem
) {
final CalendarSystem<D> calsys = calendarSystem;
Builder<U, D> builder =
new Builder<>(
unitType,
chronoType,
merger,
calsys.transform(calsys.getMinimumSinceUTC()),
calsys.transform(calsys.getMaximumSinceUTC()),
calsys,
null
);
for (EpochDays element : EpochDays.values()) {
builder.appendElement(element, element.derive(calsys));
}
return builder;
}
@Override
public <V> Builder<U, T> appendElement(
ChronoElement<V> element,
ElementRule<T, V> rule
) {
super.appendElement(element, rule);
return this;
}
/**
* <p>Registers a new element with associated rule and a base
* unit. </p>
*
* @param <V> generic type of element values
* @param element chronological element to be registered
* @param rule associated element rule
* @param baseUnit base unit for rolling operations
* @return this instance for method chaining
* @throws IllegalArgumentException if given element is already
* registered (duplicate)
*/
/*[deutsch]
* <p>Registriert ein neues Element mitsamt der assoziierten Regel
* und einer Basiseinheit. </p>
*
* @param <V> generic type of element values
* @param element chronological element to be registered
* @param rule associated element rule
* @param baseUnit base unit for rolling operations
* @return this instance for method chaining
* @throws IllegalArgumentException if given element is already
* registered (duplicate)
*/
public <V> Builder<U, T> appendElement(
ChronoElement<V> element,
ElementRule<T, V> rule,
U baseUnit
) {
if (baseUnit == null) {
throw new NullPointerException("Missing base unit.");
}
super.appendElement(element, rule);
this.baseUnits.put(element, baseUnit);
return this;
}
/**
* <p>Registers a non-convertible time unit with an associated
* unit rule. </p>
*
* <p>Is equivalent to {@link #appendUnit(Object,UnitRule,double,Set)
* appendUnit(U, rule, length, Collections.emptySet())}. </p>
*
* @param unit time unit to be registered
* @param rule associated unit rule
* @param length estimated standard length in seconds
* @return this instance for method chaining
* @throws IllegalArgumentException if given time unit is already
* registered (duplicate) or if given length does not represent
* any decimal number
*/
/*[deutsch]
* <p>Registriert eine neue nicht-konvertierbare Zeiteinheit mitsamt
* Einheitsregel. </p>
*
* <p>Entspricht {@link #appendUnit(Object,UnitRule,double,Set)
* appendUnit(U, rule, length, Collections.emptySet())}. </p>
*
* @param unit time unit to be registered
* @param rule associated unit rule
* @param length estimated standard length in seconds
* @return this instance for method chaining
* @throws IllegalArgumentException if given time unit is already
* registered (duplicate) or if given length does not represent
* any decimal number
*/
public Builder<U, T> appendUnit(
U unit,
UnitRule<T> rule,
double length
) {
Set<U> none = Collections.emptySet();
return this.appendUnit(unit, rule, length, none);
}
/**
* <p>Registers a new time unit with an associated unit rule. </p>
*
* <p>The unit rule defines the time arithmetic for addition and
* subtraction of given unit suitable for the time axis. </p>
*
* <p>If the unit has a length of a whole day then the given length
* must be equal to {@code 86400.0}. The time unit of a second has
* always the length {@code 1.0}. </p>
*
* <p>The default length of a time unit primarily serves for
* conversion purposes. Rare anomalies like leap seconds or
* timezone-induced jumps are not taken into account. Therefore
* this estimated length is usually not equal to the real length
* in a given time context. In case of non-convertible units the
* estimated length has only informational meaning. Example: Months
* have as length in ISO-systems the length of a gregorian year
* in seconds divided by {@code 12} while in coptic calendar the
* divisor {@code 13} is used. However, the definition of the length
* of a month in days is not suitable because months and days are
* not convertible. </p>
*
* <p>Convertibility exists if a time unit con be converted to
* another time unit using a fixed integer factor. If this is not
* always the case then an empty {@code Set} is to be used. Example
* minutes/seconds: Without an UTC-context (with possible leapseonds)
* minutes are convertible to seconds using a constant factor of
* {@code 60}. In an UTC-context however, there is no convertibility
* and hence the second will be missing in the argument.
* {@code convertibleUnits} to minutes as unit to be registered
* (and reverse, too).. </p>
*
* @param unit time unit to be registered
* @param rule associated unit rule
* @param length estimated standard length in seconds
* @param convertibleUnits other time units which {@code unit}
* can be converted to
* @return this instance for method chaining
* @throws IllegalArgumentException if given time unit is already
* registered (duplicate) or if given length does not represent
* any decimal number
*/
/*[deutsch]
* <p>Registriert eine neue Zeiteinheit mitsamt Einheitsregel. </p>
*
* <p>Die Regel zur Zeiteinheit definiert die Zeitarithmetik in der
* Addition und Subtraktion passend zur aktuellen Chronologie. </p>
*
* <p>Liegt eine Zeiteinheit mit Tageslänge vor, so ist stets
* die Länge von {@code 86400.0} anzugeben. Die Zeiteinheit der
* Sekunde selbst hat die Länge {@code 1.0}. </p>
*
* <p>Die Standardlänge einer Zeiteinheit dient in erster Linie
* der Konversion von zwei nach dem Kriterium der Genauigkeit
* benachbarten Zeiteinheiten. Seltene Anomalien wie Schaltsekunden
* oder zeitzonenbedingte Lücken werden dabei nicht kalkuliert.
* Deshalb ist die Standardlänge nicht mit der im gegebenen
* Zeitkontext gegebenen realen Länge einer Zeiteinheit
* gleichzusetzen. Sind außerdem Zeiteinheiten nicht
* konvertierbar, so hat auch die Standardlänge nur eine
* rein informelle Bedeutung. Beispiel: Monate haben in ISO-Systemen
* die Länge eines Jahres in Sekunden dividiert durch {@code 12},
* während im koptischen Kalender konstant der Dividend {@code 13}
* beträgt. Hingegen ist eine Definition der Länge eines
* Monats in Form von x Tagen ungeeignet, weil Monate und Tage
* zueinander nicht konvertierbar sind. </p>
*
* <p>Als konvertierbar gilt die Zeiteinheit genau dann, wenn sie sich
* mit einem festen ganzzahligen Faktor in eine andere Zeiteinheit
* umrechnen lässt. Ist das nicht der Fall, dann ist ein leeres
* {@code Set} anzugeben. Beispiel Minuten/Sekunden: Ohne einen
* vorhandenen UTC-Kontext (mit möglichen Schaltsekunden) sind
* Minuten zu konstant 60 Sekunden konvertierbar. In einem UTC-Kontext
* darf jedoch die Sekundeneinheit nicht als konvertierbar angegeben
* werden und fehlt deshalb im Argument {@code convertibleUnits} zu
* Minuten als zu registrierender Zeiteinheit (und umgekehrt). </p>
*
* @param unit time unit to be registered
* @param rule associated unit rule
* @param length estimated standard length in seconds
* @param convertibleUnits other time units which {@code unit}
* can be converted to
* @return this instance for method chaining
* @throws IllegalArgumentException if given time unit is already
* registered (duplicate) or if given length does not represent
* any decimal number
*/
public Builder<U, T> appendUnit(
U unit,
UnitRule<T> rule,
double length,
Set<? extends U> convertibleUnits
) {
if (unit == null) {
throw new NullPointerException("Missing time unit.");
} else if (rule == null) {
throw new NullPointerException("Missing unit rule.");
}
this.checkUnitDuplicates(unit);
if (convertibleUnits.contains(null)) {
throw new NullPointerException(
"Found convertible unit which is null.");
}
if (Double.isNaN(length)) {
throw new IllegalArgumentException("Not a number: " + length);
} else if (Double.isInfinite(length)) {
throw new IllegalArgumentException("Infinite: " + length);
}
this.useEnumUnits = this.useEnumUnits && (unit instanceof Enum);
this.unitRules.put(unit, rule);
this.unitLengths.put(unit, length);
Set<U> set = new HashSet<>(convertibleUnits);
set.remove(unit); // Selbstbezug entfernen
this.convertibleUnits.put(unit, set);
return this;
}
@Override
public Builder<U, T> appendExtension(ChronoExtension extension) {
super.appendExtension(extension);
return this;
}
/**
* <p>Defines the argument as timeline to be used for stepping
* forward or backwards. </p>
*
* @param timeline time line to be used
* @return this instance for method chaining
* @since 2.0
*/
/*[deutsch]
* <p>Definiert das Argument als Zeitstrahl, auf dem schrittweise
* vorwärts oder rückwärts gegangen wird. </p>
*
* @param timeline time line to be used
* @return this instance for method chaining
* @since 2.0
*/
public Builder<U, T> withTimeLine(TimeLine<T> timeline) {
if (timeline == null) {
throw new NullPointerException("Missing time line.");
}
this.timeline = timeline;
return this;
}
/**
* <p>Creates and registers a time axis. </p>
*
* @return new chronology as time axis
* @throws IllegalStateException if already registered or in
* case of inconsistencies
*/
/*[deutsch]
* <p>Erzeugt und registriert eine Zeitachse. </p>
*
* @return new chronology as time axis
* @throws IllegalStateException if already registered or in
* case of inconsistencies
*/
@Override
public TimeAxis<U, T> build() {
if (this.unitRules.isEmpty()) {
throw new IllegalStateException("No time unit was registered.");
}
TimeAxis<U, T> engine =
new TimeAxis<>(
this.chronoType,
this.unitType,
this.merger,
this.ruleMap,
this.unitRules,
this.unitLengths,
this.convertibleUnits,
this.extensions,
this.baseUnits,
this.min,
this.max,
this.calendarSystem,
this.timeline,
this.useEnumUnits
);
Chronology.register(engine);
return engine;
}
private void checkUnitDuplicates(U unit) {
if (this.time4j) {
return;
}
// Instanzprüfung
for (U key : this.unitRules.keySet()) {
if (key.equals(unit)) {
throw new IllegalArgumentException(
"Unit duplicate found: " + unit.toString());
}
}
// Namensprüfung
if (unit instanceof Enum) {
String name = Enum.class.cast(unit).name();
for (U key : this.unitRules.keySet()) {
if (
(key instanceof Enum)
&& Enum.class.cast(key).name().equals(name)
) {
throw new IllegalArgumentException(
"Unit duplicate found: " + name);
}
}
}
}
}
private static class SelfElement<T extends TimePoint<?, T>>
extends BasicElement<T>
implements ElementRule<T, T> {
//~ Statische Felder/Initialisierungen ----------------------------
private static final long serialVersionUID = 4777240530511579802L;
//~ Instanzvariablen ----------------------------------------------
private final Class<T> type;
private final T min;
private final T max;
//~ Konstruktoren -------------------------------------------------
private SelfElement(
Class<T> type,
T min,
T max
) {
super(type.getName() + "-AXIS");
this.type = type;
this.min = min;
this.max = max;
}
//~ Methoden ------------------------------------------------------
@Override
public Class<T> getType() {
return this.type;
}
@Override
public T getDefaultMinimum() {
return this.min;
}
@Override
public T getDefaultMaximum() {
return this.max;
}
@Override
public boolean isDateElement() {
return false;
}
@Override
public boolean isTimeElement() {
return false;
}
@Override
public T getValue(T context) {
return context;
}
@Override
public T getMinimum(T context) {
return this.getDefaultMinimum();
}
@Override
public T getMaximum(T context) {
return this.getDefaultMaximum();
}
@Override
public boolean isValid(
T context,
T value
) {
return (value != null);
}
@Override
public T withValue(
T context,
T value,
boolean lenient
) {
if (value == null) {
throw new IllegalArgumentException("Missing value.");
}
return value;
}
@Override
public ChronoElement<?> getChildAtFloor(T context) {
throw new UnsupportedOperationException();
}
@Override
public ChronoElement<?> getChildAtCeiling(T context) {
throw new UnsupportedOperationException();
}
@Override
@SuppressWarnings("unchecked")
protected <X extends ChronoEntity<X>> ElementRule<X, T> derive(Chronology<X> chronology) {
if (chronology.getChronoType().equals(this.type)) {
return (ElementRule<X, T>) this;
}
return null;
}
@Override
protected boolean isSingleton() {
return true; // used only once per chronology
}
@Override
protected String getVeto(Chronology<?> chronology) {
return null;
}
}
private static class DefaultTimeLine<U, T extends TimePoint<U, T>>
implements TimeLine<T> {
//~ Instanzvariablen ----------------------------------------------
private final U step;
private final T min;
private final T max;
//~ Konstruktoren -------------------------------------------------
DefaultTimeLine(
U step,
T min,
T max
) {
super();
this.step = step;
this.min = min;
this.max = max;
}
//~ Methoden ------------------------------------------------------
@Override
public T stepForward(T timepoint) {
if (timepoint.compareTo(this.max) >= 0) {
return null;
}
return timepoint.plus(1, this.step);
}
@Override
public T stepBackwards(T timepoint) {
if (timepoint.compareTo(this.min) <= 0) {
return null;
}
return timepoint.minus(1, this.step);
}
@Override
public T getMinimum() {
return this.min;
}
@Override
public T getMaximum() {
return this.max;
}
@Override
public int compare(T t1, T t2) {
return t1.compareTo(t2);
}
}
}