| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You 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 java.util; |
| |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.security.AccessController; |
| import java.security.PrivilegedAction; |
| |
| // BEGIN android-changed |
| // import org.apache.harmony.kernel.vm.VM; |
| import com.ibm.icu4jni.util.Resources; |
| import dalvik.system.VMStack; |
| // END android-changed |
| import org.apache.harmony.luni.util.Msg; |
| |
| /** |
| * {@code ResourceBundle} is an abstract class which is the superclass of classes which |
| * provide {@code Locale}-specific resources. A bundle contains a number of named |
| * resources, where the names are {@code Strings}. A bundle may have a parent bundle, |
| * and when a resource is not found in a bundle, the parent bundle is searched for |
| * the resource. If the fallback mechanism reaches the base bundle and still |
| * can't find the resource it throws a {@code MissingResourceException}. |
| * |
| * <ul> |
| * <li>All bundles for the same group of resources share a common base bundle. |
| * This base bundle acts as the root and is the last fallback in case none of |
| * its children was able to respond to a request.</li> |
| * <li>The first level contains changes between different languages. Only the |
| * differences between a language and the language of the base bundle need to be |
| * handled by a language-specific {@code ResourceBundle}.</li> |
| * <li>The second level contains changes between different countries that use |
| * the same language. Only the differences between a country and the country of |
| * the language bundle need to be handled by a country-specific {@code ResourceBundle}. |
| * </li> |
| * <li>The third level contains changes that don't have a geographic reason |
| * (e.g. changes that where made at some point in time like {@code PREEURO} where the |
| * currency of come countries changed. The country bundle would return the |
| * current currency (Euro) and the {@code PREEURO} variant bundle would return the old |
| * currency (e.g. DM for Germany).</li> |
| * </ul> |
| * |
| * <strong>Examples</strong> |
| * <ul> |
| * <li>BaseName (base bundle) |
| * <li>BaseName_de (german language bundle) |
| * <li>BaseName_fr (french language bundle) |
| * <li>BaseName_de_DE (bundle with Germany specific resources in german) |
| * <li>BaseName_de_CH (bundle with Switzerland specific resources in german) |
| * <li>BaseName_fr_CH (bundle with Switzerland specific resources in french) |
| * <li>BaseName_de_DE_PREEURO (bundle with Germany specific resources in german of |
| * the time before the Euro) |
| * <li>BaseName_fr_FR_PREEURO (bundle with France specific resources in french of |
| * the time before the Euro) |
| * </ul> |
| * |
| * It's also possible to create variants for languages or countries. This can be |
| * done by just skipping the country or language abbreviation: |
| * BaseName_us__POSIX or BaseName__DE_PREEURO. But it's not allowed to |
| * circumvent both language and country: BaseName___VARIANT is illegal. |
| * |
| * @see Properties |
| * @see PropertyResourceBundle |
| * @see ListResourceBundle |
| * @since 1.1 |
| */ |
| public abstract class ResourceBundle { |
| |
| /** |
| * The parent of this {@code ResourceBundle} that is used if this bundle doesn't |
| * include the requested resource. |
| */ |
| protected ResourceBundle parent; |
| |
| private Locale locale; |
| |
| static class MissingBundle extends ResourceBundle { |
| @Override |
| public Enumeration<String> getKeys() { |
| return null; |
| } |
| |
| @Override |
| public Object handleGetObject(String name) { |
| return null; |
| } |
| } |
| |
| private static final ResourceBundle MISSING = new MissingBundle(); |
| |
| private static final ResourceBundle MISSINGBASE = new MissingBundle(); |
| |
| private static final WeakHashMap<Object, Hashtable<String, ResourceBundle>> cache = new WeakHashMap<Object, Hashtable<String, ResourceBundle>>(); |
| |
| // BEGIN android-added |
| private static Locale defaultLocale = Locale.getDefault(); |
| // END android-added |
| |
| /** |
| * Constructs a new instance of this class. |
| */ |
| public ResourceBundle() { |
| /* empty */ |
| } |
| |
| /** |
| * Finds the named resource bundle for the default {@code Locale} and the caller's |
| * {@code ClassLoader}. |
| * |
| * @param bundleName |
| * the name of the {@code ResourceBundle}. |
| * @return the requested {@code ResourceBundle}. |
| * @throws MissingResourceException |
| * if the {@code ResourceBundle} cannot be found. |
| */ |
| public static final ResourceBundle getBundle(String bundleName) |
| throws MissingResourceException { |
| // BEGIN android-changed |
| return getBundleImpl(bundleName, Locale.getDefault(), VMStack |
| .getCallingClassLoader()); |
| // END android-changed |
| } |
| |
| /** |
| * Finds the named {@code ResourceBundle} for the specified {@code Locale} and the caller |
| * {@code ClassLoader}. |
| * |
| * @param bundleName |
| * the name of the {@code ResourceBundle}. |
| * @param locale |
| * the {@code Locale}. |
| * @return the requested resource bundle. |
| * @throws MissingResourceException |
| * if the resource bundle cannot be found. |
| */ |
| public static final ResourceBundle getBundle(String bundleName, |
| Locale locale) { |
| // BEGIN android-changed |
| return getBundleImpl(bundleName, locale, |
| VMStack.getCallingClassLoader()); |
| // END android-changed |
| } |
| |
| /** |
| * Finds the named resource bundle for the specified {@code Locale} and {@code ClassLoader}. |
| * |
| * The passed base name and {@code Locale} are used to create resource bundle names. |
| * The first name is created by concatenating the base name with the result |
| * of {@link Locale#toString()}. From this name all parent bundle names are |
| * derived. Then the same thing is done for the default {@code Locale}. This results |
| * in a list of possible bundle names. |
| * |
| * <strong>Example</strong> For the basename "BaseName", the {@code Locale} of the |
| * German part of Switzerland (de_CH) and the default {@code Locale} en_US the list |
| * would look something like this: |
| * |
| * <ol> |
| * <li>BaseName_de_CH</li> |
| * <li>BaseName_de</li> |
| * <li>Basename_en_US</li> |
| * <li>Basename_en</li> |
| * <li>BaseName</li> |
| * </ol> |
| * |
| * This list also shows the order in which the bundles will be searched for a requested |
| * resource in the German part of Switzerland (de_CH). |
| * |
| * As a first step, this method tries to instantiate |
| * a {@code ResourceBundle} with the names provided. |
| * If such a class can be instantiated and initialized, it is returned and |
| * all the parent bundles are instantiated too. If no such class can be |
| * found this method tries to load a {@code .properties} file with the names by |
| * replacing dots in the base name with a slash and by appending |
| * "{@code .properties}" at the end of the string. If such a resource can be found |
| * by calling {@link ClassLoader#getResource(String)} it is used to |
| * initialize a {@link PropertyResourceBundle}. If this succeeds, it will |
| * also load the parents of this {@code ResourceBundle}. |
| * |
| * For compatibility with older code, the bundle name isn't required to be |
| * a fully qualified class name. It's also possible to directly pass |
| * the path to a properties file (without a file extension). |
| * |
| * @param bundleName |
| * the name of the {@code ResourceBundle}. |
| * @param locale |
| * the {@code Locale}. |
| * @param loader |
| * the {@code ClassLoader} to use. |
| * @return the requested {@code ResourceBundle}. |
| * @throws MissingResourceException |
| * if the {@code ResourceBundle} cannot be found. |
| */ |
| public static ResourceBundle getBundle(String bundleName, Locale locale, |
| ClassLoader loader) throws MissingResourceException { |
| if (loader == null) { |
| throw new NullPointerException(); |
| } |
| // BEGIN android-changed |
| return getBundleImpl(bundleName, locale, loader); |
| // END android-changed |
| } |
| |
| private static ResourceBundle getBundleImpl(String bundleName, |
| Locale locale, ClassLoader loader) throws MissingResourceException { |
| if (bundleName != null) { |
| ResourceBundle bundle; |
| // BEGIN android-added |
| if (!defaultLocale.equals(Locale.getDefault())) { |
| cache.clear(); |
| defaultLocale = Locale.getDefault(); |
| } |
| // END android-added |
| if (!locale.equals(Locale.getDefault())) { |
| String localeName = locale.toString(); |
| if (localeName.length() > 0) { |
| localeName = "_" + localeName; //$NON-NLS-1$ |
| } |
| if ((bundle = handleGetBundle(bundleName, localeName, false, |
| loader)) != null) { |
| return bundle; |
| } |
| } |
| String localeName = Locale.getDefault().toString(); |
| if (localeName.length() > 0) { |
| localeName = "_" + localeName; //$NON-NLS-1$ |
| } |
| if ((bundle = handleGetBundle(bundleName, localeName, true, loader)) != null) { |
| return bundle; |
| } |
| throw new MissingResourceException(Msg.getString("KA029", bundleName, locale), bundleName + '_' + locale, //$NON-NLS-1$ |
| ""); //$NON-NLS-1$ |
| } |
| throw new NullPointerException(); |
| } |
| |
| /** |
| * Returns the names of the resources contained in this {@code ResourceBundle}. |
| * |
| * @return an {@code Enumeration} of the resource names. |
| */ |
| public abstract Enumeration<String> getKeys(); |
| |
| /** |
| * Gets the {@code Locale} of this {@code ResourceBundle}. In case a bundle was not |
| * found for the requested {@code Locale}, this will return the actual {@code Locale} of |
| * this resource bundle that was found after doing a fallback. |
| * |
| * @return the {@code Locale} of this {@code ResourceBundle}. |
| */ |
| public Locale getLocale() { |
| return locale; |
| } |
| |
| /** |
| * Returns the named resource from this {@code ResourceBundle}. If the resource |
| * cannot be found in this bundle, it falls back to the parent bundle (if |
| * it's not null) by calling the {@link #handleGetObject} method. If the resource still |
| * can't be found it throws a {@code MissingResourceException}. |
| * |
| * @param key |
| * the name of the resource. |
| * @return the resource object. |
| * @throws MissingResourceException |
| * if the resource is not found. |
| */ |
| public final Object getObject(String key) { |
| ResourceBundle last, theParent = this; |
| do { |
| Object result = theParent.handleGetObject(key); |
| if (result != null) { |
| return result; |
| } |
| last = theParent; |
| theParent = theParent.parent; |
| } while (theParent != null); |
| throw new MissingResourceException(Msg.getString("KA029", last.getClass().getName(), key), last.getClass().getName(), key); //$NON-NLS-1$ |
| } |
| |
| /** |
| * Returns the named string resource from this {@code ResourceBundle}. |
| * |
| * @param key |
| * the name of the resource. |
| * @return the resource string. |
| * @throws MissingResourceException |
| * if the resource is not found. |
| * @throws ClassCastException |
| * if the resource found is not a string. |
| * @see #getObject(String) |
| */ |
| public final String getString(String key) { |
| return (String) getObject(key); |
| } |
| |
| /** |
| * Returns the named resource from this {@code ResourceBundle}. |
| * |
| * @param key |
| * the name of the resource. |
| * @return the resource string array. |
| * @throws MissingResourceException |
| * if the resource is not found. |
| * @throws ClassCastException |
| * if the resource found is not an array of strings. |
| * @see #getObject(String) |
| */ |
| public final String[] getStringArray(String key) { |
| return (String[]) getObject(key); |
| } |
| |
| private static ResourceBundle handleGetBundle(String base, String locale, |
| boolean loadBase, final ClassLoader loader) { |
| ResourceBundle bundle = null; |
| String bundleName = base + locale; |
| Object cacheKey = loader != null ? (Object) loader : (Object) "null"; //$NON-NLS-1$ |
| Hashtable<String, ResourceBundle> loaderCache; |
| synchronized (cache) { |
| loaderCache = cache.get(cacheKey); |
| if (loaderCache == null) { |
| loaderCache = new Hashtable<String, ResourceBundle>(); |
| cache.put(cacheKey, loaderCache); |
| } |
| } |
| ResourceBundle result = loaderCache.get(bundleName); |
| if (result != null) { |
| if (result == MISSINGBASE) { |
| return null; |
| } |
| if (result == MISSING) { |
| if (!loadBase) { |
| return null; |
| } |
| String extension = strip(locale); |
| if (extension == null) { |
| return null; |
| } |
| return handleGetBundle(base, extension, loadBase, loader); |
| } |
| return result; |
| } |
| |
| try { |
| // BEGIN android-changed |
| /* |
| * Intercept loading of ResourceBundles that contain Harmony |
| * I18N data. Deliver our special, ICU-based bundles in this case. |
| * All other ResourceBundles use the ordinary mechanism, so user |
| * code behaves as it should. |
| */ |
| if(bundleName.startsWith("org.apache.harmony.luni.internal.locale.")) { |
| String icuBundleName = bundleName.substring(40); |
| String icuLocale = (locale.length() > 0 ? locale.substring(1) : locale); |
| // we know that Resources will deliver an assignable class |
| bundle = Resources.getInstance(icuBundleName, icuLocale); |
| } else { |
| Class<?> bundleClass = Class.forName(bundleName, true, loader); |
| if (ResourceBundle.class.isAssignableFrom(bundleClass)) { |
| bundle = (ResourceBundle) bundleClass.newInstance(); |
| } |
| } |
| // END android-changed |
| } catch (LinkageError e) { |
| } catch (Exception e) { |
| } |
| |
| if (bundle != null) { |
| bundle.setLocale(locale); |
| } else { |
| final String fileName = bundleName.replace('.', '/'); |
| InputStream stream = AccessController |
| .doPrivileged(new PrivilegedAction<InputStream>() { |
| public InputStream run() { |
| return loader == null ? ClassLoader |
| .getSystemResourceAsStream(fileName |
| + ".properties") : loader //$NON-NLS-1$ |
| .getResourceAsStream(fileName |
| + ".properties"); //$NON-NLS-1$ |
| } |
| }); |
| if (stream != null) { |
| try { |
| try { |
| bundle = new PropertyResourceBundle(stream); |
| } finally { |
| stream.close(); |
| } |
| bundle.setLocale(locale); |
| } catch (IOException e) { |
| } |
| } |
| } |
| |
| String extension = strip(locale); |
| if (bundle != null) { |
| if (extension != null) { |
| ResourceBundle parent = handleGetBundle(base, extension, true, |
| loader); |
| if (parent != null) { |
| bundle.setParent(parent); |
| } |
| } |
| loaderCache.put(bundleName, bundle); |
| return bundle; |
| } |
| |
| if (extension != null && (loadBase || extension.length() > 0)) { |
| bundle = handleGetBundle(base, extension, loadBase, loader); |
| if (bundle != null) { |
| loaderCache.put(bundleName, bundle); |
| return bundle; |
| } |
| } |
| loaderCache.put(bundleName, loadBase ? MISSINGBASE : MISSING); |
| return null; |
| } |
| |
| /** |
| * Returns the named resource from this {@code ResourceBundle}, or null if the |
| * resource is not found. |
| * |
| * @param key |
| * the name of the resource. |
| * @return the resource object. |
| */ |
| protected abstract Object handleGetObject(String key); |
| |
| /** |
| * Sets the parent resource bundle of this {@code ResourceBundle}. The parent is |
| * searched for resources which are not found in this {@code ResourceBundle}. |
| * |
| * @param bundle |
| * the parent {@code ResourceBundle}. |
| */ |
| protected void setParent(ResourceBundle bundle) { |
| parent = bundle; |
| } |
| |
| private static String strip(String name) { |
| int index = name.lastIndexOf('_'); |
| if (index != -1) { |
| return name.substring(0, index); |
| } |
| return null; |
| } |
| |
| private void setLocale(String name) { |
| String language = "", country = "", variant = ""; //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$ |
| if (name.length() > 1) { |
| int nextIndex = name.indexOf('_', 1); |
| if (nextIndex == -1) { |
| nextIndex = name.length(); |
| } |
| language = name.substring(1, nextIndex); |
| if (nextIndex + 1 < name.length()) { |
| int index = nextIndex; |
| nextIndex = name.indexOf('_', nextIndex + 1); |
| if (nextIndex == -1) { |
| nextIndex = name.length(); |
| } |
| country = name.substring(index + 1, nextIndex); |
| if (nextIndex + 1 < name.length()) { |
| variant = name.substring(nextIndex + 1, name.length()); |
| } |
| } |
| } |
| locale = new Locale(language, country, variant); |
| } |
| } |