ResourceBundleWrapper.java
// © 2016 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
/*
******************************************************************************
* Copyright (C) 2004-2016, International Business Machines Corporation and
* others. All Rights Reserved.
******************************************************************************
*/
package com.ibm.icu.impl;
import com.ibm.icu.util.ULocale;
import com.ibm.icu.util.UResourceBundle;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.MissingResourceException;
import java.util.Objects;
import java.util.PropertyResourceBundle;
import java.util.ResourceBundle;
/**
* just a wrapper for Java ListResourceBundles and
*
* @author ram
*/
public final class ResourceBundleWrapper extends UResourceBundle {
private ResourceBundle bundle = null;
private String localeID = null;
private String baseName = null;
private List<String> keys = null;
/** Loader for bundle instances, for caching. */
private abstract static class Loader {
abstract ResourceBundleWrapper load();
}
private static class BundleCacheKey {
public final String baseName;
public final ClassLoader classLoader;
private BundleCacheKey(String baseName, ClassLoader classLoader) {
this.baseName = baseName;
this.classLoader = classLoader;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
BundleCacheKey that = (BundleCacheKey) o;
return Objects.equals(baseName, that.baseName)
&& Objects.equals(classLoader, that.classLoader);
}
@Override
public int hashCode() {
return Objects.hash(baseName, classLoader);
}
}
private static CacheBase<BundleCacheKey, ResourceBundleWrapper, Loader> BUNDLE_CACHE =
new SoftCache<BundleCacheKey, ResourceBundleWrapper, Loader>() {
@Override
protected ResourceBundleWrapper createInstance(
BundleCacheKey unusedKey, Loader loader) {
return loader.load();
}
};
private ResourceBundleWrapper(ResourceBundle bundle) {
this.bundle = bundle;
}
@Override
protected Object handleGetObject(String aKey) {
ResourceBundleWrapper current = this;
Object obj = null;
while (current != null) {
try {
obj = current.bundle.getObject(aKey);
break;
} catch (MissingResourceException ex) {
current = (ResourceBundleWrapper) current.getParent();
}
}
if (obj == null) {
throw new MissingResourceException(
"Can't find resource for bundle " + baseName + ", key " + aKey,
this.getClass().getName(),
aKey);
}
return obj;
}
@Override
public Enumeration<String> getKeys() {
return Collections.enumeration(keys);
}
private void initKeysVector() {
ResourceBundleWrapper current = this;
keys = new ArrayList<String>();
while (current != null) {
Enumeration<String> e = current.bundle.getKeys();
while (e.hasMoreElements()) {
String elem = e.nextElement();
if (!keys.contains(elem)) {
keys.add(elem);
}
}
current = (ResourceBundleWrapper) current.getParent();
}
}
@Override
protected String getLocaleID() {
return localeID;
}
@Override
protected String getBaseName() {
return bundle.getClass().getName().replace('.', '/');
}
@Override
public ULocale getULocale() {
return new ULocale(localeID);
}
@Override
public UResourceBundle getParent() {
return (UResourceBundle) parent;
}
// Flag for enabling/disabling debugging code
private static final boolean DEBUG = ICUDebug.enabled("resourceBundleWrapper");
// This method is for super class's instantiateBundle method
public static ResourceBundleWrapper getBundleInstance(
String baseName, String localeID, ClassLoader root, boolean disableFallback) {
if (root == null) {
root = ClassLoaderUtil.getClassLoader();
}
ResourceBundleWrapper b;
if (disableFallback) {
b = instantiateBundle(baseName, localeID, null, root, disableFallback);
} else {
b =
instantiateBundle(
baseName,
localeID,
ULocale.getDefault().getBaseName(),
root,
disableFallback);
}
if (b == null) {
String separator = "_";
if (baseName.indexOf('/') >= 0) {
separator = "/";
}
throw new MissingResourceException(
"Could not find the bundle " + baseName + separator + localeID, "", "");
}
return b;
}
private static boolean localeIDStartsWithLangSubtag(String localeID, String lang) {
return localeID.startsWith(lang)
&& (localeID.length() == lang.length() || localeID.charAt(lang.length()) == '_');
}
private static ResourceBundleWrapper instantiateBundle(
final String baseName,
final String localeID,
final String defaultID,
final ClassLoader root,
final boolean disableFallback) {
final String name = localeID.isEmpty() ? baseName : baseName + '_' + localeID;
BundleCacheKey cacheKey =
new BundleCacheKey(disableFallback ? name : name + '#' + defaultID, root);
return BUNDLE_CACHE.getInstance(
cacheKey,
new Loader() {
@Override
public ResourceBundleWrapper load() {
ResourceBundleWrapper parent = null;
int i = localeID.lastIndexOf('_');
boolean loadFromProperties = false;
boolean parentIsRoot = false;
if (i != -1) {
String locName = localeID.substring(0, i);
parent =
instantiateBundle(
baseName, locName, defaultID, root, disableFallback);
} else if (!localeID.isEmpty()) {
parent =
instantiateBundle(
baseName, "", defaultID, root, disableFallback);
parentIsRoot = true;
}
ResourceBundleWrapper b = null;
try {
Class<? extends ResourceBundle> cls =
root.loadClass(name).asSubclass(ResourceBundle.class);
ResourceBundle bx = cls.newInstance();
b = new ResourceBundleWrapper(bx);
if (parent != null) {
b.setParent(parent);
}
b.baseName = baseName;
b.localeID = localeID;
} catch (ClassNotFoundException e) {
loadFromProperties = true;
} catch (NoClassDefFoundError e) {
loadFromProperties = true;
} catch (Exception e) {
if (DEBUG) System.out.println("failure");
if (DEBUG) System.out.println(e);
}
if (loadFromProperties) {
try {
final String resName = name.replace('.', '/') + ".properties";
InputStream stream =
java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction<InputStream>() {
@Override
public InputStream run() {
return root.getResourceAsStream(resName);
}
});
if (stream != null) {
// make sure it is buffered
stream = new java.io.BufferedInputStream(stream);
try {
b =
new ResourceBundleWrapper(
new PropertyResourceBundle(stream));
if (parent != null) {
b.setParent(parent);
}
b.baseName = baseName;
b.localeID = localeID;
} catch (Exception ex) {
// throw away exception
} finally {
try {
stream.close();
} catch (Exception ex) {
// throw away exception
}
}
}
// if a bogus locale is passed then the parent should be
// the default locale not the root locale!
if (b == null
&& !disableFallback
&& !localeID.isEmpty()
&& localeID.indexOf('_') < 0
&& !localeIDStartsWithLangSubtag(defaultID, localeID)) {
// localeID is only a language subtag, different from the
// default language.
b =
instantiateBundle(
baseName,
defaultID,
defaultID,
root,
disableFallback);
}
// if still could not find the bundle then return the parent
if (b == null && (!parentIsRoot || !disableFallback)) {
b = parent;
}
} catch (Exception e) {
if (DEBUG) System.out.println("failure");
if (DEBUG) System.out.println(e);
}
}
if (b != null) {
b.initKeysVector();
} else {
if (DEBUG)
System.out.println(
"Returning null for " + baseName + "_" + localeID);
}
return b;
}
});
}
}