/* * ----------------------------------------------------------------------- * Copyright © 2013-2016 Meno Hochschild, <http://www.menodata.de/> * ----------------------------------------------------------------------- * This file (BasicElement.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.base.UnixTime; import java.io.Serializable; /** * <p>Abstract base implementation of a chronological element which has * a name and can also define an (unregistered) element rule. </p> * * @param <V> generic type of element values * @author Meno Hochschild */ /*[deutsch] * <p>Abstrakte Basisimplementierung eines chronologischen Elements, das * einen Namen hat und bei Bedarf auch eigene Regeln definieren kann. </p> * * @param <V> generic type of element values * @author Meno Hochschild */ public abstract class BasicElement<V extends Comparable<V>> implements ChronoElement<V>, Serializable { //~ Instanzvariablen -------------------------------------------------- /** * @serial name of this element */ /*[deutsch] * @serial Elementname */ private final String name; /** * @serial identity code */ /*[deutsch] * @serial Identitätscode */ private final int identity; /** * @serial hash code */ /*[deutsch] * @serial Hash-Code */ private final int hash; //~ Konstruktoren ----------------------------------------------------- /** * <p>Called by subclasses which will usually assign an instance to * a static constant (creating a singleton). </p> * * @param name name of element * @throws IllegalArgumentException if the name is empty or only * contains <i>white space</i> (spaces, tabs etc.) * @see ChronoElement#name() */ /*[deutsch] * <p>Konstruktor für Subklassen, die eine so erzeugte Instanz * in der Regel statischen Konstanten zuweisen und damit Singletons * erzeugen können. </p> * * @param name name of element * @throws IllegalArgumentException if the name is empty or only * contains <i>white space</i> (spaces, tabs etc.) * @see ChronoElement#name() */ protected BasicElement(String name) { super(); if (name.trim().isEmpty()) { throw new IllegalArgumentException( "Element name is empty or contains only white space."); } this.name = name; this.hash = name.hashCode(); this.identity = (this.isSingleton() ? ((this.hash == -1) ? ~this.hash : this.hash) : -1); } //~ Methoden ---------------------------------------------------------- @Override public final String name() { return this.name; } /** * <p>Compares the values of this element based on their natural order. </p> * * @throws ChronoException if this element is not registered in any entity * and/or if no element rule exists to extract the element value * @since 3.5/4.3 */ /*[deutsch] * <p>Vergleicht die Werte dieses Elements auf Basis ihrer * natürlichen Ordnung. </p> * * @throws ChronoException if this element is not registered in any entity * and/or if no element rule exists to extract the element value * @since 3.5/4.3 */ @Override public int compare( ChronoDisplay o1, ChronoDisplay o2 ) { return o1.get(this).compareTo(o2.get(this)); } /** * <p>There is no format symbol by default. </p> * * <p>In order to define a format symbol subclasses must override this * methode. In that case such an element instance should be annotated * with the annotation {@code FormattableElement} for documentation * support. </p> * * @return ASCII-0 (placeholder for an undefined format symbol) * @see FormattableElement */ /*[deutsch] * <p>Standardmäßig gibt es kein Formatsymbol. </p> * * <p>Um ein Formatsymbol zu definieren, müssen Subklassen diese * Methode geeignet überschreiben. Gleichzeitig sollte eine solche * Elementinstanz mittels der Annotation {@code FormattableElement} * das Symbol dokumentieren. </p> * * @return ASCII-0 (placeholder for an undefined format symbol) * @see FormattableElement */ @Override public char getSymbol() { return '\u0000'; } /** * <p>Chronological elements are strict by default. </p> * * @return {@code false} */ /*[deutsch] * <p>Chronologische Elemente verhalten sich standardmäßig * strikt und nicht nachsichtig. </p> * * @return {@code false} */ @Override public boolean isLenient() { return false; } /** * <p>Elements are local by default and can therefore not be used * in a global context. </p> * * @return {@code true} * @since 2.0 * @see #getVeto(Chronology) */ /*[deutsch] * <p>Elemente sind normalerweise lokal und können deshalb nicht * in einem globalen Kontext verwendet werden. </p> * * @return {@code true} * @since 2.0 * @see #getVeto(Chronology) */ public boolean isLocal() { return true; } /** * <p>Based on equality of element names AND element classes. </p> * * @return {@code true} if this instance and the argument are of same * class and have same names else {@code false} */ /*[deutsch] * <p>Basiert auf der Gleichheit der Elementnamen UND Elementklassen. </p> * * @return {@code true} if this instance and the argument are of same * class and have same names else {@code false} */ @Override public final boolean equals(Object obj) { if (this == obj) { return true; } else if (obj == null) { return false; } else if (this.getClass() == obj.getClass()) { BasicElement<?> that = (BasicElement<?>) obj; int id1 = this.identity; int id2 = that.identity; return ((id1 == id2) && ((id1 != -1) || (this.name().equals(that.name()) && this.doEquals(that)))); } else { return false; } } /** * <p>Based on the element name. </p> * * @return int */ /*[deutsch] * <p>Basiert auf dem Elementnamen. </p> * * @return int */ @Override public final int hashCode() { return this.hash; } /** * <p>Serves mainly for debugging support. </p> * * <p>For display purpose the method {@link #name()} is to be * preferred. </p> * * @return String */ /*[deutsch] * <p>Dient vornehmlich der Debugging-Unterstützung. </p> * * <p>Für Anzeigezwecke sollte die Methode {@link #name()} * verwendet werden. </p> * * @return String */ @Override public String toString() { String className = this.getClass().getName(); StringBuilder sb = new StringBuilder(className.length() + 32); sb.append(className); sb.append('@'); sb.append(this.name); return sb.toString(); } /** * <p>Derives an optional element rule for given chronology. </p> * * <p>Note: This implementation yields {@code null}. Subclasses whose * element instances are not registered in a given chronology must * override this method returning a suitable element rule. </p> * * @param <T> generic type of chronology * @param chronology chronology an element rule is searched for * @return element rule or {@code null} if given chronology is unsupported */ /*[deutsch] * <p>Leitet eine optionale Elementregel für die angegebene * Chronologie ab. </p> * * <p>Hinweis: Diese Implementierung liefert {@code null}. Subklassen, * deren Elementinstanzen nicht in einer Chronologie registriert sind, * müssen die Methode geeignet überschreiben. </p> * * @param <T> generic type of chronology * @param chronology chronology an element rule is searched for * @return element rule or {@code null} if given chronology is unsupported */ protected <T extends ChronoEntity<T>> ElementRule<T, V> derive(Chronology<T> chronology) { return null; } /** * <p>Points to another element which can have a base unit in a given * chronology. </p> * * <p>This method can be overridden by unregistered extension elements * in order to help a chronology to see which base unit belongs to * this element. </p> * * @return parent element registered on a time axis for helping * retrieving a base unit for this element or {@code null} * @see TimeAxis#getBaseUnit(ChronoElement) */ /*[deutsch] * <p>Verweist auf ein anderes Element, das eine Basiseinheit in einer * Chronologie haben kann. </p> * * <p>Diese Methode kann von nicht-registrierten Erweiterungselementen * überschrieben werden, um einer Chronologie zu helfen, welche * Basiseinheit mit diesem Element zu verknüpfen ist. </p> * * @return parent element registered on a time axis for helping * retrieving a base unit for this element or {@code null} * @see TimeAxis#getBaseUnit(ChronoElement) */ protected ChronoElement<?> getParent() { return null; } /** * <p>If this element is not registered in given chronology then this method * will be called by Time4J in order to generate a suitable error message * in cases where this element shall not support the chronological context. </p> * * <p>This implementation yields {@code null} to indicate that there is no * veto against usage in given chronology unless this element is local but * the given chronology is global. </p> * * @param chronology chronological context * @return error message as veto or {@code null} * @since 2.0 */ /*[deutsch] * <p>Falls dieses Element in der angegebenen Chronologie nicht registriert * ist, wird diese Methode aufgerufen, um eine passende Veto-Fehlermeldung * zu generieren, wenn dieses Element nicht den Kontext unterstützen * soll. </p> * * <p>Diese Implementierung liefert {@code null}, um anzuzeigen, daß * per Standard kein Veto gegen den Gebrauch dieses Elements in der * angegebenen Chronologie eingelegt wird, es sei denn, dieses Element * ist lokal und die angegebene Chronologie global. </p> * * @param chronology chronologischer Kontext * @return Fehlermeldung als Veto oder {@code null} * @since 2.0 */ protected String getVeto(Chronology<?> chronology) { if ( this.isLocal() && UnixTime.class.isAssignableFrom(chronology.getChronoType()) ) { return "Accessing the local element [" + this.name + "] from a global type requires a timezone.\n" + "- Try to apply a zonal query like \"" + this.name + ".atUTC()\".\n" + "- Or try to first convert the global type to " + "a zonal timestamp: " + "\"moment.toZonalTimestamp(...)\".\n" + "- If used in formatting then consider " + "\"ChronoFormatter.withTimezone(TZID)\"."; } return null; } /** * <p>Determines if this element only exists one time as constant in the JVM. </p> * * <p>Any override of this method MUST NOT refer directly or indirectly to any state of this object * because it is called during construction of this object at a time where this instance might not * yet be finished. </p> * * @return boolean (default value is {@code false}) * @since 3.15/4.12 */ /*[deutsch] * <p>Bestimmt, ob dieses Element nur einmal als Konstante in der JVM vorhanden ist. </p> * * <p>Jedes Überschreiben dieser Methode darf sich NICHT direkt oder indirekt auf den Zustand * dieser Instanz beziehen, weil die Methode zu einem Zeitpunkt aufgerufen wird, zu dem diese * Instanz noch nicht fertig konstruiert ist. </p> * * @return boolean (default value is {@code false}) * @since 3.15/4.12 */ protected boolean isSingleton() { return false; } /** * <p>Will be called by {@code equals(Object)}. </p> * * <p>Subclasses should override this method if other state attributes than just the element name are to * be taken into account when comparing elements. The parameter can be safely casted to an instance of * this class. </p> * * @param obj other element to be compared with * @return boolean (default value is {@code true}) * @since 3.15/4.12 */ /** * <p>Wird von {@code equals(Object)} aufgerufen. </p> * * <p>Subklassen sollten diese Methode überschreiben, wenn anderer Zustandsattribute als nur * der Elementname berücksichtigt werdne müssen. Der Parameter kann sicher zu einem * Typ dieser Klasse umgewandelt werden. </p> * * @param obj other element to be compared with * @return boolean (default value is {@code true}) * @since 3.15/4.12 */ protected boolean doEquals(BasicElement<?> obj) { return true; } }