From 5305155265572250d8dfd13c8d38c45b46650d83 Mon Sep 17 00:00:00 2001 From: Hannes Wellmann <44067969+HannesWell@users.noreply.github.com> Date: Tue, 17 Aug 2021 15:17:29 +0200 Subject: [PATCH 1/5] Support directory extraction in modular OSGi runtimes (issue #506) --- pom.xml | 7 ++ .../java/org/bytedeco/javacpp/Loader.java | 103 +++++++++++++++--- 2 files changed, 93 insertions(+), 17 deletions(-) diff --git a/pom.xml b/pom.xml index e5c1ae2d2..7afc8645f 100644 --- a/pom.xml +++ b/pom.xml @@ -105,6 +105,13 @@ 7.0.0 provided + + org.osgi + osgi.core + 7.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..411167a2c 100644 --- a/src/main/java/org/bytedeco/javacpp/Loader.java +++ b/src/main/java/org/bytedeco/javacpp/Loader.java @@ -55,12 +55,16 @@ 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.osgi.framework.Bundle; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.Version; /** * The Loader contains functionality to load native libraries, but also has a bit @@ -89,6 +93,18 @@ public class Loader { } }; + 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 class Detector { /** * Returns either the value of the "org.bytedeco.javacpp.platform" @@ -414,7 +430,7 @@ public static File cacheResource(String name) throws IOException { */ public static File cacheResource(Class cls, String name) throws IOException { URL u = findResource(cls, name); - return u != null ? cacheResource(u) : null; + return u != null ? cacheResource(cls, u, null) : null; } /** @@ -440,14 +456,14 @@ public static File[] cacheResources(Class cls, String name) throws IOException { URL[] urls = findResources(cls, name); File[] files = new File[urls.length]; for (int i = 0; i < urls.length; i++) { - files[i] = cacheResource(urls[i]); + files[i] = cacheResource(cls, urls[i], null); } return files; } /** Returns {@code cacheResource(resourceUrl, null)} */ public static File cacheResource(URL resourceURL) throws IOException { - return cacheResource(resourceURL, null); + return cacheResource(null, resourceURL, null); } /** * Extracts a resource, if the size or last modified timestamp differs from what is in cache, @@ -462,6 +478,10 @@ public static File cacheResource(URL resourceURL) throws IOException { * @see #cacheDir */ public static File cacheResource(URL resourceURL, String target) throws IOException { + return cacheResource(null, resourceURL, target); + } + + private static File cacheResource(Class cls, URL resourceURL, String target) throws IOException { // Find appropriate subdirectory in cache for the resource ... File urlFile; String[] splitURL = resourceURL.toString().split("#"); @@ -522,6 +542,21 @@ public static File cacheResource(URL resourceURL, String target) throws IOExcept if (!noSubdir) { cacheSubdir = new File(cacheSubdir, urlFile.getParentFile().getName()); } + } else if (IS_OSGI_RUNTIME && cls != null) { + size = urlConnection.getContentLengthLong(); + timestamp = urlConnection.getLastModified(); + + Bundle bundle = FrameworkUtil.getBundle(cls); + if (bundle != null) { + Version v = bundle.getVersion(); + String version = v.getMajor() + "." + v.getMinor() + "." + v.getMicro(); // skip qualifier + String subdirName = bundle.getSymbolicName() + "_" + version; + String parentName = urlFile.getParentFile().toString(); + if (parentName != null) { + subdirName = subdirName + File.separator + parentName; + } + cacheSubdir = new File(cacheSubdir, subdirName); + } } else { if (urlFile.exists()) { size = urlFile.length(); @@ -654,7 +689,7 @@ public static File cacheResource(URL resourceURL, String target) throws IOExcept logger.debug("Extracting " + resourceURL); } file.delete(); - extractResource(resourceURL, file, null, null, true); + extractResource(cls, resourceURL, file, null, null, true); file.setLastModified(timestamp); } } finally { @@ -692,7 +727,7 @@ public static File extractResource(String name, File directory, public static File extractResource(Class cls, String name, File directory, String prefix, String suffix) throws IOException { URL u = findResource(cls, name); - return u != null ? extractResource(u, directory, prefix, suffix) : null; + return u != null ? extractResource(cls, u, directory, prefix, suffix, false) : null; } /** @@ -718,7 +753,7 @@ public static File[] extractResources(Class cls, String name, File directory, URL[] urls = findResources(cls, name); File[] files = new File[urls.length]; for (int i = 0; i < urls.length; i++) { - files[i] = extractResource(urls[i], directory, prefix, suffix); + files[i] = extractResource(cls, urls[i], directory, prefix, suffix, false); } return files; } @@ -726,7 +761,7 @@ public static File[] extractResources(Class cls, String name, File directory, /** Returns {@code extractResource(resourceURL, directoryOrFile, prefix, suffix, false)}. */ public static File extractResource(URL resourceURL, File directoryOrFile, String prefix, String suffix) throws IOException { - return extractResource(resourceURL, directoryOrFile, prefix, suffix, false); + return extractResource(null, resourceURL, directoryOrFile, prefix, suffix, false); } /** @@ -744,11 +779,16 @@ public static File extractResource(URL resourceURL, File directoryOrFile, */ public static File extractResource(URL resourceURL, File directoryOrFile, String prefix, String suffix, boolean cacheDirectory) throws IOException { + return extractResource(null, directoryOrFile, prefix, suffix, cacheDirectory); + } + + private static File extractResource(Class cls, URL resourceURL, File directoryOrFile, String prefix, String suffix, + boolean cacheDirectory) throws IOException { URLConnection urlConnection = resourceURL != null ? resourceURL.openConnection() : null; + String resourceURLStr = resourceURL.toString(); if (urlConnection instanceof JarURLConnection) { JarFile jarFile = ((JarURLConnection)urlConnection).getJarFile(); JarEntry jarEntry = ((JarURLConnection)urlConnection).getJarEntry(); - String jarFileName = jarFile.getName(); String jarEntryName = jarEntry.getName(); if (!jarEntryName.endsWith("/")) { jarEntryName += "/"; @@ -759,9 +799,9 @@ public static File extractResource(URL resourceURL, File directoryOrFile, while (entries.hasMoreElements()) { JarEntry entry = entries.nextElement(); String entryName = entry.getName(); - long entrySize = entry.getSize(); - long entryTimestamp = entry.getTime(); if (entryName.startsWith(jarEntryName)) { + long entrySize = entry.getSize(); + long entryTimestamp = entry.getTime(); File file = new File(directoryOrFile, entryName.substring(jarEntryName.length())); if (entry.isDirectory()) { file.mkdirs(); @@ -769,9 +809,8 @@ public static File extractResource(URL resourceURL, File directoryOrFile, || file.lastModified() != entryTimestamp || !file.equals(file.getCanonicalFile())) { // ... extract it from our resources ... file.delete(); - String s = resourceURL.toString(); - URL u = new URL(s.substring(0, s.indexOf("!/") + 2) + entryName); - file = extractResource(u, file, prefix, suffix); + URL u = new URL(resourceURLStr.substring(0, resourceURLStr.indexOf("!/") + 2) + entryName); + file = extractResource(cls, u, file, prefix, suffix, false); } file.setLastModified(entryTimestamp); } @@ -779,6 +818,36 @@ public static File extractResource(URL resourceURL, File directoryOrFile, return directoryOrFile; } } + if (IS_OSGI_RUNTIME && cls != null) { + // TODO: check if the URL is connected to the given class, respectively obtain + // the bundle from the URL. + Bundle bundle = FrameworkUtil.getBundle(cls); + if (bundle != null) { + String path = resourceURL.getPath(); + Enumeration entries = bundle.findEntries(path, null, true); + if (entries != null && entries.hasMoreElements()) { // a not empty directory + while (entries.hasMoreElements()) { + URL entry = entries.nextElement(); + String entryPath = entry.getPath(); + URLConnection entryConnection = entry.openConnection(); + long entrySize = entryConnection.getContentLengthLong(); + long entryTimestamp = entryConnection.getLastModified(); + File file = new File(directoryOrFile, entryPath.substring(path.length())); + if (entryPath.endsWith("/")) { // is directory + file.mkdirs(); + } else if (!cacheDirectory || !file.exists() || file.length() != entrySize + || file.lastModified() != entryTimestamp || !file.equals(file.getCanonicalFile())) { + // ... extract it from our resources ... + file.delete(); + // optimization: pass null-class to not check for directories again + extractResource(null, entry, file, prefix, suffix, false); + } + file.setLastModified(entryTimestamp); + } + return directoryOrFile; + } + } + } InputStream is = urlConnection != null ? urlConnection.getInputStream() : null; OutputStream os = null; if (is == null) { @@ -795,7 +864,7 @@ public static File extractResource(URL resourceURL, File directoryOrFile, if (directoryOrFile.isDirectory()) { directory = directoryOrFile; try { - file = new File(directoryOrFile, new File(new URI(resourceURL.toString().split("#")[0])).getName()); + file = new File(directoryOrFile, new File(new URI(resourceURLStr.split("#")[0])).getName()); } catch (IllegalArgumentException | URISyntaxException ex) { file = new File(directoryOrFile, new File(resourceURL.getPath()).getName()); } @@ -1283,7 +1352,7 @@ public static String load(Class cls, Properties properties, boolean pathsFirst, for (String preload : preloads) { URL[] urls = findLibrary(cls, p, preload, true); for (URL url : urls) { - File f = cacheResource(url); + File f = cacheResource(cls, url, null); if (f != null) { f.setExecutable(true); break; @@ -1309,7 +1378,7 @@ public static String load(Class cls, Properties properties, boolean pathsFirst, f.set(u, filename2); } } - File f = cacheResource(u); + File f = cacheResource(cls, u, null); if (f != null) { f.setExecutable(true); executablePaths.put(e, f.getAbsolutePath()); @@ -1596,7 +1665,7 @@ public static synchronized String loadLibrary(Class cls, URL[] urls, String l file = new File(uri); } catch (Exception exc) { // ... extract it from resources into the cache, if necessary ... - File f = cacheResource(url, filename); + File f = cacheResource(cls, url, filename); try { if (f != null) { file = f; From e5399d1bdfb8d461b055f27401af6f163bd168ef Mon Sep 17 00:00:00 2001 From: Hannes Wellmann <44067969+HannesWell@users.noreply.github.com> Date: Wed, 18 Aug 2021 14:22:52 +0200 Subject: [PATCH 2/5] [SQUASH] extract bundle from URL and move OSGi related code to own class - fixes extraction paths and OSGi related code into own class - prevents NoClassDefFoundError in non-OSGi environments --- .../java/org/bytedeco/javacpp/Loader.java | 147 +++++++-------- .../tools/OSGiBundleResourceLoader.java | 178 ++++++++++++++++++ 2 files changed, 241 insertions(+), 84 deletions(-) create mode 100644 src/main/java/org/bytedeco/javacpp/tools/OSGiBundleResourceLoader.java diff --git a/src/main/java/org/bytedeco/javacpp/Loader.java b/src/main/java/org/bytedeco/javacpp/Loader.java index 411167a2c..b5070a0d5 100644 --- a/src/main/java/org/bytedeco/javacpp/Loader.java +++ b/src/main/java/org/bytedeco/javacpp/Loader.java @@ -62,9 +62,7 @@ import org.bytedeco.javacpp.annotation.Raw; import org.bytedeco.javacpp.tools.Builder; import org.bytedeco.javacpp.tools.Logger; -import org.osgi.framework.Bundle; -import org.osgi.framework.FrameworkUtil; -import org.osgi.framework.Version; +import org.bytedeco.javacpp.tools.OSGiBundleResourceLoader; /** * The Loader contains functionality to load native libraries, but also has a bit @@ -93,18 +91,6 @@ public class Loader { } }; - 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 class Detector { /** * Returns either the value of the "org.bytedeco.javacpp.platform" @@ -430,7 +416,7 @@ public static File cacheResource(String name) throws IOException { */ public static File cacheResource(Class cls, String name) throws IOException { URL u = findResource(cls, name); - return u != null ? cacheResource(cls, u, null) : null; + return u != null ? cacheResource(u) : null; } /** @@ -456,14 +442,14 @@ public static File[] cacheResources(Class cls, String name) throws IOException { URL[] urls = findResources(cls, name); File[] files = new File[urls.length]; for (int i = 0; i < urls.length; i++) { - files[i] = cacheResource(cls, urls[i], null); + files[i] = cacheResource(urls[i]); } return files; } /** Returns {@code cacheResource(resourceUrl, null)} */ public static File cacheResource(URL resourceURL) throws IOException { - return cacheResource(null, resourceURL, null); + return cacheResource(resourceURL, null); } /** * Extracts a resource, if the size or last modified timestamp differs from what is in cache, @@ -478,10 +464,6 @@ public static File cacheResource(URL resourceURL) throws IOException { * @see #cacheDir */ public static File cacheResource(URL resourceURL, String target) throws IOException { - return cacheResource(null, resourceURL, target); - } - - private static File cacheResource(Class cls, URL resourceURL, String target) throws IOException { // Find appropriate subdirectory in cache for the resource ... File urlFile; String[] splitURL = resourceURL.toString().split("#"); @@ -542,22 +524,22 @@ private static File cacheResource(Class cls, URL resourceURL, String target) if (!noSubdir) { cacheSubdir = new File(cacheSubdir, urlFile.getParentFile().getName()); } - } else if (IS_OSGI_RUNTIME && cls != null) { - size = urlConnection.getContentLengthLong(); - timestamp = urlConnection.getLastModified(); - - Bundle bundle = FrameworkUtil.getBundle(cls); - if (bundle != null) { - Version v = bundle.getVersion(); - String version = v.getMajor() + "." + v.getMinor() + "." + v.getMicro(); // skip qualifier - String subdirName = bundle.getSymbolicName() + "_" + version; - String parentName = urlFile.getParentFile().toString(); - if (parentName != null) { - subdirName = subdirName + File.separator + parentName; - } - cacheSubdir = new File(cacheSubdir, subdirName); - } - } else { + } else if (OSGiBundleResourceLoader.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.getContainerBundleName(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(); timestamp = urlFile.lastModified(); @@ -689,7 +671,7 @@ private static File cacheResource(Class cls, URL resourceURL, String target) logger.debug("Extracting " + resourceURL); } file.delete(); - extractResource(cls, resourceURL, file, null, null, true); + extractResource(resourceURL, file, null, null, true); file.setLastModified(timestamp); } } finally { @@ -727,7 +709,7 @@ public static File extractResource(String name, File directory, public static File extractResource(Class cls, String name, File directory, String prefix, String suffix) throws IOException { URL u = findResource(cls, name); - return u != null ? extractResource(cls, u, directory, prefix, suffix, false) : null; + return u != null ? extractResource(u, directory, prefix, suffix) : null; } /** @@ -753,7 +735,7 @@ public static File[] extractResources(Class cls, String name, File directory, URL[] urls = findResources(cls, name); File[] files = new File[urls.length]; for (int i = 0; i < urls.length; i++) { - files[i] = extractResource(cls, urls[i], directory, prefix, suffix, false); + files[i] = extractResource(urls[i], directory, prefix, suffix); } return files; } @@ -761,7 +743,7 @@ public static File[] extractResources(Class cls, String name, File directory, /** Returns {@code extractResource(resourceURL, directoryOrFile, prefix, suffix, false)}. */ public static File extractResource(URL resourceURL, File directoryOrFile, String prefix, String suffix) throws IOException { - return extractResource(null, resourceURL, directoryOrFile, prefix, suffix, false); + return extractResource(resourceURL, directoryOrFile, prefix, suffix, false); } /** @@ -779,16 +761,12 @@ public static File extractResource(URL resourceURL, File directoryOrFile, */ public static File extractResource(URL resourceURL, File directoryOrFile, String prefix, String suffix, boolean cacheDirectory) throws IOException { - return extractResource(null, directoryOrFile, prefix, suffix, cacheDirectory); - } - - private static File extractResource(Class cls, URL resourceURL, File directoryOrFile, String prefix, String suffix, - boolean cacheDirectory) throws IOException { URLConnection urlConnection = resourceURL != null ? resourceURL.openConnection() : null; - String resourceURLStr = resourceURL.toString(); + long start = System.currentTimeMillis(); if (urlConnection instanceof JarURLConnection) { - JarFile jarFile = ((JarURLConnection)urlConnection).getJarFile(); - JarEntry jarEntry = ((JarURLConnection)urlConnection).getJarEntry(); + JarFile jarFile = ((JarURLConnection) urlConnection).getJarFile(); + JarEntry jarEntry = ((JarURLConnection) urlConnection).getJarEntry(); + String jarFileName = jarFile.getName(); String jarEntryName = jarEntry.getName(); if (!jarEntryName.endsWith("/")) { jarEntryName += "/"; @@ -799,9 +777,9 @@ private static File extractResource(Class cls, URL resourceURL, File directoryOr while (entries.hasMoreElements()) { JarEntry entry = entries.nextElement(); String entryName = entry.getName(); + long entrySize = entry.getSize(); + long entryTimestamp = entry.getTime(); if (entryName.startsWith(jarEntryName)) { - long entrySize = entry.getSize(); - long entryTimestamp = entry.getTime(); File file = new File(directoryOrFile, entryName.substring(jarEntryName.length())); if (entry.isDirectory()) { file.mkdirs(); @@ -809,45 +787,45 @@ private static File extractResource(Class cls, URL resourceURL, File directoryOr || file.lastModified() != entryTimestamp || !file.equals(file.getCanonicalFile())) { // ... extract it from our resources ... file.delete(); - URL u = new URL(resourceURLStr.substring(0, resourceURLStr.indexOf("!/") + 2) + entryName); - file = extractResource(cls, u, file, prefix, suffix, false); + 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 (IS_OSGI_RUNTIME && cls != null) { - // TODO: check if the URL is connected to the given class, respectively obtain - // the bundle from the URL. - Bundle bundle = FrameworkUtil.getBundle(cls); - if (bundle != null) { - String path = resourceURL.getPath(); - Enumeration entries = bundle.findEntries(path, null, true); - if (entries != null && entries.hasMoreElements()) { // a not empty directory - while (entries.hasMoreElements()) { - URL entry = entries.nextElement(); - String entryPath = entry.getPath(); - URLConnection entryConnection = entry.openConnection(); - long entrySize = entryConnection.getContentLengthLong(); - long entryTimestamp = entryConnection.getLastModified(); - File file = new File(directoryOrFile, entryPath.substring(path.length())); - if (entryPath.endsWith("/")) { // is directory - file.mkdirs(); - } else if (!cacheDirectory || !file.exists() || file.length() != entrySize - || file.lastModified() != entryTimestamp || !file.equals(file.getCanonicalFile())) { - // ... extract it from our resources ... - file.delete(); - // optimization: pass null-class to not check for directories again - extractResource(null, entry, file, prefix, suffix, false); - } - file.setLastModified(entryTimestamp); + if (OSGiBundleResourceLoader.isOSGiRuntime()) { + Enumeration directoryEntries = OSGiBundleResourceLoader.getBundleDirectoryContent(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 || !file.exists() || file.length() != entrySize + || file.lastModified() != entryTimestamp || !file.equals(file.getCanonicalFile())) { + // ... extract it from our resources ... + file.delete(); + // FIXME: check if directories have to be extracted again?! + file = extractResource(entry, file, prefix, suffix); } - return directoryOrFile; + 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) { @@ -864,7 +842,7 @@ private static File extractResource(Class cls, URL resourceURL, File directoryOr if (directoryOrFile.isDirectory()) { directory = directoryOrFile; try { - file = new File(directoryOrFile, new File(new URI(resourceURLStr.split("#")[0])).getName()); + file = new File(directoryOrFile, new File(new URI(resourceURL.toString().split("#")[0])).getName()); } catch (IllegalArgumentException | URISyntaxException ex) { file = new File(directoryOrFile, new File(resourceURL.getPath()).getName()); } @@ -879,6 +857,7 @@ private static File extractResource(Class cls, URL resourceURL, File directoryOr } 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]; @@ -1352,7 +1331,7 @@ public static String load(Class cls, Properties properties, boolean pathsFirst, for (String preload : preloads) { URL[] urls = findLibrary(cls, p, preload, true); for (URL url : urls) { - File f = cacheResource(cls, url, null); + File f = cacheResource(url); if (f != null) { f.setExecutable(true); break; @@ -1378,7 +1357,7 @@ public static String load(Class cls, Properties properties, boolean pathsFirst, f.set(u, filename2); } } - File f = cacheResource(cls, u, null); + File f = cacheResource(u); if (f != null) { f.setExecutable(true); executablePaths.put(e, f.getAbsolutePath()); @@ -1665,7 +1644,7 @@ public static synchronized String loadLibrary(Class cls, URL[] urls, String l file = new File(uri); } catch (Exception exc) { // ... extract it from resources into the cache, if necessary ... - File f = cacheResource(cls, url, filename); + File f = cacheResource(url, filename); try { if (f != null) { file = f; 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..96bd726af --- /dev/null +++ b/src/main/java/org/bytedeco/javacpp/tools/OSGiBundleResourceLoader.java @@ -0,0 +1,178 @@ +/* + * 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.net.URL; +import java.util.Enumeration; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; +import org.osgi.framework.BundleEvent; +import org.osgi.framework.BundleException; +import org.osgi.framework.BundleListener; +import org.osgi.framework.FrameworkEvent; +import org.osgi.framework.FrameworkListener; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.Version; + +public class OSGiBundleResourceLoader { + + private OSGiBundleResourceLoader() { // static use only + } + + public static boolean isOSGiRuntime() { + return IS_OSGI_RUNTIME; + } + + public static String getContainerBundleName(URL resourceURL) { + requireOSGi(); + return OSGiEnvironmentLoader.getContainerBundleName(resourceURL); + } + + public static Enumeration getBundleDirectoryContent(URL resourceURL) { + requireOSGi(); + return OSGiEnvironmentLoader.getBundleDirectoryContent(resourceURL); + } + + private static void requireOSGi() { + if (!IS_OSGI_RUNTIME) { + throw new IllegalStateException( + OSGiBundleResourceLoader.class.getSimpleName() + " must only be used within a OSGi runtime"); + } + } + + private static final Map HOST_2_BUNDLE = new ConcurrentHashMap(); + 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; + if (IS_OSGI_RUNTIME) { + OSGiEnvironmentLoader.initialize(); + } + } + + private static class OSGiEnvironmentLoader { + // Code using OSGi APIs has to be encapsulated into own class + // to prevent NoClassDefFoundErrors in OSGi environments + + private static void initialize() { + BundleContext context = getBundleContext(); + if (context != null) { + indexAllBundles(context); + context.addBundleListener(new BundleListener() { + @Override + public void bundleChanged(BundleEvent event) { + Bundle bundle = event.getBundle(); + switch (event.getType()) { + case BundleEvent.RESOLVED: + HOST_2_BUNDLE.put(getBundleURLHost(bundle), bundle.getBundleId()); + break; + case BundleEvent.UNRESOLVED: + HOST_2_BUNDLE.remove(getBundleURLHost(bundle)); + break; + default: + break; + } + } + }); + context.addFrameworkListener(new FrameworkListener() { + @Override + public void frameworkEvent(FrameworkEvent event) { + if (event.getType() == FrameworkEvent.PACKAGES_REFRESHED) { + HOST_2_BUNDLE.clear(); + indexAllBundles(getBundleContext()); + // don't keep a reference on the BundleContext + } + } + }); + } + } + + private static BundleContext getBundleContext() { + Bundle bundle = FrameworkUtil.getBundle(OSGiEnvironmentLoader.class); + if (bundle != null) { + int state = bundle.getState(); + if (state != Bundle.ACTIVE + && (state == Bundle.INSTALLED || state == Bundle.RESOLVED || state == Bundle.STARTING)) { + try { + bundle.start(); + } catch (BundleException e) { // ignore + } + } + if (bundle.getState() == Bundle.ACTIVE) { + return bundle.getBundleContext(); + } + } + return null; + } + + private static void indexAllBundles(BundleContext context) { + if (context != null) { + for (Bundle bundle : context.getBundles()) { + HOST_2_BUNDLE.put(getBundleURLHost(bundle), bundle.getBundleId()); + } + } + } + + private static String getBundleURLHost(Bundle bundle) { + return bundle.getEntry("/").getHost(); + } + + private static Bundle getContainerBundle(URL url) { + Long bundleId = HOST_2_BUNDLE.get(url.getHost()); + if (bundleId != null) { + BundleContext context = getBundleContext(); + if (context != null) { + return context.getBundle(bundleId); + } + } + return null; + } + + public 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; + } + + public static Enumeration getBundleDirectoryContent(URL resourceURL) { + Bundle bundle = getContainerBundle(resourceURL); + if (bundle != null) { + return bundle.findEntries(resourceURL.getPath(), null, true); + } + return null; + } + } +} From caed1607fb53aa6cb761100466153d00bdc391ad Mon Sep 17 00:00:00 2001 From: Hannes Wellmann <44067969+HannesWell@users.noreply.github.com> Date: Fri, 20 Aug 2021 18:26:34 +0200 Subject: [PATCH 3/5] [FIXUP] apply javacpp formatting --- .../java/org/bytedeco/javacpp/Loader.java | 32 +-- .../tools/OSGiBundleResourceLoader.java | 272 +++++++++--------- 2 files changed, 151 insertions(+), 153 deletions(-) diff --git a/src/main/java/org/bytedeco/javacpp/Loader.java b/src/main/java/org/bytedeco/javacpp/Loader.java index b5070a0d5..dc02dd146 100644 --- a/src/main/java/org/bytedeco/javacpp/Loader.java +++ b/src/main/java/org/bytedeco/javacpp/Loader.java @@ -524,22 +524,22 @@ public static File cacheResource(URL resourceURL, String target) throws IOExcept if (!noSubdir) { cacheSubdir = new File(cacheSubdir, urlFile.getParentFile().getName()); } - } else if (OSGiBundleResourceLoader.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.getContainerBundleName(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 { + } else if (OSGiBundleResourceLoader.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.getContainerBundleName(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(); timestamp = urlFile.lastModified(); diff --git a/src/main/java/org/bytedeco/javacpp/tools/OSGiBundleResourceLoader.java b/src/main/java/org/bytedeco/javacpp/tools/OSGiBundleResourceLoader.java index 96bd726af..f89ba0fb3 100644 --- a/src/main/java/org/bytedeco/javacpp/tools/OSGiBundleResourceLoader.java +++ b/src/main/java/org/bytedeco/javacpp/tools/OSGiBundleResourceLoader.java @@ -38,141 +38,139 @@ public class OSGiBundleResourceLoader { - private OSGiBundleResourceLoader() { // static use only - } - - public static boolean isOSGiRuntime() { - return IS_OSGI_RUNTIME; - } - - public static String getContainerBundleName(URL resourceURL) { - requireOSGi(); - return OSGiEnvironmentLoader.getContainerBundleName(resourceURL); - } - - public static Enumeration getBundleDirectoryContent(URL resourceURL) { - requireOSGi(); - return OSGiEnvironmentLoader.getBundleDirectoryContent(resourceURL); - } - - private static void requireOSGi() { - if (!IS_OSGI_RUNTIME) { - throw new IllegalStateException( - OSGiBundleResourceLoader.class.getSimpleName() + " must only be used within a OSGi runtime"); - } - } - - private static final Map HOST_2_BUNDLE = new ConcurrentHashMap(); - 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; - if (IS_OSGI_RUNTIME) { - OSGiEnvironmentLoader.initialize(); - } - } - - private static class OSGiEnvironmentLoader { - // Code using OSGi APIs has to be encapsulated into own class - // to prevent NoClassDefFoundErrors in OSGi environments - - private static void initialize() { - BundleContext context = getBundleContext(); - if (context != null) { - indexAllBundles(context); - context.addBundleListener(new BundleListener() { - @Override - public void bundleChanged(BundleEvent event) { - Bundle bundle = event.getBundle(); - switch (event.getType()) { - case BundleEvent.RESOLVED: - HOST_2_BUNDLE.put(getBundleURLHost(bundle), bundle.getBundleId()); - break; - case BundleEvent.UNRESOLVED: - HOST_2_BUNDLE.remove(getBundleURLHost(bundle)); - break; - default: - break; - } - } - }); - context.addFrameworkListener(new FrameworkListener() { - @Override - public void frameworkEvent(FrameworkEvent event) { - if (event.getType() == FrameworkEvent.PACKAGES_REFRESHED) { - HOST_2_BUNDLE.clear(); - indexAllBundles(getBundleContext()); - // don't keep a reference on the BundleContext - } - } - }); - } - } - - private static BundleContext getBundleContext() { - Bundle bundle = FrameworkUtil.getBundle(OSGiEnvironmentLoader.class); - if (bundle != null) { - int state = bundle.getState(); - if (state != Bundle.ACTIVE - && (state == Bundle.INSTALLED || state == Bundle.RESOLVED || state == Bundle.STARTING)) { - try { - bundle.start(); - } catch (BundleException e) { // ignore - } - } - if (bundle.getState() == Bundle.ACTIVE) { - return bundle.getBundleContext(); - } - } - return null; - } - - private static void indexAllBundles(BundleContext context) { - if (context != null) { - for (Bundle bundle : context.getBundles()) { - HOST_2_BUNDLE.put(getBundleURLHost(bundle), bundle.getBundleId()); - } - } - } - - private static String getBundleURLHost(Bundle bundle) { - return bundle.getEntry("/").getHost(); - } - - private static Bundle getContainerBundle(URL url) { - Long bundleId = HOST_2_BUNDLE.get(url.getHost()); - if (bundleId != null) { - BundleContext context = getBundleContext(); - if (context != null) { - return context.getBundle(bundleId); - } - } - return null; - } - - public 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; - } - - public static Enumeration getBundleDirectoryContent(URL resourceURL) { - Bundle bundle = getContainerBundle(resourceURL); - if (bundle != null) { - return bundle.findEntries(resourceURL.getPath(), null, true); - } - return null; - } - } + private OSGiBundleResourceLoader() { // static use only + } + + public static boolean isOSGiRuntime() { + return IS_OSGI_RUNTIME; + } + + public static String getContainerBundleName(URL resourceURL) { + requireOSGi(); + return OSGiEnvironmentLoader.getContainerBundleName(resourceURL); + } + + public static Enumeration getBundleDirectoryContent(URL resourceURL) { + requireOSGi(); + return OSGiEnvironmentLoader.getBundleDirectoryContent(resourceURL); + } + + private static void requireOSGi() { + if (!IS_OSGI_RUNTIME) { + throw new IllegalStateException(OSGiBundleResourceLoader.class.getSimpleName() + " must only be used within a OSGi runtime"); + } + } + + private static final Map HOST_2_BUNDLE = new ConcurrentHashMap(); + 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; + if (IS_OSGI_RUNTIME) { + OSGiEnvironmentLoader.initialize(); + } + } + + private static class OSGiEnvironmentLoader { + // Code using OSGi APIs has to be encapsulated into own class + // to prevent NoClassDefFoundErrors in OSGi environments + + private static void initialize() { + BundleContext context = getBundleContext(); + if (context != null) { + indexAllBundles(context); + context.addBundleListener(new BundleListener() { + @Override + public void bundleChanged(BundleEvent event) { + Bundle bundle = event.getBundle(); + switch (event.getType()) { + case BundleEvent.RESOLVED: + HOST_2_BUNDLE.put(getBundleURLHost(bundle), bundle.getBundleId()); + break; + case BundleEvent.UNRESOLVED: + HOST_2_BUNDLE.remove(getBundleURLHost(bundle)); + break; + default: + break; + } + } + }); + context.addFrameworkListener(new FrameworkListener() { + @Override + public void frameworkEvent(FrameworkEvent event) { + if (event.getType() == FrameworkEvent.PACKAGES_REFRESHED) { + HOST_2_BUNDLE.clear(); + indexAllBundles(getBundleContext()); + // don't keep a reference on the BundleContext + } + } + }); + } + } + + private static BundleContext getBundleContext() { + Bundle bundle = FrameworkUtil.getBundle(OSGiEnvironmentLoader.class); + if (bundle != null) { + int state = bundle.getState(); + if (state != Bundle.ACTIVE && (state == Bundle.INSTALLED || state == Bundle.RESOLVED || state == Bundle.STARTING)) { + try { + bundle.start(); + } catch (BundleException e) { // ignore + } + } + if (bundle.getState() == Bundle.ACTIVE) { + return bundle.getBundleContext(); + } + } + return null; + } + + private static void indexAllBundles(BundleContext context) { + if (context != null) { + for (Bundle bundle : context.getBundles()) { + HOST_2_BUNDLE.put(getBundleURLHost(bundle), bundle.getBundleId()); + } + } + } + + private static String getBundleURLHost(Bundle bundle) { + return bundle.getEntry("/").getHost(); + } + + private static Bundle getContainerBundle(URL url) { + Long bundleId = HOST_2_BUNDLE.get(url.getHost()); + if (bundleId != null) { + BundleContext context = getBundleContext(); + if (context != null) { + return context.getBundle(bundleId); + } + } + return null; + } + + public 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; + } + + public static Enumeration getBundleDirectoryContent(URL resourceURL) { + Bundle bundle = getContainerBundle(resourceURL); + if (bundle != null) { + return bundle.findEntries(resourceURL.getPath(), null, true); + } + return null; + } + } } From 10f20205694b160ddcbefa371c036336494b6b79 Mon Sep 17 00:00:00 2001 From: Hannes Wellmann <44067969+HannesWell@users.noreply.github.com> Date: Sun, 29 Aug 2021 16:04:47 +0200 Subject: [PATCH 4/5] [SQUASH] use BundleTracker --- .../java/org/bytedeco/javacpp/Loader.java | 12 +- .../tools/OSGiBundleResourceLoader.java | 121 ++++++++++++++---- 2 files changed, 103 insertions(+), 30 deletions(-) diff --git a/src/main/java/org/bytedeco/javacpp/Loader.java b/src/main/java/org/bytedeco/javacpp/Loader.java index dc02dd146..06123a33e 100644 --- a/src/main/java/org/bytedeco/javacpp/Loader.java +++ b/src/main/java/org/bytedeco/javacpp/Loader.java @@ -766,7 +766,6 @@ public static File extractResource(URL resourceURL, File directoryOrFile, if (urlConnection instanceof JarURLConnection) { JarFile jarFile = ((JarURLConnection) urlConnection).getJarFile(); JarEntry jarEntry = ((JarURLConnection) urlConnection).getJarEntry(); - String jarFileName = jarFile.getName(); String jarEntryName = jarEntry.getName(); if (!jarEntryName.endsWith("/")) { jarEntryName += "/"; @@ -783,8 +782,7 @@ 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(); @@ -812,8 +810,7 @@ public static File extractResource(URL resourceURL, File directoryOrFile, File file = new File(directoryOrFile, entryName.substring(directoryName.length())); if (entryName.endsWith("/")) { // is directory 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(); // FIXME: check if directories have to be extracted again?! @@ -879,6 +876,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); diff --git a/src/main/java/org/bytedeco/javacpp/tools/OSGiBundleResourceLoader.java b/src/main/java/org/bytedeco/javacpp/tools/OSGiBundleResourceLoader.java index f89ba0fb3..e4d4523ba 100644 --- a/src/main/java/org/bytedeco/javacpp/tools/OSGiBundleResourceLoader.java +++ b/src/main/java/org/bytedeco/javacpp/tools/OSGiBundleResourceLoader.java @@ -22,19 +22,33 @@ package org.bytedeco.javacpp.tools; import java.net.URL; +import java.util.AbstractMap.SimpleImmutableEntry; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collections; import java.util.Enumeration; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; +import java.util.Map.Entry; +import java.util.Queue; +import java.util.Set; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.BundleEvent; import org.osgi.framework.BundleException; -import org.osgi.framework.BundleListener; import org.osgi.framework.FrameworkEvent; import org.osgi.framework.FrameworkListener; import org.osgi.framework.FrameworkUtil; import org.osgi.framework.Version; +import org.osgi.framework.wiring.BundleWire; +import org.osgi.framework.wiring.BundleWiring; +import org.osgi.util.tracker.BundleTracker; public class OSGiBundleResourceLoader { @@ -61,7 +75,6 @@ private static void requireOSGi() { } } - private static final Map HOST_2_BUNDLE = new ConcurrentHashMap(); private static final boolean IS_OSGI_RUNTIME; static { @@ -82,33 +95,72 @@ private static class OSGiEnvironmentLoader { // Code using OSGi APIs has to be encapsulated into own class // to prevent NoClassDefFoundErrors in OSGi environments + private static final Map HOST_2_BUNDLE_ID = new HashMap<>(); + private static final ReadWriteLock LOCK = new ReentrantReadWriteLock(); + private static void initialize() { BundleContext context = getBundleContext(); if (context != null) { - indexAllBundles(context); - context.addBundleListener(new BundleListener() { + final BundleTracker bundleTracker = new BundleTracker(context, Bundle.ACTIVE | Bundle.RESOLVED | Bundle.STARTING, + null) { @Override - public void bundleChanged(BundleEvent event) { - Bundle bundle = event.getBundle(); - switch (event.getType()) { - case BundleEvent.RESOLVED: - HOST_2_BUNDLE.put(getBundleURLHost(bundle), bundle.getBundleId()); - break; - case BundleEvent.UNRESOLVED: - HOST_2_BUNDLE.remove(getBundleURLHost(bundle)); - break; - default: - break; + public Bundle addingBundle(Bundle bundle, BundleEvent event) { + Bundle javaCppBundle = context.getBundle(); + if (requires(bundle, javaCppBundle)) { + String bundleURLHost = getBundleURLHost(bundle); + Long bundleId = bundle.getBundleId(); + LOCK.writeLock().lock(); + try { + HOST_2_BUNDLE_ID.put(bundleURLHost, bundleId); + } finally { + LOCK.writeLock().unlock(); + } + return bundle; } + return null; } - }); + + @Override + public void removedBundle(Bundle bundle, BundleEvent event, Bundle object) { + String bundleURLHost = getBundleURLHost(bundle); + LOCK.writeLock().lock(); + try { + HOST_2_BUNDLE_ID.remove(bundleURLHost); + } finally { + LOCK.writeLock().unlock(); + } + } + + }; + bundleTracker.open(); context.addFrameworkListener(new FrameworkListener() { @Override public void frameworkEvent(FrameworkEvent event) { if (event.getType() == FrameworkEvent.PACKAGES_REFRESHED) { - HOST_2_BUNDLE.clear(); - indexAllBundles(getBundleContext()); - // don't keep a reference on the BundleContext + BundleContext bundleContext = getBundleContext(); // don't keep a reference on the BundleContext + if (bundleContext != null) { + LOCK.writeLock().lock(); + try { + List> toAdd = new ArrayList<>(); + for (Iterator> iterator = HOST_2_BUNDLE_ID.entrySet().iterator(); iterator.hasNext();) { + Entry entry = iterator.next(); + Long bundleId = entry.getValue(); + Bundle bundle = bundleContext.getBundle(bundleId); + String bundleURLHost = getBundleURLHost(bundle); + if (bundleURLHost.equals(entry.getKey())) { + iterator.remove(); + toAdd.add(new SimpleImmutableEntry<>(bundleURLHost, bundleId)); + } + } + for (Entry entry : toAdd) { + HOST_2_BUNDLE_ID.put(entry.getKey(), entry.getValue()); + } + } finally { + LOCK.writeLock().unlock(); + } + } + } else if (event.getType() == FrameworkEvent.STOPPED) { + bundleTracker.close(); } } }); @@ -132,12 +184,25 @@ private static BundleContext getBundleContext() { return null; } - private static void indexAllBundles(BundleContext context) { - if (context != null) { - for (Bundle bundle : context.getBundles()) { - HOST_2_BUNDLE.put(getBundleURLHost(bundle), bundle.getBundleId()); + private static boolean requires(Bundle source, Bundle target) { + BundleWiring sourceWiring = source.adapt(BundleWiring.class); + Queue pending = new ArrayDeque<>(Collections.singleton(sourceWiring)); + Set visited = new HashSet<>(Collections.singleton(sourceWiring)); + + while (!pending.isEmpty()) { // perform iterative bfs + BundleWiring wiring = pending.remove(); + if (wiring.getBundle().equals(target)) { + return true; + } + List requiredWires = wiring.getRequiredWires(null); + for (BundleWire requiredWire : requiredWires) { + BundleWiring provider = requiredWire.getProviderWiring(); + if (visited.add(provider)) { + pending.add(provider); + } } } + return false; } private static String getBundleURLHost(Bundle bundle) { @@ -145,7 +210,13 @@ private static String getBundleURLHost(Bundle bundle) { } private static Bundle getContainerBundle(URL url) { - Long bundleId = HOST_2_BUNDLE.get(url.getHost()); + LOCK.readLock().lock(); + Long bundleId; + try { + bundleId = HOST_2_BUNDLE_ID.get(url.getHost()); + } finally { + LOCK.readLock().unlock(); + } if (bundleId != null) { BundleContext context = getBundleContext(); if (context != null) { From 7f428b1daf501435608c708d72c641d767c396b7 Mon Sep 17 00:00:00 2001 From: Hannes Wellmann <44067969+HannesWell@users.noreply.github.com> Date: Sun, 29 Aug 2021 16:20:47 +0200 Subject: [PATCH 5/5] [SQUASH] store URL and targeted bundle in a WeakHashMap --- pom.xml | 6 +- .../java/org/bytedeco/javacpp/Loader.java | 18 +- .../tools/OSGiBundleResourceLoader.java | 223 ++++++------------ 3 files changed, 83 insertions(+), 164 deletions(-) diff --git a/pom.xml b/pom.xml index 7afc8645f..e9ecc1151 100644 --- a/pom.xml +++ b/pom.xml @@ -96,19 +96,19 @@ true org.osgi osgi.annotation - 7.0.0 + 8.0.0 provided org.osgi osgi.core - 7.0.0 + 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 06123a33e..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; @@ -524,11 +528,11 @@ public static File cacheResource(URL resourceURL, String target) throws IOExcept if (!noSubdir) { cacheSubdir = new File(cacheSubdir, urlFile.getParentFile().getName()); } - } else if (OSGiBundleResourceLoader.isOSGiRuntime()) { + } 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.getContainerBundleName(resourceURL); + String subdirName = OSGiBundleResourceLoader.getOSGiContainerBundleName(resourceURL); if (subdirName != null) { String parentName = urlFile.getParentFile().toString(); if (parentName != null) { @@ -797,8 +801,8 @@ public static File extractResource(URL resourceURL, File directoryOrFile, return directoryOrFile; } } - if (OSGiBundleResourceLoader.isOSGiRuntime()) { - Enumeration directoryEntries = OSGiBundleResourceLoader.getBundleDirectoryContent(resourceURL); + if (isOSGiRuntime()) { + Enumeration directoryEntries = OSGiBundleResourceLoader.getOSGiBundleDirectoryContent(resourceURL); if (directoryEntries != null && directoryEntries.hasMoreElements()) { // a not empty directory String directoryName = resourceURL.getPath(); while (directoryEntries.hasMoreElements()) { @@ -912,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}; } @@ -932,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); @@ -944,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 index e4d4523ba..a451fce37 100644 --- a/src/main/java/org/bytedeco/javacpp/tools/OSGiBundleResourceLoader.java +++ b/src/main/java/org/bytedeco/javacpp/tools/OSGiBundleResourceLoader.java @@ -21,73 +21,66 @@ */ package org.bytedeco.javacpp.tools; +import java.io.IOException; +import java.lang.ref.WeakReference; import java.net.URL; -import java.util.AbstractMap.SimpleImmutableEntry; -import java.util.ArrayDeque; -import java.util.ArrayList; import java.util.Collections; import java.util.Enumeration; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Queue; -import java.util.Set; +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.BundleContext; -import org.osgi.framework.BundleEvent; -import org.osgi.framework.BundleException; -import org.osgi.framework.FrameworkEvent; -import org.osgi.framework.FrameworkListener; import org.osgi.framework.FrameworkUtil; import org.osgi.framework.Version; -import org.osgi.framework.wiring.BundleWire; -import org.osgi.framework.wiring.BundleWiring; -import org.osgi.util.tracker.BundleTracker; 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 getContainerBundleName(URL resourceURL) { + public static String getOSGiContainerBundleName(URL resourceURL) { requireOSGi(); return OSGiEnvironmentLoader.getContainerBundleName(resourceURL); } - public static Enumeration getBundleDirectoryContent(URL resourceURL) { + public static Enumeration getOSGiBundleDirectoryContent(URL resourceURL) { requireOSGi(); return OSGiEnvironmentLoader.getBundleDirectoryContent(resourceURL); } - private static void requireOSGi() { - if (!IS_OSGI_RUNTIME) { - throw new IllegalStateException(OSGiBundleResourceLoader.class.getSimpleName() + " must only be used within a OSGi runtime"); - } + public static URL getOSGiClassResource(Class c, String name) { + requireOSGi(); + return OSGiEnvironmentLoader.getClassResource(c, name); } - private static final boolean IS_OSGI_RUNTIME; + public static Enumeration getOSGiClassLoaderResources(ClassLoader cl, String name) throws IOException { + requireOSGi(); + return OSGiEnvironmentLoader.getClassLoaderResources(cl, name); + } - static { - boolean isOSGI; - try { - Bundle.class.getName(); - isOSGI = true; - } catch (NoClassDefFoundError e) { - isOSGI = false; - } - IS_OSGI_RUNTIME = isOSGI; - if (IS_OSGI_RUNTIME) { - OSGiEnvironmentLoader.initialize(); + private static void requireOSGi() { + if (!IS_OSGI_RUNTIME) { + throw new IllegalStateException(OSGiBundleResourceLoader.class.getSimpleName() + " must only be used within a OSGi runtime"); } } @@ -95,138 +88,60 @@ private static class OSGiEnvironmentLoader { // Code using OSGi APIs has to be encapsulated into own class // to prevent NoClassDefFoundErrors in OSGi environments - private static final Map HOST_2_BUNDLE_ID = new HashMap<>(); private static final ReadWriteLock LOCK = new ReentrantReadWriteLock(); + private static final WeakHashMap> URL_TO_BUNDLE = new WeakHashMap<>(); - private static void initialize() { - BundleContext context = getBundleContext(); - if (context != null) { - final BundleTracker bundleTracker = new BundleTracker(context, Bundle.ACTIVE | Bundle.RESOLVED | Bundle.STARTING, - null) { - @Override - public Bundle addingBundle(Bundle bundle, BundleEvent event) { - Bundle javaCppBundle = context.getBundle(); - if (requires(bundle, javaCppBundle)) { - String bundleURLHost = getBundleURLHost(bundle); - Long bundleId = bundle.getBundleId(); - LOCK.writeLock().lock(); - try { - HOST_2_BUNDLE_ID.put(bundleURLHost, bundleId); - } finally { - LOCK.writeLock().unlock(); - } - return bundle; - } - return null; - } - - @Override - public void removedBundle(Bundle bundle, BundleEvent event, Bundle object) { - String bundleURLHost = getBundleURLHost(bundle); - LOCK.writeLock().lock(); - try { - HOST_2_BUNDLE_ID.remove(bundleURLHost); - } finally { - LOCK.writeLock().unlock(); - } - } - - }; - bundleTracker.open(); - context.addFrameworkListener(new FrameworkListener() { - @Override - public void frameworkEvent(FrameworkEvent event) { - if (event.getType() == FrameworkEvent.PACKAGES_REFRESHED) { - BundleContext bundleContext = getBundleContext(); // don't keep a reference on the BundleContext - if (bundleContext != null) { - LOCK.writeLock().lock(); - try { - List> toAdd = new ArrayList<>(); - for (Iterator> iterator = HOST_2_BUNDLE_ID.entrySet().iterator(); iterator.hasNext();) { - Entry entry = iterator.next(); - Long bundleId = entry.getValue(); - Bundle bundle = bundleContext.getBundle(bundleId); - String bundleURLHost = getBundleURLHost(bundle); - if (bundleURLHost.equals(entry.getKey())) { - iterator.remove(); - toAdd.add(new SimpleImmutableEntry<>(bundleURLHost, bundleId)); - } - } - for (Entry entry : toAdd) { - HOST_2_BUNDLE_ID.put(entry.getKey(), entry.getValue()); - } - } finally { - LOCK.writeLock().unlock(); - } - } - } else if (event.getType() == FrameworkEvent.STOPPED) { - bundleTracker.close(); - } - } - }); - } + private static void associateURLtoBundle(URL resource, Bundle bundle) { + URL_TO_BUNDLE.put(resource, new WeakReference<>(bundle)); } - private static BundleContext getBundleContext() { - Bundle bundle = FrameworkUtil.getBundle(OSGiEnvironmentLoader.class); - if (bundle != null) { - int state = bundle.getState(); - if (state != Bundle.ACTIVE && (state == Bundle.INSTALLED || state == Bundle.RESOLVED || state == Bundle.STARTING)) { - try { - bundle.start(); - } catch (BundleException e) { // ignore - } - } - if (bundle.getState() == Bundle.ACTIVE) { - return bundle.getBundleContext(); - } + private static Bundle getContainerBundle(URL url) { + WeakReference bundle; + LOCK.readLock().lock(); + try { + bundle = URL_TO_BUNDLE.get(url); + } finally { + LOCK.readLock().unlock(); } - return null; + return bundle != null ? bundle.get() : null; } - private static boolean requires(Bundle source, Bundle target) { - BundleWiring sourceWiring = source.adapt(BundleWiring.class); - Queue pending = new ArrayDeque<>(Collections.singleton(sourceWiring)); - Set visited = new HashSet<>(Collections.singleton(sourceWiring)); - - while (!pending.isEmpty()) { // perform iterative bfs - BundleWiring wiring = pending.remove(); - if (wiring.getBundle().equals(target)) { - return true; - } - List requiredWires = wiring.getRequiredWires(null); - for (BundleWire requiredWire : requiredWires) { - BundleWiring provider = requiredWire.getProviderWiring(); - if (visited.add(provider)) { - pending.add(provider); - } + 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 false; + return resource; } - private static String getBundleURLHost(Bundle bundle) { - return bundle.getEntry("/").getHost(); - } - - private static Bundle getContainerBundle(URL url) { - LOCK.readLock().lock(); - Long bundleId; - try { - bundleId = HOST_2_BUNDLE_ID.get(url.getHost()); - } finally { - LOCK.readLock().unlock(); - } - if (bundleId != null) { - BundleContext context = getBundleContext(); - if (context != null) { - return context.getBundle(bundleId); + 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 null; + return resources; } - public static String getContainerBundleName(URL resourceURL) { + private static String getContainerBundleName(URL resourceURL) { Bundle bundle = getContainerBundle(resourceURL); if (bundle != null) { Version v = bundle.getVersion(); @@ -236,7 +151,7 @@ public static String getContainerBundleName(URL resourceURL) { return null; } - public static Enumeration getBundleDirectoryContent(URL resourceURL) { + private static Enumeration getBundleDirectoryContent(URL resourceURL) { Bundle bundle = getContainerBundle(resourceURL); if (bundle != null) { return bundle.findEntries(resourceURL.getPath(), null, true);