Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support directory extraction in modular OSGi runtimes (issue #506) #511

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
147 changes: 63 additions & 84 deletions src/main/java/org/bytedeco/javacpp/Loader.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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;
}

/**
Expand All @@ -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,
Expand All @@ -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("#");
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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;
}

/**
Expand All @@ -753,15 +735,15 @@ 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;
}

/** 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);
}

/**
Expand All @@ -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 += "/";
Expand All @@ -799,55 +777,55 @@ 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();
} else if (!cacheDirectory || !file.exists() || file.length() != entrySize
|| 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<URL> 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<URL> directoryEntries = OSGiBundleResourceLoader.getBundleDirectoryContent(resourceURL);
if (directoryEntries != null && directoryEntries.hasMoreElements()) { // a not empty directory
String directoryName = resourceURL.getPath();
while (directoryEntries.hasMoreElements()) {
Copy link
Member

@saudet saudet Aug 21, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This while loop looks pretty similar to the one above. Is the idea that some OSGi implementations may support other things than JAR files?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is intentionally. Actually I would have liked to move the extraction part into an own method. But because in case of a JarURLConnection the URL is created just before extractResource is called again this is not possible without creating the URL before (and therefore also in cases of a directory.

And yes a bundle can be a jar but also can be extracted into a directory (which is for example very common if you develop Eclipse plug-ins and use them in a debugged application started from the IDE).

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) {
Expand All @@ -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());
}
Expand All @@ -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];
Expand Down Expand Up @@ -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;
Expand All @@ -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());
Expand Down Expand Up @@ -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;
Expand Down
Loading