jp.furplag.util.Localizer.java Source code

Java tutorial

Introduction

Here is the source code for jp.furplag.util.Localizer.java

Source

/**
 * Copyright (C) 2015+ furplag (https://github.com/furplag/)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package jp.furplag.util;

import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.joda.time.DateTimeZone;
import org.joda.time.LocalTime;
import org.joda.time.format.DateTimeFormatter;
import org.joda.time.format.ISODateTimeFormat;

import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;

import jp.furplag.util.commons.NumberUtils;
import jp.furplag.util.commons.ObjectUtils;
import jp.furplag.util.commons.StringUtils;

/**
 * utilities for {@link TimeZone} and {@link Locale}.
 *
 * @author furplag
 */
public final class Localizer {

    /** a regular expression of timezone offsets. */
    private static final Pattern OFFSET = Pattern
            .compile("^(\\+|\\-)?\\d{1,2}((:\\d{1,2}){1,2}|(\\d{1,2}:){2}\\d{1,2}(\\.\\d{1,3})?)?$");

    private static final DateTimeFormatter OFFSET_FORMAT = ISODateTimeFormat.hourMinuteSecondMillis();

    /** if true, Locale.getScript() available. */
    private static final boolean LOCALE_SCRIPTABLE;

    static {
        boolean scriptable = false;
        try {
            Locale.class.getMethod("getScript");
            scriptable = true;
        } catch (NoSuchMethodException e) {
        }

        LOCALE_SCRIPTABLE = scriptable;
    }

    static final class LazyInitializer {

        static final Map<String, Locale> AVAILABLE_LOCALES = initializeLocales();

        static final Set<String> AVAILABLE_ZONE_IDS = initializeZoneIDs();

        static final Map<String, String> ZONE_DUPRECATED = initializeZoneDuprecated();

        private static Map<String, Locale> initializeLocales() {
            Map<String, Locale> map = new HashMap<String, Locale>();
            for (Locale locale : Locale.getAvailableLocales()) {
                map.put(locale.toString(), locale);
                String localeArgs = StringUtils
                        .join(new String[] { locale.getLanguage(), locale.getCountry(), locale.getVariant() }, "_");
                if (locale.toString().equals(StringUtils.truncateLast(localeArgs, "_+$")))
                    continue;
                map.put(localeArgs, locale);
                if (LOCALE_SCRIPTABLE && locale.toString().endsWith("#Latn"))
                    map.put(locale.toString().replaceAll("^" + locale.getLanguage() + "_" + locale.getCountry(),
                            locale.getLanguage() + "_" + locale.getCountry() + "_"), locale);
            }
            if (!LOCALE_SCRIPTABLE) {
                map.put("ja_JP_JP_#u-ca-japanese", map.get("ja_JP_JP"));
                map.put("th_TH_TH_#u-nu-thai", map.get("th_TH_TH"));
            }

            return ImmutableMap.copyOf(map);
        }

        private static Set<String> initializeZoneIDs() {
            return ImmutableSet.copyOf(TimeZone.getAvailableIDs());
        }

        private static Map<String, String> initializeZoneDuprecated() {
            Map<String, String> map = new HashMap<String, String>();
            map.put("Asia/Riyadh87", "+03:07:04");
            map.put("Asia/Riyadh88", "+03:07:04");
            map.put("Asia/Riyadh89", "+03:07:04");
            map.put("Mideast/Riyadh87", "+03:07:04");
            map.put("Mideast/Riyadh88", "+03:07:04");
            map.put("Mideast/Riyadh89", "+03:07:04");
            map.put("SystemV/AST4", "America/Puerto_Rico");
            map.put("SystemV/AST4ADT", "America/Halifax");
            map.put("SystemV/CST6", "Etc/GMT+6");
            map.put("SystemV/CST6CDT", "America/Chicago");
            map.put("SystemV/EST5", "Etc/GMT+5");
            map.put("SystemV/EST5EDT", "America/New_York");
            map.put("SystemV/HST10", "HST");
            map.put("SystemV/MST7", "Etc/GMT+7");
            map.put("SystemV/MST7MDT", "America/Denver");
            map.put("SystemV/PST8", "Etc/GMT+8");
            map.put("SystemV/PST8PDT", "America/Los_Angeles");
            map.put("SystemV/YST9", "Etc/GMT+9");
            map.put("SystemV/YST9YDT", "America/Anchorage");
            map.put("EST", "America/New_York");
            map.put("MST", "America/Denver");

            return ImmutableMap.copyOf(map);
        }
    }

    /**
     * Localizer instances should NOT be constructed in standard programming.
     */
    private Localizer() {
    }

    /**
     * create {@link DateTimeZone}.
     *
     * <pre>
     * newDateTimeZone(null) = DateTimeZone.getDefault()
     * newDateTimeZone("") = DateTimeZone.UTC
     * newDateTimeZone(9) = DateTimeZone.forTimeZone(TimeZone.getTimeZone("GMT+0900"))
     * newDateTimeZone(-9) = DateTimeZone.forTimeZone(TimeZone.getTimeZone("GMT-0900"))
     * newDateTimeZone("9") = DateTimeZone.forTimeZone(TimeZone.getTimeZone("GMT+0900"))
     * newDateTimeZone("-9") = DateTimeZone.forTimeZone(TimeZone.getTimeZone("GMT-0900"))
     * newDateTimeZone("Etc/GMT-9") = DateTimeZone.forTimeZone(TimeZone.getTimeZone("GMT-0900"))
     * newDateTimeZone("Etc/GMT-9") = DateTimeZone.forTimeZone(TimeZone.getTimeZone("GMT+0900"))
     * </pre>
     * <p>
     * fallback to similar timezone if deprecated time zone specified.
     * </p>
     *
     * <pre>
     * newDateTimeZone("SystemV/EST5") = DateTimeZone.forID("Etc/GMT+5")
     * newDateTimeZone("Mideast/Riyadh87") = DateTimeZone.forTimeZone(TimeZone.getTimeZone("GMT+0307"))
     * </pre>
     * <p>
     * minutes, millis also specifiable.
     * </p>
     *
     * <pre>
     * newDateTimeZone("SystemV/EST5") = DateTimeZone.forID("Etc/GMT+5")
     * newDateTimeZone("Mideast/Riyadh87") = DateTimeZone.forTimeZone(TimeZone.getTimeZone("GMT+0307"))
     * </pre>
     *
     * @param zone timezone ( {@link String}, {@link TimeZone}, and {@link DateTimeZone} specifiable ). Use default if null.
     * @return a {@link DateTimeZone} instance for the specified timezone.
     */
    public static DateTimeZone getDateTimeZone(final Object zone) {
        if (zone == null)
            return DateTimeZone.getDefault();
        if (zone instanceof DateTimeZone)
            return (DateTimeZone) zone;
        if (zone instanceof TimeZone)
            return getDateTimeZone((TimeZone) zone);
        if (ObjectUtils.isAny(zone.getClass(), Byte.class, Short.class, Integer.class, Long.class))
            return getDateTimeZone(NumberUtils.valueOf(zone.toString(), Long.class));
        if (zone instanceof String)
            return getDateTimeZone(zone.toString());

        return DateTimeZone.UTC;
    }

    private static DateTimeZone getDateTimeZone(final String zone) {
        if (zone == null)
            return DateTimeZone.getDefault();
        if (StringUtils.isSimilarToBlank(zone))
            return DateTimeZone.UTC;
        if (DateTimeZone.getAvailableIDs().contains(zone))
            return DateTimeZone.forID(zone.toString());
        if (LazyInitializer.ZONE_DUPRECATED.containsKey(zone))
            return DateTimeZone.forTimeZone(TimeZone.getTimeZone(LazyInitializer.ZONE_DUPRECATED.get(zone)));
        if (LazyInitializer.AVAILABLE_ZONE_IDS.contains(zone))
            return DateTimeZone.forTimeZone(TimeZone.getTimeZone(zone));
        Matcher base = OFFSET.matcher(StringUtils.normalize(zone, true));
        if (base.find()) {
            String offsetString = base.group();

            int[] offsetTime = JSONifier.parseLazy(
                    JSONifier.stringifyLazy(StringUtils.truncateFirst(offsetString, "[\\+\\-]").split("[:\\.]")),
                    int[].class);
            if (offsetTime.length < 1)
                return DateTimeZone.UTC;
            LocalTime offset = new LocalTime(offsetTime.length > 0 ? offsetTime[0] : 0,
                    offsetTime.length > 1 ? offsetTime[1] : 0, offsetTime.length > 2 ? offsetTime[2] : 0,
                    offsetTime.length > 3 ? offsetTime[3] : 0);

            return DateTimeZone.forID((offsetString.startsWith("-") ? "-" : "+") + offset.toString(OFFSET_FORMAT));
        }

        return DateTimeZone.UTC;
    }

    private static DateTimeZone getDateTimeZone(final TimeZone zone) {
        if (zone == null)
            return DateTimeZone.getDefault();
        String zoneID = ((TimeZone) zone).getID();
        if (DateTimeZone.getAvailableIDs().contains(zoneID))
            return DateTimeZone.forID(zoneID);
        if (LazyInitializer.ZONE_DUPRECATED.containsKey(zoneID))
            return DateTimeZone.forTimeZone(TimeZone.getTimeZone(LazyInitializer.ZONE_DUPRECATED.get(zoneID)));

        return DateTimeZone.forTimeZone((TimeZone) zone);
    }

    private static DateTimeZone getDateTimeZone(final Long millis) {
        if (millis == null)
            return DateTimeZone.getDefault();
        if (millis % 86400000L == 0)
            return DateTimeZone.UTC;
        LocalTime offset = LocalTime.fromMillisOfDay((millis % 86400000L) * (millis < 0 ? -1 : 1));

        return DateTimeZone.forID((millis < 0 ? "-" : "+") + offset.toString(OFFSET_FORMAT));
    }

    /**
     * create {@code Locale}.
     * <p>
     * <ul>
     * <li>{@code Localizer.newLocale(null)} => default locale ({@code Locale.getDefault()})</li>
     * <li>{@code Localizer.newLocale("invalid_LOCALE")} => default locale ({@code Locale.getDefault()})</li>
     * <li>{@code Localizer.newLocale("")} => {@code Locale.ROOT}</li>
     * <li>{@code Localizer.newLocale("ja")} => {@code Locale.JAPANESE}</li>
     * <li>{@code Localizer.newLocale("ja_JP")} => {@code Locale.JAPAN}</li>
     * <li>{@code Localizer.newLocale("ja_JP_JP")} => {@code "ja_JP_JP_#u-ca-japanese"} (1.7 later), {@code "ja_JP_JP"} (1.6).</li>
     * </ul>
     * </p>
     *
     * @param locale the language for Localization ( {@code String} and {@code Locale} specifiable ). Use default if {@code locale} is null.
     * @return
     */
    public static Locale getAvailableLocale(final Object locale) {
        if (locale == null)
            return Locale.getDefault();
        if (locale instanceof Locale)
            return (Locale) locale;
        if (locale instanceof Boolean)
            return ((Boolean) locale) ? Locale.getDefault() : Locale.ROOT;
        if (locale instanceof String)
            return getAvailableLocale(new String[] { locale.toString() });

        return Locale.ROOT;
    }

    public static Locale getAvailableLocale(final String... localeArgs) {
        if (!(localeArgs != null && localeArgs.length > 0))
            return Locale.getDefault();
        String localeID = StringUtils.join(localeArgs, ",").replaceAll("_,_$", "__").replaceAll("_?,_?", "_")
                .replaceAll("_+$", "");
        if (!LOCALE_SCRIPTABLE)
            localeID = localeID.replaceAll("_#.*$", "");
        if (LazyInitializer.AVAILABLE_LOCALES.containsKey(localeID))
            return LazyInitializer.AVAILABLE_LOCALES.get(localeID);

        return Locale.ROOT;
    }

    public static Locale[] getAvailableLocales() {
        return LazyInitializer.AVAILABLE_LOCALES.values().toArray(new Locale[] {});
    }

    public static String[] getAvailableZoneIDs() {
        return LazyInitializer.AVAILABLE_ZONE_IDS.toArray(new String[] {});
    }
}