net.time4j.engine.Chronology Maven / Gradle / Ivy
/*
* -----------------------------------------------------------------------
* Copyright © 2013-2015 Meno Hochschild,
* -----------------------------------------------------------------------
* This file (Chronology.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 net.time4j.base.TimeSource;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* Represents a system of chronological elements which form any kind
* of temporal value.
*
* @param generic type compatible to {@link ChronoEntity}
* @author Meno Hochschild
*/
/*[deutsch]
* Repräsentiert ein System von chronologischen Elementen, die
* zusammen einen zeitlichen Wert formen.
*
* @param generic type compatible to {@link ChronoEntity}
* @author Meno Hochschild
*/
public class Chronology>
implements ChronoMerger {
//~ Statische Felder/Initialisierungen --------------------------------
private static final List CHRONOS =
new CopyOnWriteArrayList();
private static final ReferenceQueue> QUEUE =
new ReferenceQueue>();
//~ Instanzvariablen --------------------------------------------------
private final Class chronoType;
private final ChronoMerger merger;
private final Map, ElementRule> ruleMap;
private final List extensions;
//~ Konstruktoren -----------------------------------------------------
/**
* Standard-Konstruktor.
*
* Implementierungshinweis: Subklassen sollten grundsätzlich
* dem Singleton-Muster folgen und deshalb keinen öffentlichen
* Konstruktor bieten.
*
* @param chronoType chronological type
* @param chronoMerger creates a new instance of T based on infos
* from another source
* @param ruleMap registered elements and rules
* @param extensions optional extensions
*/
Chronology(
Class chronoType,
ChronoMerger chronoMerger,
Map, ElementRule> ruleMap,
List extensions
) {
super();
if (chronoType == null) {
throw new NullPointerException("Missing chronological type.");
} else if (chronoMerger == null) {
throw new NullPointerException("Missing chronological merger.");
}
this.chronoType = chronoType;
this.merger = chronoMerger;
this.ruleMap = Collections.unmodifiableMap(ruleMap);
this.extensions = Collections.unmodifiableList(extensions);
}
//~ Methoden ----------------------------------------------------------
/**
* Returns the chronological type.
*
* @return type of time context
*/
/*[deutsch]
* Liefert den chronologischen Typ.
*
* @return type of time context
*/
public Class getChronoType() {
return this.chronoType;
}
/**
* Returns all registered chronological elements.
*
* @return unmodifiable set of elements without duplicates
*/
/*[deutsch]
* Liefert den zu dieser Chronologie zugehörigen Satz
* von registrierten chronologischen Elementen.
*
* @return unmodifiable set of elements without duplicates
*/
public Set> getRegisteredElements() {
return this.ruleMap.keySet();
}
/**
* Queries if given chronological element is registered together
* with its element rule.
*
* @param element element to be asked (optional)
* @return {@code true} if registered else {@code false}
*/
/*[deutsch]
* Ist das angegebene chronologische Element inklusive Regel
* registriert?
*
* @param element element to be asked (optional)
* @return {@code true} if registered else {@code false}
*/
public boolean isRegistered(ChronoElement> element) {
return ((element != null) && this.ruleMap.containsKey(element));
}
/**
* Queries if given chronological element is supported by this
* chronology.
*
* The element will be supported if it is either registered or
* defines a suitable element rule for this chronology.
*
* @param element element to be asked (optional)
* @return {@code true} if supported else {@code false}
*/
/*[deutsch]
* Wird das angegebene chronologische Element unterstützt?
*
* Unterstützung ist gegeben, wenn das Element entweder registriert
* ist oder eine zu dieser Chronologie passende Regel definiert.
*
* @param element element to be asked (optional)
* @return {@code true} if supported else {@code false}
*/
public boolean isSupported(ChronoElement> element) {
if (element == null) {
return false;
} else {
return (
this.isRegistered(element)
|| (this.getDerivedRule(element, false) != null)
);
}
}
@Override
public T createFrom(
TimeSource> clock,
AttributeQuery attributes
) {
if (attributes == null) {
throw new NullPointerException("Missing attributes.");
}
return this.merger.createFrom(clock, attributes);
}
@Override
public T createFrom(
ChronoEntity> entity,
AttributeQuery attributes,
boolean preparsing
) {
if (attributes == null) {
throw new NullPointerException("Missing attributes.");
}
return this.merger.createFrom(entity, attributes, preparsing);
}
@Override
public ChronoDisplay preformat(
T context,
AttributeQuery attributes
) {
return this.merger.preformat(context, attributes);
}
@Override
public Chronology> preparser() {
return this.merger.preparser();
}
@Override
public String getFormatPattern(
DisplayStyle style,
Locale locale
) {
return this.merger.getFormatPattern(style, locale);
}
@Override
public StartOfDay getDefaultStartOfDay() {
return this.merger.getDefaultStartOfDay();
}
/**
* Returns all registered chronological extensions.
*
* This method will be called by format-API in order to collect
* all extension elements which are relevant for formatting.
*
* @return unmodifiable list of extensions
*/
/*[deutsch]
* Liefert die registrierten chronologischen Erweiterungen.
*
* Diese Methode wird vom Format-API aufgerufen, um zusätzlich
* zu den registrierten Elementen auch alle Erweiterungselemente zu
* sammeln, die für die Formatierung von Bedeutung sind.
*
* @return unmodifiable list of extensions
*/
public List getExtensions() {
return this.extensions;
}
/**
* Queries if this chronology has a calendar system.
*
* @return {@code true} if this chronology has a calendar system else {@code false}
* @see #getCalendarSystem()
*/
/*[deutsch]
* Ermittelt, ob diese Chronologie ein Kalendersystem hat.
*
* @return {@code true} if this chronology has a calendar system else {@code false}
* @see #getCalendarSystem()
*/
public boolean hasCalendarSystem() {
return false;
}
/**
* Returns the associated calendar system if available.
*
* @return calendar system, not {@code null}
* @throws ChronoException if the calendar system is unavailable or if there is more than one variant
* @see #hasCalendarSystem()
*/
/*[deutsch]
* Liefert das assoziierte Kalendersystem, wenn verfügbar.
*
* @return calendar system, not {@code null}
* @throws ChronoException if the calendar system is unavailable or if there is more than one variant
* @see #hasCalendarSystem()
*/
public CalendarSystem getCalendarSystem() {
throw new ChronoException("Calendar system is not available.");
}
/**
* Returns the calendar system for given calendar variant if available.
*
* @param variant name of calendar variant
* @return calendar system, not {@code null}
* @throws ChronoException if a calendar system is unavailable for given variant (invalid variant name)
* @since 3.4/4.3
* @see CalendarVariant#getVariant()
*/
/*[deutsch]
* Liefert das Kalendersystem zur angegebenen Kalendervariante, wenn verfügbar.
*
* @param variant name of calendar variant
* @return calendar system, not {@code null}
* @throws ChronoException if a calendar system is unavailable for given variant (invalid variant name)
* @since 3.4/4.3
* @see CalendarVariant#getVariant()
*/
public CalendarSystem getCalendarSystem(String variant) {
throw new ChronoException("Calendar variant is not available: " + variant);
}
/**
* Returns a typed singleton per {@code ChronoEntity}-class.
*
* @param generic type of time context
* @param chronoType chronological type
* @return chronology or {@code null} if not found
*/
/*[deutsch]
* Liefert ein getyptes Singleton pro {@code ChronoEntity}-Klasse.
*
* @param generic type of time context
* @param chronoType chronological type
* @return chronology or {@code null} if not found
*/
public static > Chronology lookup(Class chronoType) {
try {
// Initialisierung der Klasse anstoßen, wenn noch nicht erfolgt
Class.forName(
chronoType.getName(),
true,
chronoType.getClassLoader());
} catch (ClassNotFoundException cnfe) {
throw new IllegalStateException(cnfe);
}
Chronology> ret = null;
boolean purged = false;
for (ChronoReference cref : CHRONOS) {
Chronology> chronology = cref.get();
if (chronology == null) {
purged = true;
} else if (chronology.getChronoType() == chronoType) {
ret = chronology;
break;
}
}
if (purged) {
purgeQueue();
}
return cast(ret); // type-safe
}
/**
* Registriert die angegebene Chronologie.
*
* Die Registrierung ist zur Unterstützung der Methode {@link #lookup(Class)} gedacht und wird
* einmalig nach Konstruktion einer Chronologie während des Ladens der assoziierten Entitätsklasse
* aufgerufen.
*
* @param chronology new instance to be registered
*/
static void register(Chronology> chronology) {
CHRONOS.add(new ChronoReference(chronology, QUEUE));
}
/**
* Bestimmt eine chronologische Regel zum angegebenen Element.
*
* @param Elementwerttyp
* @param element chronologisches Element
* @return Regelobjekt
* @throws RuleNotFoundException if given element is not registered in
* this chronology and there is also no element rule which can
* be derived from element
*/
ElementRule getRule(ChronoElement element) {
if (element == null) {
throw new NullPointerException("Missing chronological element.");
}
ElementRule, ?> rule = this.ruleMap.get(element);
if (rule == null) {
rule = this.getDerivedRule(element, true);
if (rule == null) {
throw new RuleNotFoundException(this, element);
}
}
return cast(rule); // type-safe
}
// optional
private ElementRule getDerivedRule(
ChronoElement> element,
boolean wantsVeto
) {
if (element instanceof BasicElement) {
BasicElement> e = BasicElement.class.cast(element);
String veto = (wantsVeto ? e.getVeto(this) : null);
if (veto == null) {
return e.derive(this);
} else {
throw new RuleNotFoundException(veto);
}
}
return null;
}
// vom GC behandelte Referenzen wegräumen
private static void purgeQueue() {
ChronoReference cref;
while ((cref = (ChronoReference) QUEUE.poll()) != null) {
for (ChronoReference test : CHRONOS) {
if (test.name.equals(cref.name)) {
CHRONOS.remove(test);
break;
}
}
}
}
@SuppressWarnings("unchecked")
private static T cast(Object obj) {
return (T) obj;
}
//~ Innere Klassen ----------------------------------------------------
/**
* Builder for creating a new chronology without any time axis.
*
*
This class will be used during loading of a {@code ChronoEntity}-class
* T in a static initializer.
*
* @param generic type of time context
* @author Meno Hochschild
*/
/*[deutsch]
* Erzeugt eine neue Chronologie ohne Zeitachse und wird
* beim Laden einer {@code ChronoEntity}-Klasse T in einem
* static initializer benutzt.
*
* @param generic type of time context
* @author Meno Hochschild
*/
public static class Builder> {
//~ Instanzvariablen ----------------------------------------------
final Class chronoType;
final boolean time4j;
final ChronoMerger merger;
final Map, ElementRule> ruleMap;
final List extensions;
//~ Konstruktoren -------------------------------------------------
/**
* Konstruiert eine neue Instanz.
*
* @param chronoType chronological type
* @param merger creates a new instance of T from another
* source (clock or parsed values)
*/
Builder(
Class chronoType,
ChronoMerger merger
) {
super();
if (merger == null) {
throw new NullPointerException("Missing chronological merger.");
}
this.chronoType = chronoType;
this.time4j = chronoType.getName().startsWith("net.time4j.");
this.merger = merger;
this.ruleMap = new HashMap, ElementRule>();
this.extensions = new ArrayList();
}
//~ Methoden ------------------------------------------------------
/**
* Creates a builder for building a new chronological system.
*
* @param generic type of time context
* @param chronoType chronological type
* @param chronoMerger creates a new instance of T from another
* source (clock or parsed values)
* @return new {@code Builder} object
* @throws UnsupportedOperationException if T represents a subclass
* of {@code TimePoint}
*/
/*[deutsch]
* Erzeugt ein Hilfsobjekt zum Bauen eines chronologischen
* Systems.
*
* @param generic type of time context
* @param chronoType chronological type
* @param chronoMerger creates a new instance of T from another
* source (clock or parsed values)
* @return new {@code Builder} object
* @throws UnsupportedOperationException if T represents a subclass
* of {@code TimePoint}
*/
public static > Builder setUp(
Class chronoType,
ChronoMerger chronoMerger
) {
if (TimePoint.class.isAssignableFrom(chronoType)) {
throw new UnsupportedOperationException(
"This builder cannot construct a chronology "
+ "with a time axis, use TimeAxis.Builder instead.");
}
return new Builder(chronoType, chronoMerger);
}
/**
* Registers a new element together with its associated
* element rule.
*
* @param generic type of element value
* @param element chronological element to be registered
* @param rule rule associated with the element
* @return this instance for method chaining
* @throws IllegalArgumentException if given element is already
* registered (duplicate)
*/
/*[deutsch]
* Registriert ein neues Element mitsamt der assoziierten Regel.
*
* @param generic type of element value
* @param element chronological element to be registered
* @param rule rule associated with the element
* @return this instance for method chaining
* @throws IllegalArgumentException if given element is already
* registered (duplicate)
*/
public Builder appendElement(
ChronoElement element,
ElementRule rule
) {
this.checkElementDuplicates(element);
this.ruleMap.put(element, rule);
return this;
}
/**
* Registers a state extension which can create models with their
* own state separated from standard time value context.
*
* @param extension chronological extension
* @return this instance for method chaining
*/
/*[deutsch]
* Registriert eine Zustandserweiterung, die Modelle mit einem
* eigenen Zustand separat vom Zeitwertkontext erzeugen kann.
*
* @param extension chronological extension
* @return this instance for method chaining
*/
public Builder appendExtension(ChronoExtension extension) {
if (extension == null) {
throw new NullPointerException(
"Missing chronological extension.");
} else if (!this.extensions.contains(extension)) {
this.extensions.add(extension);
}
return this;
}
/**
* Finishes the build of a new chronology.
*
* Internally the new chronology will be weakly registered for
* {@code lookup()}. Therefore it is strongly recommended to
* reference the created chronology in a static constant within
* the chronological type in question.
*
* @return new instance of chronology
* @throws IllegalStateException if already registered
* @see Chronology#lookup(Class)
*/
/*[deutsch]
* Schließt den Build-Vorgang ab.
*
* Intern wird die neue Chronologie für {@code lookup()}
* schwach registriert. Es wird daher empfohlen, daß eine
* Anwendung zusätzlich die erzeugte Chronologie in einer
* eigenen statischen Konstanten referenziert.
*
* @return new instance of chronology
* @throws IllegalStateException if already registered
* @see Chronology#lookup(Class)
*/
public Chronology build() {
final Chronology chronology =
new Chronology(
this.chronoType,
this.merger,
this.ruleMap,
this.extensions
);
Chronology.register(chronology);
return chronology;
}
private void checkElementDuplicates(ChronoElement> element) {
if (this.time4j) {
return;
} else if (element == null) {
throw new NullPointerException(
"Static initialization problem: "
+ "Check if given element statically refer "
+ "to any chronology causing premature class loading.");
}
String elementName = element.name();
for (ChronoElement> key : this.ruleMap.keySet()) {
if (
key.equals(element)
|| key.name().equals(elementName)
) {
throw new IllegalArgumentException(
"Element duplicate found: " + elementName);
}
}
}
}
// Schwache Referenz auf ein chronologisches System
private static class ChronoReference
extends WeakReference> {
//~ Instanzvariablen ----------------------------------------------
private final String name;
//~ Konstruktoren -------------------------------------------------
ChronoReference(
Chronology> chronology,
ReferenceQueue> queue
) {
super(chronology, queue);
this.name = chronology.chronoType.getName();
}
}
}