/*
* -----------------------------------------------------------------------
* Copyright © 2013-2016 Meno Hochschild, <http://www.menodata.de/>
* -----------------------------------------------------------------------
* This file (IsoTextProviderSPI.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.i18n;
import net.time4j.format.DisplayMode;
import net.time4j.format.OutputContext;
import net.time4j.format.TextProvider;
import net.time4j.format.TextWidth;
import net.time4j.format.internal.ExtendedPatterns;
import net.time4j.history.HistoricEra;
import java.util.Collections;
import java.util.HashSet;
import java.util.Locale;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.Set;
import static net.time4j.format.CalendarText.ISO_CALENDAR_TYPE;
/**
* <p>{@code ServiceProvider}-implementation for accessing localized calendrical
* names in ISO-8601-types. </p>
*
* <p>The underlying properties files are located in the folder
* "calendar" relative to class path and are encoded in UTF-8.
* The basic bundle name is "iso8601". </p>
*
* @author Meno Hochschild
*/
public final class IsoTextProviderSPI
implements TextProvider, ExtendedPatterns {
//~ Statische Felder/Initialisierungen --------------------------------
private static final Set<String> LANGUAGES;
private static final Set<Locale> LOCALES;
static {
ResourceBundle rb =
ResourceBundle.getBundle(
"names/" + ISO_CALENDAR_TYPE,
Locale.ROOT,
getDefaultLoader(),
UTF8ResourceControl.SINGLETON);
String[] languages = rb.getString("languages").split(" ");
Set<String> tmp = new HashSet<>();
Collections.addAll(tmp, languages);
LANGUAGES = Collections.unmodifiableSet(tmp);
Set<Locale> locs = new HashSet<>();
for (String lang : LANGUAGES) {
locs.add(new Locale(lang));
}
// defensive strategy in case JDK should change its behaviour
for (LanguageMatch lm : LanguageMatch.values()) {
locs.add(new Locale(lm.name())); // in Java 8 or earlier no effect
}
LOCALES = Collections.unmodifiableSet(locs);
}
//~ Konstruktoren -----------------------------------------------------
/** For {@code java.util.ServiceLoader}. */
public IsoTextProviderSPI() {
super();
}
//~ Methoden ----------------------------------------------------------
@Override
public String[] getSupportedCalendarTypes() {
return new String[] { ISO_CALENDAR_TYPE };
}
@Override
public Locale[] getAvailableLocales() {
return LOCALES.toArray(new Locale[LOCALES.size()]);
}
@Override
public String[] months(
String calendarType,
Locale locale,
TextWidth tw,
OutputContext oc,
boolean leapForm
) {
return months(locale, tw, oc);
}
@Override
public String[] quarters(
String calendarType,
Locale locale,
TextWidth tw,
OutputContext oc
) {
return quarters(locale, tw, oc);
}
@Override
public String[] weekdays(
String calendarType,
Locale locale,
TextWidth tw,
OutputContext oc
) {
return weekdays(locale, tw, oc);
}
@Override
public String[] eras(
String calendarType,
Locale locale,
TextWidth tw
) {
return eras(locale, tw);
}
@Override
public String[] meridiems(
String calendarType,
Locale locale,
TextWidth tw
) {
return meridiems(locale, tw, OutputContext.FORMAT);
}
@Override
public String[] meridiems(
String calendarType,
Locale locale,
TextWidth tw,
OutputContext oc
) {
return meridiems(locale, tw, oc);
}
@Override
public String getDatePattern(
DisplayMode mode,
Locale locale
) {
StringBuilder sb = new StringBuilder();
sb.append("F(");
sb.append(toChar(mode));
sb.append(")_d");
String key = sb.toString();
return getPatterns(locale).getString(key);
}
@Override
public String getTimePattern(
DisplayMode mode,
Locale locale
) {
return this.getTimePattern(mode, locale, false);
}
@Override
public String getTimePattern(
DisplayMode mode,
Locale locale,
boolean alt
) {
String key;
if (alt && (mode == DisplayMode.FULL)) {
key = "F(alt)";
} else {
StringBuilder sb = new StringBuilder();
sb.append("F(");
sb.append(toChar(mode));
sb.append(")_t");
key = sb.toString();
}
return getPatterns(locale).getString(key);
}
@Override
public String getDateTimePattern(
DisplayMode dateMode,
DisplayMode timeMode,
Locale locale
) {
DisplayMode total = dateMode;
if (dateMode.compareTo(timeMode) < 0) {
total = timeMode;
}
StringBuilder sb = new StringBuilder();
sb.append("F(");
sb.append(toChar(total));
sb.append(")_dt");
String key = sb.toString();
return getPatterns(locale).getString(key);
}
@Override
public String getIntervalPattern(Locale locale) {
return getPatterns(locale).getString("I");
}
@Override
public ResourceBundle.Control getControl() {
return UTF8ResourceControl.SINGLETON;
}
@Override
public String toString() {
return "IsoTextProviderSPI";
}
/**
* <p>Liefert jene Sprachen, die speziell über properties-Dateien
* unterstützt werden. </p>
*
* <p>Dient dem Zugriff durch Testklassen. </p>
*
* @return unmodifiable {@code Set} of ISO-639-1-language codes
* @since 2.1.2
*/
static Set<String> getPrimaryLanguages() {
return LANGUAGES;
}
private static String[] months(
Locale locale,
TextWidth tw,
OutputContext oc
) throws MissingResourceException {
String[] names = null;
ResourceBundle rb = getBundle(locale);
if (rb != null) {
if (tw == TextWidth.SHORT) {
tw = TextWidth.ABBREVIATED;
}
String key = getKey(rb, "MONTH_OF_YEAR");
names = lookupBundle(rb, 12, key, tw, oc, 1);
// fallback rules as found in CLDR-root-properties via alias paths
if (names == null) {
if (oc == OutputContext.STANDALONE) {
if (tw != TextWidth.NARROW) {
names = months(locale, tw, OutputContext.FORMAT);
}
} else {
if (tw == TextWidth.ABBREVIATED) {
names = months(locale, TextWidth.WIDE, OutputContext.FORMAT);
} else if (tw == TextWidth.NARROW) {
names = months(locale, tw, OutputContext.STANDALONE);
}
}
}
}
if (names == null) {
throw new MissingResourceException(
"Cannot find ISO-8601-month.",
IsoTextProviderSPI.class.getName(),
locale.toString());
}
return names;
}
private static String[] quarters(
Locale locale,
TextWidth tw,
OutputContext oc
) throws MissingResourceException {
String[] names = null;
ResourceBundle rb = getBundle(locale);
if (rb != null) {
if (tw == TextWidth.SHORT) {
tw = TextWidth.ABBREVIATED;
}
String key = getKey(rb, "QUARTER_OF_YEAR");
names = lookupBundle(rb, 4, key, tw, oc, 1);
// fallback rules as found in CLDR-root-properties via alias paths
if (names == null) {
if (oc == OutputContext.STANDALONE) {
if (tw != TextWidth.NARROW) {
names = quarters(locale, tw, OutputContext.FORMAT);
}
} else {
if (tw == TextWidth.ABBREVIATED) {
names = quarters(locale, TextWidth.WIDE, OutputContext.FORMAT);
} else if (tw == TextWidth.NARROW) {
names = quarters(locale, tw, OutputContext.STANDALONE);
}
}
}
}
if (names == null) {
throw new MissingResourceException(
"Cannot find ISO-8601-quarter-of-year.",
IsoTextProviderSPI.class.getName(),
locale.toString());
}
return names;
}
private static String[] weekdays(
Locale locale,
TextWidth tw,
OutputContext oc
) throws MissingResourceException {
String[] names = null;
ResourceBundle rb = getBundle(locale);
if (rb != null) {
String key = getKey(rb, "DAY_OF_WEEK");
names = lookupBundle(rb, 7, key, tw, oc, 1);
// fallback rules as found in CLDR-root-properties via alias paths
if (names == null) {
if (oc == OutputContext.STANDALONE) {
if (tw != TextWidth.NARROW) {
names = weekdays(locale, tw, OutputContext.FORMAT);
}
} else {
if (tw == TextWidth.ABBREVIATED) {
names = weekdays(locale, TextWidth.WIDE, OutputContext.FORMAT);
} else if (tw == TextWidth.SHORT) {
names = weekdays(locale, TextWidth.ABBREVIATED, OutputContext.FORMAT);
} else if (tw == TextWidth.NARROW) {
names = weekdays(locale, tw, OutputContext.STANDALONE);
}
}
}
}
if (names == null) {
throw new MissingResourceException(
"Cannot find ISO-8601-quarter-of-year.",
IsoTextProviderSPI.class.getName(),
locale.toString());
}
return names;
}
private static String[] eras(
Locale locale,
TextWidth tw
) throws MissingResourceException {
String[] names = null;
ResourceBundle rb = getBundle(locale);
if (rb != null) {
if (tw == TextWidth.SHORT) {
tw = TextWidth.ABBREVIATED;
}
String key = getKey(rb, "ERA");
names = lookupBundle(rb, HistoricEra.values().length, key, tw, OutputContext.FORMAT, 0);
// fallback rules as found in CLDR-root-properties via alias paths
if ((names == null) && (tw != TextWidth.ABBREVIATED)) {
names = eras(locale, TextWidth.ABBREVIATED);
}
}
if (names == null) {
throw new MissingResourceException(
"Cannot find ISO-8601-resource for era.",
IsoTextProviderSPI.class.getName(),
locale.toString());
}
return names;
}
private static String[] meridiems(
Locale locale,
TextWidth tw,
OutputContext oc
) throws MissingResourceException {
ResourceBundle rb = getBundle(locale);
if (rb != null) {
if (tw == TextWidth.SHORT) {
tw = TextWidth.ABBREVIATED;
}
String amKey = meridiemKey("am", tw, oc);
String pmKey = meridiemKey("pm", tw, oc);
if (rb.containsKey(amKey) && rb.containsKey(pmKey)) {
String[] names = new String[2];
names[0] = rb.getString(amKey);
names[1] = rb.getString(pmKey);
return names;
}
// fallback
if (oc == OutputContext.STANDALONE) {
if (tw == TextWidth.ABBREVIATED) {
return meridiems(locale, tw, OutputContext.FORMAT);
} else {
return meridiems(locale, TextWidth.ABBREVIATED, oc);
}
} else if (tw != TextWidth.ABBREVIATED) {
return meridiems(locale, TextWidth.ABBREVIATED, oc);
}
}
throw new MissingResourceException(
"Cannot find ISO-8601-resource for am/pm.",
IsoTextProviderSPI.class.getName(),
locale.toString());
}
private static ResourceBundle getBundle(Locale desired)
throws MissingResourceException {
if (LANGUAGES.contains(LanguageMatch.getAlias(desired))) {
return ResourceBundle.getBundle(
"names/" + ISO_CALENDAR_TYPE,
desired,
getDefaultLoader(),
UTF8ResourceControl.SINGLETON);
}
return null;
}
private static ResourceBundle getPatterns(Locale desired) {
return ResourceBundle.getBundle(
"names/" + ISO_CALENDAR_TYPE,
desired,
getDefaultLoader(),
UTF8ResourceControl.SINGLETON);
}
private static char toChar(DisplayMode mode) {
return Character.toLowerCase(mode.name().charAt(0));
}
private static String[] lookupBundle(
ResourceBundle rb,
int len,
String elementName,
TextWidth tw,
OutputContext oc,
int baseIndex
) {
String[] names = new String[len];
boolean shortKey = (elementName.length() == 1);
for (int i = 0; i < len; i++) {
StringBuilder b = new StringBuilder();
b.append(elementName);
b.append('(');
if (shortKey) {
char c = tw.name().charAt(0);
if (oc != OutputContext.STANDALONE) {
c = Character.toLowerCase(c);
}
b.append(c);
} else {
b.append(tw.name());
if (oc == OutputContext.STANDALONE) {
b.append('|');
b.append(oc.name());
}
}
b.append(')');
b.append('_');
b.append(i + baseIndex);
String key = b.toString();
if (rb.containsKey(key)) {
names[i] = rb.getString(key);
} else {
return null;
}
}
return names;
}
private static String getKey(
ResourceBundle bundle,
String elementName
) {
if (
bundle.containsKey("useShortKeys")
&& "true".equals(bundle.getString("useShortKeys"))
) {
return elementName.substring(0, 1);
}
return elementName;
}
private static ClassLoader getDefaultLoader() {
return IsoTextProviderSPI.class.getClassLoader();
}
private static String meridiemKey(
String meridiem,
TextWidth tw,
OutputContext oc
) {
char c = tw.name().charAt(0);
if (oc == OutputContext.FORMAT) {
c = Character.toLowerCase(c);
}
return "P(" + String.valueOf(c) + ")_" + meridiem;
}
}