Skip to content

Commit 3e5e1b8

Browse files
committed
Implement ResourceBundle module awareness
1 parent bcb3177 commit 3e5e1b8

File tree

10 files changed

+252
-177
lines changed

10 files changed

+252
-177
lines changed

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/ClassLoaderQuery.java renamed to substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/ClassLoaderSupport.java

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2020, 2021, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -24,11 +24,28 @@
2424
*/
2525
package com.oracle.svm.core;
2626

27+
import java.util.List;
28+
import java.util.Locale;
29+
import java.util.ResourceBundle;
30+
2731
import org.graalvm.nativeimage.Platform;
2832
import org.graalvm.nativeimage.Platforms;
2933

3034
@Platforms(Platform.HOSTED_ONLY.class)
31-
public interface ClassLoaderQuery {
35+
public abstract class ClassLoaderSupport {
36+
37+
public boolean isNativeImageClassLoader(ClassLoader classLoader) {
38+
ClassLoader loader = classLoader;
39+
while (loader != null) {
40+
if (isNativeImageClassLoaderImpl(loader)) {
41+
return true;
42+
}
43+
loader = loader.getParent();
44+
}
45+
return false;
46+
}
47+
48+
protected abstract boolean isNativeImageClassLoaderImpl(ClassLoader classLoader);
3249

33-
boolean isNativeImageClassLoader(ClassLoader c);
50+
public abstract List<ResourceBundle> getResourceBundle(String bundleName, Locale locale);
3451
}

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/RuntimeAssertionsSupport.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ private boolean desiredAssertionStatusImpl(String name, boolean fallback) {
184184
}
185185

186186
private boolean desiredAssertionStatusImpl(String name, ClassLoader classLoader) {
187-
boolean isNativeImageClassLoader = ImageSingletons.lookup(ClassLoaderQuery.class).isNativeImageClassLoader(classLoader);
187+
boolean isNativeImageClassLoader = ImageSingletons.lookup(ClassLoaderSupport.class).isNativeImageClassLoader(classLoader);
188188
return desiredAssertionStatusImpl(name, isNativeImageClassLoader ? defaultAssertionStatus : systemAssertionStatus);
189189
}
190190

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/localization/LocalizationFeature.java

Lines changed: 8 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
import org.graalvm.nativeimage.Platforms;
7070
import org.graalvm.nativeimage.hosted.Feature;
7171

72+
import com.oracle.svm.core.ClassLoaderSupport;
7273
import com.oracle.svm.core.annotate.Substitute;
7374
import com.oracle.svm.core.jdk.localization.compression.GzipBundleCompression;
7475
import com.oracle.svm.core.jdk.localization.substitutions.Target_sun_util_locale_provider_LocaleServiceProviderPool_OptimizedLocaleMode;
@@ -77,7 +78,6 @@
7778
import com.oracle.svm.core.option.OptionUtils;
7879
import com.oracle.svm.core.util.UserError;
7980
import com.oracle.svm.core.util.VMError;
80-
import com.oracle.svm.util.ModuleSupport;
8181
import com.oracle.svm.util.ReflectionUtil;
8282

8383
import jdk.vm.ci.meta.ResolvedJavaField;
@@ -456,29 +456,16 @@ public void prepareBundle(String baseName, Collection<Locale> wantedLocales) {
456456

457457
boolean somethingFound = false;
458458
for (Locale locale : wantedLocales) {
459-
ResourceBundle resourceBundle;
459+
List<ResourceBundle> resourceBundle;
460460
try {
461-
resourceBundle = ModuleSupport.getResourceBundle(baseName, locale, Thread.currentThread().getContextClassLoader());
461+
resourceBundle = ImageSingletons.lookup(ClassLoaderSupport.class).getResourceBundle(baseName, locale);
462462
} catch (MissingResourceException mre) {
463-
if (!baseName.contains("/")) {
464-
// fallthrough
465-
continue;
466-
}
467-
// Due to a possible bug in the JDK, bundle names not following proper naming
468-
// convention
469-
// need to be
470-
// converted to fully qualified class names before loading can succeed.
471-
// see GR-24211
472-
String dotBundleName = baseName.replace("/", ".");
473-
try {
474-
resourceBundle = ModuleSupport.getResourceBundle(dotBundleName, locale, Thread.currentThread().getContextClassLoader());
475-
} catch (MissingResourceException ex) {
476-
// fallthrough
477-
continue;
478-
}
463+
continue;
464+
}
465+
somethingFound = !resourceBundle.isEmpty();
466+
for (ResourceBundle bundle : resourceBundle) {
467+
prepareBundle(baseName, bundle, locale);
479468
}
480-
somethingFound = true;
481-
prepareBundle(baseName, resourceBundle, locale);
482469
}
483470

484471
if (!somethingFound) {
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
/*
2+
* Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation. Oracle designates this
8+
* particular file as subject to the "Classpath" exception as provided
9+
* by Oracle in the LICENSE file that accompanied this code.
10+
*
11+
* This code is distributed in the hope that it will be useful, but WITHOUT
12+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14+
* version 2 for more details (a copy is included in the LICENSE file that
15+
* accompanied this code).
16+
*
17+
* You should have received a copy of the GNU General Public License version
18+
* 2 along with this work; if not, write to the Free Software Foundation,
19+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20+
*
21+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22+
* or visit www.oracle.com if you need additional information or have any
23+
* questions.
24+
*/
25+
26+
package com.oracle.svm.hosted;
27+
28+
import java.util.ArrayDeque;
29+
import java.util.ArrayList;
30+
import java.util.Collections;
31+
import java.util.Deque;
32+
import java.util.HashMap;
33+
import java.util.HashSet;
34+
import java.util.List;
35+
import java.util.Locale;
36+
import java.util.Map;
37+
import java.util.MissingResourceException;
38+
import java.util.ResourceBundle;
39+
import java.util.Set;
40+
import java.util.stream.Collectors;
41+
42+
import com.oracle.svm.core.ClassLoaderSupport;
43+
44+
import jdk.internal.module.Modules;
45+
46+
public final class ClassLoaderSupportImpl extends ClassLoaderSupport {
47+
48+
private final NativeImageClassLoaderSupport classLoaderSupport;
49+
private final Map<String, Set<Module>> packageToModules;
50+
51+
ClassLoaderSupportImpl(NativeImageClassLoaderSupport classLoaderSupport) {
52+
this.classLoaderSupport = classLoaderSupport;
53+
packageToModules = new HashMap<>();
54+
buildPackageToModulesMap(classLoaderSupport);
55+
}
56+
57+
@Override
58+
protected boolean isNativeImageClassLoaderImpl(ClassLoader loader) {
59+
return loader == classLoaderSupport.getClassLoader() || loader instanceof NativeImageSystemClassLoader;
60+
}
61+
62+
@Override
63+
public List<ResourceBundle> getResourceBundle(String bundleSpec, Locale locale) {
64+
String[] specParts = bundleSpec.split(":", 2);
65+
String moduleName;
66+
String bundleName;
67+
if (specParts.length > 1) {
68+
moduleName = specParts[0];
69+
bundleName = specParts[1];
70+
} else {
71+
moduleName = null;
72+
bundleName = specParts[0];
73+
}
74+
String packageName = packageName(bundleName);
75+
if (packageName == null) {
76+
throw new MissingResourceException("ResourceBundle does not seem to be a fully qualified class name.", bundleName, locale.toLanguageTag());
77+
}
78+
Set<Module> modules;
79+
if (moduleName != null) {
80+
modules = classLoaderSupport.findModule(moduleName).stream().collect(Collectors.toSet());
81+
} else {
82+
modules = packageToModules.getOrDefault(packageName, Collections.emptySet());
83+
}
84+
if (modules.isEmpty()) {
85+
throw new MissingResourceException("ResourceBundle cannot be found.", bundleSpec, locale.toLanguageTag());
86+
}
87+
ArrayList<ResourceBundle> resourceBundles = new ArrayList<>();
88+
for (Module module : modules) {
89+
Module exportTargetModule = ClassLoaderSupportImpl.class.getModule();
90+
if (!module.isExported(packageName, exportTargetModule)) {
91+
System.out.printf("!!! Open for ResourceBundle access: %s:%s%n", module.getName(), packageName);
92+
Modules.addOpens(module, packageName, exportTargetModule);
93+
}
94+
resourceBundles.add(ResourceBundle.getBundle(bundleName, locale, module));
95+
}
96+
return resourceBundles;
97+
}
98+
99+
private static String packageName(String bundleName) {
100+
int classSep = bundleName.replace('/', '.').lastIndexOf('.');
101+
if (classSep == -1) {
102+
/* The bundle is not specified via a java.class or java.properties format. */
103+
return null;
104+
}
105+
return bundleName.substring(0, classSep);
106+
}
107+
108+
private void buildPackageToModulesMap(NativeImageClassLoaderSupport classLoaderSupport) {
109+
for (ModuleLayer layer : allLayers(classLoaderSupport.moduleLayerForImageBuild)) {
110+
for (Module module : layer.modules()) {
111+
for (String packageName : module.getDescriptor().packages()) {
112+
addToPackageNameModules(module, packageName);
113+
}
114+
}
115+
}
116+
dumpPackageNameModulesMapping();
117+
}
118+
119+
private static List<ModuleLayer> allLayers(ModuleLayer moduleLayer) {
120+
/** Implementation taken from {@link ModuleLayer#layers()} */
121+
List<ModuleLayer> allLayers = new ArrayList<>();
122+
Set<ModuleLayer> visited = new HashSet<>();
123+
Deque<ModuleLayer> stack = new ArrayDeque<>();
124+
visited.add(moduleLayer);
125+
stack.push(moduleLayer);
126+
127+
while (!stack.isEmpty()) {
128+
ModuleLayer layer = stack.pop();
129+
allLayers.add(layer);
130+
131+
// push in reverse order
132+
for (int i = layer.parents().size() - 1; i >= 0; i--) {
133+
ModuleLayer parent = layer.parents().get(i);
134+
if (!visited.contains(parent)) {
135+
visited.add(parent);
136+
stack.push(parent);
137+
}
138+
}
139+
}
140+
return allLayers;
141+
}
142+
143+
private void addToPackageNameModules(Module moduleName, String packageName) {
144+
Set<Module> prevValue = packageToModules.get(packageName);
145+
if (prevValue == null) {
146+
/* Mostly packageName is only used in a single module */
147+
packageToModules.put(packageName, Collections.singleton(moduleName));
148+
} else if (prevValue.size() == 1) {
149+
/* Transition to HashSet - happens rarely */
150+
HashSet<Module> newValue = new HashSet<>();
151+
newValue.add(prevValue.iterator().next());
152+
newValue.add(moduleName);
153+
packageToModules.put(packageName, newValue);
154+
} else if (prevValue.size() > 1) {
155+
/* Add to exiting HashSet - happens rarely */
156+
prevValue.add(moduleName);
157+
}
158+
}
159+
160+
public void dumpPackageNameModulesMapping() {
161+
packageToModules.entrySet().stream()
162+
.sorted(Map.Entry.comparingByKey())
163+
.map(e -> e.getKey() + " -> " + e.getValue().stream()
164+
.map(Module::getName)
165+
.collect(Collectors.joining(", ")))
166+
.forEach(System.out::println);
167+
}
168+
}

substratevm/src/com.oracle.svm.hosted.jdk11/src/com/oracle/svm/hosted/NativeImageClassLoaderSupport.java

Lines changed: 1 addition & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -33,15 +33,9 @@
3333
import java.lang.module.ModuleReference;
3434
import java.nio.file.Path;
3535
import java.nio.file.Paths;
36-
import java.util.ArrayDeque;
37-
import java.util.ArrayList;
3836
import java.util.Arrays;
3937
import java.util.Collections;
40-
import java.util.Deque;
41-
import java.util.HashMap;
42-
import java.util.HashSet;
4338
import java.util.List;
44-
import java.util.Map;
4539
import java.util.Objects;
4640
import java.util.Optional;
4741
import java.util.Set;
@@ -65,27 +59,16 @@ public class NativeImageClassLoaderSupport extends AbstractNativeImageClassLoade
6559
private final List<Path> buildmp;
6660

6761
private final ClassLoader classLoader;
68-
private final ModuleLayer moduleLayerForImageBuild;
69-
private final Map<String, Set<Module>> packageToModuleNames;
62+
final ModuleLayer moduleLayerForImageBuild;
7063

7164
NativeImageClassLoaderSupport(ClassLoader defaultSystemClassLoader, String[] classpath, String[] modulePath) {
7265
super(defaultSystemClassLoader, classpath);
7366

74-
packageToModuleNames = new HashMap<>();
75-
7667
imagemp = Arrays.stream(modulePath).map(Paths::get).collect(Collectors.toUnmodifiableList());
7768
buildmp = Arrays.stream(System.getProperty("jdk.module.path", "").split(File.pathSeparator)).map(Paths::get).collect(Collectors.toUnmodifiableList());
7869

7970
ModuleLayer moduleLayer = createModuleLayer(imagemp.toArray(Path[]::new), classPathClassLoader);
8071
adjustBootLayerQualifiedExports(moduleLayer);
81-
for (ModuleLayer layer : allLayers(moduleLayer)) {
82-
for (Module module : layer.modules()) {
83-
for (String packageName : module.getDescriptor().packages()) {
84-
addToPackageNameModules(module, packageName);
85-
}
86-
}
87-
}
88-
// dumpPackageNameModulesMapping();
8972
moduleLayerForImageBuild = moduleLayer;
9073
classLoader = getSingleClassloader(moduleLayer);
9174
}
@@ -102,56 +85,6 @@ private static ModuleLayer createModuleLayer(Path[] modulePaths, ClassLoader par
10285
return ModuleLayer.defineModulesWithOneLoader(configuration, List.of(ModuleLayer.boot()), parent).layer();
10386
}
10487

105-
private List<ModuleLayer> allLayers(ModuleLayer moduleLayer) {
106-
/** Implementation taken from {@link ModuleLayer#layers()} */
107-
List<ModuleLayer> allLayers = new ArrayList<>();
108-
Set<ModuleLayer> visited = new HashSet<>();
109-
Deque<ModuleLayer> stack = new ArrayDeque<>();
110-
visited.add(moduleLayer);
111-
stack.push(moduleLayer);
112-
113-
while (!stack.isEmpty()) {
114-
ModuleLayer layer = stack.pop();
115-
allLayers.add(layer);
116-
117-
// push in reverse order
118-
for (int i = layer.parents().size() - 1; i >= 0; i--) {
119-
ModuleLayer parent = layer.parents().get(i);
120-
if (!visited.contains(parent)) {
121-
visited.add(parent);
122-
stack.push(parent);
123-
}
124-
}
125-
}
126-
return allLayers;
127-
}
128-
129-
private void addToPackageNameModules(Module moduleName, String packageName) {
130-
Set<Module> prevValue = packageToModuleNames.get(packageName);
131-
if (prevValue == null) {
132-
/* Mostly packageName is only used in a single module */
133-
packageToModuleNames.put(packageName, Collections.singleton(moduleName));
134-
} else if (prevValue.size() == 1) {
135-
/* Transition to HashSet - happens rarely */
136-
HashSet<Module> newValue = new HashSet<>();
137-
newValue.add(prevValue.iterator().next());
138-
newValue.add(moduleName);
139-
packageToModuleNames.put(packageName, newValue);
140-
} else if (prevValue.size() > 1) {
141-
/* Add to exiting HashSet - happens rarely */
142-
prevValue.add(moduleName);
143-
}
144-
}
145-
146-
public void dumpPackageNameModulesMapping() {
147-
packageToModuleNames.entrySet().stream()
148-
.sorted(Map.Entry.comparingByKey())
149-
.map(e -> e.getKey() + " -> " + e.getValue().stream()
150-
.map(Module::getName)
151-
.collect(Collectors.joining(", ")))
152-
.forEach(System.out::println);
153-
}
154-
15588
private void adjustBootLayerQualifiedExports(ModuleLayer layer) {
15689
/*
15790
* For all qualified exports packages of modules in the the boot layer we check if layer

0 commit comments

Comments
 (0)