Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,6 @@
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
Expand All @@ -47,7 +45,6 @@
import com.oracle.svm.core.annotate.AutomaticFeature;
import com.oracle.svm.core.jdk.resources.NativeImageResourcePath;
import com.oracle.svm.core.jdk.resources.ResourceStorageEntry;
import com.oracle.svm.core.jdk.resources.ResourceURLConnection;
import com.oracle.svm.core.util.ImageHeapMap;
import com.oracle.svm.core.util.VMError;

Expand Down Expand Up @@ -81,29 +78,11 @@ public EconomicMap<Pair<String, String>, ResourceStorageEntry> resources() {
}

public static byte[] inputStreamToByteArray(InputStream is) {
// TODO: Replace this with is.readAllBytes() once Java 8 support is removed
byte[] arr = new byte[4096];
int pos = 0;
try {
for (;;) {
if (pos == arr.length) {
byte[] tmp = new byte[arr.length * 2];
System.arraycopy(arr, 0, tmp, 0, arr.length);
arr = tmp;
}
int len = is.read(arr, pos, arr.length - pos);
if (len == -1) {
break;
}
pos += len;
}
return is.readAllBytes();
} catch (IOException ex) {
throw VMError.shouldNotReachHere(ex);
}

byte[] data = new byte[pos];
System.arraycopy(arr, 0, data, 0, pos);
return data;
}

private static void addEntry(String moduleName, String resourceName, boolean isDirectory, byte[] data) {
Expand Down Expand Up @@ -159,19 +138,13 @@ public static ResourceStorageEntry get(String moduleName, String resourceName) {
return singleton().resources.get(Pair.create(moduleName, resourceName));
}

private static URL createURL(String resourceName, int index) {
private static URL createURL(String moduleName, String resourceName, int index) {
try {
return new URL(JavaNetSubstitutions.RESOURCE_PROTOCOL, null, -1, resourceName,
new URLStreamHandler() {
@Override
protected URLConnection openConnection(URL url) {
return new ResourceURLConnection(url, index);
}
});
String refPart = index != 0 ? '#' + Integer.toString(index) : "";
return new URL(JavaNetSubstitutions.RESOURCE_PROTOCOL, moduleName, -1, '/' + resourceName + refPart);
} catch (MalformedURLException ex) {
throw new IllegalStateException(ex);
}

}

public static URL createURL(String resourceName) {
Expand Down Expand Up @@ -213,18 +186,34 @@ public static Enumeration<URL> createURLs(String moduleName, String resourceName
if (resourceName == null) {
return null;
}

String canonicalResourceName = toCanonicalForm(resourceName);
ResourceStorageEntry entry = Resources.get(moduleName, canonicalResourceName);
if (entry == null) {

List<URL> resourcesURLs = new ArrayList<>();

/* If moduleName was unspecified we have to consider all modules in the image */
if (moduleName == null) {
for (Module module : BootModuleLayerSupport.instance().getBootLayer().modules()) {
ResourceStorageEntry entry = Resources.get(module.getName(), canonicalResourceName);
addURLEntries(resourcesURLs, entry, module.getName(), canonicalResourceName);
}
}
ResourceStorageEntry explicitEntry = Resources.get(moduleName, canonicalResourceName);
addURLEntries(resourcesURLs, explicitEntry, moduleName, canonicalResourceName);

if (resourcesURLs.isEmpty()) {
return Collections.emptyEnumeration();
}
return Collections.enumeration(resourcesURLs);
}

private static void addURLEntries(List<URL> resourcesURLs, ResourceStorageEntry entry, String moduleName, String canonicalResourceName) {
if (entry == null) {
return;
}
int numberOfResources = entry.getData().size();
List<URL> resourcesURLs = new ArrayList<>(numberOfResources);
for (int index = 0; index < numberOfResources; index++) {
resourcesURLs.add(createURL(canonicalResourceName, index));
resourcesURLs.add(createURL(moduleName, canonicalResourceName, index));
}
return Collections.enumeration(resourcesURLs);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,26 +33,15 @@
import java.net.URLConnection;
import java.util.List;

import com.oracle.svm.core.jdk.JavaNetSubstitutions;
import com.oracle.svm.core.jdk.Resources;

public class ResourceURLConnection extends URLConnection {

private final URL url;
private final int index;
private byte[] data;

public ResourceURLConnection(URL url) {
this(url, 0);
}

public ResourceURLConnection(URL url, int index) {
super(url);
this.url = url;
this.index = index;
}

private static String resolveName(String resourceName) {
return resourceName.startsWith("/") ? resourceName.substring(1) : resourceName;
}

@Override
Expand All @@ -62,10 +51,25 @@ public void connect() {
}
connected = true;

String resourceName = resolveName(url.getPath());
ResourceStorageEntry entry = Resources.get(Resources.toCanonicalForm(resourceName));
String urlHost = url.getHost();
String hostNameOrNull = urlHost != null && !urlHost.isEmpty() ? urlHost : null;
String urlPath = url.getPath();
if (urlPath.isEmpty()) {
throw new IllegalArgumentException("Empty URL path not allowed in " + JavaNetSubstitutions.RESOURCE_PROTOCOL + " URL");
}
String resourceName = urlPath.substring(1);
ResourceStorageEntry entry = Resources.get(hostNameOrNull, Resources.toCanonicalForm(resourceName));
if (entry != null) {
List<byte[]> bytes = entry.getData();
String urlRef = url.getRef();
int index = 0;
if (urlRef != null) {
try {
index = Integer.valueOf(urlRef);
} catch (NumberFormatException e) {
throw new IllegalArgumentException("URL anchor '#" + urlRef + "' not allowed in " + JavaNetSubstitutions.RESOURCE_PROTOCOL + " URL");
}
}
if (index < bytes.size()) {
this.data = bytes.get(index);
} else {
Expand Down Expand Up @@ -96,5 +100,4 @@ public long getContentLengthLong() {
connect();
return data != null ? data.length : -1L;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@ Args= \
--features=com.oracle.svm.test.AbstractServiceLoaderTest$TestFeature \
--features=com.oracle.svm.test.NoProviderConstructorServiceLoaderTest$TestFeature \
--features=com.oracle.svm.test.NativeImageResourceFileSystemProviderTest$TestFeature \
--add-opens=java.base/java.lang=ALL-UNNAMED \
-H:+AllowVMInspection \
--add-exports=org.graalvm.nativeimage.builder/com.oracle.svm.core.containers=ALL-UNNAMED
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright (c) 2022, 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.oracle.svm.test;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Specifies packages concealed in JDK modules used by a test. The mx unit test runner will ensure
* the packages are exported to the module containing annotated test class.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface AddExports {
/**
* The qualified name of the concealed package in {@code <module>/<package>} format (e.g.,
* "jdk.internal.vm.ci/jdk.vm.ci.code").
*/
String[] value() default "";
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@

import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
Expand All @@ -48,11 +49,15 @@
import java.nio.file.attribute.FileTime;
import java.nio.file.spi.FileSystemProvider;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

import org.graalvm.nativeimage.ImageSingletons;
import org.graalvm.nativeimage.hosted.Feature;
Expand All @@ -62,6 +67,7 @@

import com.oracle.svm.core.configure.ResourcesRegistry;

@AddExports("java.base/java.lang")
public class NativeImageResourceFileSystemProviderTest {

private static final String RESOURCE_DIR = "/resources";
Expand All @@ -79,6 +85,11 @@ public void beforeAnalysis(BeforeAnalysisAccess access) {
// Remove leading / for the resource patterns
registry.addResources(ConfigurationCondition.alwaysTrue(), RESOURCE_FILE_1.substring(1));
registry.addResources(ConfigurationCondition.alwaysTrue(), RESOURCE_FILE_2.substring(1));

/** Needed for {@link #testURLExternalFormEquivalence()} */
for (Module module : ModuleLayer.boot().modules()) {
registry.addResources(ConfigurationCondition.alwaysTrue(), module.getName() + ":" + "module-info.class");
}
}
}

Expand Down Expand Up @@ -572,4 +583,52 @@ public void writingFileAttributes() {
// 3. Closing file system.
closeFileSystem(fileSystem);
}

@Test
public void moduleResourceURLAccess() {
URL url = Class.class.getResource("uniName.dat");
Assert.assertNotNull("URL for resource java.base/java/lang/uniName.dat must not be null", url);
try (InputStream in = url.openStream()) {
try {
Assert.assertNotEquals("uniName.dat does not seem to contain valid data", in.read(), 0);
} catch (IOException e) {
Assert.fail("IOException in in.read(): " + e.getMessage());
}
} catch (IOException e) {
Assert.fail("IOException in url.openStream(): " + e.getMessage());
}
}

@Test
public void testURLExternalFormEquivalence() {
Enumeration<URL> urlEnumeration = null;
try {
urlEnumeration = ClassLoader.getSystemResources("module-info.class");
} catch (IOException e) {
Assert.fail("IOException in ClassLoader.getSystemResources(\"module-info.class\"): " + e.getMessage());
}

Assert.assertNotNull(urlEnumeration);
Enumeration<URL> finalVar = urlEnumeration;
Iterable<URL> urlIterable = () -> finalVar.asIterator();
List<URL> urlList = StreamSupport.stream(urlIterable.spliterator(), false).collect(Collectors.toList());
Assert.assertTrue("ClassLoader.getSystemResources(\"module-info.class\") must return many module-info.class URLs",
urlList.size() > 3);

URL thirdEntry = urlList.get(2);
String thirdEntryExternalForm = thirdEntry.toExternalForm();
URL thirdEntryFromExternalForm = null;
try {
thirdEntryFromExternalForm = new URL(thirdEntryExternalForm);
} catch (MalformedURLException e) {
Assert.fail("Creating a new URL from the ExternalForm of another has to work: " + e.getMessage());
}

try {
boolean compareResult = compareTwoURLs(thirdEntry, thirdEntryFromExternalForm);
Assert.assertTrue("Contents of original URL and one created from originals ExternalForm must be the same", compareResult);
} catch (IOException e) {
Assert.fail("Contents of original URL and one created from originals ExternalForm must be the same: " + e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -172,30 +172,6 @@
]}
]
},
{
"name" : "com.oracle.svm.core.jdk.Resources",
"methods" : [
{ "name" : "createURL" ,
"parameterTypes" : [
"java.lang.String"
]},
{ "name" : "createURL" ,
"parameterTypes" : [
"java.lang.String",
"int"
]}
]
},
{
"name" : "com.oracle.svm.core.jdk.ResourcesHelper",
"methods" : [
{ "name" : "urlToResource" ,
"parameterTypes" : [
"java.lang.String",
"java.net.URL"
]}
]
},
{
"name" : "com.oracle.svm.core.handles.ObjectHandlesImpl",
"allDeclaredMethods" : true,
Expand Down