net.time4j.format.PluralRules Maven / Gradle / Ivy
/*
* -----------------------------------------------------------------------
* Copyright © 2013-2016 Meno Hochschild,
* -----------------------------------------------------------------------
* This file (PluralRules.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.format;
import net.time4j.base.ResourceLoader;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import static net.time4j.format.PluralCategory.FEW;
import static net.time4j.format.PluralCategory.ONE;
import static net.time4j.format.PluralCategory.OTHER;
import static net.time4j.format.PluralCategory.TWO;
/**
* Helps to determine the plural category for a given number of units.
*
* The predefined rules for any given language are based on
* CLDR-version 26 but can be overridden if necessary. The source data
* of the underlying algorithms to determine the plural category can be
* found in CLDR-repository-file "core.zip" along the path
* "common/supplemental/plurals.xml" for cardinal numbers and
* "common/supplemental/ordinals.xml" for ordinal numbers.
*
* @author Meno Hochschild
* @since 1.2
* @doctags.spec All concrete classes must be immutable.
*/
/*[deutsch]
* Hilfsklasse zur Bestimmung der Pluralkategorie für eine gegebene
* Sprache und eine entsprechende Anzahl von Zeiteinheiten.
*
* Die vordefinierten Regeln für irgendeine Sprache basieren auf
* der CLDR-Version 26, können bei Bedarf aber überschrieben
* werden. Die Quelldaten der zugrundeliegenden Algorithmen, die die
* Pluralkategorie zu bestimmen helfen, können im CLDR-Repositorium
* "core.zip" und dem Pfad "common/supplemental/plurals.xml"
* (für Grundzahlen) gefunden werden. Ordinalzahlregeln sind in der
* Datei "common/supplemental/ordinals.xml" zu finden.
*
* @author Meno Hochschild
* @since 1.2
* @doctags.spec All concrete classes must be immutable.
*/
public abstract class PluralRules {
//~ Statische Felder/Initialisierungen --------------------------------
private static final PluralRules FALLBACK_CARDINAL_ENGLISH =
new FallbackRules(NumberType.CARDINALS, true);
private static final PluralRules FALLBACK_CARDINAL_OTHER =
new FallbackRules(NumberType.CARDINALS, false);
private static final PluralRules FALLBACK_ORDINAL_ENGLISH =
new FallbackRules(NumberType.ORDINALS, true);
private static final PluralRules FALLBACK_ORDINAL_OTHER =
new FallbackRules(NumberType.ORDINALS, false);
private static final Map CARDINAL_MAP = new ConcurrentHashMap<>();
private static final Map ORDINAL_MAP = new ConcurrentHashMap<>();
//~ Methoden ----------------------------------------------------------
/**
* Gets the localized plural rules for given language or
* country.
*
* If no rules can be found then Time4J will choose the default rules
* for cardinals which apply {@code PluralCategory.ONE} to n=1 and else
* apply the fallback category {@code PluralCategory.OTHER}.
*
* @param locale locale which specifies the suitable plural rules
* @param numType number type
* @return localized plural rules
* @since 1.2
*/
/*[deutsch]
* Ermittelt die Pluralregeln für die angegebene Sprache oder
* das Land.
*
* Wenn keine Regeln gefunden werden können, dann wird Time4J
* die Standardregeln wählen, die bei Kardinalzahlen
* {@code PluralCategory.ONE} auf n=1 und sonst die Kategorie
* {@code PluralCategory.OTHER} anwenden.
*
* @param locale locale which specifies the suitable plural rules
* @param numType number type
* @return localized plural rules
* @since 1.2
*/
public static PluralRules of(
Locale locale,
NumberType numType
) {
Map map = getRuleMap(numType);
PluralRules rules = null;
if (!map.isEmpty()) {
if (!locale.getCountry().equals("")) {
rules = map.get(toKey(locale));
}
if (rules == null) {
rules = map.get(locale.getLanguage());
}
}
if (rules == null) {
rules = Holder.PROVIDER.load(locale, numType);
}
return rules;
}
/**
* Registers given plural rules for a language, possibly overriding
* CLDR-default setting.
*
* @param locale language or country which the rules shall be assigned to
* @param rules localized plural rules
* @since 1.2
*/
/*[deutsch]
* Registriert die angegebenen Pluralregeln für eine Sprache,
* wobei die CLDR-Vorgabe überschrieben werden kann.
*
* @param locale language or country which the rules shall be assigned to
* @param rules localized plural rules
* @since 1.2
*/
public static void register(
Locale locale,
PluralRules rules
) {
Map map = getRuleMap(rules.getNumberType());
String key = locale.getLanguage();
if (!locale.getCountry().equals("")) {
key = toKey(locale);
}
map.put(key, rules);
}
/**
* Determines the plural category for given number of units.
*
* @param count integral number of units
* @return plural category, never {@code null}
* @since 1.2
*/
/*[deutsch]
* Bestimmt die Pluralkategorie für die angegebene Anzahl von
* Zeiteinheiten.
*
* @param count integral number of units
* @return plural category, never {@code null}
* @since 1.2
*/
public abstract PluralCategory getCategory(long count);
/**
* Yields the number type these rules are referring to.
*
* @return number type
* @since 1.2
*/
/*[deutsch]
* Liefert den Zahltyp, auf den sich diese Regeln beziehen.
*
* @return number type
* @since 1.2
*/
public abstract NumberType getNumberType();
private static Map getRuleMap(NumberType numType) {
switch (numType) {
case CARDINALS:
return CARDINAL_MAP;
case ORDINALS:
return ORDINAL_MAP;
default:
throw new UnsupportedOperationException(numType.name());
}
}
private static String toKey(Locale country) {
StringBuilder kb = new StringBuilder();
kb.append(country.getLanguage());
kb.append('_');
kb.append(country.getCountry());
return kb.toString();
}
//~ Innere Klassen ----------------------------------------------------
private static class FallbackRules
extends PluralRules {
//~ Instanzvariablen ----------------------------------------------
private final NumberType numType;
private final boolean english;
//~ Konstruktoren -------------------------------------------------
private FallbackRules(
NumberType numType,
boolean english
) {
super();
this.numType = numType;
this.english = english;
}
//~ Methoden ------------------------------------------------------
@Override
public PluralCategory getCategory(long count) {
switch (this.numType) {
case CARDINALS:
return (count == 1 ? ONE : OTHER);
case ORDINALS:
if (this.english) {
long mod10 = count % 10;
long mod100 = count % 100;
if ((mod10 == 1) && (mod100 != 11)) {
return ONE;
} else if ((mod10 == 2) && (mod100 != 12)) {
return TWO;
} else if ((mod10 == 3) && (mod100 != 13)) {
return FEW;
}
}
return OTHER;
default:
throw new UnsupportedOperationException(
this.numType.name());
}
}
@Override
public NumberType getNumberType() {
return this.numType;
}
}
private static class FallbackProvider
implements PluralProvider {
//~ Methoden ------------------------------------------------------
@Override
public PluralRules load(
Locale country,
NumberType numType
) {
boolean english = country.getLanguage().equals("en");
switch (numType) {
case CARDINALS:
return (
english
? FALLBACK_CARDINAL_ENGLISH
: FALLBACK_CARDINAL_OTHER);
case ORDINALS:
return (
english
? FALLBACK_ORDINAL_ENGLISH
: FALLBACK_ORDINAL_OTHER);
default:
throw new UnsupportedOperationException(numType.name());
}
}
}
private static class Holder { // lazy class loading
//~ Statische Felder/Initialisierungen ----------------------------
private static final PluralProvider PROVIDER;
static {
PluralProvider p = null;
for (PluralProvider tmp : ResourceLoader.getInstance().services(PluralProvider.class)) {
p = tmp;
break;
}
if (p == null) {
p = new FallbackProvider();
}
PROVIDER = p;
}
}
}