Skip to content

Commit 7795578

Browse files
[GR-37154] Make Class.getResource compatible with java in all cases.
PullRequest: graal/11226
2 parents e629186 + 7c4257f commit 7795578

File tree

12 files changed

+345
-166
lines changed

12 files changed

+345
-166
lines changed

substratevm/mx.substratevm/suite.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# pylint: disable=line-too-long
22
suite = {
3-
"mxversion": "5.319.0",
3+
"mxversion": "5.316.15",
44
"name": "substratevm",
55
"version" : "22.1.0",
66
"release" : False,

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,9 @@ public interface ResourceCollector {
5252

5353
boolean isIncluded(String moduleName, String resourceName);
5454

55-
void addResource(String moduleName, String resourceName, InputStream resourceStream);
55+
void addResource(String moduleName, String resourceName, InputStream resourceStream, boolean fromJar);
5656

57-
void addDirectoryResource(String moduleName, String dir, String content);
57+
void addDirectoryResource(String moduleName, String dir, String content, boolean fromJar);
5858
}
5959

6060
public abstract void collectResources(ResourceCollector resourceCollector);

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

Lines changed: 74 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import java.io.InputStream;
3030
import java.net.MalformedURLException;
3131
import java.net.URL;
32+
import java.nio.charset.StandardCharsets;
3233
import java.util.ArrayList;
3334
import java.util.Collections;
3435
import java.util.Enumeration;
@@ -42,6 +43,7 @@
4243
import org.graalvm.nativeimage.hosted.Feature;
4344

4445
import com.oracle.svm.core.annotate.AutomaticFeature;
46+
import com.oracle.svm.core.jdk.resources.NativeImageResourcePath;
4547
import com.oracle.svm.core.jdk.resources.ResourceStorageEntry;
4648
import com.oracle.svm.core.util.ImageHeapMap;
4749
import com.oracle.svm.core.util.VMError;
@@ -83,57 +85,108 @@ public static byte[] inputStreamToByteArray(InputStream is) {
8385
}
8486
}
8587

86-
private static String getResourceWithoutTrailingSlash(String name) {
87-
return name.endsWith("/") ? name.substring(0, name.length() - 1) : name;
88-
}
89-
90-
private static void addEntry(String moduleName, String resourceName, boolean isDirectory, byte[] data) {
88+
private static void addEntry(String moduleName, String resourceName, boolean isDirectory, byte[] data, boolean fromJar) {
9189
Resources support = singleton();
92-
Pair<String, String> key = Pair.create(moduleName, getResourceWithoutTrailingSlash(resourceName));
90+
Pair<String, String> key = Pair.create(moduleName, resourceName);
9391
ResourceStorageEntry entry = support.resources.get(key);
9492
if (entry == null) {
95-
entry = new ResourceStorageEntry(isDirectory);
93+
entry = new ResourceStorageEntry(isDirectory, fromJar);
9694
support.resources.put(key, entry);
9795
}
9896
entry.getData().add(data);
9997
}
10098

10199
@Platforms(Platform.HOSTED_ONLY.class)
102100
public static void registerResource(String resourceName, InputStream is) {
103-
registerResource(null, resourceName, is);
101+
registerResource(null, resourceName, is, true);
102+
}
103+
104+
@Platforms(Platform.HOSTED_ONLY.class)
105+
public static void registerResource(String resourceName, InputStream is, boolean fromJar) {
106+
registerResource(null, resourceName, is, fromJar);
104107
}
105108

106109
@Platforms(Platform.HOSTED_ONLY.class)
107110
public static void registerResource(String moduleName, String resourceName, InputStream is) {
108-
addEntry(moduleName, resourceName, false, inputStreamToByteArray(is));
111+
registerResource(moduleName, resourceName, is, true);
112+
}
113+
114+
@Platforms(Platform.HOSTED_ONLY.class)
115+
public static void registerResource(String moduleName, String resourceName, InputStream is, boolean fromJar) {
116+
addEntry(moduleName, resourceName, false, inputStreamToByteArray(is), fromJar);
109117
}
110118

111119
@Platforms(Platform.HOSTED_ONLY.class)
112120
public static void registerDirectoryResource(String resourceDirName, String content) {
113-
registerDirectoryResource(null, resourceDirName, content);
121+
registerDirectoryResource(null, resourceDirName, content, true);
122+
}
123+
124+
@Platforms(Platform.HOSTED_ONLY.class)
125+
public static void registerDirectoryResource(String resourceDirName, String content, boolean fromJar) {
126+
registerDirectoryResource(null, resourceDirName, content, fromJar);
114127
}
115128

116129
@Platforms(Platform.HOSTED_ONLY.class)
117130
public static void registerDirectoryResource(String moduleName, String resourceDirName, String content) {
131+
registerDirectoryResource(moduleName, resourceDirName, content, true);
132+
}
133+
134+
@Platforms(Platform.HOSTED_ONLY.class)
135+
public static void registerDirectoryResource(String moduleName, String resourceDirName, String content, boolean fromJar) {
118136
/*
119137
* A directory content represents the names of all files and subdirectories located in the
120138
* specified directory, separated with new line delimiter and joined into one string which
121139
* is later converted into a byte array and placed into the resources map.
122140
*/
123-
addEntry(moduleName, resourceDirName, true, content.getBytes());
141+
addEntry(moduleName, resourceDirName, true, content.getBytes(), fromJar);
142+
}
143+
144+
/**
145+
* Avoid pulling native file system by using {@link NativeImageResourcePath} implementation to
146+
* convert <code>resourceName</code> to canonical variant.
147+
*/
148+
public static String toCanonicalForm(String resourceName) {
149+
NativeImageResourcePath path = new NativeImageResourcePath(null, removeTrailingSlash(resourceName).getBytes(StandardCharsets.UTF_8), true);
150+
return new String(NativeImageResourcePath.getResolved(path));
151+
}
152+
153+
private static boolean hasTrailingSlash(String resourceName) {
154+
return resourceName.endsWith("/");
155+
}
156+
157+
private static String removeTrailingSlash(String resourceName) {
158+
return hasTrailingSlash(resourceName) ? resourceName.substring(0, resourceName.length() - 1) : resourceName;
159+
}
160+
161+
private static boolean wasAlreadyInCanonicalForm(String resourceName, String canonicalResourceName) {
162+
return resourceName.equals(canonicalResourceName) || removeTrailingSlash(resourceName).equals(canonicalResourceName);
124163
}
125164

126165
public static ResourceStorageEntry get(String name) {
127166
return get(null, name);
128167
}
129168

130169
public static ResourceStorageEntry get(String moduleName, String resourceName) {
131-
ResourceStorageEntry resourceStorageEntry = singleton().resources.get(Pair.create(moduleName, getResourceWithoutTrailingSlash(resourceName)));
132-
if (resourceStorageEntry != null && (resourceStorageEntry.isDirectory() || !resourceName.endsWith("/"))) {
133-
return resourceStorageEntry;
134-
} else {
170+
String canonicalResourceName = toCanonicalForm(resourceName);
171+
ResourceStorageEntry entry = singleton().resources.get(Pair.create(moduleName, canonicalResourceName));
172+
if (entry == null) {
173+
return null;
174+
}
175+
if (entry.isFromJar() && !wasAlreadyInCanonicalForm(resourceName, canonicalResourceName)) {
176+
/*
177+
* The resource originally came from a jar file, thus behave like ZipFileSystem behaves
178+
* for non-canonical paths.
179+
*/
135180
return null;
136181
}
182+
if (!entry.isDirectory() && hasTrailingSlash(resourceName)) {
183+
/*
184+
* It this an actual resource file (not a directory) we do not tolerate a trailing
185+
* slash.
186+
*/
187+
return null;
188+
}
189+
return entry;
137190
}
138191

139192
private static URL createURL(String moduleName, String resourceName, int index) {
@@ -186,30 +239,31 @@ public static Enumeration<URL> createURLs(String moduleName, String resourceName
186239
}
187240

188241
List<URL> resourcesURLs = new ArrayList<>();
189-
242+
String canonicalResourceName = toCanonicalForm(resourceName);
243+
boolean shouldAppendTrailingSlash = hasTrailingSlash(resourceName);
190244
/* If moduleName was unspecified we have to consider all modules in the image */
191245
if (moduleName == null) {
192246
for (Module module : BootModuleLayerSupport.instance().getBootLayer().modules()) {
193247
ResourceStorageEntry entry = Resources.get(module.getName(), resourceName);
194-
addURLEntries(resourcesURLs, entry, module.getName(), resourceName);
248+
addURLEntries(resourcesURLs, entry, module.getName(), shouldAppendTrailingSlash ? canonicalResourceName + '/' : canonicalResourceName);
195249
}
196250
}
197251
ResourceStorageEntry explicitEntry = Resources.get(moduleName, resourceName);
198-
addURLEntries(resourcesURLs, explicitEntry, moduleName, resourceName);
252+
addURLEntries(resourcesURLs, explicitEntry, moduleName, shouldAppendTrailingSlash ? canonicalResourceName + '/' : canonicalResourceName);
199253

200254
if (resourcesURLs.isEmpty()) {
201255
return Collections.emptyEnumeration();
202256
}
203257
return Collections.enumeration(resourcesURLs);
204258
}
205259

206-
private static void addURLEntries(List<URL> resourcesURLs, ResourceStorageEntry entry, String moduleName, String resourceName) {
260+
private static void addURLEntries(List<URL> resourcesURLs, ResourceStorageEntry entry, String moduleName, String canonicalResourceName) {
207261
if (entry == null) {
208262
return;
209263
}
210264
int numberOfResources = entry.getData().size();
211265
for (int index = 0; index < numberOfResources; index++) {
212-
resourcesURLs.add(createURL(moduleName, resourceName, index));
266+
resourcesURLs.add(createURL(moduleName, canonicalResourceName, index));
213267
}
214268
}
215269
}

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/resources/ResourceStorageEntry.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,17 +31,23 @@
3131
public class ResourceStorageEntry {
3232

3333
private final boolean isDirectory;
34+
private final boolean fromJar;
3435
private final List<byte[]> data;
3536

36-
public ResourceStorageEntry(boolean isDirectory) {
37+
public ResourceStorageEntry(boolean isDirectory, boolean fromJar) {
3738
this.isDirectory = isDirectory;
39+
this.fromJar = fromJar;
3840
this.data = new ArrayList<>();
3941
}
4042

4143
public boolean isDirectory() {
4244
return isDirectory;
4345
}
4446

47+
public boolean isFromJar() {
48+
return fromJar;
49+
}
50+
4551
public List<byte[]> getData() {
4652
return data;
4753
}

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ private static void scanDirectory(Path root, ResourceCollector collector) throws
110110
} else {
111111
if (collector.isIncluded(null, relativeFilePath)) {
112112
try (InputStream is = Files.newInputStream(entry)) {
113-
collector.addResource(null, relativeFilePath, is);
113+
collector.addResource(null, relativeFilePath, is, false);
114114
}
115115
}
116116
}
@@ -127,7 +127,7 @@ private static void scanDirectory(Path root, ResourceCollector collector) throws
127127

128128
matchedDirectoryResources.forEach((dir, content) -> {
129129
content.sort(Comparator.naturalOrder());
130-
collector.addDirectoryResource(null, dir, String.join(System.lineSeparator(), content));
130+
collector.addDirectoryResource(null, dir, String.join(System.lineSeparator(), content), false);
131131
});
132132
}
133133

@@ -140,12 +140,12 @@ private static void scanJar(Path jarPath, ResourceCollector collector) throws IO
140140
String dirName = entry.getName().substring(0, entry.getName().length() - 1);
141141
if (collector.isIncluded(null, dirName)) {
142142
// Register the directory with empty content to preserve Java behavior
143-
collector.addDirectoryResource(null, dirName, "");
143+
collector.addDirectoryResource(null, dirName, "", true);
144144
}
145145
} else {
146146
if (collector.isIncluded(null, entry.getName())) {
147147
try (InputStream is = jf.getInputStream(entry)) {
148-
collector.addResource(null, entry.getName(), is);
148+
collector.addResource(null, entry.getName(), is, true);
149149
}
150150
}
151151
}

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

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -233,15 +233,15 @@ public boolean isIncluded(String moduleName, String resourceName) {
233233
}
234234

235235
@Override
236-
public void addResource(String moduleName, String resourceName, InputStream resourceStream) {
236+
public void addResource(String moduleName, String resourceName, InputStream resourceStream, boolean fromJar) {
237237
collectModuleName(moduleName);
238-
registerResource(debugContext, moduleName, resourceName, resourceStream);
238+
registerResource(debugContext, moduleName, resourceName, resourceStream, fromJar);
239239
}
240240

241241
@Override
242-
public void addDirectoryResource(String moduleName, String dir, String content) {
242+
public void addDirectoryResource(String moduleName, String dir, String content, boolean fromJar) {
243243
collectModuleName(moduleName);
244-
registerDirectoryResource(debugContext, moduleName, dir, content);
244+
registerDirectoryResource(debugContext, moduleName, dir, content, fromJar);
245245
}
246246

247247
private void collectModuleName(String moduleName) {
@@ -319,20 +319,20 @@ public void beforeCompilation(BeforeCompilationAccess access) {
319319
}
320320

321321
@SuppressWarnings("try")
322-
private static void registerResource(DebugContext debugContext, String moduleName, String resourceName, InputStream resourceStream) {
322+
private static void registerResource(DebugContext debugContext, String moduleName, String resourceName, InputStream resourceStream, boolean fromJar) {
323323
try (DebugContext.Scope s = debugContext.scope("registerResource")) {
324324
String moduleNamePrefix = moduleName == null ? "" : moduleName + ":";
325325
debugContext.log(DebugContext.VERBOSE_LEVEL, "ResourcesFeature: registerResource: %s%s", moduleNamePrefix, resourceName);
326-
Resources.registerResource(moduleName, resourceName, resourceStream);
326+
Resources.registerResource(moduleName, resourceName, resourceStream, fromJar);
327327
}
328328
}
329329

330330
@SuppressWarnings("try")
331-
private static void registerDirectoryResource(DebugContext debugContext, String moduleName, String dir, String content) {
331+
private static void registerDirectoryResource(DebugContext debugContext, String moduleName, String dir, String content, boolean fromJar) {
332332
try (DebugContext.Scope s = debugContext.scope("registerResource")) {
333333
String moduleNamePrefix = moduleName == null ? "" : moduleName + ":";
334334
debugContext.log(DebugContext.VERBOSE_LEVEL, "ResourcesFeature: registerResource: %s%s", moduleNamePrefix, moduleName, dir);
335-
Resources.registerDirectoryResource(moduleName, dir, content);
335+
Resources.registerDirectoryResource(moduleName, dir, content, fromJar);
336336
}
337337
}
338338
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -362,7 +362,7 @@ private boolean handleType(AnalysisType type, DuringAnalysisAccessImpl access) {
362362
try (DebugContext.Scope s = debugContext.scope("registerResource")) {
363363
debugContext.log("ServiceLoaderFeature: registerResource: " + serviceResourceLocation);
364364
}
365-
Resources.registerResource(null, serviceResourceLocation, new ByteArrayInputStream(newResourceValue.toString().getBytes(StandardCharsets.UTF_8)));
365+
Resources.registerResource(null, serviceResourceLocation, new ByteArrayInputStream(newResourceValue.toString().getBytes(StandardCharsets.UTF_8)), false);
366366

367367
/* Ensure that the static analysis runs again for the new implementation classes. */
368368
access.requireAnalysisIteration();

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/ClassLoaderSupportFeatureJDK11OrLater.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ private static void collectResourceFromModule(ResourceCollector resourceCollecto
8989
continue;
9090
}
9191
try (InputStream is = content.get()) {
92-
resourceCollector.addResource(moduleName, resName, is);
92+
resourceCollector.addResource(moduleName, resName, is, false);
9393
}
9494
}
9595
} catch (IOException e) {

substratevm/src/com.oracle.svm.test/src/META-INF/native-image/com.oracle.svm.test/native-image.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ Args= \
44
--features=com.oracle.svm.test.SerializationRegistrationTestFeature \
55
--features=com.oracle.svm.test.AbstractServiceLoaderTest$TestFeature \
66
--features=com.oracle.svm.test.NoProviderConstructorServiceLoaderTest$TestFeature \
7-
--features=com.oracle.svm.test.NativeImageResourceFileSystemProviderTest$TestFeature \
7+
--features=com.oracle.svm.test.NativeImageResourceUtils$TestFeature \
88
--add-opens=java.base/java.lang=ALL-UNNAMED \
99
-H:+AllowVMInspection \
1010
--add-exports=org.graalvm.nativeimage.builder/com.oracle.svm.core.containers=ALL-UNNAMED

0 commit comments

Comments
 (0)