Skip to content

Commit 9c7e37d

Browse files
committed
Implement ResourcesFeature Module awareness
1 parent 9358dea commit 9c7e37d

File tree

8 files changed

+292
-225
lines changed

8 files changed

+292
-225
lines changed

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
*/
2525
package com.oracle.svm.core;
2626

27+
import java.io.InputStream;
2728
import java.util.List;
2829
import java.util.Locale;
2930
import java.util.ResourceBundle;
@@ -47,5 +48,16 @@ public boolean isNativeImageClassLoader(ClassLoader classLoader) {
4748

4849
protected abstract boolean isNativeImageClassLoaderImpl(ClassLoader classLoader);
4950

51+
public interface ResourceCollector {
52+
53+
boolean isIncluded(String moduleName, String resourceName);
54+
55+
void addResource(String moduleName, String resourceName, InputStream resourceStream);
56+
57+
void addDirectoryResource(String moduleName, String dir, String content);
58+
}
59+
60+
public abstract void collectResources(ResourceCollector resourceCollector);
61+
5062
public abstract List<ResourceBundle> getResourceBundle(String bundleName, Locale locale);
5163
}

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Resources.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ public EconomicMap<String, ResourceStorageEntry> resources() {
7676
}
7777

7878
public static byte[] inputStreamToByteArray(InputStream is) {
79+
// TODO: Replace this with is.readAllBytes() once Java 8 support is removed
7980
byte[] arr = new byte[4096];
8081
int pos = 0;
8182
try {

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

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -24,18 +24,6 @@
2424
*/
2525
package com.oracle.svm.hosted;
2626

27-
import com.oracle.graal.pointsto.meta.AnalysisUniverse;
28-
import com.oracle.svm.core.annotate.AutomaticFeature;
29-
import com.oracle.svm.core.jdk11.BootModuleLayerSupport;
30-
import com.oracle.svm.core.jdk.JDK11OrLater;
31-
import com.oracle.svm.core.util.VMError;
32-
import com.oracle.svm.util.ModuleSupport;
33-
import com.oracle.svm.util.ReflectionUtil;
34-
import org.graalvm.nativeimage.ImageSingletons;
35-
import org.graalvm.nativeimage.Platform;
36-
import org.graalvm.nativeimage.Platforms;
37-
import org.graalvm.nativeimage.hosted.Feature;
38-
3927
import java.lang.module.Configuration;
4028
import java.lang.module.FindException;
4129
import java.lang.module.ModuleDescriptor;
@@ -61,6 +49,19 @@
6149
import java.util.stream.Collectors;
6250
import java.util.stream.Stream;
6351

52+
import org.graalvm.nativeimage.ImageSingletons;
53+
import org.graalvm.nativeimage.Platform;
54+
import org.graalvm.nativeimage.Platforms;
55+
import org.graalvm.nativeimage.hosted.Feature;
56+
57+
import com.oracle.graal.pointsto.meta.AnalysisUniverse;
58+
import com.oracle.svm.core.annotate.AutomaticFeature;
59+
import com.oracle.svm.core.jdk.JDK11OrLater;
60+
import com.oracle.svm.core.jdk11.BootModuleLayerSupport;
61+
import com.oracle.svm.core.util.VMError;
62+
import com.oracle.svm.util.ModuleSupport;
63+
import com.oracle.svm.util.ReflectionUtil;
64+
6465
/**
6566
* This feature:
6667
* <ul>
@@ -129,13 +130,22 @@ public void afterAnalysis(AfterAnalysisAccess access) {
129130
FeatureImpl.AfterAnalysisAccessImpl accessImpl = (FeatureImpl.AfterAnalysisAccessImpl) access;
130131
AnalysisUniverse universe = accessImpl.getUniverse();
131132

132-
Stream<Module> analysisReachableModules = universe.getTypes()
133+
Set<Module> analysisReachableModules = universe.getTypes()
133134
.stream()
134135
.filter(t -> t.isReachable() && !t.isArray())
135136
.map(t -> t.getJavaClass().getModule())
136-
.distinct();
137+
.collect(Collectors.toSet());
138+
139+
ImageSingletons.lookup(ResourcesFeature.class).includedResourcesModules.forEach(moduleName -> {
140+
Optional<?> module = accessImpl.imageClassLoader.findModule(moduleName);
141+
if (module.isPresent()) {
142+
analysisReachableModules.add((Module) module.get());
143+
} else {
144+
System.out.println("Hmmmmm ... have to think about that ....");
145+
}
146+
});
137147

138-
Set<String> allReachableModules = analysisReachableModules
148+
Set<String> allReachableModules = analysisReachableModules.stream()
139149
.filter(Module::isNamed)
140150
.filter(m -> !m.getDescriptor().modifiers().contains(ModuleDescriptor.Modifier.SYNTHETIC))
141151
.flatMap(ModuleLayerFeature::extractRequiredModuleNames)

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

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,19 @@
2525

2626
package com.oracle.svm.hosted.jdk11;
2727

28+
import java.io.IOException;
29+
import java.io.InputStream;
30+
import java.lang.module.ModuleReader;
31+
import java.lang.module.ModuleReference;
32+
import java.lang.module.ResolvedModule;
2833
import java.util.ArrayList;
2934
import java.util.Collections;
3035
import java.util.HashMap;
3136
import java.util.HashSet;
3237
import java.util.List;
3338
import java.util.Locale;
3439
import java.util.Map;
40+
import java.util.Optional;
3541
import java.util.ResourceBundle;
3642
import java.util.Set;
3743
import java.util.stream.Collectors;
@@ -42,25 +48,55 @@
4248

4349
import com.oracle.svm.core.ClassLoaderSupport;
4450
import com.oracle.svm.core.annotate.AutomaticFeature;
51+
import com.oracle.svm.core.util.VMError;
52+
import com.oracle.svm.hosted.ClassLoaderSupportImpl;
4553
import com.oracle.svm.hosted.FeatureImpl;
46-
import com.oracle.svm.hosted.NativeImageSystemClassLoader;
4754

4855
import jdk.internal.module.Modules;
4956

50-
public final class ClassLoaderSupportImplJDK11OrLater extends ClassLoaderSupport {
57+
public final class ClassLoaderSupportImplJDK11OrLater extends ClassLoaderSupportImpl {
5158

5259
private final NativeImageClassLoaderSupportJDK11OrLater classLoaderSupport;
5360
private final Map<String, Set<Module>> packageToModules;
5461

5562
ClassLoaderSupportImplJDK11OrLater(NativeImageClassLoaderSupportJDK11OrLater classLoaderSupport) {
63+
super(classLoaderSupport);
5664
this.classLoaderSupport = classLoaderSupport;
5765
packageToModules = new HashMap<>();
5866
buildPackageToModulesMap(classLoaderSupport);
5967
}
6068

6169
@Override
62-
protected boolean isNativeImageClassLoaderImpl(ClassLoader loader) {
63-
return loader == classLoaderSupport.getClassLoader() || loader instanceof NativeImageSystemClassLoader;
70+
public void collectResources(ResourceCollector resourceCollector) {
71+
/* Collect resources from modules */
72+
NativeImageClassLoaderSupportJDK11OrLater.allLayers(classLoaderSupport.moduleLayerForImageBuild).stream()
73+
.flatMap(moduleLayer -> moduleLayer.configuration().modules().stream())
74+
.forEach(resolvedModule -> collectResourceFromModule(resourceCollector, resolvedModule));
75+
76+
/* Collect remaining resources from classpath */
77+
super.collectResources(resourceCollector);
78+
}
79+
80+
private void collectResourceFromModule(ResourceCollector resourceCollector, ResolvedModule resolvedModule) {
81+
ModuleReference moduleReference = resolvedModule.reference();
82+
try (ModuleReader moduleReader = moduleReference.open()) {
83+
String moduleName = resolvedModule.name();
84+
List<String> foundResources = moduleReader.list()
85+
.filter(resourceName -> resourceCollector.isIncluded(moduleName, resourceName))
86+
.collect(Collectors.toList());
87+
88+
for (String resName : foundResources) {
89+
Optional<InputStream> content = moduleReader.open(resName);
90+
if (content.isEmpty()) {
91+
continue;
92+
}
93+
try (InputStream is = content.get()) {
94+
resourceCollector.addResource(moduleName, resName, is);
95+
}
96+
}
97+
} catch (IOException e) {
98+
throw VMError.shouldNotReachHere(e);
99+
}
64100
}
65101

66102
@Override
@@ -84,7 +120,7 @@ public List<ResourceBundle> getResourceBundle(String bundleSpec, Locale locale)
84120
}
85121
if (modules.isEmpty()) {
86122
/* If bundle is not located in any module get it via classloader (from ALL_UNNAMED) */
87-
return Collections.singletonList(ResourceBundle.getBundle(bundleName, locale, classLoaderSupport.getClassLoader()));
123+
return super.getResourceBundle(bundleName, locale);
88124
}
89125
ArrayList<ResourceBundle> resourceBundles = new ArrayList<>();
90126
for (Module module : modules) {

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ClassLoaderSupportImpl.java

Lines changed: 110 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,23 +25,45 @@
2525

2626
package com.oracle.svm.hosted;
2727

28+
import static com.oracle.svm.core.jdk.Resources.RESOURCES_INTERNAL_PATH_SEPARATOR;
29+
30+
import java.io.File;
31+
import java.io.IOException;
32+
import java.io.InputStream;
33+
import java.nio.file.Files;
34+
import java.nio.file.Path;
35+
import java.util.ArrayDeque;
36+
import java.util.ArrayList;
2837
import java.util.Collections;
38+
import java.util.Comparator;
39+
import java.util.Enumeration;
40+
import java.util.HashMap;
41+
import java.util.HashSet;
2942
import java.util.List;
3043
import java.util.Locale;
44+
import java.util.Map;
3145
import java.util.ResourceBundle;
46+
import java.util.Set;
47+
import java.util.jar.JarEntry;
48+
import java.util.jar.JarFile;
49+
import java.util.stream.Stream;
3250

3351
import org.graalvm.compiler.serviceprovider.JavaVersionUtil;
3452
import org.graalvm.nativeimage.ImageSingletons;
3553
import org.graalvm.nativeimage.hosted.Feature;
3654

3755
import com.oracle.svm.core.ClassLoaderSupport;
3856
import com.oracle.svm.core.annotate.AutomaticFeature;
57+
import com.oracle.svm.core.util.ClasspathUtils;
58+
import com.oracle.svm.core.util.UserError;
3959

40-
public final class ClassLoaderSupportImpl extends ClassLoaderSupport {
60+
public class ClassLoaderSupportImpl extends ClassLoaderSupport {
4161

62+
private final AbstractNativeImageClassLoaderSupport classLoaderSupport;
4263
private final ClassLoader imageClassLoader;
4364

44-
ClassLoaderSupportImpl(NativeImageClassLoaderSupport classLoaderSupport) {
65+
protected ClassLoaderSupportImpl(AbstractNativeImageClassLoaderSupport classLoaderSupport) {
66+
this.classLoaderSupport = classLoaderSupport;
4567
this.imageClassLoader = classLoaderSupport.getClassLoader();
4668
}
4769

@@ -50,6 +72,92 @@ protected boolean isNativeImageClassLoaderImpl(ClassLoader loader) {
5072
return loader == imageClassLoader || loader instanceof NativeImageSystemClassLoader;
5173
}
5274

75+
@Override
76+
public void collectResources(ResourceCollector resourceCollector) {
77+
classLoaderSupport.classpath().stream().forEach(classpathFile -> {
78+
try {
79+
if (Files.isDirectory(classpathFile)) {
80+
scanDirectory(classpathFile, resourceCollector);
81+
} else if (ClasspathUtils.isJar(classpathFile)) {
82+
scanJar(classpathFile, resourceCollector);
83+
}
84+
} catch (IOException ex) {
85+
throw UserError.abort("Unable to handle classpath element '%s'. Make sure that all classpath entries are either directories or valid jar files.", classpathFile);
86+
}
87+
});
88+
}
89+
90+
private static void scanDirectory(Path root, ResourceCollector collector) throws IOException {
91+
Map<String, List<String>> matchedDirectoryResources = new HashMap<>();
92+
Set<String> allEntries = new HashSet<>();
93+
94+
ArrayDeque<Path> queue = new ArrayDeque<>();
95+
queue.push(root);
96+
while (!queue.isEmpty()) {
97+
Path entry = queue.pop();
98+
99+
/* Resources always use / as the separator, as do our resource inclusion patterns */
100+
String relativeFilePath;
101+
if (entry != root) {
102+
relativeFilePath = root.relativize(entry).toString().replace(File.separatorChar, RESOURCES_INTERNAL_PATH_SEPARATOR);
103+
allEntries.add(relativeFilePath);
104+
} else {
105+
relativeFilePath = "";
106+
}
107+
108+
if (Files.isDirectory(entry)) {
109+
if (collector.isIncluded(null, relativeFilePath)) {
110+
matchedDirectoryResources.put(relativeFilePath, new ArrayList<>());
111+
}
112+
try (Stream<Path> files = Files.list(entry)) {
113+
files.forEach(queue::push);
114+
}
115+
} else {
116+
if (collector.isIncluded(null, relativeFilePath)) {
117+
try (InputStream is = Files.newInputStream(entry)) {
118+
collector.addResource(null, relativeFilePath, is);
119+
}
120+
}
121+
}
122+
}
123+
124+
for (String entry : allEntries) {
125+
int last = entry.lastIndexOf(RESOURCES_INTERNAL_PATH_SEPARATOR);
126+
String key = last == -1 ? "" : entry.substring(0, last);
127+
List<String> dirContent = matchedDirectoryResources.get(key);
128+
if (dirContent != null && !dirContent.contains(entry)) {
129+
dirContent.add(entry.substring(last + 1));
130+
}
131+
}
132+
133+
matchedDirectoryResources.forEach((dir, content) -> {
134+
content.sort(Comparator.naturalOrder());
135+
collector.addDirectoryResource(null, dir, String.join(System.lineSeparator(), content));
136+
});
137+
}
138+
139+
private static void scanJar(Path jarPath, ResourceCollector collector) throws IOException {
140+
try (JarFile jf = new JarFile(jarPath.toFile())) {
141+
Enumeration<JarEntry> entries = jf.entries();
142+
while (entries.hasMoreElements()) {
143+
JarEntry entry = entries.nextElement();
144+
if (entry.isDirectory()) {
145+
String dirName = entry.getName().substring(0, entry.getName().length() - 1);
146+
if (collector.isIncluded(null, dirName)) {
147+
// Register the directory with empty content to preserve Java behavior
148+
collector.addDirectoryResource(null, dirName, "");
149+
}
150+
} else {
151+
if (collector.isIncluded(null, entry.getName())) {
152+
try (InputStream is = jf.getInputStream(entry)) {
153+
collector.addResource(null, entry.getName(), is);
154+
}
155+
}
156+
}
157+
}
158+
}
159+
}
160+
53161
@Override
54162
public List<ResourceBundle> getResourceBundle(String bundleName, Locale locale) {
55163
return Collections.singletonList(ResourceBundle.getBundle(bundleName, locale, imageClassLoader));

0 commit comments

Comments
 (0)