net.time4j.engine.TimeAxis Maven / Gradle / Ivy
/*
* -----------------------------------------------------------------------
* Copyright © 2013-2016 Meno Hochschild,
* -----------------------------------------------------------------------
* 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 .
* -----------------------------------------------------------------------
*/
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;
/**
* 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.
*
* @param generic type of time units
* @param generic type of time context compatible to {@link TimePoint})
* @author Meno Hochschild
*/
/*[deutsch]
* 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.
*
* @param generic type of time units
* @param generic type of time context compatible to {@link TimePoint})
* @author Meno Hochschild
*/
public final class TimeAxis>
extends Chronology
implements Comparator, TimeLine {
//~ Instanzvariablen --------------------------------------------------
private final Class unitType;
private final Map> unitRules;
private final Map unitLengths;
private final Map> convertibleUnits;
private final Map, U> baseUnits;
private final T min;
private final T max;
private final CalendarSystem calendarSystem;
private final ChronoElement self;
private final TimeLine timeline;
//~ Konstruktoren -----------------------------------------------------
private TimeAxis(
Class chronoType,
Class unitType,
ChronoMerger merger,
Map, ElementRule> ruleMap,
Map> unitRules,
final Map unitLengths,
Map> convertibleUnits,
List extensions,
Map, U> baseUnits,
T min,
T max,
CalendarSystem calendarSystem, // optional
TimeLine timeline, // optional
boolean useEnumUnits
) {
super(chronoType, merger, ruleMap, extensions);
Map> 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 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 ----------------------------------------------------------
/**
* Returns the type of supported time units.
*
* @return reified type of time unit
*/
/*[deutsch]
* Liefert den Zeiteinheitstyp.
*
* @return reified type of time unit
*/
public Class getUnitType() {
return this.unitType;
}
/**
* Returns all registered time units.
*
* @return unmodifiable set of registered units without duplicates
*/
/*[deutsch]
* Liefert alle registrierten Zeiteinheiten.
*
* @return unmodifiable set of registered units without duplicates
*/
public Set getRegisteredUnits() {
return Collections.unmodifiableSet(this.unitRules.keySet());
}
/**
* Queries if given time unit is registered.
*
* @param unit time unit (optional)
* @return {@code true} if registered else {@code false}
*/
/*[deutsch]
* Ist die angegebene Zeiteinheit registriert?
*
* @param unit time unit (optional)
* @return {@code true} if registered else {@code false}
*/
public boolean isRegistered(U unit) {
return this.unitRules.containsKey(unit);
}
/**
* Queries if given time unit is supported.
*
* A time unit is supported if it is either registered or
* if it defines a suitable rule.
*
* @param unit time unit (optional)
* @return {@code true} if supported else {@code false}
* @see BasicUnit#derive(Chronology)
*/
/*[deutsch]
* Wird die angegebene Zeiteinheit unterstützt?
*
* Unterstützung ist gegeben, wenn die Einheit entweder registriert
* ist oder eine zu dieser Chronologie passende Regel definiert.
*
* @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;
}
}
/**
* Returns the length of given time unit in seconds as it is
* usual or estimated on this time axis.
*
* 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.
*
* 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.
*
* @param unit time unit
* @return estimated standard length in seconds or
* {@code Double.NaN} if not calculatable
* @see ChronoUnit
*/
/*[deutsch]
* Liefert die in dieser Chronologie übliche Länge der
* angegebenen Zeiteinheit in Sekunden.
*
* 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.
*
* 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.
*
* @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);
}
/**
* Queries if given time units are convertible.
*
* 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.
*
* 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.
*
* @param unit1 first time unit
* @param unit2 second time unit
* @return {@code true} if convertible else {@code false}
* @see #getLength(Object) getLength(U)
*/
/*[deutsch]
* Sind die angegebenen Zeiteinheiten ineinander konvertierbar?
*
* 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.
*
* 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.
*
* @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 set = this.convertibleUnits.get(unit1);
return ((set != null) && set.contains(unit2));
}
/**
* Compares time units by ascending precision
* (that is descending length).
*/
/*[deutsch]
* Vergleicht Zeiteinheiten nach aufsteigender Genauigkeit.
*/
@Override
public int compare(
U unit1,
U unit2
) {
return Double.compare(this.getLength(unit2), this.getLength(unit1));
}
/**
* Queries if given element has a base unit.
*
* @param element chronological element (optional)
* @return {@code true} if given element has a base unit else {@code false}
* @see #getBaseUnit(ChronoElement)
*/
/*[deutsch]
* Ermittelt, ob das angegebene Element eine Basiseinheit hat.
*
* @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;
}
/**
* Returns the base unit of given element if available.
*
* Only registred elements can have a base unit unless the element
* is a {@code BasicElement} and refers another registered element with
* a base unit.
*
* @param element chronological element
* @return found base unit
* @throws ChronoException if there is no base unit
* @see #hasBaseUnit(ChronoElement)
* @see BasicElement#getParent()
*/
/*[deutsch]
* Liefert die Basiseinheit zum angegebenen Element.
*
* 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.
*
* @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;
}
/**
* Yields the minimum of this time axis.
*
* @return earliest possible time point
*/
/*[deutsch]
* Ermittelt das Minimum auf der Zeitachse.
*
* @return earliest possible time point
*/
public T getMinimum() {
return this.min;
}
/**
* Yields the maximum of this time axis.
*
* @return latest possible time point
*/
/*[deutsch]
* Ermittelt das Maximum auf der Zeitachse.
*
* @return latest possible time point
*/
public T getMaximum() {
return this.max;
}
@Override
public boolean hasCalendarSystem() {
return (this.calendarSystem != null);
}
@Override
public CalendarSystem getCalendarSystem() {
if (this.calendarSystem == null) {
return super.getCalendarSystem();
} else {
return this.calendarSystem;
}
}
@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);
}
/**
* Yields this time axis as chronological self-referencing
* element.
*
* @return self-referencing element
*/
/*[deutsch]
* Liefert diese Zeitachse als chronologisches Element mit
* Selbstbezug.
*
* @return self-referencing element
*/
public ChronoElement 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);
}
/**
* Liefert die chronologische Regel zur angegebenen Zeiteinheit.
*
* @param unit time unit
* @return unit rule or {@code null} if not registered
*/
UnitRule getRule(U unit) {
if (unit == null) {
throw new NullPointerException("Missing chronological unit.");
}
UnitRule 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 double getLength(
Map 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 ----------------------------------------------------
/**
* 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 static initializer.
*
* Instances of this class will be created by the static factory
* methods {@code setUp()}.
*
* @param generic type of time unit
* @param 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]
* Erzeugt einen Builder für eine neue Zeitachse respektive
* Chronologie und wird ausschließlich beim Laden einer
* {@code TimePoint}-Klasse T in einem static initializer
* benutzt.
*
* Instanzen dieser Klasse werden über die statischen
* {@code setUp()}-Fabrikmethoden erzeugt.
*
* @param generic type of time unit
* @param 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>
extends Chronology.Builder {
//~ Instanzvariablen ----------------------------------------------
private final Class unitType;
private final Map> unitRules;
private final Map unitLengths;
private final Map> convertibleUnits;
private final Map, U> baseUnits;
private final T min;
private final T max;
private final CalendarSystem calendarSystem;
private TimeLine timeline = null;
private boolean useEnumUnits = true;
//~ Konstruktoren -------------------------------------------------
private Builder(
Class unitType,
Class chronoType,
ChronoMerger merger,
T min,
T max,
CalendarSystem calendarSystem, // optional
TimeLine 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 ------------------------------------------------------
/**
* Creates a builder for building a chronological but
* non-calendrical system.
*
* @param generic type of time unit
* @param 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]
* Erzeugt ein Hilfsobjekt zum Bauen eines chronologischen, aber
* nicht kalendarischen Systems.
*
* @param generic type of time unit
* @param 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 > Builder setUp(
Class unitType,
Class chronoType,
ChronoMerger merger,
T min,
T max
) {
return new Builder<>(
unitType,
chronoType,
merger,
min,
max,
null,
null);
}
/**
* Creates a builder for building a time axis for
* plain calendrical objects.
*
* @param generic type of time unit
* @param 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]
* Erzeugt ein Hilfsobjekt zum Bauen einer Zeitachse für
* reine Datumsangaben.
*
* @param generic type of time unit
* @param 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 > Builder setUp(
Class unitType,
Class chronoType,
ChronoMerger merger,
CalendarSystem calendarSystem
) {
final CalendarSystem calsys = calendarSystem;
Builder 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 Builder appendElement(
ChronoElement element,
ElementRule rule
) {
super.appendElement(element, rule);
return this;
}
/**
* Registers a new element with associated rule and a base
* unit.
*
* @param 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]
* Registriert ein neues Element mitsamt der assoziierten Regel
* und einer Basiseinheit.
*
* @param 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 Builder appendElement(
ChronoElement element,
ElementRule rule,
U baseUnit
) {
if (baseUnit == null) {
throw new NullPointerException("Missing base unit.");
}
super.appendElement(element, rule);
this.baseUnits.put(element, baseUnit);
return this;
}
/**
* Registers a non-convertible time unit with an associated
* unit rule.
*
* Is equivalent to {@link #appendUnit(Object,UnitRule,double,Set)
* appendUnit(U, rule, length, Collections.emptySet())}.
*
* @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]
* Registriert eine neue nicht-konvertierbare Zeiteinheit mitsamt
* Einheitsregel.
*
* Entspricht {@link #appendUnit(Object,UnitRule,double,Set)
* appendUnit(U, rule, length, Collections.emptySet())}.
*
* @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 appendUnit(
U unit,
UnitRule rule,
double length
) {
Set none = Collections.emptySet();
return this.appendUnit(unit, rule, length, none);
}
/**
* Registers a new time unit with an associated unit rule.
*
* The unit rule defines the time arithmetic for addition and
* subtraction of given unit suitable for the time axis.
*
* 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}.
*
* 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.
*
* 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)..
*
* @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]
* Registriert eine neue Zeiteinheit mitsamt Einheitsregel.
*
* Die Regel zur Zeiteinheit definiert die Zeitarithmetik in der
* Addition und Subtraktion passend zur aktuellen Chronologie.
*
* 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}.
*
* 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.
*
* 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).
*
* @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 appendUnit(
U unit,
UnitRule 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 set = new HashSet<>(convertibleUnits);
set.remove(unit); // Selbstbezug entfernen
this.convertibleUnits.put(unit, set);
return this;
}
@Override
public Builder appendExtension(ChronoExtension extension) {
super.appendExtension(extension);
return this;
}
/**
* Defines the argument as timeline to be used for stepping
* forward or backwards.
*
* @param timeline time line to be used
* @return this instance for method chaining
* @since 2.0
*/
/*[deutsch]
* Definiert das Argument als Zeitstrahl, auf dem schrittweise
* vorwärts oder rückwärts gegangen wird.
*
* @param timeline time line to be used
* @return this instance for method chaining
* @since 2.0
*/
public Builder withTimeLine(TimeLine timeline) {
if (timeline == null) {
throw new NullPointerException("Missing time line.");
}
this.timeline = timeline;
return this;
}
/**
* Creates and registers a time axis.
*
* @return new chronology as time axis
* @throws IllegalStateException if already registered or in
* case of inconsistencies
*/
/*[deutsch]
* Erzeugt und registriert eine Zeitachse.
*
* @return new chronology as time axis
* @throws IllegalStateException if already registered or in
* case of inconsistencies
*/
@Override
public TimeAxis build() {
if (this.unitRules.isEmpty()) {
throw new IllegalStateException("No time unit was registered.");
}
TimeAxis 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>
extends BasicElement
implements ElementRule {
//~ Statische Felder/Initialisierungen ----------------------------
private static final long serialVersionUID = 4777240530511579802L;
//~ Instanzvariablen ----------------------------------------------
private final Class type;
private final T min;
private final T max;
//~ Konstruktoren -------------------------------------------------
private SelfElement(
Class type,
T min,
T max
) {
super(type.getName() + "-AXIS");
this.type = type;
this.min = min;
this.max = max;
}
//~ Methoden ------------------------------------------------------
@Override
public Class 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 > ElementRule derive(
Chronology chronology
) {
if (chronology.getChronoType().equals(this.type)) {
return (ElementRule) 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>
implements TimeLine {
//~ 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;
}
}
}