diff --git a/pom.xml b/pom.xml
index e5c1ae2d2..e9ecc1151 100644
--- a/pom.xml
+++ b/pom.xml
@@ -96,15 +96,22 @@
true
org.osgi
osgi.annotation
- 7.0.0
+ 8.0.0
provided
+
+ org.osgi
+ osgi.core
+ 8.0.0
+ provided
+
+
diff --git a/src/main/java/org/bytedeco/javacpp/Loader.java b/src/main/java/org/bytedeco/javacpp/Loader.java
index 9015b9e33..cd8ce94ea 100644
--- a/src/main/java/org/bytedeco/javacpp/Loader.java
+++ b/src/main/java/org/bytedeco/javacpp/Loader.java
@@ -22,6 +22,10 @@
package org.bytedeco.javacpp;
+import static org.bytedeco.javacpp.tools.OSGiBundleResourceLoader.getOSGiClassLoaderResources;
+import static org.bytedeco.javacpp.tools.OSGiBundleResourceLoader.getOSGiClassResource;
+import static org.bytedeco.javacpp.tools.OSGiBundleResourceLoader.isOSGiRuntime;
+
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
@@ -55,12 +59,14 @@
import java.util.WeakHashMap;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
+
import org.bytedeco.javacpp.annotation.Cast;
import org.bytedeco.javacpp.annotation.Name;
import org.bytedeco.javacpp.annotation.Platform;
import org.bytedeco.javacpp.annotation.Raw;
import org.bytedeco.javacpp.tools.Builder;
import org.bytedeco.javacpp.tools.Logger;
+import org.bytedeco.javacpp.tools.OSGiBundleResourceLoader;
/**
* The Loader contains functionality to load native libraries, but also has a bit
@@ -522,6 +528,21 @@ public static File cacheResource(URL resourceURL, String target) throws IOExcept
if (!noSubdir) {
cacheSubdir = new File(cacheSubdir, urlFile.getParentFile().getName());
}
+ } else if (isOSGiRuntime()) {
+ // TODO: what happens if this is another URL in a OSGi environment?
+ // I think it is unlikely that is called with URL-schema?!
+ if (!noSubdir) {
+ String subdirName = OSGiBundleResourceLoader.getOSGiContainerBundleName(resourceURL);
+ if (subdirName != null) {
+ String parentName = urlFile.getParentFile().toString();
+ if (parentName != null) {
+ subdirName = subdirName + File.separator + parentName;
+ }
+ cacheSubdir = new File(cacheSubdir, subdirName);
+ }
+ size = urlConnection.getContentLengthLong();
+ timestamp = urlConnection.getLastModified();
+ }
} else {
if (urlFile.exists()) {
size = urlFile.length();
@@ -745,10 +766,10 @@ public static File extractResource(URL resourceURL, File directoryOrFile,
public static File extractResource(URL resourceURL, File directoryOrFile,
String prefix, String suffix, boolean cacheDirectory) throws IOException {
URLConnection urlConnection = resourceURL != null ? resourceURL.openConnection() : null;
+ long start = System.currentTimeMillis();
if (urlConnection instanceof JarURLConnection) {
- JarFile jarFile = ((JarURLConnection)urlConnection).getJarFile();
- JarEntry jarEntry = ((JarURLConnection)urlConnection).getJarEntry();
- String jarFileName = jarFile.getName();
+ JarFile jarFile = ((JarURLConnection) urlConnection).getJarFile();
+ JarEntry jarEntry = ((JarURLConnection) urlConnection).getJarEntry();
String jarEntryName = jarEntry.getName();
if (!jarEntryName.endsWith("/")) {
jarEntryName += "/";
@@ -765,20 +786,47 @@ public static File extractResource(URL resourceURL, File directoryOrFile,
File file = new File(directoryOrFile, entryName.substring(jarEntryName.length()));
if (entry.isDirectory()) {
file.mkdirs();
- } else if (!cacheDirectory || !file.exists() || file.length() != entrySize
- || file.lastModified() != entryTimestamp || !file.equals(file.getCanonicalFile())) {
+ } else if (!cacheDirectory || isCacheFileCurrent(file, entrySize, entryTimestamp)) {
// ... extract it from our resources ...
file.delete();
String s = resourceURL.toString();
URL u = new URL(s.substring(0, s.indexOf("!/") + 2) + entryName);
+ // FIXME: check if directories have to be extracted again?!
file = extractResource(u, file, prefix, suffix);
}
file.setLastModified(entryTimestamp);
}
}
+ System.out.println("Extract took " + (System.currentTimeMillis() - start) + "ms, for:" + resourceURL);
return directoryOrFile;
}
}
+ if (isOSGiRuntime()) {
+ Enumeration directoryEntries = OSGiBundleResourceLoader.getOSGiBundleDirectoryContent(resourceURL);
+ if (directoryEntries != null && directoryEntries.hasMoreElements()) { // a not empty directory
+ String directoryName = resourceURL.getPath();
+ while (directoryEntries.hasMoreElements()) {
+ URL entry = directoryEntries.nextElement();
+ String entryName = entry.getPath();
+ URLConnection entryConnection = entry.openConnection();
+ long entrySize = entryConnection.getContentLengthLong();
+ long entryTimestamp = entryConnection.getLastModified();
+ File file = new File(directoryOrFile, entryName.substring(directoryName.length()));
+ if (entryName.endsWith("/")) { // is directory
+ file.mkdirs();
+ } else if (!cacheDirectory || isCacheFileCurrent(file, entrySize, entryTimestamp)) {
+ // ... extract it from our resources ...
+ file.delete();
+ // FIXME: check if directories have to be extracted again?!
+ file = extractResource(entry, file, prefix, suffix);
+ }
+ file.setLastModified(entryTimestamp);
+ }
+ System.out.println("Extract took " + (System.currentTimeMillis() - start) + "ms, for:" + resourceURL);
+ return directoryOrFile;
+ }
+ }
+
InputStream is = urlConnection != null ? urlConnection.getInputStream() : null;
OutputStream os = null;
if (is == null) {
@@ -810,6 +858,7 @@ public static File extractResource(URL resourceURL, File directoryOrFile,
} else {
file = File.createTempFile(prefix, suffix, directoryOrFile);
}
+ System.out.println("Extract resource (" + resourceURL + ") to " + file);
file.delete();
os = new FileOutputStream(file);
byte[] buffer = new byte[64 * 1024];
@@ -831,6 +880,11 @@ public static File extractResource(URL resourceURL, File directoryOrFile,
return file;
}
+ private static boolean isCacheFileCurrent(File file, long entrySize, long entryTimestamp) throws IOException {
+ return !file.exists() || file.length() != entrySize || file.lastModified() != entryTimestamp
+ || !file.equals(file.getCanonicalFile());
+ }
+
/** Returns {@code findResources(cls, name, 1)[0]} or null if none. */
public static URL findResource(Class cls, String name) throws IOException {
URL[] url = findResources(cls, name, 1);
@@ -862,7 +916,7 @@ public static URL[] findResources(Class cls, String name, int maxLength) throws
}
// Under JPMS, Class.getResource() and ClassLoader.getResources() do not return the same URLs
- URL url = cls.getResource(name);
+ URL url = !isOSGiRuntime() ? cls.getResource(name) : getOSGiClassResource(cls, name);
if (url != null && maxLength == 1) {
return new URL[] {url};
}
@@ -882,7 +936,7 @@ public static URL[] findResources(Class cls, String name, int maxLength) throws
// This is the bootstrap class loader, let's try the system class loader instead
classLoader = ClassLoader.getSystemClassLoader();
}
- Enumeration urls = classLoader.getResources(path + name);
+ Enumeration urls = !isOSGiRuntime() ? classLoader.getResources(path + name) : getOSGiClassLoaderResources(classLoader, path + name);
ArrayList array = new ArrayList();
if (url != null) {
array.add(url);
@@ -894,7 +948,7 @@ public static URL[] findResources(Class cls, String name, int maxLength) throws
} else {
path = "";
}
- urls = classLoader.getResources(path + name);
+ urls = !isOSGiRuntime() ? classLoader.getResources(path + name) : getOSGiClassLoaderResources(classLoader, path + name);
}
while (urls.hasMoreElements() && (maxLength < 0 || array.size() < maxLength)) {
url = urls.nextElement();
diff --git a/src/main/java/org/bytedeco/javacpp/tools/OSGiBundleResourceLoader.java b/src/main/java/org/bytedeco/javacpp/tools/OSGiBundleResourceLoader.java
new file mode 100644
index 000000000..a451fce37
--- /dev/null
+++ b/src/main/java/org/bytedeco/javacpp/tools/OSGiBundleResourceLoader.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2021-2021 Hannes Wellmann
+ *
+ * Licensed either under the Apache License, Version 2.0, or (at your option)
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation (subject to the "Classpath" exception),
+ * either version 2, or any later version (collectively, the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.gnu.org/licenses/
+ * http://www.gnu.org/software/classpath/license.html
+ *
+ * or as provided in the LICENSE.txt file that accompanied this code.
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.bytedeco.javacpp.tools;
+
+import java.io.IOException;
+import java.lang.ref.WeakReference;
+import java.net.URL;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Optional;
+import java.util.WeakHashMap;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.Version;
+
+public class OSGiBundleResourceLoader {
+
+ private OSGiBundleResourceLoader() { // static use only
+ }
+
+ private static final boolean IS_OSGI_RUNTIME;
+
+ static {
+ boolean isOSGI;
+ try {
+ Bundle.class.getName();
+ isOSGI = true;
+ } catch (NoClassDefFoundError e) {
+ isOSGI = false;
+ }
+ IS_OSGI_RUNTIME = isOSGI;
+ }
+
+ public static boolean isOSGiRuntime() {
+ return IS_OSGI_RUNTIME;
+ }
+
+ public static String getOSGiContainerBundleName(URL resourceURL) {
+ requireOSGi();
+ return OSGiEnvironmentLoader.getContainerBundleName(resourceURL);
+ }
+
+ public static Enumeration getOSGiBundleDirectoryContent(URL resourceURL) {
+ requireOSGi();
+ return OSGiEnvironmentLoader.getBundleDirectoryContent(resourceURL);
+ }
+
+ public static URL getOSGiClassResource(Class> c, String name) {
+ requireOSGi();
+ return OSGiEnvironmentLoader.getClassResource(c, name);
+ }
+
+ public static Enumeration getOSGiClassLoaderResources(ClassLoader cl, String name) throws IOException {
+ requireOSGi();
+ return OSGiEnvironmentLoader.getClassLoaderResources(cl, name);
+ }
+
+ private static void requireOSGi() {
+ if (!IS_OSGI_RUNTIME) {
+ throw new IllegalStateException(OSGiBundleResourceLoader.class.getSimpleName() + " must only be used within a OSGi runtime");
+ }
+ }
+
+ private static class OSGiEnvironmentLoader {
+ // Code using OSGi APIs has to be encapsulated into own class
+ // to prevent NoClassDefFoundErrors in OSGi environments
+
+ private static final ReadWriteLock LOCK = new ReentrantReadWriteLock();
+ private static final WeakHashMap> URL_TO_BUNDLE = new WeakHashMap<>();
+
+ private static void associateURLtoBundle(URL resource, Bundle bundle) {
+ URL_TO_BUNDLE.put(resource, new WeakReference<>(bundle));
+ }
+
+ private static Bundle getContainerBundle(URL url) {
+ WeakReference bundle;
+ LOCK.readLock().lock();
+ try {
+ bundle = URL_TO_BUNDLE.get(url);
+ } finally {
+ LOCK.readLock().unlock();
+ }
+ return bundle != null ? bundle.get() : null;
+ }
+
+ private static URL getClassResource(Class> c, String name) {
+ URL resource = c.getResource(name);
+ if (resource != null) {
+ Bundle bundle = FrameworkUtil.getBundle(c);
+ LOCK.writeLock().lock();
+ try {
+ associateURLtoBundle(resource, bundle);
+ } finally {
+ LOCK.writeLock().unlock();
+ }
+ }
+ return resource;
+ }
+
+ private static Enumeration getClassLoaderResources(ClassLoader cl, String name) throws IOException {
+ Enumeration resources = cl.getResources(name);
+ if (resources != null && resources.hasMoreElements()) {
+ Optional bundleOpt = FrameworkUtil.getBundle(cl);
+ if (bundleOpt.isPresent()) {
+ Bundle bundle = bundleOpt.get();
+ List resourcesList = Collections.list(resources);
+ LOCK.writeLock().lock();
+ try {
+ for (URL url : resourcesList) {
+ associateURLtoBundle(url, bundle);
+ }
+ } finally {
+ LOCK.writeLock().unlock();
+ }
+ return Collections.enumeration(resourcesList);
+ }
+ }
+ return resources;
+ }
+
+ private static String getContainerBundleName(URL resourceURL) {
+ Bundle bundle = getContainerBundle(resourceURL);
+ if (bundle != null) {
+ Version v = bundle.getVersion();
+ String version = v.getMajor() + "." + v.getMinor() + "." + v.getMicro(); // skip qualifier
+ return bundle.getSymbolicName() + "_" + version;
+ }
+ return null;
+ }
+
+ private static Enumeration getBundleDirectoryContent(URL resourceURL) {
+ Bundle bundle = getContainerBundle(resourceURL);
+ if (bundle != null) {
+ return bundle.findEntries(resourceURL.getPath(), null, true);
+ }
+ return null;
+ }
+ }
+}