
org.tinygroup.commons.i18n.LocaleUtil Maven / Gradle / Ivy
/**
* Copyright (c) 1997-2013, www.tinygroup.org ([email protected]).
*
* Licensed under the GPL, Version 3.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.gnu.org/licenses/gpl.html
*
* 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 org.tinygroup.commons.i18n;
import org.tinygroup.commons.io.StreamUtil;
import org.tinygroup.commons.tools.ClassLoaderUtil;
import org.tinygroup.commons.tools.StringUtil;
import java.net.URL;
import java.nio.charset.Charset;
import java.nio.charset.UnsupportedCharsetException;
import java.util.*;
import static org.tinygroup.commons.tools.BasicConstant.EMPTY_STRING;
import static org.tinygroup.commons.tools.CollectionUtil.createHashSet;
import static org.tinygroup.commons.tools.CollectionUtil.createLinkedList;
import static org.tinygroup.commons.tools.StringUtil.trimToNull;
/**
* 用来处理地域和字符编码的工具类。
*
* 由于系统locale和charset是不可靠的,不同的环境可能会有不同的系统设置,因此应用程序最好不要依赖这个系统值。
* LocaleUtil
提供了一个方案,可以“修改”默认locale和charset。
*
*
* LocaleUtil
提供了以下几个作用域的locale/charset设定:
*
*
* - 系统作用域:由JVM所运行的操作系统环境决定,在JVM生命期内不改变。可通过
LocaleUtil.getSystem()
* 取得。
* - 默认作用域:在整个JVM中全局有效,可被改变。可通过
LocaleUtil.getDefault()
* 取得。如未明确指定,则取“系统作用域”的值。
* - 线程作用域:在整个线程中全局有效,可被改变。可通过
LocaleUtil.getContext()
* 取得。如未明确指定,则取“默认作用域”的值。每个线程都可以有自己的locale和charset设置,不会干扰其它线程。
*
*
* Util工具箱里的其它工具类,当需要时,将从LocaleUtil.getContext()
* 中取得当前的locale和charset设置。例如:StringEscapeUtil.escapeURL(value)
* ,如不指定charset
* ,将从context中取得charset。这样,框架往往可以修改context值,而所有线程中的方法调用将服从于框架的locale和charset设定。
*
*
* @author Michael Zhou
*/
public class LocaleUtil {
private static final LocaleInfo systemLocaleInfo = new LocaleInfo();
private static LocaleInfo defaultLocalInfo = systemLocaleInfo;
private static final ThreadLocal contextLocaleInfoHolder = new ThreadLocal();
/**
* 判断locale是否被支持。
*
* @param locale 要检查的locale
*/
public static boolean isLocaleSupported(Locale locale) {
return locale != null && AvailableLocalesLoader.locales.AVAILABLE_LANGUAGES.contains(locale.getLanguage())
&& AvailableLocalesLoader.locales.AVAILABLE_COUNTRIES.contains(locale.getCountry());
}
/**
* 判断指定的charset是否被支持。
*
* @param charset 要检查的charset
*/
public static boolean isCharsetSupported(String charset) {
return charset != null && Charset.isSupported(charset);
}
/**
* 解析locale字符串。
*
* Locale字符串是符合下列格式:language_country_variant
。
*
*
* @param localeString 要解析的字符串
* @return Locale
对象,如果locale字符串为空,则返回null
*/
public static Locale parseLocale(String localeString) {
localeString = trimToNull(localeString);
if (localeString == null) {
return null;
}
String language = EMPTY_STRING;
String country = EMPTY_STRING;
String variant = EMPTY_STRING;
// language
int start = 0;
int index = localeString.indexOf("_");
if (index >= 0) {
language = localeString.substring(start, index).trim();
// country
start = index + 1;
index = localeString.indexOf("_", start);
if (index >= 0) {
country = localeString.substring(start, index).trim();
// variant
variant = localeString.substring(index + 1).trim();
} else {
country = localeString.substring(start).trim();
}
} else {
language = localeString.trim();
}
return new Locale(language, country, variant);
}
/**
* 取得正规的字符集名称, 如果指定字符集不存在, 则抛出UnsupportedEncodingException
.
*
* @param charset 字符集名称
* @return 正规的字符集名称
* @throws java.nio.charset.IllegalCharsetNameException 如果指定字符集名称非法
* @throws java.nio.charset.UnsupportedCharsetException 如果指定字符集不存在
*/
public static String getCanonicalCharset(String charset) {
return Charset.forName(charset).name();
}
/**
* 取得备选的resource bundle风格的名称列表。
*
* 例如:
* calculateBundleNames("hello.jsp", new Locale("zh", "CN", "variant"))
* 将返回下面列表:
*
* - hello_zh_CN_variant.jsp
* - hello_zh_CN.jsp
* - hello_zh.jsp
* - hello.jsp
*
*
*
* @param baseName bundle的基本名
* @param locale 区域设置
* @return 所有备选的bundle名
*/
public static List calculateBundleNames(String baseName, Locale locale) {
return calculateBundleNames(baseName, locale, false);
}
/**
* 取得备选的resource bundle风格的名称列表。
*
* 例如:
* calculateBundleNames("hello.jsp", new Locale("zh", "CN", "variant"),
* false)
将返回下面列表:
*
* - hello_zh_CN_variant.jsp
* - hello_zh_CN.jsp
* - hello_zh.jsp
* - hello.jsp
*
*
*
* 当noext
为true
时,不计算后缀名,例如
* calculateBundleNames("hello.world",
* new Locale("zh", "CN", "variant"), true)
将返回下面列表:
*
* - hello.world_zh_CN_variant
* - hello.world_zh_CN
* - hello.world_zh
* - hello.world
*
*
*
* @param baseName bundle的基本名
* @param locale 区域设置
* @return 所有备选的bundle名
*/
public static List calculateBundleNames(String baseName, Locale locale, boolean noext) {
baseName = StringUtil.trimToEmpty(baseName);
if (locale == null) {
locale = new Locale(EMPTY_STRING);
}
// 取后缀。
String ext = EMPTY_STRING;
int extLength = 0;
if (!noext) {
int extIndex = baseName.lastIndexOf(".");
if (extIndex != -1) {
ext = baseName.substring(extIndex, baseName.length());
extLength = ext.length();
baseName = baseName.substring(0, extIndex);
if (extLength == 1) {
ext = EMPTY_STRING;
extLength = 0;
}
}
}
// 计算locale后缀。
LinkedList result = createLinkedList();
String language = locale.getLanguage();
int languageLength = language.length();
String country = locale.getCountry();
int countryLength = country.length();
String variant = locale.getVariant();
int variantLength = variant.length();
StringBuilder buffer = new StringBuilder(baseName);
buffer.append(ext);
result.addFirst(buffer.toString());
buffer.setLength(buffer.length() - extLength);
// 如果locale是("", "", "").
if (languageLength + countryLength + variantLength == 0) {
return result;
}
// 加入baseName_language,如果baseName为空,则不加下划线。
if (buffer.length() > 0) {
buffer.append('_');
}
buffer.append(language);
if (languageLength > 0) {
buffer.append(ext);
result.addFirst(buffer.toString());
buffer.setLength(buffer.length() - extLength);
}
if (countryLength + variantLength == 0) {
return result;
}
// 加入baseName_language_country
buffer.append('_').append(country);
if (countryLength > 0) {
buffer.append(ext);
result.addFirst(buffer.toString());
buffer.setLength(buffer.length() - extLength);
}
if (variantLength == 0) {
return result;
}
// 加入baseName_language_country_variant
buffer.append('_').append(variant);
buffer.append(ext);
result.addFirst(buffer.toString());
buffer.setLength(buffer.length() - extLength);
return result;
}
/**
* 取得操作系统默认的区域。
*
* @return 操作系统默认的区域
*/
public static LocaleInfo getSystem() {
return systemLocaleInfo;
}
/**
* 取得默认的区域。
*
* @return 默认的区域
*/
public static LocaleInfo getDefault() {
return defaultLocalInfo == null ? systemLocaleInfo : defaultLocalInfo;
}
/**
* 设置默认的区域。
*
* @param locale 区域
* @return 原来的默认区域
*/
public static LocaleInfo setDefault(Locale locale) {
LocaleInfo old = getDefault();
setDefaultAndNotify(new LocaleInfo(locale, null, systemLocaleInfo));
return old;
}
/**
* 设置默认的区域。
*
* @param locale 区域
* @param charset 编码字符集
* @return 原来的默认区域
*/
public static LocaleInfo setDefault(Locale locale, String charset) throws UnsupportedCharsetException {
LocaleInfo old = getDefault();
setDefaultAndNotify(new LocaleInfo(locale, charset, systemLocaleInfo));
return old;
}
/**
* 设置默认的区域。
*
* @param localeInfo 区域和编码字符集信息
* @return 原来的默认区域
*/
public static LocaleInfo setDefault(LocaleInfo localeInfo) throws UnsupportedCharsetException {
if (localeInfo == null) {
return setDefault(null, null);
} else {
LocaleInfo old = getDefault();
setDefaultAndNotify(localeInfo);
return old;
}
}
private static void setDefaultAndNotify(LocaleInfo localeInfo) throws UnsupportedCharsetException {
defaultLocalInfo = localeInfo.assertCharsetSupported();
for (Notifier notifier : notifiers) {
notifier.defaultChanged(localeInfo);
}
}
/**
* 复位默认的区域设置。
*/
public static void resetDefault() {
defaultLocalInfo = systemLocaleInfo;
for (Notifier notifier : notifiers) {
notifier.defaultReset();
}
}
/**
* 取得当前thread默认的区域。
*
* @return 当前thread默认的区域
*/
public static LocaleInfo getContext() {
LocaleInfo contextLocaleInfo = contextLocaleInfoHolder.get();
return contextLocaleInfo == null ? getDefault() : contextLocaleInfo;
}
/**
* 设置当前thread默认的区域。
*
* @param locale 区域
* @return 原来的thread默认的区域
*/
public static LocaleInfo setContext(Locale locale) {
LocaleInfo old = getContext();
setContextAndNotify(new LocaleInfo(locale, null, defaultLocalInfo));
return old;
}
/**
* 设置当前thread默认的区域。
*
* @param locale 区域
* @param charset 编码字符集
* @return 原来的thread默认的区域
*/
public static LocaleInfo setContext(Locale locale, String charset) throws UnsupportedCharsetException {
LocaleInfo old = getContext();
setContextAndNotify(new LocaleInfo(locale, charset, defaultLocalInfo));
return old;
}
/**
* 设置当前thread默认的区域。
*
* @param localeInfo 区域和编码字符集信息
* @return 原来的thread默认的区域
*/
public static LocaleInfo setContext(LocaleInfo localeInfo) throws UnsupportedCharsetException {
if (localeInfo == null) {
return setContext(null, null);
} else {
LocaleInfo old = getContext();
setContextAndNotify(localeInfo);
return old;
}
}
private static void setContextAndNotify(LocaleInfo localeInfo) throws UnsupportedCharsetException {
contextLocaleInfoHolder.set(localeInfo.assertCharsetSupported());
for (Notifier notifier : notifiers) {
notifier.contextChanged(localeInfo);
}
}
/**
* 复位当前thread的区域设置。
*/
public static void resetContext() {
contextLocaleInfoHolder.remove();
for (Notifier notifier : notifiers) {
notifier.contextReset();
}
}
private static Notifier[] notifiers = getNotifiers();
private static Notifier[] getNotifiers() {
try {
URL[] files = ClassLoaderUtil.getResources("META-INF/services/localeNotifiers", ClassLoaderUtil.class);
List list = createLinkedList();
for (URL file : files) {
for (String className : StringUtil.split(StreamUtil.readText(file.openStream(), "UTF-8", true), "\r\n ")) {
list.add(Notifier.class.cast(ClassLoaderUtil.newInstance(className, ClassLoaderUtil.class)));
}
}
return list.toArray(new Notifier[list.size()]);
} catch (Exception e) {
System.err.println("Failure in LocaleUtil.getNotifiers()" + e.toString());
return new Notifier[0];
}
}
/**
* 当default或context locale被改变时,通知监听器。
*/
public interface Notifier extends EventListener {
void defaultChanged(LocaleInfo newValue);
void defaultReset();
void contextChanged(LocaleInfo newValue);
void contextReset();
}
/**
* 延迟加载所有可用的国家和语言。
*/
private static class AvailableLocalesLoader {
private static final AvailableLocales locales = new AvailableLocales();
}
private static class AvailableLocales {
private final Set AVAILABLE_LANGUAGES = createHashSet();
private final Set AVAILABLE_COUNTRIES = createHashSet();
private AvailableLocales() {
Locale[] availableLocales = Locale.getAvailableLocales();
for (Locale locale : availableLocales) {
AVAILABLE_LANGUAGES.add(locale.getLanguage());
AVAILABLE_COUNTRIES.add(locale.getCountry());
}
}
}
}