< prev index next >

src/java.base/share/classes/java/util/ResourceBundle.java

Print this page

        

@@ -377,11 +377,11 @@
                 }
 
                 @Override
                 public ResourceBundle getBundle(String baseName, Locale locale, Module module) {
                     // use the given module as the caller to bypass the access check
-                    return getBundleImpl(module, module, getLoader(module),
+                    return getBundleImpl(module, module,
                                          baseName, locale, Control.INSTANCE);
                 }
 
                 @Override
                 public ResourceBundle newResourceBundle(Class<? extends ResourceBundle> bundleClass) {

@@ -564,67 +564,23 @@
      */
     public Locale getLocale() {
         return locale;
     }
 
-    /*
-     * Automatic determination of the ClassLoader to be used to load
-     * resources on behalf of the client.
-     */
-    private static ClassLoader getLoader(Class<?> caller) {
-        ClassLoader cl = caller == null ? null : caller.getClassLoader();
-        if (cl == null) {
-            // When the caller's loader is the boot class loader, cl is null
-            // here. In that case, ClassLoader.getSystemClassLoader() may
-            // return the same class loader that the application is
-            // using. We therefore use a wrapper ClassLoader to create a
-            // separate scope for bundles loaded on behalf of the Java
-            // runtime so that these bundles cannot be returned from the
-            // cache to the application (5048280).
-            cl = RBClassLoader.INSTANCE;
-        }
-        return cl;
-    }
-
     private static ClassLoader getLoader(Module module) {
         PrivilegedAction<ClassLoader> pa = module::getClassLoader;
         return AccessController.doPrivileged(pa);
     }
 
     /**
-     * A wrapper of ClassLoader.getSystemClassLoader().
+     * @param module a non-null-screened module form the {@link CacheKey#getModule()}.
+     * @return the ClassLoader to use in {@link Control#needsReload}
+     *         and {@link Control#newBundle}
      */
-    private static class RBClassLoader extends ClassLoader {
-        private static final RBClassLoader INSTANCE = AccessController.doPrivileged(
-                    new PrivilegedAction<RBClassLoader>() {
-                        public RBClassLoader run() {
-                            return new RBClassLoader();
-                        }
-                    });
-        private RBClassLoader() {
-        }
-        public Class<?> loadClass(String name) throws ClassNotFoundException {
-            ClassLoader loader = ClassLoader.getSystemClassLoader();
-            if (loader != null) {
-                return loader.loadClass(name);
-            }
-            return Class.forName(name);
-        }
-        public URL getResource(String name) {
-            ClassLoader loader = ClassLoader.getSystemClassLoader();
-            if (loader != null) {
-                return loader.getResource(name);
-            }
-            return ClassLoader.getSystemResource(name);
-        }
-        public InputStream getResourceAsStream(String name) {
-            ClassLoader loader = ClassLoader.getSystemClassLoader();
-            if (loader != null) {
-                return loader.getResourceAsStream(name);
-            }
-            return ClassLoader.getSystemResourceAsStream(name);
-        }
+    private static ClassLoader getLoaderForControl(Module module) {
+        ClassLoader loader = getLoader(module);
+        return loader == null ? ClassLoader.getSystemClassLoader() : loader;
     }
 
     /**
      * Sets the parent bundle of this bundle.
      * The parent bundle is searched by {@link #getObject getObject}

@@ -637,22 +593,23 @@
         this.parent = parent;
     }
 
     /**
      * Key used for cached resource bundles.  The key checks the base
-     * name, the locale, the class loader, and the caller module
+     * name, the locale, the bundle module, and the caller module
      * to determine if the resource is a match to the requested one.
-     * The loader may be null, but the base name, the locale and
-     * module must have a non-null value.
+     * The base name, the locale and both modules must have a non-null value.
      */
     private static class CacheKey implements Cloneable {
         // These four are the actual keys for lookup in Map.
         private String name;
         private Locale locale;
-        private KeyElementReference<ClassLoader> loaderRef;
         private KeyElementReference<Module> moduleRef;
-
+        private KeyElementReference<Module> callerRef;
+        // this is the part of hashCode that pertains to module and callerModule
+        // which can be GCed..
+        private int modulesHash;
 
         // bundle format which is necessary for calling
         // Control.needsReload().
         private String format;
 

@@ -667,65 +624,52 @@
         private volatile long expirationTime;
 
         // Placeholder for an error report by a Throwable
         private Throwable cause;
 
-        // Hash code value cache to avoid recalculating the hash code
-        // of this instance.
-        private int hashCodeCache;
-
         // ResourceBundleProviders for loading ResourceBundles
         private ServiceLoader<ResourceBundleProvider> providers;
         private boolean providersChecked;
 
         // Boolean.TRUE if the factory method caller provides a ResourceBundleProvier.
         private Boolean callerHasProvider;
 
-        CacheKey(String baseName, Locale locale, ClassLoader loader, Module module) {
+        CacheKey(String baseName, Locale locale, Module module, Module caller) {
             Objects.requireNonNull(module);
+            Objects.requireNonNull(caller);
 
             this.name = baseName;
             this.locale = locale;
-            if (loader == null) {
-                this.loaderRef = null;
-            } else {
-                this.loaderRef = new KeyElementReference<>(loader, referenceQueue, this);
-            }
             this.moduleRef = new KeyElementReference<>(module, referenceQueue, this);
-            calculateHashCode();
+            this.callerRef = new KeyElementReference<>(caller, referenceQueue, this);
+            this.modulesHash = module.hashCode() ^ caller.hashCode();
         }
 
         String getName() {
             return name;
         }
 
         CacheKey setName(String baseName) {
-            if (!this.name.equals(baseName)) {
                 this.name = baseName;
-                calculateHashCode();
-            }
             return this;
         }
 
         Locale getLocale() {
             return locale;
         }
 
         CacheKey setLocale(Locale locale) {
-            if (!this.locale.equals(locale)) {
                 this.locale = locale;
-                calculateHashCode();
-            }
             return this;
         }
 
-        ClassLoader getLoader() {
-            return (loaderRef != null) ? loaderRef.get() : null;
+        Module getModule() {
+            return moduleRef == null ? null : moduleRef.get();
         }
 
-        Module getModule() {
-            return moduleRef.get();
+        Module getCallerModule() {
+            return callerRef == null ? null : callerRef.get();
         }
 
         ServiceLoader<ResourceBundleProvider> getProviders() {
             if (!providersChecked) {
                 providers = getServiceLoader(getModule(), name);

@@ -748,68 +692,55 @@
                 return true;
             }
             try {
                 final CacheKey otherEntry = (CacheKey)other;
                 //quick check to see if they are not equal
-                if (hashCodeCache != otherEntry.hashCodeCache) {
+                if (modulesHash != otherEntry.modulesHash) {
                     return false;
                 }
                 //are the names the same?
                 if (!name.equals(otherEntry.name)) {
                     return false;
                 }
                 // are the locales the same?
                 if (!locale.equals(otherEntry.locale)) {
                     return false;
                 }
-                //are refs (both non-null) or (both null)?
-                if (loaderRef == null) {
-                    return otherEntry.loaderRef == null;
-                }
-                ClassLoader loader = getLoader();
+                // are modules and callerModules the same and non-null?
                 Module module = getModule();
-                return (otherEntry.loaderRef != null)
-                        // with a null reference we can no longer find
-                        // out which class loader or module was referenced; so
-                        // treat it as unequal
-                        && (loader != null)
-                        && (loader == otherEntry.getLoader())
-                        && (module != null)
-                        && (module.equals(otherEntry.getModule()));
+                Module caller = getCallerModule();
+                return ((module != null) && (module.equals(otherEntry.getModule())) &&
+                        (caller != null) && (caller.equals(otherEntry.getCallerModule())));
             } catch (NullPointerException | ClassCastException e) {
             }
             return false;
         }
 
         @Override
         public int hashCode() {
-            return hashCodeCache;
-        }
-
-        private void calculateHashCode() {
-            hashCodeCache = name.hashCode() << 3;
-            hashCodeCache ^= locale.hashCode();
-            ClassLoader loader = getLoader();
-            if (loader != null) {
-                hashCodeCache ^= loader.hashCode();
-            }
-            Module module = getModule();
-            if (module != null) {
-                hashCodeCache ^= module.hashCode();
-            }
+            return (name.hashCode() << 3) ^ locale.hashCode() ^ modulesHash;
         }
 
         @Override
         public Object clone() {
             try {
                 CacheKey clone = (CacheKey) super.clone();
-                if (loaderRef != null) {
-                    clone.loaderRef = new KeyElementReference<>(getLoader(),
-                                                                referenceQueue, clone);
-                }
-                clone.moduleRef = new KeyElementReference<>(getModule(),
-                                                            referenceQueue, clone);
+
+                Module module = getModule();
+                clone.moduleRef =
+                    module == null
+                    ? null // don't ever create a Reference for a null referent
+                           // because it will never be enqueued and consequently
+                           // its CacheKey will never be removed from cacheList...
+                    : new KeyElementReference<>(module, referenceQueue, clone);
+
+                Module caller = getCallerModule();
+                clone.callerRef =
+                    caller == null
+                    ? null
+                    : new KeyElementReference<>(caller, referenceQueue, clone);
+
                 // Clear the reference to ResourceBundleProviders and the flag
                 clone.providers = null;
                 clone.providersChecked = false;
                 // Clear the reference to a Throwable
                 clone.cause = null;

@@ -854,12 +785,16 @@
                     l = "__" + locale.getVariant();
                 } else {
                     l = "\"\"";
                 }
             }
-            return "CacheKey[" + name + ", lc=" + l + ", ldr=" + getLoader()
-                + "(format=" + format + ")]";
+            return "CacheKey[" + name +
+                   ", locale=" + l +
+                   ", module=" + getModule() +
+                   ", callerModule=" + getCallerModule() +
+                   ", format=" + format +
+                   "]";
         }
     }
 
     /**
      * The common interface to get a CacheKey in LoaderReference and

@@ -1592,11 +1527,11 @@
 
     private static ResourceBundle getBundleImpl(String baseName,
                                                 Locale locale,
                                                 Class<?> caller,
                                                 Control control) {
-        return getBundleImpl(baseName, locale, caller, getLoader(caller), control);
+        return getBundleImpl(baseName, locale, caller, caller.getClassLoader(), control);
     }
 
     /**
      * This method will find resource bundles using the legacy mechanism
      * if the caller is unnamed module or the given class loader is

@@ -1611,30 +1546,29 @@
     private static ResourceBundle getBundleImpl(String baseName,
                                                 Locale locale,
                                                 Class<?> caller,
                                                 ClassLoader loader,
                                                 Control control) {
-        if (caller != null && caller.getModule().isNamed()) {
-            Module module = caller.getModule();
-            ClassLoader ml = getLoader(module);
-            // get resource bundles for a named module only
-            // if loader is the module's class loader
-            if (loader == ml || (ml == null && loader == RBClassLoader.INSTANCE)) {
-                return getBundleImpl(module, module, loader, baseName, locale, control);
+        if (caller == null) {
+            throw new InternalError("null caller");
             }
+        Module callerModule = caller.getModule();
+
+        // get resource bundles for a named module only if loader is the module's class loader
+        if (callerModule.isNamed() && loader == getLoader(callerModule)) {
+            return getBundleImpl(callerModule, callerModule, baseName, locale, control);
         }
-        // find resource bundles from unnamed module
-        Module unnamedModule = loader != null
-            ? loader.getUnnamedModule()
-            : ClassLoader.getSystemClassLoader().getUnnamedModule();
 
-        if (caller == null) {
-            throw new InternalError("null caller");
+        // there's no code in unnamed module of bootstrap class loader so loader
+        // must be non-null (non-bootstrap) if the caller is from unnamed module
+        if (loader == null) {
+            throw new InternalError("null loader");
         }
 
-        Module callerModule = caller.getModule();
-        return getBundleImpl(callerModule, unnamedModule, loader, baseName, locale, control);
+        // find resource bundles from unnamed module of given class loader
+        Module unnamedModule = loader.getUnnamedModule();
+        return getBundleImpl(callerModule, unnamedModule, baseName, locale, control);
     }
 
     private static ResourceBundle getBundleFromModule(Class<?> caller,
                                                       Module module,
                                                       String baseName,

@@ -1646,16 +1580,15 @@
             SecurityManager sm = System.getSecurityManager();
             if (sm != null) {
                 sm.checkPermission(GET_CLASSLOADER_PERMISSION);
             }
         }
-        return getBundleImpl(callerModule, module, getLoader(module), baseName, locale, control);
+        return getBundleImpl(callerModule, module, baseName, locale, control);
     }
 
     private static ResourceBundle getBundleImpl(Module callerModule,
                                                 Module module,
-                                                ClassLoader loader,
                                                 String baseName,
                                                 Locale locale,
                                                 Control control) {
         if (locale == null || control == null) {
             throw new NullPointerException();

@@ -1663,11 +1596,11 @@
 
         // We create a CacheKey here for use by this call. The base name
         // loader, and module will never change during the bundle loading
         // process. We have to make sure that the locale is set before
         // using it as a cache key.
-        CacheKey cacheKey = new CacheKey(baseName, locale, loader, module);
+        CacheKey cacheKey = new CacheKey(baseName, locale, module, callerModule);
         ResourceBundle bundle = null;
 
         // Quick lookup of the cache.
         BundleReference bundleRef = cacheList.get(cacheKey);
         if (bundleRef != null) {

@@ -2015,16 +1948,24 @@
 
         // Here we actually load the bundle in the order of formats
         // specified by the getFormats() value.
         Locale targetLocale = cacheKey.getLocale();
 
+        Module module = cacheKey.getModule();
+        if (module == null) {
+            // should not happen
+            throw new InternalError(
+                "Module for cache key: " + cacheKey + " has been GCed.");
+        }
+        ClassLoader loader = getLoaderForControl(module);
+
         ResourceBundle bundle = null;
         for (String format : formats) {
             try {
                 // ResourceBundle.Control.newBundle may be overridden
                 bundle = control.newBundle(cacheKey.getName(), targetLocale, format,
-                                           cacheKey.getLoader(), reload);
+                                           loader, reload);
             } catch (LinkageError | Exception error) {
                 // We need to handle the LinkageError case due to
                 // inconsistent case-sensitivity in ClassLoader.
                 // See 6572242 for details.
                 cacheKey.setCause(error);

@@ -2162,14 +2103,17 @@
                     synchronized (bundle) {
                         expirationTime = key.expirationTime;
                         if (!bundle.expired && expirationTime >= 0 &&
                             expirationTime <= System.currentTimeMillis()) {
                             try {
-                                bundle.expired = control.needsReload(key.getName(),
+                                Module module = cacheKey.getModule();
+                                bundle.expired =
+                                    module == null || // already GCed
+                                    control.needsReload(key.getName(),
                                                                      key.getLocale(),
                                                                      key.getFormat(),
-                                                                     key.getLoader(),
+                                                        getLoaderForControl(module),
                                                                      bundle,
                                                                      key.loadTime);
                             } catch (Exception e) {
                                 cacheKey.setCause(e);
                             }

@@ -2263,11 +2207,11 @@
      * @see ResourceBundle.Control#getTimeToLive(String,Locale)
      */
     @CallerSensitive
     public static final void clearCache() {
         Class<?> caller = Reflection.getCallerClass();
-        clearCache(getLoader(caller), caller.getModule());
+        clearCacheImpl(caller.getModule(), caller.getClassLoader());
     }
 
     /**
      * Removes all resource bundles from the cache that have been loaded
      * by the caller's module using the given class loader.

@@ -2278,11 +2222,12 @@
      * @see ResourceBundle.Control#getTimeToLive(String,Locale)
      */
     @CallerSensitive
     public static final void clearCache(ClassLoader loader) {
         Objects.requireNonNull(loader);
-        clearCache(loader, Reflection.getCallerClass().getModule());
+        Class<?> caller = Reflection.getCallerClass();
+        clearCacheImpl(caller.getModule(), loader);
     }
 
     /**
      * Removes all resource bundles from the cache that have been loaded by the
      * given {@code module}.

@@ -2296,17 +2241,19 @@
      *         of the given {@code module}
      * @since 9
      * @see ResourceBundle.Control#getTimeToLive(String,Locale)
      */
     public static final void clearCache(Module module) {
-        clearCache(module.getClassLoader(), module);
+        Objects.requireNonNull(module);
+        clearCacheImpl(module, module.getClassLoader());
     }
 
-    private static void clearCache(ClassLoader loader, Module module) {
-        Set<CacheKey> set = cacheList.keySet();
-        set.stream().filter((key) -> (key.getLoader() == loader && key.getModule() == module))
-                .forEach(set::remove);
+    private static void clearCacheImpl(Module callerModule, ClassLoader loader) {
+        cacheList.keySet().removeIf(
+            key -> key.getCallerModule() == callerModule &&
+                   getLoader(key.getModule()) == loader
+        );
     }
 
     /**
      * Gets an object for the given key from this resource bundle.
      * Returns null if this resource bundle does not contain an
< prev index next >