diff --git a/substratevm/mx.substratevm/suite.py b/substratevm/mx.substratevm/suite.py
index d96dc9334b74b..0c134a65cdb7c 100644
--- a/substratevm/mx.substratevm/suite.py
+++ b/substratevm/mx.substratevm/suite.py
@@ -206,6 +206,7 @@
"jdk.internal.module",
"jdk.internal.misc",
"jdk.internal.logger",
+ "jdk.internal.loader",
"sun.util.resources",
"sun.text.spi",
"jdk.internal.perf",
diff --git a/substratevm/src/com.oracle.svm.core.jdk11/src/com/oracle/svm/core/jdk/JDKVersionSpecificResourceBuilder.java b/substratevm/src/com.oracle.svm.core.jdk11/src/com/oracle/svm/core/jdk/JDKVersionSpecificResourceBuilder.java
new file mode 100644
index 0000000000000..3c3e8db542480
--- /dev/null
+++ b/substratevm/src/com.oracle.svm.core.jdk11/src/com/oracle/svm/core/jdk/JDKVersionSpecificResourceBuilder.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2021, 2021, 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.core.jdk;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.net.URLConnection;
+
+public class JDKVersionSpecificResourceBuilder {
+
+ public static Object buildResource(String name, URL url, URLConnection urlConnection) {
+ return new jdk.internal.loader.Resource() {
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public URL getURL() {
+ return url;
+ }
+
+ @Override
+ public URL getCodeSourceURL() {
+ // We are deleting resource URL class path during native image build,
+ // so in runtime we don't have this information.
+ return null;
+ }
+
+ @Override
+ public InputStream getInputStream() throws IOException {
+ return urlConnection.getInputStream();
+ }
+
+ @Override
+ public int getContentLength() throws IOException {
+ return urlConnection.getContentLength();
+ }
+ };
+ }
+}
diff --git a/substratevm/src/com.oracle.svm.core.jdk8/src/com/oracle/svm/core/jdk/JDKVersionSpecificResourceBuilder.java b/substratevm/src/com.oracle.svm.core.jdk8/src/com/oracle/svm/core/jdk/JDKVersionSpecificResourceBuilder.java
new file mode 100644
index 0000000000000..b319fbbc6a493
--- /dev/null
+++ b/substratevm/src/com.oracle.svm.core.jdk8/src/com/oracle/svm/core/jdk/JDKVersionSpecificResourceBuilder.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2021, 2021, 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.core.jdk;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.net.URLConnection;
+
+public class JDKVersionSpecificResourceBuilder {
+
+ public static Object buildResource(String name, URL url, URLConnection urlConnection) {
+ return new sun.misc.Resource() {
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public URL getURL() {
+ return url;
+ }
+
+ @Override
+ public URL getCodeSourceURL() {
+ return null;
+ }
+
+ @Override
+ public InputStream getInputStream() throws IOException {
+ return urlConnection.getInputStream();
+ }
+
+ @Override
+ public int getContentLength() throws IOException {
+ return urlConnection.getContentLength();
+ }
+ };
+ }
+}
diff --git a/substratevm/src/com.oracle.svm.core/src/META-INF/services/java.nio.file.spi.FileSystemProvider b/substratevm/src/com.oracle.svm.core/src/META-INF/services/java.nio.file.spi.FileSystemProvider
new file mode 100644
index 0000000000000..2c8308d652d37
--- /dev/null
+++ b/substratevm/src/com.oracle.svm.core/src/META-INF/services/java.nio.file.spi.FileSystemProvider
@@ -0,0 +1 @@
+com.oracle.svm.core.jdk.resources.NativeImageResourceFileSystemProvider
\ No newline at end of file
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHub.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHub.java
index 81b5880932c4c..6864a09498921 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHub.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHub.java
@@ -26,7 +26,6 @@
//Checkstyle: allow reflection
-import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.InputStream;
import java.io.Serializable;
@@ -81,7 +80,6 @@
import com.oracle.svm.core.jdk.JDK16OrLater;
import com.oracle.svm.core.jdk.JDK8OrEarlier;
import com.oracle.svm.core.jdk.Package_jdk_internal_reflect;
-import com.oracle.svm.core.jdk.Resources;
import com.oracle.svm.core.jdk.Target_java_lang_Module;
import com.oracle.svm.core.jdk.Target_jdk_internal_reflect_Reflection;
import com.oracle.svm.core.meta.SharedType;
@@ -720,34 +718,18 @@ public Enum>[] getEnumConstantsShared() {
return (Enum>[]) enumConstantsReference;
}
- @Substitute
- private InputStream getResourceAsStream(String resourceName) {
- final String path = resolveName(getName(), resourceName);
- List arr = Resources.get(path);
- return arr == null ? null : new ByteArrayInputStream(arr.get(0));
- }
+ @KeepOriginal
+ public native URL getResource(String resourceName);
- @Substitute
- private URL getResource(String resourceName) {
- final String path = resolveName(getName(), resourceName);
- List arr = Resources.get(path);
- return arr == null ? null : Resources.createURL(path, arr.get(0));
- }
+ @KeepOriginal
+ public native InputStream getResourceAsStream(String resourceName);
- private String resolveName(String baseName, String resourceName) {
- if (resourceName == null) {
- return resourceName;
- }
- if (resourceName.startsWith("/")) {
- return resourceName.substring(1);
- }
- int index = baseName.lastIndexOf('.');
- if (index != -1) {
- return baseName.substring(0, index).replace('.', '/') + "/" + resourceName;
- } else {
- return resourceName;
- }
- }
+ @KeepOriginal
+ private native String resolveName(String resourceName);
+
+ @KeepOriginal
+ @TargetElement(name = "isOpenToCaller", onlyWith = JDK11OrLater.class)
+ private native boolean isOpenToCaller(String resourceName, Class> caller);
@KeepOriginal
private native ClassLoader getClassLoader();
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JDKVersionSpecificResourceBuilder.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JDKVersionSpecificResourceBuilder.java
new file mode 100644
index 0000000000000..a9390de78eb59
--- /dev/null
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JDKVersionSpecificResourceBuilder.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2021, 2021, 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.core.jdk;
+
+import java.net.URL;
+import java.net.URLConnection;
+
+public class JDKVersionSpecificResourceBuilder {
+
+ @SuppressWarnings("unused")
+ public static Object buildResource(String name, URL url, URLConnection urlConnection) {
+ return null;
+ }
+}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JavaLangSubstitutions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JavaLangSubstitutions.java
index 3f325032ce916..6b164b0140b9a 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JavaLangSubstitutions.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JavaLangSubstitutions.java
@@ -29,11 +29,9 @@
import static com.oracle.svm.core.snippets.KnownIntrinsics.readHub;
import java.io.File;
-import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.net.URL;
-import java.util.Enumeration;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
@@ -726,28 +724,6 @@ private static boolean hasClassPath() {
return true;
}
- @SuppressWarnings("unused")
- @Substitute
- private static URL findResource(String mn, String name) {
- return ClassLoader.getSystemClassLoader().getResource(name);
- }
-
- @SuppressWarnings("unused")
- @Substitute
- private static InputStream findResourceAsStream(String mn, String name) {
- return ClassLoader.getSystemClassLoader().getResourceAsStream(name);
- }
-
- @Substitute
- private static URL findResource(String name) {
- return ClassLoader.getSystemClassLoader().getResource(name);
- }
-
- @Substitute
- private static Enumeration findResources(String name) throws IOException {
- return ClassLoader.getSystemClassLoader().getResources(name);
- }
-
/**
* All ClassLoaderValue are reset at run time for now. See also
* {@link Target_java_lang_ClassLoader#classLoaderValueMap} for resetting of individual class
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JavaNetSubstitutions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JavaNetSubstitutions.java
index e33e9f5b80210..b0d5c9080d81b 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JavaNetSubstitutions.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JavaNetSubstitutions.java
@@ -26,10 +26,6 @@
// Checkstyle: allow reflection
-import java.io.ByteArrayInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
@@ -58,6 +54,7 @@
import com.oracle.svm.core.annotate.TargetClass;
import com.oracle.svm.core.c.CGlobalData;
import com.oracle.svm.core.c.CGlobalDataFactory;
+import com.oracle.svm.core.jdk.resources.ResourceURLConnection;
import com.oracle.svm.core.option.OptionUtils;
import com.oracle.svm.core.option.SubstrateOptionsParser;
import com.oracle.svm.core.util.VMError;
@@ -240,31 +237,8 @@ static URLStreamHandler getURLStreamHandler(String protocol) throws MalformedURL
static URLStreamHandler createResourcesURLStreamHandler() {
return new URLStreamHandler() {
@Override
- protected URLConnection openConnection(URL url) throws IOException {
- return new URLConnection(url) {
- private InputStream in;
-
- @Override
- public void connect() throws IOException {
- if (connected) {
- return;
- }
- connected = true;
- // remove "resource:" from url to get the resource name
- String resName = url.toString().substring(1 + JavaNetSubstitutions.RESOURCE_PROTOCOL.length());
- final List bytes = Resources.get(resName);
- if (bytes == null || bytes.size() < 1) {
- throw new FileNotFoundException(url.toString());
- }
- in = new ByteArrayInputStream(bytes.get(0));
- }
-
- @Override
- public InputStream getInputStream() throws IOException {
- connect();
- return in;
- }
- };
+ protected URLConnection openConnection(URL url) {
+ return new ResourceURLConnection(url);
}
};
}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Resources.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Resources.java
index 42d3de41be90f..538a1027fe1c4 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Resources.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Resources.java
@@ -24,7 +24,6 @@
*/
package com.oracle.svm.core.jdk;
-import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
@@ -32,8 +31,12 @@
import java.net.URLConnection;
import java.net.URLStreamHandler;
import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
import java.util.List;
+import com.oracle.svm.core.jdk.resources.ResourceStorageEntry;
+import com.oracle.svm.core.jdk.resources.ResourceURLConnection;
import org.graalvm.collections.EconomicMap;
import org.graalvm.nativeimage.ImageSingletons;
import org.graalvm.nativeimage.Platform;
@@ -53,23 +56,21 @@
*/
public final class Resources {
- static Resources singleton() {
+ public static Resources singleton() {
return ImageSingletons.lookup(Resources.class);
}
/** The hosted map used to collect registered resources. */
- private final EconomicMap> resources = ImageHeapMap.create();
+ private final EconomicMap resources = ImageHeapMap.create();
Resources() {
}
- public EconomicMap> resources() {
+ public EconomicMap resources() {
return resources;
}
- @Platforms(Platform.HOSTED_ONLY.class)
- public static void registerResource(String name, InputStream is) {
-
+ public static byte[] inputStreamToByteArray(InputStream is) {
byte[] arr = new byte[4096];
int pos = 0;
try {
@@ -89,71 +90,71 @@ public static void registerResource(String name, InputStream is) {
throw VMError.shouldNotReachHere(ex);
}
- byte[] res = new byte[pos];
- System.arraycopy(arr, 0, res, 0, pos);
+ byte[] data = new byte[pos];
+ System.arraycopy(arr, 0, data, 0, pos);
+ return data;
+ }
+ private static void addEntry(String resourceName, boolean isDirectory, byte[] data) {
Resources support = singleton();
- List list = support.resources.get(name);
- if (list == null) {
- list = new ArrayList<>();
- support.resources.put(name, list);
+ ResourceStorageEntry entry = support.resources.get(resourceName);
+ if (entry == null) {
+ entry = new ResourceStorageEntry(isDirectory);
+ support.resources.put(resourceName, entry);
}
- list.add(res);
+ entry.getData().add(data);
+ }
+
+ @Platforms(Platform.HOSTED_ONLY.class)
+ public static void registerResource(String resourceName, InputStream is) {
+ addEntry(resourceName, false, inputStreamToByteArray(is));
}
@Platforms(Platform.HOSTED_ONLY.class)
- public static void registerDirectoryResource(String dir, String content) {
+ public static void registerDirectoryResource(String resourceDirName, String content) {
/*
* A directory content represents the names of all files and subdirectories located in the
* specified directory, separated with new line delimiter and joined into one string which
* is later converted into a byte array and placed into the resources map.
*/
- Resources support = singleton();
-
- byte[] arr = content.getBytes();
- List list = support.resources.get(dir);
- if (list == null) {
- list = new ArrayList<>();
- support.resources.put(dir, list);
- }
- list.add(arr);
+ addEntry(resourceDirName, true, content.getBytes());
}
- public static List get(String name) {
+ public static ResourceStorageEntry get(String name) {
return singleton().resources.get(name);
}
- public static URL createURL(String name, byte[] resourceBytes) {
- class Conn extends URLConnection {
- Conn(URL url) {
- super(url);
- }
+ private static URL createURL(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);
+ }
+ });
+ } catch (MalformedURLException ex) {
+ throw new IllegalStateException(ex);
+ }
- @Override
- public void connect() throws IOException {
- }
+ }
- @Override
- public InputStream getInputStream() throws IOException {
- return new ByteArrayInputStream(resourceBytes);
- }
+ public static URL createURL(String resourceName) {
+ Enumeration urls = createURLs(resourceName);
+ return urls.hasMoreElements() ? urls.nextElement() : null;
+ }
- @Override
- public long getContentLengthLong() {
- return resourceBytes.length;
- }
+ public static Enumeration createURLs(String resourceName) {
+ ResourceStorageEntry entry = Resources.get(resourceName);
+ if (entry == null) {
+ return Collections.emptyEnumeration();
}
-
- try {
- return new URL("resource", null, -1, name, new URLStreamHandler() {
- @Override
- protected URLConnection openConnection(URL u) throws IOException {
- return new Conn(u);
- }
- });
- } catch (MalformedURLException ex) {
- throw new IllegalStateException(ex);
+ int numberOfResources = entry.getData().size();
+ List resourcesURLs = new ArrayList<>(numberOfResources);
+ for (int index = 0; index < numberOfResources; index++) {
+ resourcesURLs.add(createURL(resourceName, index));
}
+ return Collections.enumeration(resourcesURLs);
}
}
@@ -172,8 +173,8 @@ public void afterCompilation(AfterCompilationAccess access) {
* of lazily initialized fields. Only the byte[] arrays themselves can be safely made
* read-only.
*/
- for (List resourceList : Resources.singleton().resources().getValues()) {
- for (byte[] resource : resourceList) {
+ for (ResourceStorageEntry resourceList : Resources.singleton().resources().getValues()) {
+ for (byte[] resource : resourceList.getData()) {
access.registerAsImmutable(resource);
}
}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_java_lang_ClassLoader.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_java_lang_ClassLoader.java
index 6ac33fac00adf..ae54e9dd6cdf9 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_java_lang_ClassLoader.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_java_lang_ClassLoader.java
@@ -24,13 +24,13 @@
*/
package com.oracle.svm.core.jdk;
-import java.io.ByteArrayInputStream;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLClassLoader;
+import java.net.URLConnection;
import java.security.ProtectionDomain;
import java.util.ArrayList;
import java.util.Collections;
@@ -58,8 +58,75 @@
import jdk.vm.ci.meta.MetaAccessProvider;
import jdk.vm.ci.meta.ResolvedJavaField;
+@TargetClass(className = "jdk.internal.loader.Resource", onlyWith = JDK11OrLater.class)
+@SuppressWarnings("unused")
+final class Target_jdk_internal_loader_Resource_JDK11OrLater {
+
+}
+
+@TargetClass(className = "sun.misc.Resource", onlyWith = JDK8OrEarlier.class)
+@SuppressWarnings("unused")
+final class Target_sun_misc_Resource_JDK8OrEarlier {
+
+}
+
+@SuppressWarnings("unchecked")
+class ResourcesHelper {
+
+ private static T urlToResource(String resourceName, URL url) {
+ try {
+ if (url == null) {
+ return null;
+ }
+ URLConnection urlConnection = url.openConnection();
+ Object resource = JDKVersionSpecificResourceBuilder.buildResource(resourceName, url, urlConnection);
+ VMError.guarantee(resource != null);
+ return (T) resource;
+ } catch (IOException e) {
+ return null;
+ } catch (ClassCastException classCastException) {
+ throw VMError.shouldNotReachHere(classCastException);
+ }
+ }
+
+ static T nameToResource(String resourceName) {
+ return urlToResource(resourceName, nameToResourceURL(resourceName));
+ }
+
+ static Enumeration nameToResources(String resourceName) {
+ Enumeration urls = Resources.createURLs(resourceName);
+ List resourceURLs = new ArrayList<>();
+ while (urls.hasMoreElements()) {
+ resourceURLs.add(urlToResource(resourceName, urls.nextElement()));
+ }
+ return Collections.enumeration(resourceURLs);
+ }
+
+ static URL nameToResourceURL(String resourceName) {
+ return Resources.createURL(resourceName);
+ }
+
+ static InputStream nameToResourceInputStream(String resourceName) throws IOException {
+ URL url = nameToResourceURL(resourceName);
+ return url != null ? url.openStream() : null;
+ }
+
+ static List nameToResourceListURLs(String resourcesName) {
+ Enumeration urls = Resources.createURLs(resourcesName);
+ List resourceURLs = new ArrayList<>();
+ while (urls.hasMoreElements()) {
+ resourceURLs.add(urls.nextElement());
+ }
+ return resourceURLs;
+ }
+
+ static Enumeration nameToResourceEnumerationURLs(String resourcesName) {
+ return Collections.enumeration(nameToResourceListURLs(resourcesName));
+ }
+}
+
@TargetClass(classNameProvider = Package_jdk_internal_loader.class, className = "URLClassPath")
-@SuppressWarnings("static-method")
+@SuppressWarnings({"unused", "static-method"})
final class Target_jdk_internal_loader_URLClassPath {
/* Reset fields that can store a Zip file via sun.misc.URLClassPath$JarLoader.jar. */
@@ -91,20 +158,50 @@ final class Target_jdk_internal_loader_URLClassPath {
@Delete
@TargetElement(onlyWith = JDK8OrEarlier.class)
private static native boolean knownToNotExist0(ClassLoader loader, String className);
+
+ @Substitute
+ public URL findResource(String name, boolean check) {
+ return Resources.createURL(name);
+ }
+
+ @Substitute
+ public Enumeration findResources(final String name, final boolean check) {
+ return Resources.createURLs(name);
+ }
+
+ @Substitute
+ @TargetElement(name = "getResource", onlyWith = JDK11OrLater.class)
+ public Target_jdk_internal_loader_Resource_JDK11OrLater getResourceJDK11OrLater(String name, boolean check) {
+ return ResourcesHelper.nameToResource(name);
+ }
+
+ @Substitute
+ @TargetElement(name = "getResources", onlyWith = JDK11OrLater.class)
+ public Enumeration getResourcesJDK11OrLater(final String name,
+ final boolean check) {
+ return ResourcesHelper.nameToResources(name);
+ }
+
+ @Substitute
+ @TargetElement(name = "getResource", onlyWith = JDK8OrEarlier.class)
+ public Target_sun_misc_Resource_JDK8OrEarlier getResourceJDK8OrEarlier(String name, boolean check) {
+ return ResourcesHelper.nameToResource(name);
+ }
+
+ @Substitute
+ @TargetElement(name = "getResources", onlyWith = JDK8OrEarlier.class)
+ public Enumeration getResourcesJDK8OrEarlier(final String name,
+ final boolean check) {
+ return ResourcesHelper.nameToResources(name);
+ }
}
@TargetClass(URLClassLoader.class)
-@SuppressWarnings("static-method")
+@SuppressWarnings({"unused", "static-method"})
final class Target_java_net_URLClassLoader {
@Alias @RecomputeFieldValue(kind = Kind.NewInstance, declClass = WeakHashMap.class)//
private WeakHashMap closeables;
- @Substitute
- private InputStream getResourceAsStream(String name) {
- List arr = Resources.get(name);
- return arr == null ? null : new ByteArrayInputStream(arr.get(0));
- }
-
@Substitute
@SuppressWarnings("unused")
protected Class> findClass(final String name) {
@@ -113,38 +210,47 @@ protected Class> findClass(final String name) {
}
@TargetClass(className = "jdk.internal.loader.BuiltinClassLoader", onlyWith = JDK11OrLater.class)
-@SuppressWarnings("static-method")
+@SuppressWarnings({"unused", "static-method"})
final class Target_jdk_internal_loader_BuiltinClassLoader {
@Substitute
- public URL findResource(@SuppressWarnings("unused") String mn, String name) {
- List arr = Resources.get(name);
- return arr == null ? null : Resources.createURL(name, arr.get(0));
+ public URL findResource(String mn, String name) {
+ return ResourcesHelper.nameToResourceURL(name);
}
@Substitute
- public URL findResource(String name) {
- List arr = Resources.get(name);
- return arr == null ? null : Resources.createURL(name, arr.get(0));
+ public InputStream findResourceAsStream(String mn, String name) throws IOException {
+ return ResourcesHelper.nameToResourceInputStream(name);
}
@Substitute
- public InputStream findResourceAsStream(@SuppressWarnings("unused") String mn, String name) {
- List arr = Resources.get(name);
- return arr == null ? null : new ByteArrayInputStream(arr.get(0));
+ public URL findResource(String name) {
+ return ResourcesHelper.nameToResourceURL(name);
}
@Substitute
public Enumeration findResources(String name) {
- List arr = Resources.get(name);
- if (arr == null) {
- return Collections.emptyEnumeration();
- }
- List res = new ArrayList<>(arr.size());
- for (byte[] data : arr) {
- res.add(Resources.createURL(name, data));
- }
- return Collections.enumeration(res);
+ return ResourcesHelper.nameToResourceEnumerationURLs(name);
+ }
+
+ @Substitute
+ private List findMiscResource(String name) {
+ return ResourcesHelper.nameToResourceListURLs(name);
+ }
+
+ @Substitute
+ private URL findResource(Target_java_lang_module_ModuleReference mref, String name) {
+ return ResourcesHelper.nameToResourceURL(name);
+ }
+
+ @Substitute
+ private URL findResourceOnClassPath(String name) {
+ return ResourcesHelper.nameToResourceURL(name);
+ }
+
+ @Substitute
+ private Enumeration findResourcesOnClassPath(String name) {
+ return ResourcesHelper.nameToResourceEnumerationURLs(name);
}
}
@@ -153,43 +259,39 @@ public Enumeration findResources(String name) {
final class Target_jdk_internal_loader_Loader {
@Substitute
- private URL findResource(String mn, String name) {
- return findResource(name);
+ protected URL findResource(String mn, String name) {
+ return ResourcesHelper.nameToResourceURL(name);
}
@Substitute
- private URL findResource(String name) {
- List arr = Resources.get(name);
- return arr == null ? null : Resources.createURL(name, arr.get(0));
+ public URL findResource(String name) {
+ return ResourcesHelper.nameToResourceURL(name);
}
@Substitute
- private Enumeration findResources(String name) {
- List arr = Resources.get(name);
- if (arr == null) {
- return Collections.emptyEnumeration();
- }
- List res = new ArrayList<>(arr.size());
- for (byte[] data : arr) {
- res.add(Resources.createURL(name, data));
- }
- return Collections.enumeration(res);
+ public Enumeration findResources(String name) {
+ return ResourcesHelper.nameToResourceEnumerationURLs(name);
}
@Substitute
- private URL getResource(String name) {
- return findResource(name);
+ public URL getResource(String name) {
+ return ResourcesHelper.nameToResourceURL(name);
}
@Substitute
- private Enumeration getResources(String name) {
- return findResources(name);
+ public Enumeration getResources(String name) throws IOException {
+ return ResourcesHelper.nameToResourceEnumerationURLs(name);
+ }
+
+ @Substitute
+ private List findResourcesAsList(String name) {
+ return ResourcesHelper.nameToResourceListURLs(name);
}
}
@TargetClass(ClassLoader.class)
@SuppressWarnings("static-method")
-final class Target_java_lang_ClassLoader {
+public final class Target_java_lang_ClassLoader {
/**
* This field can be safely deleted, but that would require substituting the entire constructor
@@ -233,42 +335,12 @@ public static ClassLoader getSystemClassLoader() {
@Substitute
private URL getResource(String name) {
- return getSystemResource(name);
- }
-
- @Substitute
- private InputStream getResourceAsStream(String name) {
- return getSystemResourceAsStream(name);
+ return ResourcesHelper.nameToResourceURL(name);
}
@Substitute
private Enumeration getResources(String name) {
- return getSystemResources(name);
- }
-
- @Substitute
- private static URL getSystemResource(String name) {
- List arr = Resources.get(name);
- return arr == null ? null : Resources.createURL(name, arr.get(0));
- }
-
- @Substitute
- private static InputStream getSystemResourceAsStream(String name) {
- List arr = Resources.get(name);
- return arr == null ? null : new ByteArrayInputStream(arr.get(0));
- }
-
- @Substitute
- private static Enumeration getSystemResources(String name) {
- List arr = Resources.get(name);
- if (arr == null) {
- return Collections.emptyEnumeration();
- }
- List res = new ArrayList<>(arr.size());
- for (byte[] data : arr) {
- res.add(Resources.createURL(name, data));
- }
- return Collections.enumeration(res);
+ return ResourcesHelper.nameToResourceEnumerationURLs(name);
}
@Substitute
@@ -338,13 +410,6 @@ private boolean trySetObjectField(String name, Object obj) {
throw VMError.unsupportedFeature("JDK11OrLater: Target_java_lang_ClassLoader.trySetObjectField(String name, Object obj)");
}
- @Substitute //
- @TargetElement(onlyWith = JDK11OrLater.class) //
- @SuppressWarnings({"unused"})
- protected URL findResource(String moduleName, String name) throws IOException {
- throw VMError.unsupportedFeature("JDK11OrLater: Target_java_lang_ClassLoader.findResource(String, String)");
- }
-
@Substitute //
@SuppressWarnings({"unused"})
Object getClassLoadingLock(String className) {
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_java_lang_Module.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_java_lang_Module.java
index 85afea183b0a2..4e1b03662c8c9 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_java_lang_Module.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_java_lang_Module.java
@@ -26,21 +26,24 @@
import java.io.ByteArrayInputStream;
import java.io.InputStream;
-import java.util.List;
import com.oracle.svm.core.annotate.Delete;
import com.oracle.svm.core.annotate.Substitute;
import com.oracle.svm.core.annotate.TargetClass;
-import com.oracle.svm.core.annotate.TargetElement;
+import com.oracle.svm.core.jdk.resources.ResourceStorageEntry;
@TargetClass(className = "java.lang.Module", onlyWith = JDK11OrLater.class)
public final class Target_java_lang_Module {
+
@SuppressWarnings("static-method")
@Substitute
- @TargetElement(name = "getResourceAsStream")
public InputStream getResourceAsStream(String name) {
- List arr = Resources.get(name);
- return arr == null ? null : new ByteArrayInputStream(arr.get(0));
+ ResourceStorageEntry entry = Resources.get(name);
+ if (entry == null) {
+ return null;
+ } else {
+ return new ByteArrayInputStream(entry.getData().get(0));
+ }
}
/*
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_java_lang_module_ModuleReference.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_java_lang_module_ModuleReference.java
new file mode 100644
index 0000000000000..fe7bb7485eebf
--- /dev/null
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_java_lang_module_ModuleReference.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2021, 2021, 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.core.jdk;
+
+import com.oracle.svm.core.annotate.TargetClass;
+
+@TargetClass(className = "java.lang.module.ModuleReference", onlyWith = JDK11OrLater.class)
+@SuppressWarnings("unused")
+public final class Target_java_lang_module_ModuleReference {
+
+}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/resources/ByteArrayChannel.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/resources/ByteArrayChannel.java
new file mode 100644
index 0000000000000..b4941b2eb8a06
--- /dev/null
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/resources/ByteArrayChannel.java
@@ -0,0 +1,269 @@
+/*
+ * Copyright (c) 2021, 2021, 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.core.jdk.resources;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.ClosedChannelException;
+import java.nio.channels.NonWritableChannelException;
+import java.nio.channels.SeekableByteChannel;
+import java.util.Arrays;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+/**
+ *
+ * This class is copy of class jdk.nio.zipfs.ByteArrayChannel. There are two reasons why we cannot
+ * substitute this class:
+ *
+ * - This class doesn't exists on JDK8.
+ * - On JDK11, we need to extends this class, and in case of substitution, it's not possible
+ * because substituted class is final
+ *
+ *
+ */
+public class ByteArrayChannel implements SeekableByteChannel {
+
+ private final ReadWriteLock rwlock = new ReentrantReadWriteLock();
+ private byte[] buf;
+
+ /**
+ * The current position of this channel.
+ */
+ private int pos;
+
+ /**
+ * The index that is one greater than the last valid byte in the channel.
+ */
+ private int last;
+
+ private boolean closed;
+ private final boolean readonly;
+
+ /**
+ * Creates a {@code ByteArrayChannel} with size {@code sz}.
+ */
+ ByteArrayChannel(int sz, boolean readonly) {
+ this.buf = new byte[sz];
+ this.pos = this.last = 0;
+ this.readonly = readonly;
+ }
+
+ /**
+ * Creates a ByteArrayChannel with its 'pos' at 0 and its 'last' at buf's end. Note: no
+ * defensive copy of the 'buf', used directly.
+ */
+ ByteArrayChannel(byte[] buf, boolean readonly) {
+ this.buf = buf;
+ this.pos = 0;
+ this.last = buf.length;
+ this.readonly = readonly;
+ }
+
+ @Override
+ public boolean isOpen() {
+ return !closed;
+ }
+
+ @Override
+ public long position() throws IOException {
+ beginRead();
+ try {
+ ensureOpen();
+ return pos;
+ } finally {
+ endRead();
+ }
+ }
+
+ @Override
+ public SeekableByteChannel position(long position) throws IOException {
+ beginWrite();
+ try {
+ ensureOpen();
+ if (position < 0 || position >= Integer.MAX_VALUE) {
+ throw new IllegalArgumentException("Illegal position " + position);
+ }
+ this.pos = Math.min((int) position, last);
+ return this;
+ } finally {
+ endWrite();
+ }
+ }
+
+ @Override
+ public int read(ByteBuffer dst) throws IOException {
+ beginWrite();
+ try {
+ ensureOpen();
+ if (pos == last) {
+ return -1;
+ }
+ int n = Math.min(dst.remaining(), last - pos);
+ dst.put(buf, pos, n);
+ pos += n;
+ return n;
+ } finally {
+ endWrite();
+ }
+ }
+
+ @Override
+ public SeekableByteChannel truncate(long size) throws IOException {
+ if (readonly) {
+ throw new NonWritableChannelException();
+ }
+ ensureOpen();
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int write(ByteBuffer src) throws IOException {
+ if (readonly) {
+ throw new NonWritableChannelException();
+ }
+ beginWrite();
+ try {
+ ensureOpen();
+ int n = src.remaining();
+ ensureCapacity(pos + n);
+ src.get(buf, pos, n);
+ pos += n;
+ if (pos > last) {
+ last = pos;
+ }
+ return n;
+ } finally {
+ endWrite();
+ }
+ }
+
+ @Override
+ public long size() throws IOException {
+ beginRead();
+ try {
+ ensureOpen();
+ return last;
+ } finally {
+ endRead();
+ }
+ }
+
+ @Override
+ public void close() throws IOException {
+ if (closed) {
+ return;
+ }
+
+ beginWrite();
+ try {
+ closed = true;
+ buf = null;
+ pos = 0;
+ last = 0;
+ } finally {
+ endWrite();
+ }
+ }
+
+ /**
+ * Creates a newly allocated byte array. Its size is the current size of this channel and the
+ * valid contents of the buffer have been copied into it.
+ *
+ * @return the current contents of this channel, as a byte array.
+ */
+ public byte[] toByteArray() {
+ beginRead();
+ try {
+ // avoid copy if last == bytes.length?
+ return Arrays.copyOf(buf, last);
+ } finally {
+ endRead();
+ }
+ }
+
+ private void ensureOpen() throws IOException {
+ if (closed) {
+ throw new ClosedChannelException();
+ }
+ }
+
+ private void beginWrite() {
+ rwlock.writeLock().lock();
+ }
+
+ private void endWrite() {
+ rwlock.writeLock().unlock();
+ }
+
+ private void beginRead() {
+ rwlock.readLock().lock();
+ }
+
+ private void endRead() {
+ rwlock.readLock().unlock();
+ }
+
+ private void ensureCapacity(int minCapacity) {
+ // overflow-conscious code
+ if (minCapacity - buf.length > 0) {
+ grow(minCapacity);
+ }
+ }
+
+ /**
+ * The maximum size of array to allocate. Some VMs reserve some header words in an array.
+ * Attempts to allocate larger arrays may result in OutOfMemoryError: Requested array size
+ * exceeds VM limit
+ */
+ private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
+
+ /**
+ * Increases the capacity to ensure that it can hold at least the number of elements specified
+ * by the minimum capacity argument.
+ *
+ * @param minCapacity the desired minimum capacity
+ */
+ private void grow(int minCapacity) {
+ // overflow-conscious code
+ int oldCapacity = buf.length;
+ int newCapacity = oldCapacity << 1;
+ if (newCapacity - minCapacity < 0) {
+ newCapacity = minCapacity;
+ }
+ if (newCapacity - MAX_ARRAY_SIZE > 0) {
+ newCapacity = hugeCapacity(minCapacity);
+ }
+ buf = Arrays.copyOf(buf, newCapacity);
+ }
+
+ private static int hugeCapacity(int minCapacity) {
+ if (minCapacity < 0) { // overflow
+ throw new OutOfMemoryError();
+ }
+ return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
+ }
+}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/resources/NativeImageResourceDirectoryStream.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/resources/NativeImageResourceDirectoryStream.java
new file mode 100644
index 0000000000000..bd7451b1c7095
--- /dev/null
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/resources/NativeImageResourceDirectoryStream.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2021, 2021, 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.core.jdk.resources;
+
+import java.io.IOException;
+import java.nio.file.ClosedDirectoryStreamException;
+import java.nio.file.DirectoryIteratorException;
+import java.nio.file.DirectoryStream;
+import java.nio.file.NotDirectoryException;
+import java.nio.file.Path;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+public class NativeImageResourceDirectoryStream implements DirectoryStream {
+
+ private final NativeImageResourceFileSystem fileSystem;
+ private final DirectoryStream.Filter super Path> filter;
+ private final NativeImageResourcePath dir;
+ private volatile boolean isClosed = false;
+ private Iterator directoryIterator;
+
+ public NativeImageResourceDirectoryStream(NativeImageResourcePath dir, Filter super Path> filter) throws IOException {
+ this.fileSystem = dir.getFileSystem();
+ this.dir = dir;
+ this.filter = filter;
+ if (!fileSystem.isDirectory(dir.getResolvedPath())) {
+ throw new NotDirectoryException(dir.toString());
+ }
+ }
+
+ @Override
+ public Iterator iterator() {
+ if (isClosed) {
+ throw new ClosedDirectoryStreamException();
+ }
+ if (directoryIterator != null) {
+ throw new IllegalStateException("Iterator has already been returned");
+ }
+
+ try {
+ directoryIterator = fileSystem.iteratorOf(dir, filter);
+ } catch (IOException ioException) {
+ throw new DirectoryIteratorException(ioException);
+ }
+
+ return new Iterator() {
+
+ @Override
+ public boolean hasNext() {
+ if (isClosed) {
+ return false;
+ }
+ return directoryIterator.hasNext();
+ }
+
+ @Override
+ public Path next() {
+ if (isClosed) {
+ throw new NoSuchElementException();
+ }
+ return directoryIterator.next();
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ };
+ }
+
+ @Override
+ public void close() throws IOException {
+ if (!isClosed) {
+ isClosed = true;
+ }
+ }
+}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/resources/NativeImageResourceFileAttributes.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/resources/NativeImageResourceFileAttributes.java
new file mode 100644
index 0000000000000..6d8a175d295f3
--- /dev/null
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/resources/NativeImageResourceFileAttributes.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (c) 2021, 2021, 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.core.jdk.resources;
+
+import java.nio.file.attribute.BasicFileAttributes;
+import java.nio.file.attribute.FileTime;
+import java.util.Formatter;
+
+public class NativeImageResourceFileAttributes implements BasicFileAttributes {
+
+ private final NativeImageResourceFileSystem fileSystem;
+ private final NativeImageResourceFileSystem.Entry entry;
+
+ public NativeImageResourceFileAttributes(NativeImageResourceFileSystem fileSystem, NativeImageResourceFileSystem.Entry entry) {
+ this.fileSystem = fileSystem;
+ this.entry = entry;
+ }
+
+ public String getName() {
+ return fileSystem.getString(entry.name);
+ }
+
+ @Override
+ public FileTime lastModifiedTime() {
+ return FileTime.fromMillis(entry.lastModifiedTime);
+ }
+
+ @Override
+ public FileTime lastAccessTime() {
+ return FileTime.fromMillis(entry.lastAccessTime);
+ }
+
+ @Override
+ public FileTime creationTime() {
+ return FileTime.fromMillis(entry.createTime);
+ }
+
+ @Override
+ public boolean isRegularFile() {
+ return !entry.isDirectory();
+ }
+
+ @Override
+ public boolean isDirectory() {
+ return entry.isDirectory();
+ }
+
+ @Override
+ public boolean isSymbolicLink() {
+ return false;
+ }
+
+ @Override
+ public boolean isOther() {
+ return false;
+ }
+
+ @Override
+ public long size() {
+ return entry.size();
+ }
+
+ @Override
+ public Object fileKey() {
+ return null;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder(1024);
+ Formatter fm = new Formatter(sb);
+ fm.format(" name : %s%n", getName());
+ fm.format(" creationTime : %tc%n", creationTime().toMillis());
+ fm.format(" lastAccessTime : %tc%n", lastAccessTime().toMillis());
+ fm.format(" lastModifiedTime: %tc%n", lastModifiedTime().toMillis());
+ fm.format(" isRegularFile : %b%n", isRegularFile());
+ fm.format(" isDirectory : %b%n", isDirectory());
+ fm.format(" isSymbolicLink : %b%n", isSymbolicLink());
+ fm.format(" isOther : %b%n", isOther());
+ fm.format(" fileKey : %s%n", fileKey());
+ fm.format(" size : %d%n", size());
+ fm.close();
+ return sb.toString();
+ }
+}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/resources/NativeImageResourceFileAttributesView.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/resources/NativeImageResourceFileAttributesView.java
new file mode 100644
index 0000000000000..dd06a8727922e
--- /dev/null
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/resources/NativeImageResourceFileAttributesView.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (c) 2021, 2021, 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.core.jdk.resources;
+
+import java.io.IOException;
+import java.nio.file.attribute.BasicFileAttributeView;
+import java.nio.file.attribute.FileAttributeView;
+import java.nio.file.attribute.FileTime;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+
+public class NativeImageResourceFileAttributesView implements BasicFileAttributeView {
+
+ private enum AttributeID {
+ size,
+ creationTime,
+ lastAccessTime,
+ lastModifiedTime,
+ isDirectory,
+ isRegularFile,
+ isSymbolicLink,
+ isOther,
+ fileKey;
+
+ private static final Set attributeValues = new HashSet<>();
+
+ static {
+ for (AttributeID choice : AttributeID.values()) {
+ attributeValues.add(choice.name());
+ }
+ }
+
+ public static boolean contains(String value) {
+ return attributeValues.contains(value);
+ }
+ }
+
+ private final NativeImageResourcePath path;
+ private final boolean isBasic;
+
+ public NativeImageResourceFileAttributesView(NativeImageResourcePath path, boolean isBasic) {
+ this.path = path;
+ this.isBasic = isBasic;
+ }
+
+ @SuppressWarnings("unchecked")
+ static V get(NativeImageResourcePath path, Class type) {
+ if (type == null) {
+ throw new NullPointerException();
+ }
+ if (type == BasicFileAttributeView.class) {
+ return (V) new NativeImageResourceFileAttributesView(path, true);
+ }
+ if (type == NativeImageResourceFileAttributesView.class) {
+ return (V) new NativeImageResourceFileAttributesView(path, false);
+ }
+ return null;
+ }
+
+ static NativeImageResourceFileAttributesView get(NativeImageResourcePath path, String type) {
+ if (type == null) {
+ throw new NullPointerException();
+ }
+ if (type.equals("basic")) {
+ return new NativeImageResourceFileAttributesView(path, true);
+ }
+ if (type.equals("resource")) {
+ return new NativeImageResourceFileAttributesView(path, false);
+ }
+ return null;
+ }
+
+ @Override
+ public String name() {
+ return isBasic ? "basic" : "resource";
+ }
+
+ @Override
+ public NativeImageResourceFileAttributes readAttributes() throws IOException {
+ return path.getAttributes();
+ }
+
+ public void setAttribute(String attribute, Object value) throws IOException {
+ try {
+ if (AttributeID.valueOf(attribute) == AttributeID.lastModifiedTime) {
+ setTimes((FileTime) value, null, null);
+ }
+ if (AttributeID.valueOf(attribute) == AttributeID.lastAccessTime) {
+ setTimes(null, (FileTime) value, null);
+ }
+ if (AttributeID.valueOf(attribute) == AttributeID.creationTime) {
+ setTimes(null, null, (FileTime) value);
+ }
+ } catch (IllegalArgumentException x) {
+ throw new UnsupportedOperationException("'" + attribute + "' is unknown or read-only attribute");
+ }
+ }
+
+ Map readAttributes(String attributes) throws IOException {
+ NativeImageResourceFileAttributes nativeImageResourceFileAttributes = readAttributes();
+ LinkedHashMap map = new LinkedHashMap<>();
+ if ("*".equals(attributes)) {
+ for (AttributeID id : AttributeID.values()) {
+ map.put(id.name(), attribute(id, nativeImageResourceFileAttributes));
+ }
+ } else {
+ String[] as = attributes.split(",");
+ for (String a : as) {
+ if (AttributeID.contains(a)) {
+ map.put(a, attribute(AttributeID.valueOf(a), nativeImageResourceFileAttributes));
+ }
+ }
+ }
+ return map;
+ }
+
+ @Override
+ public void setTimes(FileTime lastModifiedTime, FileTime lastAccessTime, FileTime createTime) throws IOException {
+ path.setTimes(lastModifiedTime, lastAccessTime, createTime);
+ }
+
+ Object attribute(AttributeID id, NativeImageResourceFileAttributes nativeImageResourceFileAttributes) {
+ switch (id) {
+ case size:
+ return nativeImageResourceFileAttributes.size();
+ case creationTime:
+ return nativeImageResourceFileAttributes.creationTime();
+ case lastAccessTime:
+ return nativeImageResourceFileAttributes.lastAccessTime();
+ case lastModifiedTime:
+ return nativeImageResourceFileAttributes.lastModifiedTime();
+ case isDirectory:
+ return nativeImageResourceFileAttributes.isDirectory();
+ case isRegularFile:
+ return nativeImageResourceFileAttributes.isRegularFile();
+ case isSymbolicLink:
+ return nativeImageResourceFileAttributes.isSymbolicLink();
+ case isOther:
+ return nativeImageResourceFileAttributes.isOther();
+ case fileKey:
+ return nativeImageResourceFileAttributes.fileKey();
+ }
+ return null;
+ }
+}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/resources/NativeImageResourceFileStore.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/resources/NativeImageResourceFileStore.java
new file mode 100644
index 0000000000000..8578981bd3a5c
--- /dev/null
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/resources/NativeImageResourceFileStore.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) 2021, 2021, 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.core.jdk.resources;
+
+import java.io.IOException;
+import java.nio.file.FileStore;
+import java.nio.file.FileSystem;
+import java.nio.file.Path;
+import java.nio.file.attribute.BasicFileAttributeView;
+import java.nio.file.attribute.FileAttributeView;
+import java.nio.file.attribute.FileStoreAttributeView;
+
+public class NativeImageResourceFileStore extends FileStore {
+
+ private final FileSystem resourceFileSystem;
+
+ NativeImageResourceFileStore(Path path) {
+ this.resourceFileSystem = path.getFileSystem();
+ }
+
+ @Override
+ public String name() {
+ return resourceFileSystem.toString() + resourceFileSystem.getSeparator();
+ }
+
+ @Override
+ public String type() {
+ return "rfs";
+ }
+
+ @Override
+ public boolean isReadOnly() {
+ return resourceFileSystem.isReadOnly();
+ }
+
+ @Override
+ public long getTotalSpace() {
+ throw new UnsupportedOperationException("This operation is not support for native image resource file system!");
+ }
+
+ @Override
+ public long getUsableSpace() {
+ throw new UnsupportedOperationException("This operation is not support for native image resource file system!");
+ }
+
+ @Override
+ public long getUnallocatedSpace() {
+ throw new UnsupportedOperationException("This operation is not support for native image resource file system!");
+ }
+
+ @Override
+ public boolean supportsFileAttributeView(Class extends FileAttributeView> type) {
+ return type == BasicFileAttributeView.class || type == NativeImageResourceFileAttributesView.class;
+ }
+
+ @Override
+ public boolean supportsFileAttributeView(String name) {
+ return name.equals("basic") || name.equals("resource");
+ }
+
+ @Override
+ public V getFileStoreAttributeView(Class type) {
+ if (type == null) {
+ throw new NullPointerException();
+ }
+ return null;
+ }
+
+ @Override
+ public Object getAttribute(String attribute) throws IOException {
+ if (attribute.equals("totalSpace")) {
+ return getTotalSpace();
+ }
+ if (attribute.equals("usableSpace")) {
+ return getUsableSpace();
+ }
+ if (attribute.equals("unallocatedSpace")) {
+ return getUnallocatedSpace();
+ }
+ throw new UnsupportedOperationException("Attribute isn't supported!");
+ }
+}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/resources/NativeImageResourceFileSystem.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/resources/NativeImageResourceFileSystem.java
new file mode 100644
index 0000000000000..77878fd75a232
--- /dev/null
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/resources/NativeImageResourceFileSystem.java
@@ -0,0 +1,1220 @@
+/*
+ * Copyright (c) 2021, 2021, 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.core.jdk.resources;
+
+import static com.oracle.svm.core.jdk.resources.NativeImageResourceFileSystemUtil.toRegexPattern;
+import static java.lang.Boolean.TRUE;
+import static java.nio.file.StandardCopyOption.COPY_ATTRIBUTES;
+import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
+import static java.nio.file.StandardOpenOption.APPEND;
+import static java.nio.file.StandardOpenOption.CREATE;
+import static java.nio.file.StandardOpenOption.CREATE_NEW;
+import static java.nio.file.StandardOpenOption.READ;
+import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.nio.MappedByteBuffer;
+import java.nio.channels.FileChannel;
+import java.nio.channels.FileLock;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.channels.SeekableByteChannel;
+import java.nio.channels.WritableByteChannel;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.ClosedFileSystemException;
+import java.nio.file.CopyOption;
+import java.nio.file.DirectoryNotEmptyException;
+import java.nio.file.DirectoryStream;
+import java.nio.file.FileAlreadyExistsException;
+import java.nio.file.FileStore;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystemException;
+import java.nio.file.FileSystemNotFoundException;
+import java.nio.file.Files;
+import java.nio.file.NoSuchFileException;
+import java.nio.file.NotDirectoryException;
+import java.nio.file.OpenOption;
+import java.nio.file.Path;
+import java.nio.file.PathMatcher;
+import java.nio.file.StandardOpenOption;
+import java.nio.file.WatchService;
+import java.nio.file.attribute.FileAttribute;
+import java.nio.file.attribute.FileTime;
+import java.nio.file.attribute.UserPrincipalLookupService;
+import java.nio.file.spi.FileSystemProvider;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+import java.util.regex.Pattern;
+
+import org.graalvm.collections.MapCursor;
+
+/**
+ *
+ * Some parts of code from this class are copied from jdk.nio.zipfs.ZipFileSystem with small tweaks.
+ * The main reason why we cannot reuse this class (without any code copying) is that we cannot
+ * extend jdk.nio.zipfs.ZipPath which is the central class in the Zip file system.
+ *
+ */
+public class NativeImageResourceFileSystem extends FileSystem {
+
+ private static final String GLOB_SYNTAX = "glob";
+ private static final String REGEX_SYNTAX = "regex";
+
+ private static final int DEFAULT_BUFFER_SIZE = 8192;
+
+ private static final Set supportedFileAttributeViews = Collections.unmodifiableSet(
+ new HashSet<>(Arrays.asList("basic", "resource")));
+
+ private final Set inputStreams = Collections.synchronizedSet(new HashSet<>());
+ private final Set outputStreams = Collections.synchronizedSet(new HashSet<>());
+ private final Set tmpPaths = Collections.synchronizedSet(new HashSet<>());
+
+ private final long defaultTimestamp = System.currentTimeMillis();
+
+ private final NativeImageResourceFileSystemProvider provider;
+ private final Path resourcePath;
+ private final NativeImageResourcePath root;
+ private boolean isOpen = true;
+ private final ReadWriteLock rwlock = new ReentrantReadWriteLock();
+
+ private static final byte[] ROOT_PATH = new byte[]{'/'};
+ private final IndexNode lookupKey = new IndexNode(null, true);
+ private final LinkedHashMap inodes = new LinkedHashMap<>(10);
+
+ public NativeImageResourceFileSystem(NativeImageResourceFileSystemProvider provider, Path resourcePath, Map env) {
+ this.provider = provider;
+ this.resourcePath = resourcePath;
+ this.root = new NativeImageResourcePath(this, new byte[]{'/'});
+ if (!isTrue(env)) {
+ throw new FileSystemNotFoundException(resourcePath.toString());
+ }
+ readAllEntries();
+ }
+
+ // Returns true if there is a name=true/"true" setting in env.
+ private static boolean isTrue(Map env) {
+ return "true".equals(env.get("create")) || TRUE.equals(env.get("create"));
+ }
+
+ private void ensureOpen() {
+ if (!isOpen) {
+ throw new ClosedFileSystemException();
+ }
+ }
+
+ private void beginWrite() {
+ rwlock.writeLock().lock();
+ }
+
+ private void endWrite() {
+ rwlock.writeLock().unlock();
+ }
+
+ private void beginRead() {
+ rwlock.readLock().lock();
+ }
+
+ private void endRead() {
+ rwlock.readLock().unlock();
+ }
+
+ byte[] getBytes(String path) {
+ return path.getBytes(StandardCharsets.UTF_8);
+ }
+
+ String getString(byte[] path) {
+ return new String(path, StandardCharsets.UTF_8);
+ }
+
+ @Override
+ public FileSystemProvider provider() {
+ return provider;
+ }
+
+ @Override
+ public void close() throws IOException {
+ beginWrite();
+ try {
+ if (!isOpen) {
+ return;
+ }
+ isOpen = false;
+ } finally {
+ endWrite();
+ }
+
+ if (!inputStreams.isEmpty()) { // Unlock and close all remaining input streams.
+ Set copy = new HashSet<>(inputStreams);
+ for (InputStream is : copy) {
+ is.close();
+ }
+ }
+
+ if (!outputStreams.isEmpty()) { // Unlock and close all remaining output streams.
+ Set copy = new HashSet<>(outputStreams);
+ for (OutputStream os : copy) {
+ os.close();
+ }
+ }
+
+ provider.removeFileSystem();
+ }
+
+ @Override
+ public boolean isOpen() {
+ return isOpen;
+ }
+
+ @Override
+ public boolean isReadOnly() {
+ return false;
+ }
+
+ @Override
+ public String getSeparator() {
+ return "/";
+ }
+
+ @Override
+ public Iterable getRootDirectories() {
+ return Collections.singleton(root);
+ }
+
+ @Override
+ public Iterable getFileStores() {
+ return Collections.singleton(new NativeImageResourceFileStore(root));
+ }
+
+ @Override
+ public Set supportedFileAttributeViews() {
+ return supportedFileAttributeViews;
+ }
+
+ Path getResourcePath() {
+ return resourcePath;
+ }
+
+ @Override
+ public String toString() {
+ return resourcePath.toString();
+ }
+
+ @Override
+ public Path getPath(String first, String... more) {
+ String path;
+ if (more.length == 0) {
+ path = first;
+ } else {
+ StringBuilder sb = new StringBuilder();
+ sb.append(first);
+ for (String segment : more) {
+ if (segment.length() > 0) {
+ if (sb.length() > 0) {
+ sb.append('/');
+ }
+ sb.append(segment);
+ }
+ }
+ path = sb.toString();
+ }
+ return new NativeImageResourcePath(this, getBytes(path));
+ }
+
+ @Override
+ public PathMatcher getPathMatcher(String syntaxAndPattern) {
+ int pos = syntaxAndPattern.indexOf(':');
+ if (pos <= 0 || pos == syntaxAndPattern.length()) {
+ throw new IllegalArgumentException();
+ }
+ String syntax = syntaxAndPattern.substring(0, pos);
+ String input = syntaxAndPattern.substring(pos + 1);
+ String expr;
+ if (syntax.equals(GLOB_SYNTAX)) {
+ expr = toRegexPattern(input);
+ } else {
+ if (syntax.equals(REGEX_SYNTAX)) {
+ expr = input;
+ } else {
+ throw new UnsupportedOperationException("Syntax '" + syntax + "' not recognized");
+ }
+ }
+
+ Pattern pattern = Pattern.compile(expr);
+ return path -> pattern.matcher(path.toString()).matches();
+ }
+
+ @Override
+ public UserPrincipalLookupService getUserPrincipalLookupService() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public WatchService newWatchService() {
+ throw new UnsupportedOperationException();
+ }
+
+ NativeImageResourceFileAttributes getFileAttributes(byte[] path) {
+ Entry entry;
+ beginRead();
+ try {
+ ensureOpen();
+ entry = getEntry(path);
+ if (entry == null) {
+ IndexNode inode = getInode(path);
+ if (inode == null) {
+ return null;
+ }
+ entry = new Entry(inode.name, inode.isDir);
+ entry.lastModifiedTime = entry.lastAccessTime = entry.createTime = defaultTimestamp;
+ }
+ } finally {
+ endRead();
+ }
+ return new NativeImageResourceFileAttributes(this, entry);
+ }
+
+ static void checkOptions(Set extends OpenOption> options) {
+ for (OpenOption option : options) {
+ if (option == null) {
+ throw new NullPointerException();
+ }
+ if (!(option instanceof StandardOpenOption)) {
+ throw new IllegalArgumentException();
+ }
+ }
+ if (options.contains(APPEND) && options.contains(TRUNCATE_EXISTING)) {
+ throw new IllegalArgumentException("APPEND + TRUNCATE_EXISTING are not allowed!");
+ }
+ }
+
+ SeekableByteChannel newByteChannel(byte[] path, Set extends OpenOption> options) throws IOException {
+ checkOptions(options);
+ if (options.contains(StandardOpenOption.WRITE) || options.contains(StandardOpenOption.APPEND)) {
+ beginRead(); // Only need a read lock, the "update()" will obtain the write lock when
+ // the channel is closed.
+ try {
+ Entry e = getEntry(path);
+ if (e != null) {
+ if (e.isDir() || options.contains(CREATE_NEW)) {
+ throw new FileAlreadyExistsException(getString(path));
+ }
+ SeekableByteChannel sbc = new EntryOutputChannel(new Entry(e, Entry.NEW));
+ if (options.contains(APPEND)) {
+ try (InputStream is = getInputStream(e)) {
+ sbc.write(ByteBuffer.wrap(NativeImageResourceFileSystemUtil.inputStreamToByteArray(is)));
+ }
+ }
+ return sbc;
+ }
+ if (!options.contains(CREATE) && !options.contains(CREATE_NEW)) {
+ throw new NoSuchFileException(getString(path));
+ }
+ checkParents(path);
+ return new EntryOutputChannel(new Entry(path, false));
+ } finally {
+ endRead();
+ }
+ } else {
+ beginRead();
+ try {
+ ensureOpen();
+ Entry e = getEntry(path);
+ if (e == null || e.isDir()) {
+ throw new NoSuchFileException(getString(path));
+ }
+ try (InputStream is = getInputStream(e)) {
+ return new ByteArrayChannel(NativeImageResourceFileSystemUtil.inputStreamToByteArray(is), true);
+ }
+ } finally {
+ endRead();
+ }
+ }
+ }
+
+ boolean exists(byte[] path) {
+ beginRead();
+ try {
+ ensureOpen();
+ return getInode(path) != null;
+ } finally {
+ endRead();
+ }
+ }
+
+ static FileStore getFileStore(NativeImageResourcePath path) {
+ return new NativeImageResourceFileStore(path);
+ }
+
+ void checkAccess(byte[] path) throws NoSuchFileException {
+ beginRead();
+ try {
+ ensureOpen();
+ if (getInode(path) == null) {
+ throw new NoSuchFileException(toString());
+ }
+ } finally {
+ endRead();
+ }
+ }
+
+ void setTimes(byte[] path, FileTime lastModifiedTime, FileTime lastAccessTime, FileTime createTime) throws NoSuchFileException {
+ beginWrite();
+ try {
+ ensureOpen();
+ Entry e = getEntry(path);
+ if (e == null) {
+ throw new NoSuchFileException(getString(path));
+ }
+ if (lastModifiedTime != null) {
+ e.lastModifiedTime = lastModifiedTime.toMillis();
+ }
+ if (lastAccessTime != null) {
+ e.lastAccessTime = lastAccessTime.toMillis();
+ }
+ if (createTime != null) {
+ e.createTime = createTime.toMillis();
+ }
+ update(e);
+ } finally {
+ endWrite();
+ }
+ }
+
+ boolean isDirectory(byte[] path) {
+ beginRead();
+ try {
+ IndexNode n = getInode(path);
+ return n != null && n.isDir();
+ } finally {
+ endRead();
+ }
+ }
+
+ void createDirectory(byte[] dir) throws IOException {
+ beginWrite();
+ try {
+ ensureOpen();
+ if (dir.length == 0 || exists(dir)) {
+ throw new FileAlreadyExistsException(getString(dir));
+ }
+ checkParents(dir);
+ Entry e = new Entry(dir, true);
+ update(e);
+ } finally {
+ endWrite();
+ }
+ }
+
+ boolean deleteFile(byte[] path, boolean failIfNotExists) throws IOException {
+ IndexNode inode = getInode(path);
+ if (inode == null) {
+ if (path.length == 0) {
+ throw new NativeImageResourceFileSystemException("Root directory > can't be deleted!");
+ }
+ if (failIfNotExists) {
+ throw new NoSuchFileException(getString(path));
+ } else {
+ return false;
+ }
+ } else {
+ if (inode.isDir() && inode.child != null) {
+ throw new DirectoryNotEmptyException(getString(path));
+ }
+ updateDelete(inode);
+ }
+ return true;
+ }
+
+ private Path createTempFileInSameDirectoryAs() throws IOException {
+ Path tmpPath = Files.createTempFile("rfs_tmp", null);
+ tmpPaths.add(tmpPath);
+ return tmpPath;
+ }
+
+ private Path getTempPathForEntry(byte[] path) throws IOException {
+ Path tmpPath = createTempFileInSameDirectoryAs();
+ if (path != null) {
+ Entry e = getEntry(path);
+ if (e != null) {
+ try (InputStream is = newInputStream(path)) {
+ Files.copy(is, tmpPath, REPLACE_EXISTING);
+ }
+ }
+ }
+
+ return tmpPath;
+ }
+
+ private void removeTempPathForEntry(Path path) throws IOException {
+ Files.delete(path);
+ tmpPaths.remove(path);
+ }
+
+ void copyFile(boolean deleteSource, byte[] src, byte[] dst, CopyOption[] options) throws IOException {
+ if (Arrays.equals(src, dst)) {
+ return; // Do nothing, src and dst are the same.
+ }
+
+ beginWrite();
+ try {
+ ensureOpen();
+ Entry eSrc = getEntry(src); // ensureOpen checked
+
+ if (eSrc == null) {
+ throw new NoSuchFileException(getString(src));
+ }
+ if (eSrc.isDir()) { // spec says to create dst dir
+ createDirectory(dst);
+ return;
+ }
+ boolean hasReplace = false;
+ boolean hasCopyAttrs = false;
+ for (CopyOption opt : options) {
+ if (opt == REPLACE_EXISTING) {
+ hasReplace = true;
+ } else if (opt == COPY_ATTRIBUTES) {
+ hasCopyAttrs = true;
+ }
+ }
+ Entry eDst = getEntry(dst);
+ if (eDst != null) {
+ if (!hasReplace) {
+ throw new FileAlreadyExistsException(getString(dst));
+ }
+ } else {
+ checkParents(dst);
+ }
+ Entry target = new Entry(eSrc, Entry.COPY); // Copy eSrc entry.
+ target.name(dst); // Change name.
+ if (eSrc.type == Entry.NEW || eSrc.type == Entry.FILE_CH) {
+ target.type = eSrc.type; // Make it the same type.
+ if (deleteSource) { // If it's a "rename", take the data
+ target.bytes = eSrc.bytes;
+ target.file = eSrc.file;
+ } else { // If it's not "rename", copy the data.
+ if (eSrc.bytes != null) {
+ target.bytes = Arrays.copyOf(eSrc.bytes, eSrc.bytes.length);
+ } else if (eSrc.file != null) {
+ target.file = getTempPathForEntry(null);
+ Files.copy(eSrc.file, target.file, REPLACE_EXISTING);
+ }
+ }
+ }
+ if (!hasCopyAttrs) {
+ target.lastModifiedTime = target.lastAccessTime = target.createTime = System.currentTimeMillis();
+ }
+ update(target);
+ if (deleteSource) {
+ updateDelete(eSrc);
+ }
+ } finally {
+ endWrite();
+ }
+ }
+
+ private void checkParents(byte[] pathBytes) throws IOException {
+ beginRead();
+ try {
+ byte[] path = pathBytes;
+ while ((path = getParent(path)) != null && path != ROOT_PATH) {
+ if (!inodes.containsKey(IndexNode.keyOf(path))) {
+ throw new NoSuchFileException(getString(path));
+ }
+ }
+ } finally {
+ endRead();
+ }
+ }
+
+ IndexNode getInode(byte[] path) {
+ if (path == null) {
+ throw new NullPointerException("Path is null!");
+ }
+ return inodes.get(IndexNode.keyOf(path));
+ }
+
+ Entry getEntry(byte[] path) {
+ IndexNode inode = getInode(path);
+ if (inode instanceof Entry) {
+ return (Entry) inode;
+ }
+ if (inode == null) {
+ return null;
+ }
+ return new Entry(inode.name, inode.isDir);
+ }
+
+ static byte[] getParent(byte[] path) {
+ int off = getParentOff(path);
+ if (off <= 1) {
+ return ROOT_PATH;
+ }
+ return Arrays.copyOf(path, off);
+ }
+
+ private static int getParentOff(byte[] path) {
+ int off = path.length - 1;
+ if (off > 0 && path[off] == '/') {
+ off--;
+ }
+ while (off > 0 && path[off] != '/') {
+ off--;
+ }
+ return off;
+ }
+
+ private void removeFromTree(IndexNode inode) {
+ IndexNode parent = inodes.get(lookupKey.as(getParent(inode.name)));
+ IndexNode child = parent.child;
+ if (child.equals(inode)) {
+ parent.child = child.sibling;
+ } else {
+ IndexNode last = child;
+ while ((child = child.sibling) != null) {
+ if (child.equals(inode)) {
+ last.sibling = child.sibling;
+ break;
+ } else {
+ last = child;
+ }
+ }
+ }
+ }
+
+ private void updateDelete(IndexNode inode) {
+ beginWrite();
+ try {
+ removeFromTree(inode);
+ inodes.remove(inode);
+ } finally {
+ endWrite();
+ }
+ }
+
+ private void update(Entry e) {
+ beginWrite();
+ try {
+ IndexNode old = inodes.put(e, e);
+ if (old != null) {
+ removeFromTree(old);
+ }
+ if (e.type == Entry.NEW || e.type == Entry.FILE_CH || e.type == Entry.COPY) {
+ IndexNode parent = inodes.get(lookupKey.as(getParent(e.name)));
+ e.sibling = parent.child;
+ parent.child = e;
+ }
+ } finally {
+ endWrite();
+ }
+ }
+
+ private void readAllEntries() {
+ MapCursor entries = NativeImageResourceFileSystemUtil.iterator();
+ while (entries.advance()) {
+ byte[] name = getBytes(entries.getKey());
+ if (!entries.getValue().isDirectory()) {
+ IndexNode newIndexNode = new IndexNode(name, false);
+ inodes.put(newIndexNode, newIndexNode);
+ }
+ }
+ buildNodeTree();
+ }
+
+ private void buildNodeTree() {
+ beginWrite();
+ try {
+ IndexNode rootIndex = inodes.get(lookupKey.as(ROOT_PATH));
+ if (rootIndex == null) {
+ rootIndex = new IndexNode(ROOT_PATH, true);
+ } else {
+ inodes.remove(rootIndex);
+ }
+ IndexNode[] nodes = inodes.keySet().toArray(new IndexNode[0]);
+ inodes.put(rootIndex, rootIndex);
+ ParentLookup lookup = new ParentLookup();
+ for (IndexNode controlNode : nodes) {
+ IndexNode parent;
+ IndexNode node = controlNode;
+ while (true) {
+ int off = getParentOff(node.name);
+ if (off <= 1) { // Parent is root.
+ node.sibling = rootIndex.child;
+ rootIndex.child = node;
+ break;
+ }
+ lookup = lookup.as(node.name, off);
+ if (inodes.containsKey(lookup)) {
+ parent = inodes.get(lookup);
+ node.sibling = parent.child;
+ parent.child = node;
+ break;
+ }
+ // Add new pseudo directory entry.
+ parent = new IndexNode(Arrays.copyOf(node.name, off), true);
+ inodes.put(parent, parent);
+ node.sibling = parent.child;
+ parent.child = node;
+ node = parent;
+ }
+ }
+ } finally {
+ endWrite();
+ }
+ }
+
+ private InputStream getInputStream(Entry e) throws IOException {
+ InputStream eis = null;
+ if (e.type == Entry.NEW || e.type == Entry.COPY) {
+ byte[] bytes = e.getBytes(true);
+ if (bytes != null) {
+ eis = new ByteArrayInputStream(bytes);
+ } else {
+ if (e.file != null) {
+ eis = Files.newInputStream(e.file);
+ } else {
+ throw new NativeImageResourceFileSystemException("Entry data is missing!");
+ }
+ }
+ } else {
+ if (e.type == Entry.FILE_CH) {
+ eis = Files.newInputStream(e.file);
+ return eis;
+ }
+ }
+ inputStreams.add(eis);
+ return eis;
+ }
+
+ private OutputStream getOutputStream(Entry e) {
+ e.getBytes(false);
+ if (e.lastModifiedTime == -1) {
+ e.lastModifiedTime = System.currentTimeMillis();
+ }
+ OutputStream os = new EntryOutputStream(e, new ByteArrayOutputStream((e.size > 0) ? e.size : DEFAULT_BUFFER_SIZE));
+ outputStreams.add(os);
+ return os;
+ }
+
+ InputStream newInputStream(byte[] path) throws IOException {
+ beginRead();
+ try {
+ ensureOpen();
+ Entry entry = getEntry(path);
+ if (entry == null) {
+ throw new NoSuchFileException(getString(path));
+ }
+ if (entry.isDir()) {
+ throw new FileSystemException(getString(path), "is a directory", null);
+ }
+ return getInputStream(entry);
+ } finally {
+ endRead();
+ }
+ }
+
+ OutputStream newOutputStream(byte[] path, OpenOption... options) throws IOException {
+ boolean hasCreateNew = false;
+ boolean hasCreate = false;
+ boolean hasAppend = false;
+ boolean hasTruncate = false;
+ for (OpenOption opt : options) {
+ if (opt == READ) {
+ throw new IllegalArgumentException("READ not allowed!");
+ }
+ if (opt == CREATE_NEW) {
+ hasCreateNew = true;
+ }
+ if (opt == CREATE) {
+ hasCreate = true;
+ }
+ if (opt == APPEND) {
+ hasAppend = true;
+ }
+ if (opt == TRUNCATE_EXISTING) {
+ hasTruncate = true;
+ }
+ }
+
+ if (hasAppend && hasTruncate) {
+ throw new IllegalArgumentException("APPEND + TRUNCATE_EXISTING are not allowed!");
+ }
+
+ beginRead(); // Only need a read lock, the update will
+ try { // try to obtain a write lock when the os is
+ ensureOpen(); // being closed.
+ Entry e = getEntry(path);
+ if (e != null) {
+ if (e.isDir() || hasCreateNew) {
+ throw new FileAlreadyExistsException(getString(path));
+ }
+ if (hasAppend) {
+ try (InputStream is = getInputStream(e)) {
+ OutputStream os = getOutputStream(new Entry(e, Entry.NEW));
+ byte[] bytes = NativeImageResourceFileSystemUtil.inputStreamToByteArray(is);
+ os.write(bytes, 0, bytes.length);
+ return os;
+ }
+ }
+ return getOutputStream(new Entry(e, Entry.NEW));
+ } else {
+ if (!hasCreate && !hasCreateNew) {
+ throw new NoSuchFileException(getString(path));
+ }
+ checkParents(path);
+ return getOutputStream(new Entry(path, false));
+ }
+ } finally {
+ endRead();
+ }
+ }
+
+ FileChannel newFileChannel(byte[] path, Set extends OpenOption> options, FileAttribute>... attrs) throws IOException {
+ checkOptions(options);
+ boolean forWrite = (options.contains(StandardOpenOption.WRITE) || options.contains(StandardOpenOption.APPEND));
+ beginRead();
+ try {
+ ensureOpen();
+ Entry e = getEntry(path);
+ if (forWrite) {
+ if (e == null) {
+ if (!options.contains(StandardOpenOption.CREATE) && !options.contains(StandardOpenOption.CREATE_NEW)) {
+ throw new NoSuchFileException(getString(path));
+ }
+ } else {
+ if (options.contains(StandardOpenOption.CREATE_NEW)) {
+ throw new FileAlreadyExistsException(getString(path));
+ }
+ if (e.isDir()) {
+ throw new FileAlreadyExistsException("Directory <" + getString(path) + "> exists!");
+ }
+ }
+ } else if (e == null || e.isDir()) {
+ throw new NoSuchFileException(getString(path));
+ }
+
+ final boolean isFCH = (e != null && e.type == Entry.FILE_CH);
+ final Path tmpFile = isFCH ? e.file : getTempPathForEntry(path);
+ final FileChannel fch = tmpFile.getFileSystem().provider().newFileChannel(tmpFile, options, attrs);
+ final Entry target = isFCH ? e : new Entry(path, tmpFile, Entry.FILE_CH);
+ return new FileChannel() {
+
+ @Override
+ public int write(ByteBuffer src) throws IOException {
+ return fch.write(src);
+ }
+
+ @Override
+ public long write(ByteBuffer[] src, int offset, int length) throws IOException {
+ return fch.write(src, offset, length);
+ }
+
+ @Override
+ public long position() throws IOException {
+ return fch.position();
+ }
+
+ @Override
+ public FileChannel position(long newPosition) throws IOException {
+ fch.position(newPosition);
+ return this;
+ }
+
+ @Override
+ public long size() throws IOException {
+ return fch.size();
+ }
+
+ @Override
+ public FileChannel truncate(long size) throws IOException {
+ fch.truncate(size);
+ return this;
+ }
+
+ @Override
+ public void force(boolean metaData) throws IOException {
+ fch.force(metaData);
+ }
+
+ @Override
+ public long transferTo(long position, long count, WritableByteChannel byteChannel)
+ throws IOException {
+
+ return fch.transferTo(position, count, byteChannel);
+ }
+
+ @Override
+ public long transferFrom(ReadableByteChannel src, long position, long count) throws IOException {
+ return fch.transferFrom(src, position, count);
+ }
+
+ @Override
+ public int read(ByteBuffer dst) throws IOException {
+ return fch.read(dst);
+ }
+
+ @Override
+ public int read(ByteBuffer dst, long position) throws IOException {
+ return fch.read(dst, position);
+ }
+
+ @Override
+ public long read(ByteBuffer[] dst, int offset, int length) throws IOException {
+ return fch.read(dst, offset, length);
+ }
+
+ @Override
+ public int write(ByteBuffer src, long position) throws IOException {
+ return fch.write(src, position);
+ }
+
+ @Override
+ public MappedByteBuffer map(MapMode mode, long position, long size) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public FileLock lock(long position, long size, boolean shared) throws IOException {
+ return fch.lock(position, size, shared);
+ }
+
+ @Override
+ public FileLock tryLock(long position, long size, boolean shared) throws IOException {
+ return fch.tryLock(position, size, shared);
+ }
+
+ @Override
+ protected void implCloseChannel() throws IOException {
+ fch.close();
+ if (forWrite) {
+ target.lastModifiedTime = System.currentTimeMillis();
+ target.size = (int) Files.size(target.file);
+
+ update(target);
+ } else {
+ if (!isFCH) {
+ removeTempPathForEntry(tmpFile);
+ }
+ }
+ }
+ };
+ } finally {
+ endRead();
+ }
+ }
+
+ Iterator iteratorOf(NativeImageResourcePath dir, DirectoryStream.Filter super Path> filter) throws IOException {
+ beginWrite();
+ try {
+ ensureOpen();
+ byte[] path = dir.getResolvedPath();
+ IndexNode inode = getInode(path);
+ if (inode == null) {
+ throw new NotDirectoryException(getString(path));
+ }
+ List list = new ArrayList<>();
+ IndexNode child = inode.child;
+ while (child != null) {
+ byte[] childName = child.name;
+ NativeImageResourcePath childPath = new NativeImageResourcePath(this, childName, true);
+ Path childFileName = childPath.getFileName();
+ Path dirPath = null;
+ if (childFileName != null) {
+ dirPath = dir.resolve(childFileName);
+ }
+ if (filter == null || (dirPath != null && filter.accept(dirPath))) {
+ list.add(dirPath);
+ }
+ child = child.sibling;
+ }
+ return list.iterator();
+ } finally {
+ endWrite();
+ }
+ }
+
+ private static class IndexNode {
+
+ private static final ThreadLocal cachedKey = new ThreadLocal<>();
+
+ byte[] name;
+ int hashcode;
+ boolean isDir;
+
+ IndexNode child;
+ IndexNode sibling;
+
+ IndexNode() {
+ }
+
+ IndexNode(byte[] n) {
+ name(n);
+ }
+
+ IndexNode(byte[] n, boolean isDir) {
+ name(n);
+ this.isDir = isDir;
+ }
+
+ static IndexNode keyOf(byte[] n) {
+ IndexNode key = cachedKey.get();
+ if (key == null) {
+ key = new IndexNode(n);
+ cachedKey.set(key);
+ }
+ return key.as(n);
+ }
+
+ final void name(byte[] n) {
+ this.name = n;
+ this.hashcode = Arrays.hashCode(n);
+ }
+
+ final IndexNode as(byte[] n) {
+ name(n);
+ return this;
+ }
+
+ boolean isDir() {
+ return isDir;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (!(other instanceof IndexNode)) {
+ return false;
+ }
+ if (other instanceof ParentLookup) {
+ return other.equals(this);
+ }
+ return Arrays.equals(name, ((IndexNode) other).name);
+ }
+
+ @Override
+ public int hashCode() {
+ return hashcode;
+ }
+ }
+
+ // For parent lookup, so we don't have to copy the parent name every time.
+ private static class ParentLookup extends IndexNode {
+
+ private int length;
+
+ ParentLookup() {
+ }
+
+ ParentLookup as(byte[] n, int len) {
+ name(n, len);
+ return this;
+ }
+
+ void name(byte[] n, int len) {
+ this.name = n;
+ this.length = len;
+ int result = 1;
+ for (int i = 0; i < len; i++) {
+ result = 31 * result + n[i];
+ }
+ this.hashcode = result;
+ }
+
+ boolean isEquals(byte[] name1, int endIndex1, byte[] name2, int endIndex2) {
+ if (name1.length < endIndex1 || name2.length < endIndex2) {
+ return false;
+ }
+ int lenToCmp = Math.min(endIndex1, endIndex2);
+ for (int index = 0; index < lenToCmp; index++) {
+ if (name1[index] != name2[index]) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (!(other instanceof IndexNode)) {
+ return false;
+ }
+ byte[] otherName = ((IndexNode) other).name;
+ return isEquals(name, length, otherName, otherName.length);
+ }
+ }
+
+ class Entry extends IndexNode {
+
+ private static final int NEW = 1; // Updated contents in bytes or file.
+ private static final int FILE_CH = 2; // File channel update in file.
+ private static final int COPY = 3; // Copy entry.
+
+ public int size;
+ public int type;
+ public long lastModifiedTime;
+ public long lastAccessTime;
+ public long createTime;
+
+ private boolean copyOnWrite;
+ private byte[] bytes;
+ public Path file;
+
+ Entry(byte[] name, Path file, int type) {
+ this(name, type, false);
+ this.file = file;
+ }
+
+ void initTimes() {
+ this.lastModifiedTime = this.lastAccessTime = this.createTime = System.currentTimeMillis();
+ }
+
+ void initData() {
+ this.bytes = NativeImageResourceFileSystemUtil.getBytes(getString(name), true);
+ this.size = !isDir ? this.bytes.length : 0;
+ }
+
+ byte[] getBytes(boolean readOnly) {
+ if (!readOnly) {
+ // Copy On Write technique.
+ if (!copyOnWrite) {
+ copyOnWrite = true;
+ this.bytes = NativeImageResourceFileSystemUtil.getBytes(getString(name), false);
+ }
+ }
+ return this.bytes;
+ }
+
+ Entry(byte[] name, boolean isDir) {
+ name(name);
+ this.type = Entry.NEW;
+ this.isDir = isDir;
+ initData();
+ initTimes();
+ }
+
+ Entry(byte[] name, int type, boolean isDir) {
+ name(name);
+ this.type = type;
+ this.isDir = isDir;
+ initData();
+ initTimes();
+ }
+
+ Entry(Entry other, int type) {
+ name(other.name);
+ this.lastModifiedTime = other.lastModifiedTime;
+ this.lastAccessTime = other.lastAccessTime;
+ this.createTime = other.createTime;
+ this.isDir = other.isDir;
+ this.size = other.size;
+ this.bytes = other.bytes;
+ this.type = type;
+ this.copyOnWrite = true;
+ }
+
+ boolean isDirectory() {
+ return isDir;
+ }
+
+ long size() {
+ return size;
+ }
+ }
+
+ class EntryOutputChannel extends ByteArrayChannel {
+
+ Entry e;
+
+ EntryOutputChannel(Entry e) {
+ super(e.size > 0 ? e.size : DEFAULT_BUFFER_SIZE, false);
+ this.e = e;
+ if (e.lastModifiedTime == -1) {
+ e.lastModifiedTime = System.currentTimeMillis();
+ }
+ }
+
+ @Override
+ public void close() throws IOException {
+ // This will update entry.
+ try (OutputStream os = getOutputStream(e)) {
+ os.write(toByteArray());
+ }
+ super.close();
+ }
+ }
+
+ private class EntryOutputStream extends FilterOutputStream {
+ private final Entry e;
+ private int written;
+ private boolean isClosed;
+
+ EntryOutputStream(Entry e, OutputStream os) {
+ super(os);
+ this.e = Objects.requireNonNull(e, "Entry is null!");
+ }
+
+ // Checkstyle: stop
+ @Override
+ public synchronized void write(int b) throws IOException {
+ out.write(b);
+ written += 1;
+ }
+
+ @Override
+ public synchronized void write(byte[] b, int off, int len) throws IOException {
+ out.write(b, off, len);
+ written += len;
+ }
+
+ @Override
+ public synchronized void close() throws IOException {
+ if (isClosed) {
+ return;
+ }
+ isClosed = true;
+ e.size = written;
+ if (out instanceof ByteArrayOutputStream) {
+ e.bytes = ((ByteArrayOutputStream) out).toByteArray();
+ }
+ super.close();
+ update(e);
+ }
+ // Checkstyle: resume
+ }
+}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/resources/NativeImageResourceFileSystemException.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/resources/NativeImageResourceFileSystemException.java
new file mode 100644
index 0000000000000..d17553befd054
--- /dev/null
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/resources/NativeImageResourceFileSystemException.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2021, 2021, 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.core.jdk.resources;
+
+import java.io.IOException;
+
+public class NativeImageResourceFileSystemException extends IOException {
+ private static final long serialVersionUID = 8000196834066748623L;
+
+ public NativeImageResourceFileSystemException(String message) {
+ super(message);
+ }
+}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/resources/NativeImageResourceFileSystemProvider.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/resources/NativeImageResourceFileSystemProvider.java
new file mode 100644
index 0000000000000..883f56643fbdb
--- /dev/null
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/resources/NativeImageResourceFileSystemProvider.java
@@ -0,0 +1,250 @@
+/*
+ * Copyright (c) 2021, 2021, 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.core.jdk.resources;
+
+import com.oracle.svm.core.jdk.JavaNetSubstitutions;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.channels.FileChannel;
+import java.nio.channels.SeekableByteChannel;
+import java.nio.file.AccessMode;
+import java.nio.file.CopyOption;
+import java.nio.file.DirectoryStream;
+import java.nio.file.FileStore;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystemAlreadyExistsException;
+import java.nio.file.FileSystemNotFoundException;
+import java.nio.file.LinkOption;
+import java.nio.file.OpenOption;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.ProviderMismatchException;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.nio.file.attribute.FileAttribute;
+import java.nio.file.attribute.FileAttributeView;
+import java.nio.file.spi.FileSystemProvider;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+public class NativeImageResourceFileSystemProvider extends FileSystemProvider {
+
+ private final String resourcePath = "file:/resources";
+ private NativeImageResourceFileSystem fileSystem;
+ private final Lock writeLock;
+ private final Lock readLock;
+
+ private Path uriToPath(URI uri) {
+ String scheme = uri.getScheme();
+ if ((scheme == null) || !scheme.equalsIgnoreCase(getScheme())) {
+ throw new IllegalArgumentException("URI scheme is not '" + getScheme() + "'");
+ }
+
+ try {
+ // Syntax for URI resource is resource:file:resources!/{entry}
+ return Paths.get(new URI(resourcePath));
+ } catch (URISyntaxException e) {
+ throw new IllegalArgumentException(e.getMessage(), e);
+ }
+ }
+
+ public NativeImageResourceFileSystemProvider() {
+ ReadWriteLock rwlock = new ReentrantReadWriteLock();
+ this.writeLock = rwlock.writeLock();
+ this.readLock = rwlock.readLock();
+ }
+
+ private static NativeImageResourcePath toResourcePath(Path path) {
+ if (path == null) {
+ throw new NullPointerException();
+ }
+ if (!(path instanceof NativeImageResourcePath)) {
+ throw new ProviderMismatchException();
+ }
+ return (NativeImageResourcePath) path;
+ }
+
+ @Override
+ public String getScheme() {
+ return JavaNetSubstitutions.RESOURCE_PROTOCOL;
+ }
+
+ @Override
+ public FileSystem newFileSystem(URI uri, Map env) {
+ try {
+ writeLock.lock();
+ Path path = uriToPath(uri);
+ if (fileSystem != null) {
+ throw new FileSystemAlreadyExistsException();
+ }
+ fileSystem = new NativeImageResourceFileSystem(this, path, env);
+ return fileSystem;
+ } finally {
+ writeLock.unlock();
+ }
+ }
+
+ @Override
+ public FileSystem newFileSystem(Path path, Map env) {
+ try {
+ writeLock.lock();
+ if (fileSystem != null) {
+ throw new FileSystemAlreadyExistsException();
+ }
+ fileSystem = new NativeImageResourceFileSystem(this, path, env);
+ return fileSystem;
+ } finally {
+ writeLock.unlock();
+ }
+ }
+
+ @Override
+ public FileSystem getFileSystem(URI uri) {
+ try {
+ readLock.lock();
+ if (fileSystem == null) {
+ throw new FileSystemNotFoundException();
+ }
+ return fileSystem;
+ } finally {
+ readLock.unlock();
+ }
+ }
+
+ @Override
+ public Path getPath(URI uri) {
+ return getFileSystem(uri).getPath(uri.getSchemeSpecificPart());
+ }
+
+ @Override
+ public SeekableByteChannel newByteChannel(Path path, Set extends OpenOption> options, FileAttribute>... attrs) throws IOException {
+ return toResourcePath(path).newByteChannel(options);
+ }
+
+ @Override
+ public DirectoryStream newDirectoryStream(Path dir, DirectoryStream.Filter super Path> filter) throws IOException {
+ return toResourcePath(dir).newDirectoryStream(filter);
+ }
+
+ @Override
+ public void createDirectory(Path dir, FileAttribute>... attrs) throws IOException {
+ toResourcePath(dir).createDirectory();
+ }
+
+ @Override
+ public void delete(Path path) throws IOException {
+ toResourcePath(path).delete();
+ }
+
+ @Override
+ public boolean deleteIfExists(Path path) throws IOException {
+ return toResourcePath(path).deleteIfExists();
+ }
+
+ @Override
+ public void copy(Path source, Path target, CopyOption... options) throws IOException {
+ toResourcePath(source).copy(toResourcePath(target), options);
+ }
+
+ @Override
+ public void move(Path source, Path target, CopyOption... options) throws IOException {
+ toResourcePath(source).move(toResourcePath(target), options);
+ }
+
+ @Override
+ public boolean isSameFile(Path path, Path path2) throws IOException {
+ return toResourcePath(path).isSameFile(path2);
+ }
+
+ @Override
+ public boolean isHidden(Path path) {
+ return toResourcePath(path).isHidden();
+ }
+
+ @Override
+ public FileStore getFileStore(Path path) throws IOException {
+ return toResourcePath(path).getFileStore();
+ }
+
+ @Override
+ public void checkAccess(Path path, AccessMode... modes) throws IOException {
+ toResourcePath(path).checkAccess(modes);
+ }
+
+ @Override
+ public InputStream newInputStream(Path path, OpenOption... options) throws IOException {
+ return toResourcePath(path).newInputStream(options);
+ }
+
+ @Override
+ public OutputStream newOutputStream(Path path, OpenOption... options) throws IOException {
+ return toResourcePath(path).newOutputStream(options);
+ }
+
+ @Override
+ public FileChannel newFileChannel(Path path, Set extends OpenOption> options, FileAttribute>... attrs) throws IOException {
+ return toResourcePath(path).newFileChannel(options, attrs);
+ }
+
+ @Override
+ public V getFileAttributeView(Path path, Class type, LinkOption... options) {
+ return NativeImageResourceFileAttributesView.get(toResourcePath(path), type);
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public A readAttributes(Path path, Class type, LinkOption... options) throws IOException {
+ if (type == BasicFileAttributes.class || type == NativeImageResourceFileAttributes.class) {
+ return (A) toResourcePath(path).getAttributes();
+ }
+ return null;
+ }
+
+ @Override
+ public Map readAttributes(Path path, String attributes, LinkOption... options) throws IOException {
+ return toResourcePath(path).readAttributes(attributes);
+ }
+
+ @Override
+ public void setAttribute(Path path, String attribute, Object value, LinkOption... options) throws IOException {
+ toResourcePath(path).setAttribute(attribute, value);
+ }
+
+ void removeFileSystem() {
+ try {
+ writeLock.lock();
+ fileSystem = null;
+ } finally {
+ writeLock.unlock();
+ }
+ }
+}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/resources/NativeImageResourceFileSystemUtil.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/resources/NativeImageResourceFileSystemUtil.java
new file mode 100644
index 0000000000000..e3debb494652f
--- /dev/null
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/resources/NativeImageResourceFileSystemUtil.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2021, 2021, 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.core.jdk.resources;
+
+import java.io.InputStream;
+import java.util.Arrays;
+
+import org.graalvm.collections.MapCursor;
+import org.graalvm.compiler.serviceprovider.JavaVersionUtil;
+
+import com.oracle.svm.core.jdk.Resources;
+
+public final class NativeImageResourceFileSystemUtil {
+
+ private NativeImageResourceFileSystemUtil() {
+ }
+
+ public static MapCursor iterator() {
+ return Resources.singleton().resources().getEntries();
+ }
+
+ public static byte[] getBytes(String resourceName, boolean readOnly) {
+ ResourceStorageEntry entry = Resources.singleton().resources().get(resourceName);
+ if (entry == null) {
+ return new byte[0];
+ }
+ byte[] bytes = entry.getData().get(0);
+ if (readOnly) {
+ return bytes;
+ } else {
+ return Arrays.copyOf(bytes, bytes.length);
+ }
+ }
+
+ public static int getSize(String resourceName) {
+ ResourceStorageEntry entry = Resources.singleton().resources().get(resourceName);
+ if (entry == null) {
+ return 0;
+ } else {
+ return entry.getData().get(0).length;
+ }
+ }
+
+ public static String toRegexPattern(String globPattern) {
+ if (JavaVersionUtil.JAVA_SPEC >= 11) {
+ return Target_jdk_nio_zipfs_ZipUtils_JDK11OrLater.toRegexPattern(globPattern);
+ } else {
+ return Target_com_sun_nio_zipfs_ZipUtils_JDK8OrEarlier.toRegexPattern(globPattern);
+ }
+ }
+
+ public static byte[] inputStreamToByteArray(InputStream is) {
+ return Resources.inputStreamToByteArray(is);
+ }
+}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/resources/NativeImageResourcePath.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/resources/NativeImageResourcePath.java
new file mode 100644
index 0000000000000..3c7bccdd5bac5
--- /dev/null
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/resources/NativeImageResourcePath.java
@@ -0,0 +1,866 @@
+/*
+ * Copyright (c) 2021, 2021, 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.core.jdk.resources;
+
+import static java.nio.file.StandardCopyOption.COPY_ATTRIBUTES;
+import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
+import static java.nio.file.StandardOpenOption.CREATE;
+import static java.nio.file.StandardOpenOption.READ;
+import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING;
+import static java.nio.file.StandardOpenOption.WRITE;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.channels.FileChannel;
+import java.nio.channels.SeekableByteChannel;
+import java.nio.file.AccessDeniedException;
+import java.nio.file.AccessMode;
+import java.nio.file.CopyOption;
+import java.nio.file.DirectoryNotEmptyException;
+import java.nio.file.DirectoryStream;
+import java.nio.file.FileAlreadyExistsException;
+import java.nio.file.FileStore;
+import java.nio.file.Files;
+import java.nio.file.InvalidPathException;
+import java.nio.file.LinkOption;
+import java.nio.file.NoSuchFileException;
+import java.nio.file.OpenOption;
+import java.nio.file.Path;
+import java.nio.file.ProviderMismatchException;
+import java.nio.file.ReadOnlyFileSystemException;
+import java.nio.file.WatchEvent;
+import java.nio.file.WatchKey;
+import java.nio.file.WatchService;
+import java.nio.file.attribute.BasicFileAttributeView;
+import java.nio.file.attribute.FileAttribute;
+import java.nio.file.attribute.FileTime;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Set;
+
+/**
+ *
+ * Most of the code from this class is a copy of jdk.nio.zipfs.ZipPath with small tweaks. The main
+ * reason why we cannot reuse this class is that this class is final in its original implementation.
+ *
+ */
+public class NativeImageResourcePath implements Path {
+
+ private final NativeImageResourceFileSystem fileSystem;
+ private final byte[] path;
+ private volatile int[] offsets;
+ private byte[] resolved;
+ private int hashcode = 0;
+
+ public NativeImageResourcePath(NativeImageResourceFileSystem fileSystem, byte[] resourcePath) {
+ this(fileSystem, resourcePath, false);
+ }
+
+ public NativeImageResourcePath(NativeImageResourceFileSystem fileSystem, byte[] resourcePath, boolean normalized) {
+ this.fileSystem = fileSystem;
+ if (normalized) {
+ this.path = resourcePath;
+ } else {
+ this.path = normalize(resourcePath);
+ }
+ }
+
+ private void initOffsets() {
+ if (this.offsets == null) {
+ int count = 0;
+ int index = 0;
+ while (index < path.length) {
+ byte c = path[index++];
+ if (c != '/') {
+ count++;
+ while (index < path.length && path[index] != '/') {
+ index++;
+ }
+ }
+ }
+ int[] result = new int[count];
+ count = 0;
+ index = 0;
+ while (index < path.length) {
+ int m = path[index];
+ if (m == '/') {
+ index++;
+ } else {
+ result[count++] = index++;
+ while (index < path.length && path[index] != '/') {
+ index++;
+ }
+ }
+ }
+
+ // Checkstyle: stop
+ synchronized (this) {
+ if (offsets == null) {
+ offsets = result;
+ }
+ }
+ // Checkstyle: resume
+ }
+ }
+
+ @Override
+ public NativeImageResourceFileSystem getFileSystem() {
+ return fileSystem;
+ }
+
+ @Override
+ public boolean isAbsolute() {
+ return (this.path.length > 0 && path[0] == '/');
+ }
+
+ @Override
+ public NativeImageResourcePath getRoot() {
+ if (isAbsolute()) {
+ return new NativeImageResourcePath(fileSystem, new byte[]{this.path[0]});
+ }
+ return null;
+ }
+
+ @Override
+ public Path getFileName() {
+ initOffsets();
+ int nbOffsets = offsets.length;
+ if (nbOffsets == 0) {
+ return null;
+ }
+ if (nbOffsets == 1 && path[0] != '/') {
+ return this;
+ }
+ int offset = offsets[nbOffsets - 1];
+ int length = path.length - offset;
+ byte[] newPath = new byte[length];
+ System.arraycopy(this.path, offset, newPath, 0, length);
+ return new NativeImageResourcePath(fileSystem, newPath);
+ }
+
+ @Override
+ public NativeImageResourcePath getParent() {
+ initOffsets();
+ int nbOffsets = offsets.length;
+ if (nbOffsets == 0) {
+ return null;
+ }
+ int length = offsets[nbOffsets - 1] - 1;
+ if (length <= 0) {
+ return getRoot();
+ }
+ byte[] newPath = new byte[length];
+ System.arraycopy(this.path, 0, newPath, 0, length);
+ return new NativeImageResourcePath(fileSystem, newPath);
+ }
+
+ @Override
+ public int getNameCount() {
+ initOffsets();
+ return offsets.length;
+ }
+
+ @Override
+ public Path getName(int index) {
+ initOffsets();
+ if (index < 0 || index >= offsets.length) {
+ throw new IllegalArgumentException();
+ }
+ int begin = offsets[index];
+ int len;
+ if (index == (offsets.length - 1)) {
+ len = path.length - begin;
+ } else {
+ len = offsets[index + 1] - begin - 1;
+ }
+
+ byte[] result = new byte[len];
+ System.arraycopy(path, begin, result, 0, len);
+ return new NativeImageResourcePath(fileSystem, result);
+ }
+
+ @Override
+ public Path subpath(int beginIndex, int endIndex) {
+ initOffsets();
+ if (beginIndex < 0 ||
+ beginIndex >= offsets.length ||
+ endIndex > offsets.length ||
+ beginIndex >= endIndex) {
+ throw new IllegalArgumentException();
+ }
+
+ int begin = offsets[beginIndex];
+ int len;
+ if (endIndex == offsets.length) {
+ len = path.length - begin;
+ } else {
+ len = offsets[endIndex] - begin - 1;
+ }
+
+ byte[] result = new byte[len];
+ System.arraycopy(path, begin, result, 0, len);
+ return new NativeImageResourcePath(fileSystem, result);
+ }
+
+ @Override
+ public boolean startsWith(Path other) {
+ NativeImageResourcePath p1 = this;
+ NativeImageResourcePath p2 = checkPath(other);
+ if (p1.isAbsolute() != p2.isAbsolute() || p1.path.length < p2.path.length) {
+ return false;
+ }
+ int length = p2.path.length;
+ for (int idx = 0; idx < length; idx++) {
+ if (p1.path[idx] != p2.path[idx]) {
+ return false;
+ }
+ }
+ return p1.path.length == p2.path.length || p2.path[length - 1] == '/' || p1.path[length] == '/';
+ }
+
+ @Override
+ public boolean startsWith(String other) {
+ return startsWith(getFileSystem().getPath(other));
+ }
+
+ @Override
+ public boolean endsWith(Path other) {
+ NativeImageResourcePath p1 = this;
+ NativeImageResourcePath p2 = checkPath(other);
+ int i1 = p1.path.length - 1;
+ if (i1 > 0 && p1.path[i1] == '/') {
+ i1--;
+ }
+ int i2 = p2.path.length - 1;
+ if (i2 > 0 && p2.path[i2] == '/') {
+ i2--;
+ }
+ if (i2 == -1) {
+ return i1 == -1;
+ }
+ if ((p2.isAbsolute() && (!isAbsolute() || i2 != i1)) || (i1 < i2)) {
+ return false;
+ }
+ for (; i2 >= 0; i1--) {
+ if (p2.path[i2] != p1.path[i1]) {
+ return false;
+ }
+ i2--;
+ }
+ return (p2.path[i2 + 1] == '/');
+ }
+
+ @Override
+ public boolean endsWith(String other) {
+ return endsWith(getFileSystem().getPath(other));
+ }
+
+ @Override
+ public Path normalize() {
+ byte[] p = getResolved();
+ if (p == this.path) {
+ return this;
+ }
+ return new NativeImageResourcePath(fileSystem, p, true);
+ }
+
+ @Override
+ public Path resolve(Path other) {
+ NativeImageResourcePath p1 = this;
+ NativeImageResourcePath p2 = checkPath(other);
+ if (p2.isAbsolute()) {
+ return p2;
+ }
+ byte[] result;
+ if (p1.path[p1.path.length - 1] == '/') {
+ result = new byte[p1.path.length + p2.path.length];
+ System.arraycopy(p1.path, 0, result, 0, p1.path.length);
+ System.arraycopy(p2.path, 0, result, p1.path.length, p2.path.length);
+ } else {
+ result = new byte[p1.path.length + 1 + p2.path.length];
+ System.arraycopy(p1.path, 0, result, 0, p1.path.length);
+ result[p1.path.length] = '/';
+ System.arraycopy(p2.path, 0, result, p1.path.length + 1, p2.path.length);
+ }
+ return new NativeImageResourcePath(fileSystem, result);
+ }
+
+ private static NativeImageResourcePath checkPath(Path paramPath) {
+ if (paramPath == null) {
+ throw new NullPointerException();
+ }
+ if (!(paramPath instanceof NativeImageResourcePath)) {
+ throw new ProviderMismatchException();
+ }
+ return (NativeImageResourcePath) paramPath;
+ }
+
+ @Override
+ public Path resolve(String other) {
+ return resolve(getFileSystem().getPath(other));
+ }
+
+ @Override
+ public Path resolveSibling(Path other) {
+ if (other == null) {
+ throw new NullPointerException();
+ }
+ NativeImageResourcePath parent = getParent();
+ return parent == null ? other : parent.resolve(other);
+ }
+
+ @Override
+ public Path resolveSibling(String other) {
+ return resolveSibling(getFileSystem().getPath(other));
+ }
+
+ @Override
+ public Path relativize(Path other) {
+ NativeImageResourcePath p1 = this;
+ NativeImageResourcePath p2 = checkPath(other);
+ if (p2.equals(p1)) {
+ return new NativeImageResourcePath(fileSystem, new byte[0], true);
+ }
+ if (p1.isAbsolute() != p2.isAbsolute()) {
+ throw new IllegalArgumentException();
+ }
+ // Check how many segments are common.
+ int nbNames1 = p1.getNameCount();
+ int nbNames2 = p2.getNameCount();
+ int l = Math.min(nbNames1, nbNames2);
+ int nbCommon = 0;
+ while (nbCommon < l && equalsNameAt(p1, p2, nbCommon)) {
+ nbCommon++;
+ }
+ int nbUp = nbNames1 - nbCommon;
+ // Compute the resulting length.
+ int length = nbUp * 3 - 1;
+ if (nbCommon < nbNames2) {
+ length += p2.path.length - p2.offsets[nbCommon] + 1;
+ }
+ // Compute result.
+ byte[] result = new byte[length];
+ int idx = 0;
+ while (nbUp-- > 0) {
+ result[idx++] = '.';
+ result[idx++] = '.';
+ if (idx < length) {
+ result[idx++] = '/';
+ }
+ }
+ // Copy remaining segments.
+ if (nbCommon < nbNames2) {
+ System.arraycopy(p2.path, p2.offsets[nbCommon], result, idx, p2.path.length - p2.offsets[nbCommon]);
+ }
+ return new NativeImageResourcePath(fileSystem, result);
+ }
+
+ @Override
+ public URI toUri() {
+ try {
+ return new URI(
+ "resource",
+ fileSystem.getResourcePath().toUri() +
+ "!" +
+ fileSystem.getString(toAbsolutePath().path),
+ null);
+ } catch (URISyntaxException e) {
+ throw new AssertionError(e);
+ }
+ }
+
+ @Override
+ public NativeImageResourcePath toAbsolutePath() {
+ if (isAbsolute()) {
+ return this;
+ }
+ byte[] result = new byte[path.length + 1];
+ result[0] = '/';
+ System.arraycopy(path, 0, result, 1, path.length);
+ return new NativeImageResourcePath(fileSystem, result, true);
+ }
+
+ @Override
+ public Path toRealPath(LinkOption... options) throws IOException {
+ NativeImageResourcePath absolute = new NativeImageResourcePath(fileSystem, getResolvedPath()).toAbsolutePath();
+ fileSystem.provider().checkAccess(absolute);
+ return absolute;
+ }
+
+ @Override
+ public File toFile() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public WatchKey register(WatchService watcher, WatchEvent.Kind>[] events, WatchEvent.Modifier... modifiers) throws IOException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public WatchKey register(WatchService watcher, WatchEvent.Kind>... events) throws IOException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Iterator iterator() {
+ return new Iterator() {
+ private int i = 0;
+
+ @Override
+ public boolean hasNext() {
+ return (i < getNameCount());
+ }
+
+ @Override
+ public Path next() {
+ if (i < getNameCount()) {
+ Path result = getName(i);
+ i++;
+ return result;
+ } else {
+ throw new NoSuchElementException();
+ }
+ }
+
+ @Override
+ public void remove() {
+ throw new ReadOnlyFileSystemException();
+ }
+ };
+ }
+
+ @Override
+ public int compareTo(Path other) {
+ NativeImageResourcePath p1 = this;
+ NativeImageResourcePath p2 = checkPath(other);
+ byte[] a1 = p1.path;
+ byte[] a2 = p2.path;
+ int l1 = a1.length;
+ int l2 = a2.length;
+ for (int i = 0, l = Math.min(l1, l2); i < l; i++) {
+ int b1 = a1[i] & 0xFF;
+ int b2 = a2[i] & 0xFF;
+ if (b1 != b2) {
+ return b1 - b2;
+ }
+ }
+ return l1 - l2;
+ }
+
+ @Override
+ public int hashCode() {
+ int h = hashcode;
+ if (h == 0) {
+ hashcode = h = Arrays.hashCode(path);
+ }
+ return h;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return obj instanceof NativeImageResourcePath &&
+ this.fileSystem == ((NativeImageResourcePath) obj).fileSystem &&
+ compareTo((Path) obj) == 0;
+ }
+
+ @Override
+ public String toString() {
+ return fileSystem.getString(path);
+ }
+
+ SeekableByteChannel newByteChannel(Set extends OpenOption> options) throws IOException {
+ return fileSystem.newByteChannel(getResolvedPath(), options);
+ }
+
+ DirectoryStream newDirectoryStream(DirectoryStream.Filter super Path> filter) throws IOException {
+ return new NativeImageResourceDirectoryStream(this, filter);
+ }
+
+ NativeImageResourceFileAttributes getAttributes() throws NoSuchFileException {
+ NativeImageResourceFileAttributes nativeImageResourceFileAttributes = fileSystem.getFileAttributes(getResolvedPath());
+ if (nativeImageResourceFileAttributes == null) {
+ throw new NoSuchFileException(toString());
+ }
+ return nativeImageResourceFileAttributes;
+ }
+
+ Map readAttributes(String attributes) throws IOException {
+ String view;
+ String attrs;
+ int colonPos = attributes.indexOf(':');
+ if (colonPos == -1) {
+ view = "basic";
+ attrs = attributes;
+ } else {
+ view = attributes.substring(0, colonPos++);
+ attrs = attributes.substring(colonPos);
+ }
+ NativeImageResourceFileAttributesView raw = NativeImageResourceFileAttributesView.get(this, view);
+ if (raw == null) {
+ throw new UnsupportedOperationException("View is not supported!");
+ }
+ return raw.readAttributes(attrs);
+ }
+
+ byte[] getResolvedPath() {
+ byte[] r = resolved;
+ if (r == null) {
+ if (isAbsolute()) {
+ r = getResolved();
+ } else {
+ r = toAbsolutePath().getResolvedPath();
+ }
+ if (r[0] == '/') {
+ r = Arrays.copyOfRange(r, 1, r.length);
+ }
+ resolved = r;
+ }
+ return resolved;
+ }
+
+ private byte[] normalize(byte[] resourcePath) {
+ if (resourcePath.length == 0) {
+ return resourcePath;
+ }
+ int i = 0;
+ for (int j = 0; j < resourcePath.length; j++) {
+ int k = resourcePath[j];
+ if (k == '\\') {
+ return normalize(resourcePath, j);
+ }
+ if ((k == '/') && (i == '/')) {
+ return normalize(resourcePath, j - 1);
+ }
+ if (k == 0) {
+ throw new InvalidPathException(fileSystem.getString(resourcePath), "Path: nul character not allowed");
+ }
+ i = k;
+ }
+ return resourcePath;
+ }
+
+ private byte[] normalize(byte[] resourcePath, int index) {
+ byte[] arrayOfByte = new byte[resourcePath.length];
+ int i = 0;
+ while (i < index) {
+ arrayOfByte[i] = resourcePath[i];
+ i++;
+ }
+ int j = i;
+ int k = 0;
+ while (i < resourcePath.length) {
+ int m = resourcePath[i++];
+ if (m == '\\') {
+ m = '/';
+ }
+ if ((m != '/') || (k != '/')) {
+ if (m == 0) {
+ throw new InvalidPathException(fileSystem.getString(resourcePath), "Path: nul character not allowed");
+ }
+ arrayOfByte[j++] = (byte) m;
+ k = m;
+ }
+ }
+ if ((j > 1) && (arrayOfByte[j - 1] == '/')) {
+ j--;
+ }
+ return j == arrayOfByte.length ? arrayOfByte : Arrays.copyOf(arrayOfByte, j);
+ }
+
+ private byte[] getResolved() {
+ if (path.length == 0) {
+ return path;
+ }
+ for (byte c : path) {
+ if (c == '.') {
+ return doGetResolved(this);
+ }
+ }
+ return path;
+ }
+
+ private static byte[] doGetResolved(NativeImageResourcePath p) {
+ int nc = p.getNameCount();
+ byte[] path = p.path;
+ int[] offsets = p.offsets;
+ byte[] to = new byte[path.length];
+ int[] lastM = new int[nc];
+ int lastMOff = -1;
+ int m = 0;
+ for (int i = 0; i < nc; i++) {
+ int n = offsets[i];
+ int len = (i == offsets.length - 1) ? (path.length - n) : (offsets[i + 1] - n - 1);
+ if (len == 1 && path[n] == (byte) '.') {
+ if (m == 0 && path[0] == '/') { // absolute path
+ to[m++] = '/';
+ }
+ continue;
+ }
+ if (len == 2 && path[n] == '.' && path[n + 1] == '.') {
+ if (lastMOff >= 0) {
+ m = lastM[lastMOff--]; // retreat
+ continue;
+ }
+ if (path[0] == '/') { // "/../xyz" skip
+ if (m == 0) {
+ to[m++] = '/';
+ }
+ } else { // "../xyz" -> "../xyz"
+ if (m != 0 && to[m - 1] != '/') {
+ to[m++] = '/';
+ }
+ while (len-- > 0) {
+ to[m++] = path[n++];
+ }
+ }
+ continue;
+ }
+ if (m == 0 && path[0] == '/' || // absolute path
+ m != 0 && to[m - 1] != '/') { // not the first name
+ to[m++] = '/';
+ }
+ lastM[++lastMOff] = m;
+ while (len-- > 0) {
+ to[m++] = path[n++];
+ }
+ }
+ if (m > 1 && to[m - 1] == '/') {
+ m--;
+ }
+ return (m == to.length) ? to : Arrays.copyOf(to, m);
+ }
+
+ private static boolean equalsNameAt(NativeImageResourcePath p1, NativeImageResourcePath p2, int index) {
+ int beg1 = p1.offsets[index];
+ int len1;
+ if (index == p1.offsets.length - 1) {
+ len1 = p1.path.length - beg1;
+ } else {
+ len1 = p1.offsets[index + 1] - beg1 - 1;
+ }
+ int beg2 = p2.offsets[index];
+ int len2;
+ if (index == p2.offsets.length - 1) {
+ len2 = p2.path.length - beg2;
+ } else {
+ len2 = p2.offsets[index + 1] - beg2 - 1;
+ }
+ if (len1 != len2) {
+ return false;
+ }
+ for (int n = 0; n < len1; n++) {
+ if (p1.path[beg1 + n] != p2.path[beg2 + n]) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ boolean exists() {
+ return fileSystem.exists(getResolvedPath());
+ }
+
+ FileStore getFileStore() throws IOException {
+ if (exists()) {
+ return NativeImageResourceFileSystem.getFileStore(this);
+ }
+ throw new NoSuchFileException(fileSystem.getString(path));
+ }
+
+ boolean isSameFile(Path other) throws IOException {
+ if (this.equals(other)) {
+ return true;
+ }
+ if (other == null || this.getFileSystem() != other.getFileSystem()) {
+ return false;
+ }
+ this.checkAccess();
+ ((NativeImageResourcePath) other).checkAccess();
+ return Arrays.equals(this.getResolvedPath(), ((NativeImageResourcePath) other).getResolvedPath());
+ }
+
+ void checkAccess(AccessMode... modes) throws IOException {
+ boolean x = false;
+ for (AccessMode mode : modes) {
+ switch (mode) {
+ case READ:
+ case WRITE:
+ break;
+ case EXECUTE:
+ x = true;
+ break;
+ default:
+ throw new UnsupportedOperationException();
+ }
+ }
+ fileSystem.checkAccess(getResolvedPath());
+ if (x) {
+ throw new AccessDeniedException(toString());
+ }
+ }
+
+ void setAttribute(String attribute, Object value) throws IOException {
+ String type;
+ String attr;
+ int colonPos = attribute.indexOf(':');
+ if (colonPos == -1) {
+ type = "basic";
+ attr = attribute;
+ } else {
+ type = attribute.substring(0, colonPos++);
+ attr = attribute.substring(colonPos);
+ }
+ NativeImageResourceFileAttributesView view = NativeImageResourceFileAttributesView.get(this, type);
+ if (view == null) {
+ throw new UnsupportedOperationException("View is not supported");
+ }
+ view.setAttribute(attr, value);
+ }
+
+ void setTimes(FileTime lastModifiedTime, FileTime lastAccessTime, FileTime createTime) throws NoSuchFileException {
+ fileSystem.setTimes(getResolvedPath(), lastModifiedTime, lastAccessTime, createTime);
+ }
+
+ void createDirectory() throws IOException {
+ fileSystem.createDirectory(getResolvedPath());
+ }
+
+ void delete() throws IOException {
+ fileSystem.deleteFile(getResolvedPath(), true);
+ }
+
+ boolean deleteIfExists() throws IOException {
+ return fileSystem.deleteFile(getResolvedPath(), false);
+ }
+
+ void move(NativeImageResourcePath target, CopyOption... options) throws IOException {
+ if (Files.isSameFile(this.fileSystem.getResourcePath(), target.fileSystem.getResourcePath())) {
+ fileSystem.copyFile(true, getResolvedPath(), target.getResolvedPath(), options);
+ } else {
+ copyToTarget(target, options);
+ delete();
+ }
+ }
+
+ void copy(NativeImageResourcePath target, CopyOption... options) throws IOException {
+ if (Files.isSameFile(this.fileSystem.getResourcePath(), target.fileSystem.getResourcePath())) {
+ fileSystem.copyFile(false, getResolvedPath(), target.getResolvedPath(), options);
+ } else {
+ copyToTarget(target, options);
+ }
+ }
+
+ private void copyToTarget(NativeImageResourcePath target, CopyOption... options) throws IOException {
+ boolean replaceExisting = false;
+ boolean copyAttrs = false;
+ for (CopyOption opt : options) {
+ if (opt == REPLACE_EXISTING) {
+ replaceExisting = true;
+ } else if (opt == COPY_ATTRIBUTES) {
+ copyAttrs = true;
+ }
+ }
+ // Attributes of source file.
+ NativeImageResourceFileAttributes nativeImageResourceFileAttributes = getAttributes();
+
+ // Check if target exists.
+ boolean exists;
+ if (replaceExisting) {
+ try {
+ target.deleteIfExists();
+ exists = false;
+ } catch (DirectoryNotEmptyException x) {
+ exists = true;
+ }
+ } else {
+ exists = target.exists();
+ }
+ if (exists) {
+ throw new FileAlreadyExistsException(target.toString());
+ }
+
+ if (nativeImageResourceFileAttributes.isDirectory()) {
+ // Create directory or file.
+ target.createDirectory();
+ } else {
+ try (InputStream is = fileSystem.newInputStream(getResolvedPath())) {
+ try (OutputStream os = target.newOutputStream()) {
+ byte[] buf = new byte[8192];
+ int n;
+ while ((n = is.read(buf)) != -1) {
+ os.write(buf, 0, n);
+ }
+ }
+ }
+ }
+ if (copyAttrs) {
+ BasicFileAttributeView attributeView = NativeImageResourceFileAttributesView.get(target, BasicFileAttributeView.class);
+ try {
+ attributeView.setTimes(nativeImageResourceFileAttributes.lastModifiedTime(), nativeImageResourceFileAttributes.lastAccessTime(),
+ nativeImageResourceFileAttributes.creationTime());
+ } catch (IOException e) {
+ try {
+ target.delete();
+ } catch (IOException ignore) {
+ }
+ throw e;
+ }
+ }
+ }
+
+ InputStream newInputStream(OpenOption... options) throws IOException {
+ if (options.length > 0) {
+ for (OpenOption opt : options) {
+ if (opt != READ) {
+ throw new UnsupportedOperationException("'" + opt + "' not allowed");
+ }
+ }
+ }
+ return fileSystem.newInputStream(getResolvedPath());
+ }
+
+ OutputStream newOutputStream(OpenOption... options) throws IOException {
+ if (options.length == 0) {
+ return fileSystem.newOutputStream(getResolvedPath(), CREATE, TRUNCATE_EXISTING, WRITE);
+ }
+ return fileSystem.newOutputStream(getResolvedPath(), options);
+ }
+
+ FileChannel newFileChannel(Set extends OpenOption> options, FileAttribute>... attrs) throws IOException {
+ return fileSystem.newFileChannel(getResolvedPath(), options, attrs);
+ }
+
+ boolean isHidden() {
+ return false;
+ }
+}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/resources/ResourceStorageEntry.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/resources/ResourceStorageEntry.java
new file mode 100644
index 0000000000000..701e192fe7c2d
--- /dev/null
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/resources/ResourceStorageEntry.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2021, 2021, 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.core.jdk.resources;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ResourceStorageEntry {
+
+ private final boolean isDirectory;
+ private final List data;
+
+ public ResourceStorageEntry(boolean isDirectory) {
+ this.isDirectory = isDirectory;
+ this.data = new ArrayList<>();
+ }
+
+ public boolean isDirectory() {
+ return isDirectory;
+ }
+
+ public List getData() {
+ return data;
+ }
+}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/resources/ResourceURLConnection.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/resources/ResourceURLConnection.java
new file mode 100644
index 0000000000000..3aa271c5f30d4
--- /dev/null
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/resources/ResourceURLConnection.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2021, 2021, 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.core.jdk.resources;
+
+import java.io.ByteArrayInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.List;
+
+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
+ public void connect() {
+ if (connected) {
+ return;
+ }
+ connected = true;
+
+ String resourceName = resolveName(url.getPath());
+ ResourceStorageEntry entry = Resources.get(resourceName);
+ if (entry != null) {
+ List bytes = entry.getData();
+ if (index < bytes.size()) {
+ this.data = bytes.get(index);
+ } else {
+ // This will happen only in case that we are creating one URL with the second URL as
+ // a context.
+ this.data = bytes.get(0);
+ }
+ } else {
+ this.data = null;
+ }
+ }
+
+ @Override
+ public InputStream getInputStream() throws IOException {
+ // Operations that depend on being connected will implicitly perform the connection, if
+ // necessary.
+ connect();
+ if (data == null) {
+ throw new FileNotFoundException(url.toString());
+ }
+ return new ByteArrayInputStream(data);
+ }
+
+ @Override
+ public long getContentLengthLong() {
+ // Operations that depend on being connected will implicitly perform the connection, if
+ // necessary.
+ connect();
+ return data != null ? data.length : -1L;
+ }
+
+}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/resources/Target_com_sun_nio_zipfs_ZipUtils_JDK8OrEarlier.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/resources/Target_com_sun_nio_zipfs_ZipUtils_JDK8OrEarlier.java
new file mode 100644
index 0000000000000..bfb0171e6fa28
--- /dev/null
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/resources/Target_com_sun_nio_zipfs_ZipUtils_JDK8OrEarlier.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2021, 2021, 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.core.jdk.resources;
+
+import com.oracle.svm.core.annotate.Alias;
+import com.oracle.svm.core.annotate.TargetClass;
+import com.oracle.svm.core.jdk.JDK8OrEarlier;
+
+@TargetClass(className = "com.sun.nio.zipfs.ZipUtils", onlyWith = JDK8OrEarlier.class)
+@SuppressWarnings({"unused", "static-method"})
+final class Target_com_sun_nio_zipfs_ZipUtils_JDK8OrEarlier {
+
+ @Alias
+ public static native String toRegexPattern(String globPattern);
+}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/resources/Target_jdk_nio_zipfs_ZipUtils_JDK11OrLater.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/resources/Target_jdk_nio_zipfs_ZipUtils_JDK11OrLater.java
new file mode 100644
index 0000000000000..2d8d5d3639f9d
--- /dev/null
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/resources/Target_jdk_nio_zipfs_ZipUtils_JDK11OrLater.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2021, 2021, 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.core.jdk.resources;
+
+import com.oracle.svm.core.annotate.Alias;
+import com.oracle.svm.core.annotate.TargetClass;
+import com.oracle.svm.core.jdk.JDK11OrLater;
+
+@TargetClass(className = "jdk.nio.zipfs.ZipUtils", onlyWith = JDK11OrLater.class)
+@SuppressWarnings({"unused", "static-method"})
+final class Target_jdk_nio_zipfs_ZipUtils_JDK11OrLater {
+
+ @Alias
+ public static native String toRegexPattern(String globPattern);
+}
diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ResourcesFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ResourcesFeature.java
index ca26a030d23e0..a14ea09baa9ce 100644
--- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ResourcesFeature.java
+++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ResourcesFeature.java
@@ -32,12 +32,15 @@
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLClassLoader;
+import java.nio.file.FileSystem;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -51,6 +54,7 @@
import org.graalvm.compiler.options.Option;
import org.graalvm.compiler.options.OptionType;
import org.graalvm.compiler.serviceprovider.JavaVersionUtil;
+import org.graalvm.home.HomeFinder;
import org.graalvm.nativeimage.ImageSingletons;
import org.graalvm.nativeimage.hosted.Feature;
@@ -61,6 +65,10 @@
import com.oracle.svm.core.configure.ResourcesRegistry;
import com.oracle.svm.core.jdk.Resources;
import com.oracle.svm.core.jdk.localization.LocalizationFeature;
+import com.oracle.svm.core.jdk.resources.NativeImageResourceFileAttributes;
+import com.oracle.svm.core.jdk.resources.NativeImageResourceFileAttributesView;
+import com.oracle.svm.core.jdk.resources.NativeImageResourceFileSystem;
+import com.oracle.svm.core.jdk.resources.NativeImageResourceFileSystemProvider;
import com.oracle.svm.core.option.HostedOptionKey;
import com.oracle.svm.core.option.LocatableMultiOptionValue;
import com.oracle.svm.core.util.UserError;
@@ -69,6 +77,34 @@
import com.oracle.svm.hosted.config.ConfigurationParserUtils;
import com.oracle.svm.util.ModuleSupport;
+/**
+ *
+ * Resources are collected at build time in this feature and stored in a hash map in
+ * {@link Resources} class.
+ *
+ *
+ *
+ * {@link NativeImageResourceFileSystemProvider } is a core class for building a custom file system
+ * on top of resources in the native image.
+ *
+ *
+ *
+ * The {@link NativeImageResourceFileSystemProvider} provides most of the functionality of a
+ * {@link FileSystem}. It is an in-memory file system that upon creation contains a copy of the
+ * resources included in the native-image. Note that changes to files do not affect actual resources
+ * returned by resource manipulation methods like `Class.getResource`. Upon being closed, all
+ * changes are discarded.
+ *
+ *
+ *
+ * As with other file system providers, these methods provide a low-level interface and are not
+ * meant for direct usage - see {@link java.nio.file.Files}
+ *
+ *
+ * @see NativeImageResourceFileSystem
+ * @see NativeImageResourceFileAttributes
+ * @see NativeImageResourceFileAttributesView
+ */
@AutomaticFeature
public final class ResourcesFeature implements Feature {
@@ -81,21 +117,21 @@ public static class Options {
}
private boolean sealed = false;
- private Set newResources = Collections.newSetFromMap(new ConcurrentHashMap<>());
- private Set ignoredResources = Collections.newSetFromMap(new ConcurrentHashMap<>());
+ private final Set resourcePatternWorkSet = Collections.newSetFromMap(new ConcurrentHashMap<>());
+ private final Set excludedResourcePatterns = Collections.newSetFromMap(new ConcurrentHashMap<>());
private int loadedConfigurations;
private class ResourcesRegistryImpl implements ResourcesRegistry {
@Override
public void addResources(String pattern) {
UserError.guarantee(!sealed, "Resources added too late: %s", pattern);
- newResources.add(pattern);
+ resourcePatternWorkSet.add(pattern);
}
@Override
public void ignoreResources(String pattern) {
UserError.guarantee(!sealed, "Resources ignored too late: %s", pattern);
- ignoredResources.add(pattern);
+ excludedResourcePatterns.add(pattern);
}
@Override
@@ -117,20 +153,20 @@ public void beforeAnalysis(BeforeAnalysisAccess access) {
ConfigurationFiles.Options.ResourceConfigurationFiles, ConfigurationFiles.Options.ResourceConfigurationResources,
ConfigurationFile.RESOURCES.getFileName());
- newResources.addAll(Options.IncludeResources.getValue().values());
- ignoredResources.addAll(Options.ExcludeResources.getValue().values());
+ resourcePatternWorkSet.addAll(Options.IncludeResources.getValue().values());
+ excludedResourcePatterns.addAll(Options.ExcludeResources.getValue().values());
}
@Override
public void duringAnalysis(DuringAnalysisAccess access) {
- if (newResources.isEmpty()) {
+ if (resourcePatternWorkSet.isEmpty()) {
return;
}
access.requireAnalysisIteration();
DebugContext debugContext = ((DuringAnalysisAccessImpl) access).getDebugContext();
- final Pattern[] includePatterns = compilePatterns(newResources);
- final Pattern[] excludePatterns = compilePatterns(ignoredResources);
+ final Pattern[] includePatterns = compilePatterns(resourcePatternWorkSet);
+ final Pattern[] excludePatterns = compilePatterns(excludedResourcePatterns);
if (JavaVersionUtil.JAVA_SPEC > 8) {
try {
@@ -151,32 +187,40 @@ public void duringAnalysis(DuringAnalysisAccess access) {
* @formatter:on
*/
- final Set todo = new HashSet<>();
- // Checkstyle: stop
final ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
+ final LinkedHashSet userClasspathFiles = new LinkedHashSet<>();
+ final LinkedHashSet supportLibraries = new LinkedHashSet<>();
+ String homeFolder = HomeFinder.getInstance().getHomeFolder().toString();
if (contextClassLoader instanceof URLClassLoader) {
for (URL url : ((URLClassLoader) contextClassLoader).getURLs()) {
try {
final File file = new File(url.toURI());
- todo.add(file);
+ // Make sure the user resources are the first to be registered.
+ if (file.getAbsolutePath().startsWith(homeFolder)) {
+ supportLibraries.add(file);
+ } else {
+ userClasspathFiles.add(file);
+ }
} catch (URISyntaxException | IllegalArgumentException e) {
- throw UserError.abort("Unable to handle imagecp element '%s'. Make sure that all imagecp entries are either directories or valid jar files.", url.toExternalForm());
+ throw UserError.abort("Unable to handle image classpath element '%s'. Make sure that all image classpath entries are either directories or valid jar files.", url.toExternalForm());
}
}
}
- // Checkstyle: resume
- for (File element : todo) {
+
+ userClasspathFiles.addAll(supportLibraries);
+ for (File classpathFile : userClasspathFiles) {
try {
- if (element.isDirectory()) {
- scanDirectory(debugContext, element, "", includePatterns, excludePatterns);
+ if (classpathFile.isDirectory()) {
+ scanDirectory(debugContext, classpathFile, includePatterns, excludePatterns);
} else {
- scanJar(debugContext, element, includePatterns, excludePatterns);
+ scanJar(debugContext, classpathFile, includePatterns, excludePatterns);
}
} catch (IOException ex) {
- throw UserError.abort("Unable to handle classpath element '%s'. Make sure that all classpath entries are either directories or valid jar files.", element);
+ throw UserError.abort("Unable to handle classpath element '%s'. Make sure that all classpath entries are either directories or valid jar files.", classpathFile);
}
}
- newResources.clear();
+
+ resourcePatternWorkSet.clear();
}
private static Pattern[] compilePatterns(Set patterns) {
@@ -203,45 +247,36 @@ public void beforeCompilation(BeforeCompilationAccess access) {
}
}
- private void scanDirectory(DebugContext debugContext, File f, String relativePath, Pattern[] includePatterns, Pattern[] excludePatterns) throws IOException {
- if (f.isDirectory()) {
- File[] files = f.listFiles();
- if (files == null) {
- throw UserError.abort("Cannot scan directory %s", f);
- } else {
- for (File ch : files) {
- scanDirectory(debugContext, ch, relativePath.isEmpty() ? ch.getName() : relativePath + "/" + ch.getName(), includePatterns, excludePatterns);
- }
- }
- } else {
- if (matches(includePatterns, excludePatterns, relativePath)) {
- try (FileInputStream is = new FileInputStream(f)) {
- registerResource(debugContext, relativePath, is);
- }
- }
- }
- }
-
- private static void scanJar(DebugContext debugContext, File element, Pattern[] includePatterns, Pattern[] excludePatterns) throws IOException {
- JarFile jf = new JarFile(element);
- Enumeration en = jf.entries();
-
+ private static void scanDirectory(DebugContext debugContext, File root, Pattern[] includePatterns, Pattern[] excludePatterns) throws IOException {
Map> matchedDirectoryResources = new HashMap<>();
Set allEntries = new HashSet<>();
- while (en.hasMoreElements()) {
- JarEntry e = en.nextElement();
- if (e.isDirectory()) {
- String dirName = e.getName().substring(0, e.getName().length() - 1);
- allEntries.add(dirName);
- if (matches(includePatterns, excludePatterns, dirName)) {
- matchedDirectoryResources.put(dirName, new ArrayList<>());
- }
- continue;
+ ArrayList queue = new ArrayList<>();
+
+ queue.add(root);
+ while (!queue.isEmpty()) {
+ File file = queue.remove(0);
+ String relativeFilePath = "";
+ if (file != root) {
+ relativeFilePath = file.getAbsolutePath().substring(root.getAbsolutePath().length() + 1);
}
- allEntries.add(e.getName());
- if (matches(includePatterns, excludePatterns, e.getName())) {
- try (InputStream is = jf.getInputStream(e)) {
- registerResource(debugContext, e.getName(), is);
+ if (file.isDirectory()) {
+ if (!relativeFilePath.isEmpty()) {
+ allEntries.add(relativeFilePath);
+ }
+ if (matches(includePatterns, excludePatterns, relativeFilePath)) {
+ matchedDirectoryResources.put(relativeFilePath, new ArrayList<>());
+ }
+ File[] files = file.listFiles();
+ if (files == null) {
+ throw UserError.abort("Cannot scan directory %s", file);
+ }
+ queue.addAll(Arrays.asList(files));
+ } else {
+ allEntries.add(relativeFilePath);
+ if (matches(includePatterns, excludePatterns, relativeFilePath)) {
+ try (InputStream is = new FileInputStream(file)) {
+ registerResource(debugContext, relativeFilePath, is);
+ }
}
}
}
@@ -251,7 +286,7 @@ private static void scanJar(DebugContext debugContext, File element, Pattern[] i
String key = last == -1 ? "" : entry.substring(0, last);
List dirContent = matchedDirectoryResources.get(key);
if (dirContent != null && !dirContent.contains(entry)) {
- dirContent.add(entry.substring(last + 1, entry.length()));
+ dirContent.add(entry.substring(last + 1));
}
}
@@ -261,6 +296,27 @@ private static void scanJar(DebugContext debugContext, File element, Pattern[] i
});
}
+ private static void scanJar(DebugContext debugContext, File root, Pattern[] includePatterns, Pattern[] excludePatterns) throws IOException {
+ JarFile jf = new JarFile(root);
+ Enumeration entries = jf.entries();
+ while (entries.hasMoreElements()) {
+ JarEntry entry = entries.nextElement();
+ if (entry.isDirectory()) {
+ String dirName = entry.getName().substring(0, entry.getName().length() - 1);
+ if (matches(includePatterns, excludePatterns, dirName)) {
+ // Directory content is empty in JAR scanning.
+ registerDirectoryResource(debugContext, dirName, "");
+ }
+ } else {
+ if (matches(includePatterns, excludePatterns, entry.getName())) {
+ try (InputStream is = jf.getInputStream(entry)) {
+ registerResource(debugContext, entry.getName(), is);
+ }
+ }
+ }
+ }
+ }
+
private static boolean matches(Pattern[] includePatterns, Pattern[] excludePatterns, String relativePath) {
for (Pattern p : excludePatterns) {
if (p.matcher(relativePath).matches()) {
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/NativeImageResourceFileSystemProviderTest.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/NativeImageResourceFileSystemProviderTest.java
new file mode 100644
index 0000000000000..27288f5da2cf1
--- /dev/null
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/NativeImageResourceFileSystemProviderTest.java
@@ -0,0 +1,577 @@
+/*
+ * Copyright (c) 2021, 2021, 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.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.nio.ByteBuffer;
+import java.nio.channels.NonWritableChannelException;
+import java.nio.channels.SeekableByteChannel;
+import java.nio.file.DirectoryStream;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystemNotFoundException;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.LinkOption;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
+import java.nio.file.StandardOpenOption;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.nio.file.attribute.FileTime;
+import java.nio.file.spi.FileSystemProvider;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+import com.oracle.svm.core.annotate.AutomaticFeature;
+import com.oracle.svm.core.configure.ResourcesRegistry;
+import org.graalvm.nativeimage.ImageSingletons;
+import org.graalvm.nativeimage.hosted.Feature;
+import org.graalvm.nativeimage.hosted.RuntimeClassInitialization;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class NativeImageResourceFileSystemProviderTest {
+
+ private static final String RESOURCE_DIR = "/resources";
+ private static final String RESOURCE_FILE_1 = RESOURCE_DIR + "/resource-test1.txt";
+ private static final String RESOURCE_FILE_2 = RESOURCE_DIR + "/resource-test2.txt";
+ private static final String NEW_DIRECTORY = RESOURCE_DIR + "/tmp";
+
+ private static final int TIME_SPAN = 1_000_000;
+
+ // Register resources.
+ @SuppressWarnings("unused")
+ @AutomaticFeature
+ private static final class RegisterResourceFeature implements Feature {
+ @Override
+ public void beforeAnalysis(BeforeAnalysisAccess access) {
+ RuntimeClassInitialization.initializeAtBuildTime(NativeImageResourceFileSystemProviderTest.class);
+ ResourcesRegistry registry = ImageSingletons.lookup(ResourcesRegistry.class);
+ registry.addResources(RESOURCE_FILE_1);
+ registry.addResources(RESOURCE_FILE_2);
+ }
+ }
+
+ private static URL resourceNameToURL(String resourceName) {
+ URL resource = NativeImageResourceFileSystemProviderTest.class.getResource(resourceName);
+ Assert.assertNotNull("Resource " + resourceName + " is not found!", resource);
+ return resource;
+ }
+
+ private static URI resourceNameToURI(String resourceName) {
+ try {
+ return resourceNameToURL(resourceName).toURI();
+ } catch (URISyntaxException e) {
+ Assert.fail("Bad URI syntax!");
+ }
+ return null;
+ }
+
+ private static Path resourceNameToPath(String resourceName) {
+ return Paths.get(resourceNameToURI(resourceName));
+ }
+
+ private static boolean compareTwoURLs(URL url1, URL url2) throws IOException {
+ URLConnection url1Connection = url1.openConnection();
+ URLConnection url2Connection = url2.openConnection();
+
+ try (InputStream is1 = url1Connection.getInputStream(); InputStream is2 = url2Connection.getInputStream()) {
+ Assert.assertNotNull("First input stream is null!", is1);
+ Assert.assertNotNull("Second input stream is null!", is2);
+ int nextByte1 = is1.read();
+ int nextByte2 = is2.read();
+ while (nextByte1 != -1 && nextByte2 != -1) {
+ if (nextByte1 != nextByte2) {
+ return false;
+ }
+
+ nextByte1 = is1.read();
+ nextByte2 = is2.read();
+ }
+ return nextByte1 == -1 && nextByte2 == -1;
+ }
+ }
+
+ private static FileSystem createNewFileSystem() {
+ URI resource = resourceNameToURI(RESOURCE_FILE_1);
+
+ Map env = new HashMap<>();
+ env.put("create", "true");
+
+ FileSystem fileSystem = null;
+ boolean exceptionThrown = false;
+ try {
+ // Try to get file system. This should raise exception.
+ fileSystem = FileSystems.getFileSystem(resource);
+ } catch (FileSystemNotFoundException e) {
+ // File system not found. Create new one.
+ exceptionThrown = true;
+ try {
+ fileSystem = FileSystems.newFileSystem(resource, env);
+ } catch (IOException ioException) {
+ Assert.fail("Error during creating a new file system!");
+ }
+ }
+
+ Assert.assertTrue("File system is already created!", exceptionThrown);
+ Assert.assertNotNull("File system is not created!", fileSystem);
+
+ return fileSystem;
+ }
+
+ private static void closeFileSystem(FileSystem fileSystem) {
+ try {
+ fileSystem.close();
+ } catch (IOException e) {
+ Assert.fail("Exception occurs during closing file system!");
+ }
+ }
+
+ /**
+ *
+ * Combining URL and resource operations.
+ *
+ *
+ *
+ * Description: Test inspired by issues:
+ *
+ * - 1349
+ * - 2291
+ *
+ *
+ */
+ @Test
+ public void githubIssues() {
+ try {
+ URL url1 = resourceNameToURL(RESOURCE_FILE_1);
+ URL url2 = new URL(url1, RESOURCE_FILE_2);
+ Assert.assertNotNull("Second URL is null!", url2);
+ Assert.assertFalse("Two URLs are same!", compareTwoURLs(url1, url2));
+ } catch (IOException e) {
+ Assert.fail("Exception occurs during URL operations!");
+ }
+ }
+
+ /**
+ *
+ * Reading from file using {@link java.nio.channels.ByteChannel}.
+ *
+ *
+ *
+ * Description: We are doing next operations:
+ *
+ * - Create new file system
+ * - Reading from file
+ * - Closing file system
+ *
+ *
+ */
+ @Test
+ public void readingFileByteChannel() {
+ // 1. Creating new file system.
+ FileSystem fileSystem = createNewFileSystem();
+
+ Path resourceDirectory = fileSystem.getPath(RESOURCE_DIR);
+ Path resourceFile1 = resourceNameToPath(RESOURCE_FILE_1);
+
+ // 2. Reading from file.
+ try (SeekableByteChannel channel = Files.newByteChannel(resourceDirectory, StandardOpenOption.READ)) {
+ ByteBuffer byteBuffer = ByteBuffer.allocate((int) channel.size());
+ channel.read(byteBuffer);
+ Assert.fail("Trying to read from directory as a file!");
+ } catch (IOException ignored) {
+ }
+
+ try (SeekableByteChannel channel = Files.newByteChannel(resourceFile1, StandardOpenOption.READ)) {
+ ByteBuffer byteBuffer = ByteBuffer.allocate((int) channel.size());
+ channel.read(byteBuffer);
+ String content = new String(byteBuffer.array());
+ Assert.assertTrue("Nothing has been read from file!", content.length() > 0);
+ } catch (IOException ioException) {
+ Assert.fail("Exception occurs during reading from file!");
+ }
+
+ // 3. Closing file system.
+ closeFileSystem(fileSystem);
+ }
+
+ /**
+ *
+ * Writing into file using {@link java.nio.channels.ByteChannel}.
+ *
+ *
+ *
+ * Description: We are doing next operations:
+ *
+ * - Create new file system
+ * - Writing into file
+ * - Closing file system
+ *
+ *
+ */
+ @Test
+ public void writingFileByteChannel() {
+ // 1. Creating new file system.
+ FileSystem fileSystem = createNewFileSystem();
+
+ Path resourceDirectory = fileSystem.getPath(RESOURCE_DIR);
+ Path resourceFile1 = resourceNameToPath(RESOURCE_FILE_1);
+
+ // 2. Writing into file.
+ try {
+ Files.newByteChannel(resourceDirectory, StandardOpenOption.WRITE);
+ Assert.fail("Trying to write into directory as a file!");
+ } catch (IOException ignored) {
+ }
+
+ try (SeekableByteChannel channel = Files.newByteChannel(resourceFile1, StandardOpenOption.READ)) {
+ ByteBuffer byteBuffer = ByteBuffer.wrap("test string".getBytes());
+ channel.write(byteBuffer);
+ Assert.fail("Wrong write permissions!");
+ } catch (IOException | NonWritableChannelException ignored) {
+ }
+
+ try (SeekableByteChannel channel = Files.newByteChannel(resourceFile1, StandardOpenOption.READ, StandardOpenOption.WRITE)) {
+ ByteBuffer byteBuffer = ByteBuffer.wrap("test string#".getBytes());
+ channel.write(byteBuffer);
+ ByteBuffer byteBuffer2 = ByteBuffer.allocate((int) channel.size());
+ channel.read(byteBuffer2);
+ String content = new String(byteBuffer.array());
+ Assert.assertTrue("Nothing has been writen into file!", content.length() > 0);
+ Assert.assertTrue("Content has been writen into file improperly!", content.startsWith("test string#"));
+ } catch (IOException ioException) {
+ Assert.fail("Exception occurs during writing into file!");
+ }
+
+ // 3. Closing file system.
+ closeFileSystem(fileSystem);
+ }
+
+ /**
+ *
+ * Reading from file using {@link java.nio.channels.FileChannel}.
+ *
+ *
+ *
+ * Description: We are doing next operations:
+ *
+ * - Create new file system
+ * - Reading from file
+ * - Closing file system
+ *
+ *
+ */
+ @Test
+ public void readingFileFileChannel() {
+ // 1. Creating new file system.
+ FileSystem fileSystem = createNewFileSystem();
+
+ Path resourceDirectory = fileSystem.getPath(RESOURCE_DIR);
+ Path resourceFile1 = resourceNameToPath(RESOURCE_FILE_1);
+
+ // 2. Reading from file.
+ Set permissions = Collections.singleton(StandardOpenOption.READ);
+ try (SeekableByteChannel channel = fileSystem.provider().newFileChannel(resourceDirectory, permissions)) {
+ ByteBuffer byteBuffer = ByteBuffer.allocate((int) channel.size());
+ channel.read(byteBuffer);
+ Assert.fail("Trying to read from directory as a file!");
+ } catch (IOException ignored) {
+ }
+
+ try (SeekableByteChannel channel = fileSystem.provider().newFileChannel(resourceFile1, permissions)) {
+ ByteBuffer byteBuffer = ByteBuffer.allocate((int) channel.size());
+ channel.read(byteBuffer);
+ String content = new String(byteBuffer.array());
+ Assert.assertTrue("Nothing has been read from file!", content.length() > 0);
+ } catch (IOException ioException) {
+ Assert.fail("Exception occurs during reading from file!");
+ }
+
+ // 3. Closing file system.
+ closeFileSystem(fileSystem);
+ }
+
+ /**
+ *
+ * Writing into file using {@link java.nio.channels.FileChannel}.
+ *
+ *
+ *
+ * Description: We are doing next operations:
+ *
+ * - Create new file system
+ * - Writing into file
+ * - Closing file system
+ *
+ *
+ */
+ @Test
+ public void writingFileFileChannel() {
+ // 1. Creating new file system.
+ FileSystem fileSystem = createNewFileSystem();
+ FileSystemProvider provider = fileSystem.provider();
+
+ Path resourceDirectory = fileSystem.getPath(RESOURCE_DIR);
+ Path resourceFile1 = resourceNameToPath(RESOURCE_FILE_1);
+
+ Set readPermissions = Collections.singleton(StandardOpenOption.READ);
+ Set writePermissions = Collections.singleton(StandardOpenOption.WRITE);
+ Set readWritePermissions = new HashSet<>(Collections.emptySet());
+ readWritePermissions.addAll(readPermissions);
+ readWritePermissions.addAll(writePermissions);
+
+ // 2. Writing into file.
+ try {
+ provider.newFileChannel(resourceDirectory, writePermissions);
+ Assert.fail("Trying to write into directory as a file!");
+ } catch (IOException ignored) {
+ }
+
+ try (SeekableByteChannel channel = provider.newFileChannel(resourceFile1, readPermissions)) {
+ ByteBuffer byteBuffer = ByteBuffer.wrap("test string".getBytes());
+ channel.write(byteBuffer);
+ Assert.fail("Wrong write permissions!");
+ } catch (IOException | NonWritableChannelException ignored) {
+ }
+
+ try (SeekableByteChannel channel = provider.newFileChannel(resourceFile1, readWritePermissions)) {
+ ByteBuffer byteBuffer = ByteBuffer.wrap("test string#".getBytes());
+ channel.write(byteBuffer);
+ ByteBuffer byteBuffer2 = ByteBuffer.allocate((int) channel.size());
+ channel.read(byteBuffer2);
+ String content = new String(byteBuffer.array());
+ Assert.assertTrue("Nothing has been writen into file!", content.length() > 0);
+ Assert.assertTrue("Content has been writen into file improperly!", content.startsWith("test string#"));
+ } catch (IOException ioException) {
+ Assert.fail("Exception occurs during writing into file!");
+ }
+
+ // 3. Closing file system.
+ closeFileSystem(fileSystem);
+ }
+
+ /**
+ *
+ * Basic file system operations.
+ *
+ *
+ *
+ * Description: We are doing next operations:
+ *
+ * - Create new file system
+ * - Creating new directory
+ * - Copy file to newly create directory
+ * - Moving file to newly create directory
+ * - Listing newly create directory
+ * - Deleting file from newly created directory
+ * - Closing file system
+ *
+ *
+ */
+ @Test
+ public void fileSystemOperations() {
+ // 1. Creating new file system.
+ FileSystem fileSystem = createNewFileSystem();
+
+ Path resourceFile1 = resourceNameToPath(RESOURCE_FILE_1);
+ Path resourceFile2 = resourceNameToPath(RESOURCE_FILE_2);
+
+ // 2. Creating new directory.
+ Path newDirectory = fileSystem.getPath(NEW_DIRECTORY);
+ try {
+ Files.createDirectory(newDirectory);
+ } catch (IOException ioException) {
+ Assert.fail("Exception occurs during creating new directory!");
+ }
+
+ // 3. Copy file to newly create directory.
+ Path destination = fileSystem.getPath(newDirectory.toString(),
+ resourceFile1.getName(resourceFile1.getNameCount() - 1).toString());
+ try {
+ Files.copy(resourceFile1, destination, StandardCopyOption.REPLACE_EXISTING);
+ try (SeekableByteChannel channel = Files.newByteChannel(resourceFile1, StandardOpenOption.READ)) {
+ ByteBuffer byteBuffer = ByteBuffer.allocate((int) channel.size());
+ channel.read(byteBuffer);
+ String content = new String(byteBuffer.array());
+ Assert.assertTrue("Nothing has been read from new file!", content.length() > 0);
+ }
+ } catch (IOException ioException) {
+ Assert.fail("Exception occurs during copying file into new directory!");
+ }
+
+ // 4. Moving file to newly create directory.
+ destination = fileSystem.getPath(newDirectory.toString(),
+ resourceFile2.getName(resourceFile2.getNameCount() - 1).toString());
+ try {
+ Files.move(resourceFile2, destination, StandardCopyOption.REPLACE_EXISTING);
+ try (SeekableByteChannel channel = Files.newByteChannel(destination, StandardOpenOption.READ)) {
+ ByteBuffer byteBuffer = ByteBuffer.allocate((int) channel.size());
+ channel.read(byteBuffer);
+ String content = new String(byteBuffer.array());
+ Assert.assertTrue("Nothing has been read from new file!", content.length() > 0);
+ }
+ } catch (IOException ioException) {
+ Assert.fail("Exception occurs during moving file into new directory!");
+ }
+
+ try {
+ Files.newByteChannel(resourceFile2, StandardOpenOption.READ);
+ Assert.fail("File is still existing after deletion!");
+ } catch (IOException ignored) {
+ }
+
+ // 5. Listing newly create directory.
+ try (DirectoryStream directoryStream = Files.newDirectoryStream(newDirectory)) {
+ Iterator iterator = directoryStream.iterator();
+ boolean anyEntry = false;
+ while (iterator.hasNext()) {
+ Path path = iterator.next();
+ Assert.assertNotNull("Path is null!", path);
+ anyEntry = true;
+ }
+ Assert.assertTrue("New directory is empty!", anyEntry);
+ } catch (IOException e) {
+ Assert.fail("Exception occurs during listing new directory!");
+ }
+
+ // 6. Deleting file from newly created directory.
+ Path target = fileSystem.getPath(newDirectory.toString(),
+ resourceFile1.getName(resourceFile1.getNameCount() - 1).toString());
+ try {
+ Files.delete(target);
+ try {
+ Files.newByteChannel(target, StandardOpenOption.READ);
+ Assert.fail("File is still existing after deletion!");
+ } catch (IOException ignored) {
+ }
+ } catch (IOException ignored) {
+ Assert.fail("Exception occurs during file deletion!");
+ }
+
+ // 7. Closing file system.
+ closeFileSystem(fileSystem);
+ }
+
+ /**
+ *
+ * Reading file/directory attributes.
+ *
+ *
+ *
+ * Description: We are doing next operations:
+ *
+ * - Create new file system
+ * - Reading file attributes
+ * - Reading directory attributes
+ * - Closing file system
+ *
+ *
+ */
+ @Test
+ public void readingFileAttributes() {
+ // 1. Creating new file system.
+ FileSystem fileSystem = createNewFileSystem();
+
+ Path resourceDirectory = fileSystem.getPath(RESOURCE_DIR);
+ Path resourceFile1 = resourceNameToPath(RESOURCE_FILE_1);
+
+ try {
+ // 2. Reading file attributes.
+ BasicFileAttributes fileAttributes = Files.readAttributes(resourceFile1,
+ BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS);
+ Assert.assertNotNull("fileAttributes.lastAccessTime is null!", fileAttributes.lastAccessTime());
+ Assert.assertNotNull("fileAttributes.creationTime is null!", fileAttributes.creationTime());
+ Assert.assertNotNull("fileAttributes.lastModifiedTime is null!", fileAttributes.lastModifiedTime());
+ Assert.assertFalse("fileAttributes.isDirectory is true!", fileAttributes.isDirectory());
+ Assert.assertTrue("fileAttributes.isRegularFile is false!", fileAttributes.isRegularFile());
+ Assert.assertFalse("fileAttributes.isOther is true!", fileAttributes.isOther());
+ Assert.assertFalse("fileAttributes.isSymbolicLink is true!", fileAttributes.isSymbolicLink());
+ Assert.assertNull("fileAttributes.fileKey is not null!", fileAttributes.fileKey());
+
+ // 3. Reading directory attributes.
+ BasicFileAttributes directoryAttributes = Files.readAttributes(resourceDirectory,
+ BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS);
+ Assert.assertNotNull("directoryAttributes.lastAccessTime is null!", directoryAttributes.lastAccessTime());
+ Assert.assertNotNull("directoryAttributes.creationTime is null!", directoryAttributes.creationTime());
+ Assert.assertNotNull("directoryAttributes.lastModifiedTime is null!", directoryAttributes.lastModifiedTime());
+ Assert.assertTrue("directoryAttributes.isDirectory is false!", directoryAttributes.isDirectory());
+ Assert.assertFalse("directoryAttributes.isRegularFile is true!", directoryAttributes.isRegularFile());
+ Assert.assertFalse("directoryAttributes.isOther is true!", directoryAttributes.isOther());
+ Assert.assertFalse("directoryAttributes.isSymbolicLink is true!", directoryAttributes.isSymbolicLink());
+ Assert.assertNull("directoryAttributes.fileKey is not null!", directoryAttributes.fileKey());
+ } catch (IOException e) {
+ Assert.fail("Exception occurs during attributes operations!");
+ }
+
+ // 4. Closing file system.
+ closeFileSystem(fileSystem);
+ }
+
+ /**
+ *
+ * Writing file attributes.
+ *
+ *
+ *
+ * Description: We are doing next operations:
+ *
+ * - Create new file system
+ * - Writing file attributes
+ * - Closing file system
+ *
+ *
+ */
+ @Test
+ public void writingFileAttributes() {
+ // 1. Creating new file system.
+ FileSystem fileSystem = createNewFileSystem();
+ Path resourceFile1 = resourceNameToPath(RESOURCE_FILE_1);
+
+ try {
+ // 2. Writing file attributes.
+ long lastModifiedTime = System.currentTimeMillis() + TIME_SPAN;
+ Files.setAttribute(resourceFile1, "lastModifiedTime", FileTime.fromMillis(lastModifiedTime),
+ LinkOption.NOFOLLOW_LINKS);
+ BasicFileAttributes fileAttributes = Files.readAttributes(resourceFile1, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS);
+ Assert.assertEquals("lastModifiedTime is not set properly!", fileAttributes.lastModifiedTime(),
+ FileTime.fromMillis(lastModifiedTime));
+ } catch (IOException e) {
+ Assert.fail("Exception occurs during attributes operations!");
+ }
+
+ // 3. Closing file system.
+ closeFileSystem(fileSystem);
+ }
+}
diff --git a/substratevm/src/com.oracle.svm.test/src/resources/resource-test1.txt b/substratevm/src/com.oracle.svm.test/src/resources/resource-test1.txt
new file mode 100644
index 0000000000000..f16f70c912708
--- /dev/null
+++ b/substratevm/src/com.oracle.svm.test/src/resources/resource-test1.txt
@@ -0,0 +1 @@
+Java is awesome!
\ No newline at end of file
diff --git a/substratevm/src/com.oracle.svm.test/src/resources/resource-test2.txt b/substratevm/src/com.oracle.svm.test/src/resources/resource-test2.txt
new file mode 100644
index 0000000000000..549dea6322311
--- /dev/null
+++ b/substratevm/src/com.oracle.svm.test/src/resources/resource-test2.txt
@@ -0,0 +1 @@
+Native image is awesome!
\ No newline at end of file
diff --git a/substratevm/src/com.oracle.svm.truffle.tck/src/com/oracle/svm/truffle/tck/resources/jre.json b/substratevm/src/com.oracle.svm.truffle.tck/src/com/oracle/svm/truffle/tck/resources/jre.json
index 24d75e18ab79f..379395e023b0e 100644
--- a/substratevm/src/com.oracle.svm.truffle.tck/src/com/oracle/svm/truffle/tck/resources/jre.json
+++ b/substratevm/src/com.oracle.svm.truffle.tck/src/com/oracle/svm/truffle/tck/resources/jre.json
@@ -214,8 +214,7 @@
"methods" : [
{ "name" : "createURL" ,
"parameterTypes" : [
- "java.lang.String",
- "byte[]"
+ "java.lang.String"
]}
]
},