/* * ----------------------------------------------------------------------- * Copyright © 2013-2016 Meno Hochschild, <http://www.menodata.de/> * ----------------------------------------------------------------------- * This file (ChronoEntity.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 net.time4j.tz.TZID; import java.util.Set; /** * <p>Represents a temporal object which associates partial temporal values * with chronological elements and also allows some manipulations of these * element values. </p> * * <p>A {@code ChronoEntity} is usually a {@code TimePoint}, where the * (primary) element values determine the position on a time axis such * that a time arithmetic is possible. Alternatively a {@code ChronoEntity} * can also represent a partial information like the combination of month * and day-of-month for a simple display of a birthday. </p> * * <p>Chronological elements are either statically registered such that * a direct access is enabled, or there is an (external) rule which * enables read- and write-access. If no element rule can be found a * {@code RuleNotFoundException} will be thrown. </p> * * @param <T> generic type of self reference * @author Meno Hochschild * @doctags.spec All public implementations must be immutable. */ /*[deutsch] * <p>Repräsentiert ein Zeitwertobjekt, das einzelne Werte mit * chronologischen Elementen assoziiert und einen Zugriff auf diese * Werte erlaubt. </p> * * <p>Eine {@code ChronoEntity} ist gewöhnlich ein {@code TimePoint}, * bei dem die (primären) Elementwerte zusammen die Position auf einem * Zeitstrahl festlegen, so daß auch eine Zeitarithmetik möglich * ist. Alternativ kann eine {@code ChronoEntity} eine chronologische * Teilinformation wie z.B. die Kombination aus Monat und Tag zur einfachen * Darstellung eines Geburtstags repräsentieren. </p> * * <p>Chronologische Elemente sind entweder vorab registriert, so daß * ein Zugriff direkt möglich ist, oder es gibt eine Regel, die den * Lese- bzw. Schreibzugriff ermöglicht. Ist die Regel nicht vorhanden, * wird eine {@code RuleNotFoundException} geworfen. </p> * * @param <T> generic type of self reference * @author Meno Hochschild * @doctags.spec All public implementations must be immutable. */ public abstract class ChronoEntity<T extends ChronoEntity<T>> implements ChronoDisplay { //~ Methoden ---------------------------------------------------------- @Override public boolean contains(ChronoElement<?> element) { return this.getChronology().isSupported(element); } @Override public <V> V get(ChronoElement<V> element) { return this.getRule(element).getValue(this.getContext()); } @Override public int getInt(ChronoElement<Integer> element) { IntElementRule<T> intRule = this.getChronology().getIntegerRule(element); try { if (intRule == null) { return this.get(element).intValue(); } else { return intRule.getInt(this.getContext()); } } catch (ChronoException ex) { return Integer.MIN_VALUE; } } @Override public <V> V getMinimum(ChronoElement<V> element) { return this.getRule(element).getMinimum(this.getContext()); } @Override public <V> V getMaximum(ChronoElement<V> element) { return this.getRule(element).getMaximum(this.getContext()); } /** * <p>Lets given query evaluate this entity. </p> * * <p>Is equivalent to {@code function.apply(this)}. The applied * strategy pattern hereby enables the externalization of querying * the interpretation and evaluation of this entity, consequently * enabling user-defined queries with arbitrary result types R. * Main difference to chronological elements is read-only access. * Users have to consult the documentation of given query to decide * if this method will yield {@code null} or throws an exception * if the result is undefined or otherwise not obtainable. </p> * * @param <R> generic type of result of query * @param function time query * @return result of query or {@code null} if undefined * @throws ChronoException if the given query is not executable */ /*[deutsch] * <p>Läßt die angegebene Abfrage diese Entität * auswerten. </p> * * <p>Entspricht {@code function.apply(this)}. Hierüber wird der * Vorgang der Zeitinterpretation externalisiert und ermöglicht * so benutzerdefinierte Abfragen mit beliebigen Ergebnistypen. Anders * als bei chronologischen Elementen ist hier nur ein Lesezugriff * möglich. In der Dokumentation der jeweiligen {@code ChronoFunction} * ist nachzuschauen, ob diese Methode im Fall undefinierter Ergebnisse * {@code null} zurückgibt oder eine Ausnahme wirft. </p> * * @param <R> generic type of result of query * @param function time query * @return result of query or {@code null} if undefined * @throws ChronoException if the given query is not executable */ public final <R> R get(ChronoFunction<? super T, R> function) { return function.apply(this.getContext()); } /** * <p>Queries if this entity matches given condition. </p> * * <p>Is equivalent to {@code condition.test(this)}. This method * will never throw an exception. This behaviour is in contrast * to that of {@code ChronoFunction}. </p> * * @param condition temporal condition * @return {@code true} if the given condition is matched by this * entity else {@code false} * @see #get(ChronoFunction) */ /*[deutsch] * <p>Genügt diese Instanz der angegebenen Bedingung? </p> * * <p>Entspricht {@code condition.test(this)}. Diese Methode wirft * anders als eine allgemeine {@code ChronoFunction} keine Ausnahme. </p> * * @param condition temporal condition * @return {@code true} if the given condition is matched by this * entity else {@code false} * @see #get(ChronoFunction) */ public boolean matches(ChronoCondition<? super T> condition) { return condition.test(this.getContext()); } /** * <p>Tests if the value for given chronological value is invalid. </p> * * <p>Notes: This method tests if given value to be in question can * be set via the expression {@code with(element, value)}. A numerical * overflow situation (causing an {@code ArithmeticException}) will * usually not be checked. </p> * * @param <V> generic type of element value * @param element element the given value shall be assigned to * @param value candidate value to be validated (optional) * @return {@code true} if the method {@code with()} can be called * without exception else {@code false} * @see #with(ChronoElement, Object) with(ChronoElement, V) */ /*[deutsch] * <p>Ist der Wert zum angegebenen chronologischen Element gültig? </p> * * <p>Hinweise: Diese Methode testet, ob der fragliche Wert mittels des * Ausdrucks {@code with(element, value)} überhaupt gesetzt werden * kann. Eine numerische Überlaufsituation im Hinblick auf eine * {@code ArithmeticException} wird in der Regel nicht geprüft. </p> * * @param <V> generic type of element value * @param element element the given value shall be assigned to * @param value candidate value to be validated (optional) * @return {@code true} if the method {@code with()} can be called * without exception else {@code false} * @see #with(ChronoElement, Object) with(ChronoElement, V) */ public <V> boolean isValid( ChronoElement<V> element, V value ) { if (element == null) { throw new NullPointerException("Missing chronological element."); } return ( this.contains(element) && this.getRule(element).isValid(this.getContext(), value) ); } /** * <p>Tests if the value for given chronological value is invalid. </p> * * <p>Notes: This method tests if given value to be in question can * be set via the expression {@code with(element, value)}. A numerical * overflow situation (causing an {@code ArithmeticException}) will * usually not be checked. </p> * * @param element element the given value shall be assigned to * @param value candidate value to be validated * @return {@code true} if the method {@code with()} can be called * without exception else {@code false} * @see #with(ChronoElement, int) */ /*[deutsch] * <p>Ist der Wert zum angegebenen chronologischen Element gültig? </p> * * <p>Hinweis: Eine numerische Überlaufsituation im Hinblick auf eine * {@code ArithmeticException} wird nicht geprüft. </p> * * @param element element the given value shall be assigned to * @param value candidate value to be validated * @return {@code true} if the method {@code with()} can be called * without exception else {@code false} * @see #with(ChronoElement, int) */ public boolean isValid( ChronoElement<Integer> element, int value ) { IntElementRule<T> intRule = this.getChronology().getIntegerRule(element); if (intRule != null) { return intRule.isValid(this.getContext(), value); } return this.isValid(element, Integer.valueOf(value)); } /** * <p>Tests if the value for given chronological value is invalid. </p> * * <p>Notes: This method tests if given value to be in question can * be set via the expression {@code with(element, value)}. A numerical * overflow situation (causing an {@code ArithmeticException}) will * usually not be checked. </p> * * @param element element the given value shall be assigned to * @param value candidate value to be validated * @return {@code true} if the method {@code with()} can be called * without exception else {@code false} * @see #with(ChronoElement, long) */ /*[deutsch] * <p>Ist der Wert zum angegebenen chronologischen Element gültig? </p> * * <p>Hinweis: Eine numerische Überlaufsituation im Hinblick auf eine * {@code ArithmeticException} wird nicht geprüft. </p> * * @param element element the given value shall be assigned to * @param value candidate value to be validated * @return {@code true} if the method {@code with()} can be called * without exception else {@code false} * @see #with(ChronoElement, long) */ public boolean isValid( ChronoElement<Long> element, long value ) { return this.isValid(element, Long.valueOf(value)); } /** * <p>Creates a copy of this instance with the changed element value. </p> * * <p>A {@code null} value will almost ever be seen as invalid causing an * {@code IllegalArgumentException}. Subclasses which permit {@code null} * must explicitly document this feature. </p> * * @param <V> generic type of element value * @param element chronological element * @param value new element value * @return changed copy of the same type, this instance remains unaffected * @throws ChronoException if the element is not registered and there * is no element rule for setting the value * @throws IllegalArgumentException if the value is not valid * @throws ArithmeticException in case of arithmetic overflow * @see #isValid(ChronoElement, Object) isValid(ChronoElement, V) */ /*[deutsch] * <p>Erstellt eine Kopie dieser Instanz mit dem geänderten * Elementwert. </p> * * <p>Ein {@code null}-Wert wird fast immer als ungültig angesehen, * also zu einer {@code IllegalArgumentException} führen. Subklassen, * die {@code null} zulassen, müssen das explizit dokumentieren. </p> * * @param <V> generic type of element value * @param element chronological element * @param value new element value * @return changed copy of the same type, this instance remains unaffected * @throws ChronoException if the element is not registered and there * is no element rule for setting the value * @throws IllegalArgumentException if the value is not valid * @throws ArithmeticException in case of arithmetic overflow * @see #isValid(ChronoElement, Object) isValid(ChronoElement, V) */ public <V> T with( ChronoElement<V> element, V value ) { return this.getRule(element).withValue( this.getContext(), value, element.isLenient() ); } /** * <p>Creates a copy of this instance with the changed element value. </p> * * @param element chronological element * @param value new element value * @return changed copy of the same type, this instance remains unaffected * @throws ChronoException if the element is not registered and there * is no element rule for setting the value * @throws IllegalArgumentException if the value is not valid * @throws ArithmeticException in case of arithmetic overflow * @see #isValid(ChronoElement, Object) isValid(ChronoElement, V) */ /*[deutsch] * <p>Erstellt eine Kopie dieser Instanz mit dem geänderten * Elementwert. </p> * * @param element chronological element * @param value new element value * @return changed copy of the same type, this instance remains unaffected * @throws ChronoException if the element is not registered and there * is no element rule for setting the value * @throws IllegalArgumentException if the value is not valid * @throws ArithmeticException in case of arithmetic overflow * @see #isValid(ChronoElement, Object) isValid(ChronoElement, V) */ public T with( ChronoElement<Integer> element, int value ) { IntElementRule<T> intRule = this.getChronology().getIntegerRule(element); if (intRule != null) { return intRule.withValue(this.getContext(), value, element.isLenient()); } return this.with(element, Integer.valueOf(value)); } /** * <p>Creates a copy of this instance with the changed element value. </p> * * @param element chronological element * @param value new element value * @return changed copy of the same type, this instance remains unaffected * @throws ChronoException if the element is not registered and there * is no element rule for setting the value * @throws IllegalArgumentException if the value is not valid * @throws ArithmeticException in case of arithmetic overflow * @see #isValid(ChronoElement, Object) isValid(ChronoElement, V) */ /*[deutsch] * <p>Erstellt eine Kopie dieser Instanz mit dem geänderten * Elementwert. </p> * * @param element chronological element * @param value new element value * @return changed copy of the same type, this instance remains unaffected * @throws ChronoException if the element is not registered and there * is no element rule for setting the value * @throws IllegalArgumentException if the value is not valid * @throws ArithmeticException in case of arithmetic overflow * @see #isValid(ChronoElement, Object) isValid(ChronoElement, V) */ public T with( ChronoElement<Long> element, long value ) { return this.with(element, Long.valueOf(value)); } /** * <p>Creates a copy of this instance which is adjusted by given * {@code ChronoOperator} using a strategy pattern approach. </p> * * <p>Is equivalent to {@code operator.apply(this)}. Hereby a user-defined * manipulation will be externalized and is semantically similar to the * reading counterpart {@code ChronoFunction}. </p> * * @param operator operator for adjusting the element values * @return changed copy of the same type, this instance remains unaffected * @throws ChronoException if no element rule exists for setting the values * @throws IllegalArgumentException if any new value is not valid * @throws ArithmeticException in case of arithmetic overflow * @see #get(ChronoFunction) */ /*[deutsch] * <p>Liefert eine Kopie dieser Instanz zurück, die mit Hilfe * eines {@code ChronoOperator}-Objekts angepasst wird. </p> * * <p>Entspricht {@code operator.apply(this)}. Hierüber wird eine * benutzerdefinierte Manipulation externalisiert und ist semantisch * ähnlich wie im lesenden Gegenstück {@code ChronoFunction}. </p> * * @param operator operator for adjusting the element values * @return changed copy of the same type, this instance remains unaffected * @throws ChronoException if no element rule exists for setting the values * @throws IllegalArgumentException if any new value is not valid * @throws ArithmeticException in case of arithmetic overflow * @see #get(ChronoFunction) */ public T with(ChronoOperator<T> operator) { return operator.apply(this.getContext()); } /** * <p>Queries if this entity contains a timezone for display purposes. </p> * * <p>This implementation has no timezone by default and yields * {@code false}. Subclasses with a timezone reference intended for * formatted output will override the method in a suitable way. </p> * * @return {@code true} if a timezone is available and can be achieved * with {@link #getTimezone()} else {@code false} */ /*[deutsch] * <p>Ermittelt, ob eine Zeitzone für Anzeigezwecke vorhanden ist. </p> * * <p>Diese Implementierung kennt standardmäßig keine Zeitzone * und liefert {@code false}. Subklassen mit einem Zeitzonenbezug gedacht * für formatierte Darstellungen werden die Methode in geeigneter Weise * überschreiben. </p> * * @return {@code true} if a timezone is available and can be achieved * with {@link #getTimezone()} else {@code false} */ @Override public boolean hasTimezone() { return false; } /** * <p>Returns the associated timezone ID for display purposes * if available. </p> * * <p>This implementation throws a {@code ChronoException} * by default. Subclasses with a timezone reference intended for * formatted output will override the method in a suitable way. </p> * * @return timezone identifier if available * @throws ChronoException if the timezone is not available * @see #hasTimezone() */ /*[deutsch] * <p>Liefert die assoziierte Zeitzonen-ID für Anzeigezwecke, * wenn vorhanden. </p> * * <p>Diese Implementierung wirft standardmäßig eine * {@code ChronoException}. Subklassen mit einem Zeitzonenbezug * gedacht für formatierte Darstellungen werden die Methode * in geeigneter Weise überschreiben. </p> * * @return timezone identifier if available * @throws ChronoException if the timezone is not available * @see #hasTimezone() */ @Override public TZID getTimezone() { throw new ChronoException("Timezone not available: " + this); } /** * <p>Yields all registered elements of this instance. </p> * * <p>Note that some (external) elements can be supported but not registered. </p> * * @return unmodifiable set of internally registered elements * @since 3.13/4.10 */ /*[deutsch] * <p>Liefert alle registrierten Elemente dieser Instanz. </p> * * <p>Hinweis: Einige (externe) Elemente können unterstützt werden, sind aber nicht * registriert. </p> * * @return unmodifiable set of internally registered elements * @since 3.13/4.10 */ public Set<ChronoElement<?>> getRegisteredElements() { return this.getChronology().getRegisteredElements(); } /** * <p>Returns the assigned chronology which contains all necessary * chronological rules. </p> * * <p>Concrete subclasses must create in a <i>static initializer</i> a * chronology by help of {@code Chronology.Builder}, keep it as static * constant and make it available here. Using the procedure guarantees * that a basic set of registered elements and rules will be installed. </p> * * @return chronological system * @throws UnsupportedOperationException if not available (subclasses * must document this extreme rare case) * @see Chronology.Builder */ /*[deutsch] * <p>Liefert die zugehörige Chronologie, die alle notwendigen * chronologischen Regeln enthält. </p> * * <p>Konkrete Subklassen müssen in einem <i>static initializer</i> * mit Hilfe von {@code Chronology.Builder} oder {@code TimeAxis.Builder} * eine Chronologie bauen, in einer eigenen Konstanten halten und hier * verfügbar machen. Über dieses Verfahren wird zugleich ein * Basissatz von Elementen und chronologischen Regeln vorinstalliert. </p> * * @return chronological system * @throws UnsupportedOperationException if not available (subclasses * must document this rare case) * @see Chronology.Builder */ protected abstract Chronology<T> getChronology(); /** * <p>Liefert den Selbstbezug. </p> * * @return time context (usually this instance) */ protected T getContext() { Chronology<T> c = this.getChronology(); Class<T> type = c.getChronoType(); if (type.isInstance(this)) { return type.cast(this); } else { for (ChronoElement<?> element : c.getRegisteredElements()) { if (type == element.getType()) { return type.cast(this.get(element)); } } } throw new IllegalStateException( "Implementation error: Cannot find entity context."); } /** * Determines the associated element rule. * * @param <V> value type of element * @param element chronological element to be evaluated * @return element rule * @throws RuleNotFoundException if a rule cannot be determined */ <V> ElementRule<T, V> getRule(ChronoElement<V> element) { return this.getChronology().getRule(element); } }