/*
* Copyright (c) 2002-2012 Alibaba Group Holding Limited.
* All rights reserved.
*
* 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 com.alibaba.toolkit.util.resourcebundle;
import java.lang.ref.SoftReference;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.MissingResourceException;
import com.alibaba.toolkit.util.collection.SoftHashMap;
import com.alibaba.toolkit.util.resourcebundle.xml.XMLResourceBundleFactory;
/**
* 创建<code>ResourceBundle</code>的实例的工厂.
*
* @author Michael Zhou
* @version $Id: ResourceBundleFactory.java,v 1.1 2003/07/03 07:26:35 baobao Exp
* $
*/
public abstract class ResourceBundleFactory {
/**
* 使用指定的bundle基本名, 默认的locale, 默认的factory中取得resource bundle.
* 默认的factory是从线程的context class loader中取得资源文件, 并以XML的格式解释资源文件.
*
* @param baseName bundle的基本名
* @return resource bundle
* @throws MissingResourceException 指定bundle未找到, 或创建bundle错误
*/
public static final ResourceBundle getBundle(String baseName) {
return getBundle(baseName, null, (ResourceBundleFactory) null);
}
/**
* 使用指定的bundle基本名, 指定的locale, 默认的factory中取得resource bundle.
* 默认的factory是从线程的context class loader中取得资源文件, 并以XML的格式解释资源文件.
*
* @param baseName bundle的基本名
* @param locale 区域设置
* @return resource bundle
* @throws MissingResourceException 指定bundle未找到, 或创建bundle错误
*/
public static final ResourceBundle getBundle(String baseName, Locale locale) {
return getBundle(baseName, locale, (ResourceBundleFactory) null);
}
/**
* 使用指定的bundle基本名, 指定的locale, 默认的factory中取得resource bundle.
* 默认的factory是从指定的class loader中取得资源文件, 并以XML的格式解释资源文件.
*
* @param baseName bundle的基本名
* @param locale 区域设置
* @param classLoader class loader
* @return resource bundle
* @throws MissingResourceException 指定bundle未找到, 或创建bundle错误
*/
public static final ResourceBundle getBundle(String baseName, Locale locale, ClassLoader classLoader) {
return getBundle(baseName, locale, new XMLResourceBundleFactory(classLoader));
}
/**
* 使用指定的bundle基本名, 指定的locale, 指定的loader, 默认的factory中取得resource bundle.
* 默认的factory是从指定的loader中取得资源文件, 并以XML的格式解释资源文件.
*
* @param baseName bundle的基本名
* @param locale 区域设置
* @param loader bundle的装入器
* @return resource bundle
* @throws MissingResourceException 指定bundle未找到, 或创建bundle错误
*/
public static final ResourceBundle getBundle(String baseName, Locale locale, ResourceBundleLoader loader) {
return getBundle(baseName, locale, new XMLResourceBundleFactory(loader));
}
/**
* 使用指定的bundle基本名, 指定的locale, 指定的factory中取得resource bundle.
*
* @param baseName bundle的基本名
* @param locale 区域设置
* @param factory bundle工厂
* @return resource bundle
* @throws MissingResourceException 指定bundle未找到, 或创建bundle错误
*/
public static final ResourceBundle getBundle(String baseName, Locale locale, ResourceBundleFactory factory) {
if (locale == null) {
locale = Locale.getDefault();
}
if (factory == null) {
factory = new XMLResourceBundleFactory();
}
return Helper.getBundleImpl(baseName, locale, factory);
}
/**
* 创建<code>ResourceBundle</code>的实例.
*
* @param bundleName 要创建的bundle名称
* @return 新创建的<code>ResourceBundle</code>实例, 如果指定bundle不存在, 则返回
* <code>null</code>
* @throws ResourceBundleCreateException 指定bundle文件存在, 但创建bundle实例失败,
* 例如文件格式错误
*/
public abstract ResourceBundle createBundle(String bundleName) throws ResourceBundleCreateException;
/**
* 判断两个<code>ResourceBundleFactory</code>是否等效. 这将作为
* <code>ResourceBundle</code>的cache的依据.
*
* @param obj 要比较的另一个对象
* @return 如果等效, 则返回<code>true</code>
*/
@Override
public abstract boolean equals(Object obj);
/**
* 取得hash值. 等效的<code>ResourceBundleFactory</code>应该具有相同的hash值.
*
* @return hash值
*/
@Override
public abstract int hashCode();
/** 查找和创建bundle的类. */
private static final class Helper {
/**
* 将(factory, bundleName, defaultLocale)映射到bundle对象的cache. 当内存不足时,
* cache的内容会自动释放.
*/
private static final Cache cache = new Cache();
/**
* 使用指定的bundle基本名, 指定的locale, 指定的factory中取得resource bundle.
*
* @param baseName bundle的基本名
* @param locale 区域设置
* @param factory bundle工厂
* @return resource bundle
* @throws MissingResourceException 指定bundle未找到, 或创建bundle错误
*/
private static ResourceBundle getBundleImpl(String baseName, Locale locale, ResourceBundleFactory factory) {
if (baseName == null) {
throw new NullPointerException(ResourceBundleConstant.RB_BASE_NAME_IS_NULL);
}
// 使用factory作为bundle未找到的标记, 这样当factory被GC回收的时候, cache里对应的项也可以被回收.
final Object NOT_FOUND = factory;
// 从cache中取得bundle.
String bundleName = baseName;
String localeSuffix = locale.toString();
if (localeSuffix.length() > 0) {
bundleName += "_" + localeSuffix;
} else if (locale.getVariant().length() > 0) {
// 修正: new Locale("", "", "VARIANT").toString == ""
bundleName += "___" + locale.getVariant();
}
// 取得系统locale, 注意, 这个值可能被改变, 所以每次执行时都重新取.
Locale defaultLocale = Locale.getDefault();
Object lookup = cache.get(factory, bundleName, defaultLocale);
if (NOT_FOUND.equals(lookup)) {
throwResourceBundleException(true, baseName, locale, null);
} else if (lookup != null) {
return (ResourceBundle) lookup;
}
// 开始查找并创建bundle.
Object parent = NOT_FOUND;
try {
// 查找base bundle.
Object root = findBundle(factory, baseName, defaultLocale, baseName, null, NOT_FOUND);
if (root == null) {
root = NOT_FOUND;
cache.put(factory, baseName, defaultLocale, root);
}
// 查找主要路径, 例如getBundle("baseName", new Locale("zh", "CN", "Variant")),
// 主要路径为baseName_zh, baseName_zh_CN, baseName_zh_CN_Varient.
final List names = calculateBundleNames(baseName, locale);
List bundlesFound = new ArrayList(ResourceBundleConstant.MAX_BUNDLES_SEARCHED);
// 如果base bundle已经找到, 并且主路径为空.
boolean foundInMainBranch = !NOT_FOUND.equals(root) && names.size() == 0;
if (!foundInMainBranch) {
parent = root;
for (int i = 0; i < names.size(); i++) {
bundleName = (String) names.get(i);
lookup = findBundle(factory, bundleName, defaultLocale, baseName, parent, NOT_FOUND);
bundlesFound.add(lookup);
if (lookup != null) {
parent = lookup;
foundInMainBranch = true;
}
}
}
// 如果主路径未找到bundle, 则查找系统默认路径, 例如当前系统默认locale为en_US,
// 则搜索路径为: baseName_en, baseName_US.
parent = root;
if (!foundInMainBranch) {
final List fallbackNames = calculateBundleNames(baseName, defaultLocale);
for (int i = 0; i < fallbackNames.size(); i++) {
bundleName = (String) fallbackNames.get(i);
// 如果系统默认路径和主路径一致, 则不需要再找下去了
if (names.contains(bundleName)) {
break;
}
lookup = findBundle(factory, bundleName, defaultLocale, baseName, parent, NOT_FOUND);
if (lookup != null) {
parent = lookup;
} else {
// 将父bundle传递给子bundle, 例如:
// 父bundle: baseName_en.xml已经找到, 子bundle: baseName_en_US未找到,
// 则cache中:
// baseName => bundle对象: baseName.xml
// baseName_en => bundle对象: baseName_en.xml
// baseName_en_US => bundle对象: baseName_en.xml
cache.put(factory, bundleName, defaultLocale, parent);
}
}
}
// 在主路径中, 将父bundle传递给子bundle, 这里有三种情况:
// 1. bundle在主路径中, 例如getBundle("baseName", new Locale("zh", "CN")),
// baseName_zh被找到, 则cache中:
// baseName => bundle对象: baseName.xml
// baseName_zh => bundle对象: baseName_zh.xml
// baseName_zh_CN => bundle对象: baseName_zh.xml
//
// 2. bundle在系统路径中, 主路径未找到, 例如getBundle("baseName", new Locale("zh", "CN")),
// baseName_zh和baseName_zh_CN均未找到, 但系统路径中baseName_en被找到, 则cache中:
// baseName => bundle对象: baseName.xml
// baseName_zh => bundle对象: baseName_en.xml
// baseName_zh_CN => bundle对象: baseName_en.xml
// baseName_en => bundle对象: baseName_en.xml
// baseName_en_US => bundle对象: baseName_en.xml
//
// 3. bundle的基本名未找到:
// baseName => NOT_FOUND
// baseName_zh => NOT_FOUND
// baseName_zh_CN => NOT_FOUND
// baseName_en => NOT_FOUND
// baseName_en_US => NOT_FOUND
for (int i = 0; i < names.size(); i++) {
final String name = (String) names.get(i);
final Object bundleFound = bundlesFound.get(i);
if (bundleFound == null) {
cache.put(factory, name, defaultLocale, parent);
} else {
parent = bundleFound;
}
}
} catch (Exception e) {
// 可能是ResourceBundleCreateException和其它RuntimeException.
cache.cleanUpConstructionList();
throwResourceBundleException(false, baseName, locale, e);
} catch (Error e) {
cache.cleanUpConstructionList();
throw e;
}
if (NOT_FOUND.equals(parent)) {
throwResourceBundleException(true, baseName, locale, null);
}
return (ResourceBundle) parent;
}
/**
* 在cache中查找bundle, 或从factory中装入bundle. 如果此方法返回<code>null</code>,
* 则调用者必须自己定义bundle, 并调用<code>cache.put</code>方法.
*
* @param factory bundle工厂
* @param bundleName bundle名称
* @param defaultLocale 系统默认的locale
* @param baseName bundle基本名
* @param parent 父bundle, 对于根bundle, 为<code>null</code>
* @param NOT_FOUND 标记"未找到"状态的对象
* @return resource bundle, 或者<code>null</code>表示bundle未找到
* @throws ResourceBundleCreateException bundle被找到, 但构造不成功
*/
private static Object findBundle(ResourceBundleFactory factory, String bundleName, Locale defaultLocale,
String baseName, Object parent, final Object NOT_FOUND)
throws ResourceBundleCreateException {
Object result = cache.getWait(factory, bundleName, defaultLocale);
if (result != null) {
return result;
}
// 尝试从factory中装入bundle.
result = factory.createBundle(bundleName);
if (result != null) {
// 在调用factory时, 有可能递归地调用了getBundle方法, 并且这个bundle已经被创建了.
// 这种情况下, bundle一定在cache中. 为了一致性, 应返回cache中的bundle.
Object otherBundle = cache.get(factory, bundleName, defaultLocale);
if (otherBundle != null) {
result = otherBundle;
} else {
// 设置bundle的父bundle, 并把它放到cache中.
final ResourceBundle bundle = (ResourceBundle) result;
if (!NOT_FOUND.equals(parent) && bundle.getParent() == null) {
bundle.setParent((ResourceBundle) parent);
}
bundle.setBaseName(baseName);
bundle.setLocale(baseName, bundleName);
cache.put(factory, bundleName, defaultLocale, result);
}
}
return result;
}
/**
* 取得备选的bundle名.
*
* @param baseName bundle的基本名
* @param locale 区域设置
* @return 所有备选的bundle名
*/
private static List calculateBundleNames(String baseName, Locale locale) {
final List result = new ArrayList(ResourceBundleConstant.MAX_BUNDLES_SEARCHED);
final String language = locale.getLanguage();
final int languageLength = language.length();
final String country = locale.getCountry();
final int countryLength = country.length();
final String variant = locale.getVariant();
final int variantLength = variant.length();
// 如果locale是("", "", "").
if (languageLength + countryLength + variantLength == 0) {
return result;
}
final StringBuffer buffer = new StringBuffer(baseName);
// 加入baseName_language
buffer.append('_');
buffer.append(language);
if (languageLength > 0) {
result.add(buffer.toString());
}
if (countryLength + variantLength == 0) {
return result;
}
// 加入baseName_language_country
buffer.append('_');
buffer.append(country);
if (countryLength > 0) {
result.add(buffer.toString());
}
if (variantLength == 0) {
return result;
}
// 加入baseName_language_country_variant
buffer.append('_');
buffer.append(variant);
result.add(buffer.toString());
return result;
}
/**
* 掷出"resource bundle未找到"的异常.
*
* @param missing 指定bundle未找到, 还是创建bundle错误
* @param baseName 未找到的bundle基本名
* @param locale 未找到的bundle的区域设置
* @param cause 异常起因
*/
private static void throwResourceBundleException(boolean missing, String baseName, Locale locale,
Throwable cause) {
String bundleName = baseName + "_" + locale;
if (missing) {
throw new ResourceBundleException(ResourceBundleConstant.RB_MISSING_RESOURCE_BUNDLE, new Object[] {
baseName, locale }, cause, bundleName, "");
} else {
throw new ResourceBundleException(ResourceBundleConstant.RB_FAILED_LOADING_RESOURCE_BUNDLE,
new Object[] { baseName, locale }, cause, bundleName, "");
}
}
}
/** 将(factory, bundleName, defaultLocale)映射到bundle对象的cache类. */
private static final class Cache extends SoftHashMap {
/** 静态的key, 用来在cache中查找bundle. 使用静态量可以减少GC的负担. 使用cacheKey必须对整个cache进行同步. */
private static final CacheKey cacheKey = new CacheKey();
/**
* 这个hash表用来同步多个线程, 以便同时装入同一个bundle. 这个hash表保存了cacheKey到thread的映射.
* 使用此hash表必须对整个cache进行同步.
*/
private final Map underConstruction = new HashMap(ResourceBundleConstant.MAX_BUNDLES_SEARCHED,
ResourceBundleConstant.CACHE_LOAD_FACTOR);
/** 构造一个cache. */
public Cache() {
super(ResourceBundleConstant.INITIAL_CACHE_SIZE, ResourceBundleConstant.CACHE_LOAD_FACTOR);
}
/**
* 在cache中查找bundle.
*
* @param factory bundle工厂
* @param bundleName bundle名称
* @param defaultLocale 系统locale
* @return 被cache的bundle. 如果未找到, 则返回<code>null</code>
*/
public synchronized Object get(ResourceBundleFactory factory, String bundleName, Locale defaultLocale) {
cacheKey.set(factory, bundleName, defaultLocale);
Object result = get(cacheKey);
cacheKey.clear();
return result;
}
/**
* 在cache中查找bundle, 如果bundle不存在, 并且有另一个线程正在构造此bundle, 则等待之. 如果此方法返回
* <code>null</code>, 则调用者必须负责调用<code>put</code>或
* <code>cleanUpConstructionList</code>方法, 否则别的线程可能等待它, 而造成死锁.
*
* @param factory bundle工厂
* @param bundleName bundle名称
* @param defaultLocale 系统locale
* @return 被cache的bundle. 如果未找到, 则返回<code>null</code>
*/
public synchronized Object getWait(ResourceBundleFactory factory, String bundleName, Locale defaultLocale) {
Object result;
// 首先查找cache中是否已经有这个bundle了, 如果有, 直接返回.
cacheKey.set(factory, bundleName, defaultLocale);
result = get(cacheKey);
if (result != null) {
cacheKey.clear();
return result;
}
// 检查是不已经有另一个thread正在创建这个bundle.
// 注意, 有可能递归调用getBundle方法, 例如, 在factory中调用了getBundle.
// 这种情况下, beingBuilt == false
Thread builder = (Thread) underConstruction.get(cacheKey);
boolean beingBuilt = builder != null && builder != Thread.currentThread();
// 如果已经有另一个thread正在创建这个bundle.
if (beingBuilt) {
while (beingBuilt) {
cacheKey.clear();
try {
// 等待, 直到别的线程创建完成.
wait();
} catch (InterruptedException e) {
}
cacheKey.set(factory, bundleName, defaultLocale);
beingBuilt = underConstruction.containsKey(cacheKey);
}
// 如果另一个线程把这个bundle创建好了, 则直接返回即可
result = get(cacheKey);
if (result != null) {
cacheKey.clear();
return result;
}
}
// 如果bundle不在cache中, 则准备构造此bundle.
// 调用者必须在随后调用put或cleanUpConstructionList方法, 否则将会死锁.
underConstruction.put(cacheKey.clone(), Thread.currentThread());
cacheKey.clear();
return null;
}
/**
* 将bundle放入cache, 并唤醒所有等待的线程.
*
* @param factory bundle工厂
* @param bundleName bundle名称
* @param defaultLocale 系统locale
* @param bundle 将被cache的bundle对象
*/
public synchronized void put(ResourceBundleFactory factory, String bundleName, Locale defaultLocale,
Object bundle) {
cacheKey.set(factory, bundleName, defaultLocale);
put(cacheKey.clone(), bundle);
underConstruction.remove(cacheKey);
cacheKey.clear();
// 唤醒所有线程
notifyAll();
}
/** 从"正在构造bundle"的线程表中清除当前线程. 如果装入bundle失败, 则需要调用此方法. */
public synchronized void cleanUpConstructionList() {
final Collection entries = underConstruction.values();
final Thread thisThread = Thread.currentThread();
while (entries.remove(thisThread)) {
}
// 唤醒所有线程
notifyAll();
}
}
/** 和bundle对应的cache key, 由bundle工厂, bundle名称, 系统locale几个字段组成. */
private static final class CacheKey implements Cloneable {
private SoftReference factoryRef;
private String bundleName;
private Locale defaultLocale;
private int hashCode;
/**
* 设置cache key.
*
* @param factory bundle工厂
* @param bundleName bundle名称
* @param defaultLocale 系统locale
*/
public void set(ResourceBundleFactory factory, String bundleName, Locale defaultLocale) {
this.bundleName = bundleName;
this.hashCode = bundleName.hashCode();
this.defaultLocale = defaultLocale;
if (defaultLocale != null) {
hashCode ^= defaultLocale.hashCode();
}
if (factory == null) {
this.factoryRef = null;
} else {
factoryRef = new SoftReference(factory);
hashCode ^= factory.hashCode();
}
}
/** 清除cache key. */
public void clear() {
set(null, "", null);
}
/**
* 检查两个key是否匹配.
*
* @param other 另一个cache key
* @return 如果匹配, 则返回<code>true</code>
*/
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
try {
final CacheKey otherKey = (CacheKey) other;
// hash值不同, 则立即返回
if (hashCode != otherKey.hashCode) {
return false;
}
// bundle名称是否相同?
if (!eq(bundleName, otherKey.bundleName)) {
return false;
}
// locale是否相同
if (!eq(defaultLocale, otherKey.defaultLocale)) {
return false;
}
// factory是否相同?
if (factoryRef == null) {
return otherKey.factoryRef == null;
} else {
return otherKey.factoryRef != null && eq(factoryRef.get(), otherKey.factoryRef.get());
}
} catch (NullPointerException e) {
return false;
} catch (ClassCastException e) {
return false;
}
}
/**
* 比较两个对象是否相等.
*
* @param o1 对象1
* @param o2 对象2
* @return 如果相等, 则返回<code>true</code>
*/
private boolean eq(Object o1, Object o2) {
return o1 == null ? o2 == null : o1.equals(o2);
}
/**
* 取得hash值, 如果两个对象等效, 则hash值也相等.
*
* @return hash值
*/
@Override
public int hashCode() {
return hashCode;
}
/**
* 复制对象.
*
* @return cache key的复本
*/
@Override
public Object clone() {
try {
return super.clone();
} catch (CloneNotSupportedException e) {
throw new InternalError(MessageFormat.format(ResourceBundleConstant.RB_CLONE_NOT_SUPPORTED,
new Object[] { CacheKey.class.getName() }));
}
}
/**
* 取得字符串值表示.
*
* @return 字符串表示
*/
@Override
public String toString() {
return new StringBuffer("CacheKey[factory=").append(factoryRef == null ? "null" : factoryRef.get())
.append(", bundleName=").append(bundleName).append(", defaultLocale=").append(defaultLocale)
.append("]").toString();
}
}
}