/* * ----------------------------------------------------------------------- * Copyright © 2013-2015 Meno Hochschild, <http://www.menodata.de/> * ----------------------------------------------------------------------- * This file (UnitPatternProviderSPI.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.PluralCategory; import net.time4j.format.RelativeTimeProvider; import net.time4j.format.TextWidth; import java.util.Locale; import java.util.MissingResourceException; import java.util.ResourceBundle; /** * <p>{@code ServiceProvider}-implementation for accessing localized unit * patterns. </p> * * <p>The underlying properties files are located in the folder * "units" relative to class path and are encoded in UTF-8. The basic * bundle name is "upattern". This class uses a modified fallback * algorithm for searching the right properties file as documented in * <a href="http://www.unicode.org/reports/tr35/#Multiple_Inheritance" * target="_blank">CLDR</a> published by unicode consortium. </p> * * <p>The case is similar for past and future patterns - with the difference * that the folder "reltime" and the basic bundle name "relpattern" * are used instead. </p> * * @author Meno Hochschild * @since 1.2 */ public final class UnitPatternProviderSPI implements RelativeTimeProvider { //~ Methoden ---------------------------------------------------------- @Override public String getYearPattern( Locale language, TextWidth width, PluralCategory category ) { return this.getUnitPattern(language, 'Y', width, category); } @Override public String getMonthPattern( Locale language, TextWidth width, PluralCategory category ) { return this.getUnitPattern(language, 'M', width, category); } @Override public String getWeekPattern( Locale language, TextWidth width, PluralCategory category ) { return this.getUnitPattern(language, 'W', width, category); } @Override public String getDayPattern( Locale language, TextWidth width, PluralCategory category ) { return this.getUnitPattern(language, 'D', width, category); } @Override public String getHourPattern( Locale language, TextWidth width, PluralCategory category ) { return this.getUnitPattern(language, 'H', width, category); } @Override public String getMinutePattern( Locale language, TextWidth width, PluralCategory category ) { return this.getUnitPattern(language, 'N', width, category); } @Override public String getSecondPattern( Locale language, TextWidth width, PluralCategory category ) { return this.getUnitPattern(language, 'S', width, category); } @Override public String getMilliPattern( Locale lang, TextWidth width, PluralCategory category ) { return this.getUnitPattern(lang, '3', width, category); } @Override public String getMicroPattern( Locale lang, TextWidth width, PluralCategory category ) { return this.getUnitPattern(lang, '6', width, category); } @Override public String getNanoPattern( Locale lang, TextWidth width, PluralCategory category ) { return this.getUnitPattern(lang, '9', width, category); } @Override public String getYearPattern( Locale language, boolean future, PluralCategory category ) { return this.getRelativePattern(language, 'Y', future, category); } @Override public String getMonthPattern( Locale language, boolean future, PluralCategory category ) { return this.getRelativePattern(language, 'M', future, category); } @Override public String getWeekPattern( Locale language, boolean future, PluralCategory category ) { return this.getRelativePattern(language, 'W', future, category); } @Override public String getDayPattern( Locale language, boolean future, PluralCategory category ) { return this.getRelativePattern(language, 'D', future, category); } @Override public String getHourPattern( Locale language, boolean future, PluralCategory category ) { return this.getRelativePattern(language, 'H', future, category); } @Override public String getMinutePattern( Locale language, boolean future, PluralCategory category ) { return this.getRelativePattern(language, 'N', future, category); } @Override public String getSecondPattern( Locale language, boolean future, PluralCategory category ) { return this.getRelativePattern(language, 'S', future, category); } @Override public String getNowWord(Locale lang) { return this.getPattern( lang, "reltime/relpattern", "now", null, PluralCategory.OTHER); } @Override public String getShortYearPattern( Locale language, boolean future, PluralCategory category ) { return this.getRelativePattern(language, 'y', future, category); } @Override public String getShortMonthPattern( Locale language, boolean future, PluralCategory category ) { return this.getRelativePattern(language, 'm', future, category); } @Override public String getShortWeekPattern( Locale language, boolean future, PluralCategory category ) { return this.getRelativePattern(language, 'w', future, category); } @Override public String getShortDayPattern( Locale language, boolean future, PluralCategory category ) { return this.getRelativePattern(language, 'd', future, category); } @Override public String getShortHourPattern( Locale language, boolean future, PluralCategory category ) { return this.getRelativePattern(language, 'h', future, category); } @Override public String getShortMinutePattern( Locale language, boolean future, PluralCategory category ) { return this.getRelativePattern(language, 'n', future, category); } @Override public String getShortSecondPattern( Locale language, boolean future, PluralCategory category ) { return this.getRelativePattern(language, 's', future, category); } @Override public String getYesterdayWord(Locale lang) { return this.getPattern( lang, "reltime/relpattern", "yesterday", null, PluralCategory.OTHER); } @Override public String getTodayWord(Locale lang) { return this.getPattern( lang, "reltime/relpattern", "today", null, PluralCategory.OTHER); } @Override public String getTomorrowWord(Locale lang) { return this.getPattern( lang, "reltime/relpattern", "tomorrow", null, PluralCategory.OTHER); } @Override public String getListPattern( Locale desired, TextWidth width, int size ) { if (size < 2) { throw new IllegalArgumentException("Size must be greater than 1."); } ClassLoader loader = this.getClass().getClassLoader(); ResourceBundle.Control control = UTF8ResourceControl.SINGLETON; ResourceBundle rb = ResourceBundle.getBundle("units/upattern", desired, loader, control); String exact = buildListKey(width, String.valueOf(size)); if (rb.containsKey(exact)) { return rb.getString(exact); } String end = rb.getString(buildListKey(width, "end")); if (size == 2) { return end; } String start = rb.getString(buildListKey(width, "start")); String middle = rb.getString(buildListKey(width, "middle")); end = replace(end, '1', size - 1); end = replace(end, '0', size - 2); String previous = end; String result = previous; for (int i = size - 3; i >= 0; i--) { String pattern = ((i == 0) ? start : middle); int pos = -1; int n = pattern.length(); for (int j = n - 1; j >= 0; j--) { if ( (j >= 2) && (pattern.charAt(j) == '}') && (pattern.charAt(j - 1) == '1') && (pattern.charAt(j - 2) == '{') ) { pos = j - 2; break; } } if (pos > -1) { result = pattern.substring(0, pos) + previous; if (pos < n - 3) { result += pattern.substring(pos + 3); } } if (i > 0) { previous = replace(result, '0', i); } } return result; } private String getUnitPattern( Locale lang, char unitID, TextWidth width, PluralCategory category ) { return this.getPattern( lang, "units/upattern", buildKey(unitID, width, category), buildKey(unitID, width, PluralCategory.OTHER), category ); } private String getRelativePattern( Locale lang, char unitID, boolean future, PluralCategory category ) { return this.getPattern( lang, "reltime/relpattern", buildKey(unitID, future, category), buildKey(unitID, future, PluralCategory.OTHER), category ); } private String getPattern( Locale desired, String baseName, String key, String alt, PluralCategory category ) { ClassLoader loader = this.getClass().getClassLoader(); ResourceBundle.Control control = UTF8ResourceControl.SINGLETON; boolean init = true; ResourceBundle first = null; for (Locale locale : control.getCandidateLocales(baseName, desired)) { ResourceBundle rb = ( init && (first != null) ? first : ResourceBundle.getBundle(baseName, locale, loader, control)); if (init) { if (locale.equals(rb.getLocale())) { init = false; } else { first = rb; continue; } } UTF8ResourceBundle bundle = UTF8ResourceBundle.class.cast(rb); if (bundle.getInternalKeys().contains(key)) { return bundle.getString(key); } else if ( (category != PluralCategory.OTHER) && bundle.getInternalKeys().contains(alt) ) { return bundle.getString(alt); } } throw new MissingResourceException( "Can't find resource for bundle " + baseName + ".properties, key " + key, baseName + ".properties", key ); } private static String buildKey( char unitID, TextWidth width, PluralCategory category ) { StringBuilder sb = new StringBuilder(3); sb.append(unitID); switch (width) { case WIDE: sb.append('w'); break; case ABBREVIATED: case SHORT: sb.append('s'); break; case NARROW: sb.append('n'); break; default: throw new UnsupportedOperationException(width.name()); } return sb.append(category.ordinal()).toString(); } private static String buildKey( char unitID, boolean future, PluralCategory category ) { StringBuilder sb = new StringBuilder(3); sb.append(unitID); sb.append(future ? '+' : '-'); return sb.append(category.ordinal()).toString(); } private static String buildListKey( TextWidth width, String suffix ) { StringBuilder sb = new StringBuilder(); sb.append('L'); switch (width) { case WIDE: sb.append('w'); break; case ABBREVIATED: case SHORT: sb.append('s'); break; case NARROW: sb.append('n'); break; default: throw new UnsupportedOperationException(width.name()); } return sb.append('-').append(suffix).toString(); } private static String replace( String s, char search, int value ) { for (int i = 0, n = s.length() - 2; i < n; i++) { if ( (s.charAt(i) == '{') && (s.charAt(i + 1) == search) && (s.charAt(i + 2) == '}') ) { StringBuilder b = new StringBuilder(n + 10); b.append(s); b.replace(i + 1, i + 2, String.valueOf(value)); return b.toString(); } } return s; } }