diff --git a/CHANGELOG.md b/CHANGELOG.md index 79029d4b3..8777b6f81 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,7 @@ + * Replace calls to `Class.getResource()` with `Loader.findResource()` to work around issues with JPMS ([pull #276](https://github.com/bytedeco/javacpp/pull/276)) + * Enhance `Loader.findResources()` with `Class.getResource()` and search among parent packages + * Take shortest common package name among all user classes for the default output path of `Builder` * Add `Bfloat16Indexer` to access `short` arrays as `bfloat16` floating point numbers * When `Indexer.sizes.length != 3`, return -1 for `rows()`, `cols()`, `width()`, `height()`, and `channels()` ([pull #275](https://github.com/bytedeco/javacpp/pull/275)) * Synchronize `Loader.cacheResources()` on `Runtime` to avoid `OverlappingFileLockException` with multiple class loaders ([issue bytedeco/javacpp-presets#650](https://github.com/bytedeco/javacpp-presets/issues/650)) diff --git a/src/main/java/org/bytedeco/javacpp/Loader.java b/src/main/java/org/bytedeco/javacpp/Loader.java index f46ec61e8..347da7a1b 100644 --- a/src/main/java/org/bytedeco/javacpp/Loader.java +++ b/src/main/java/org/bytedeco/javacpp/Loader.java @@ -30,7 +30,6 @@ import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.JarURLConnection; -import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; @@ -320,7 +319,7 @@ public static File cacheResource(String name) throws IOException { * @see #cacheResource(URL) */ public static File cacheResource(Class cls, String name) throws IOException { - return cacheResource(cls.getResource(name)); + return cacheResource(findResource(cls, name)); } /** @@ -565,7 +564,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 { - return extractResource(cls.getResource(name), directory, prefix, suffix); + return extractResource(findResource(cls, name), directory, prefix, suffix); } /** @@ -692,32 +691,68 @@ public static File extractResource(URL resourceURL, File directoryOrFile, return file; } + /** 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); + return url.length > 0 ? url[0] : null; + } + + /** Returns {@code findResources(cls, name, -1)}. */ + public static URL[] findResources(Class cls, String name) throws IOException { + return findResources(cls, name, -1); + } + /** - * Finds by name resources using the {@link ClassLoader} of the specified {@link Class}. - * Names not prefixed with '/' are considered relative to the Class. + * Finds by name resources using the {@link Class} and its {@link ClassLoader}. + * Names not prefixed with '/' are considered in priority relative to the Class, + * but parent packages, including the default one, also get searched. * * @param cls the Class from whose ClassLoader to load resources - * @param name of the resources passed to {@link ClassLoader#getResources(String)} + * @param name of the resources passed to {@link Class#getResource(String)} and {@link ClassLoader#getResources(String)} + * @param maxLength of the array to return, or -1 for no limit * @return URLs to the resources * @throws IOException */ - public static URL[] findResources(Class cls, String name) throws IOException { + public static URL[] findResources(Class cls, String name, int maxLength) throws IOException { while (name.contains("//")) { name = name.replace("//", "/"); } + + // Under JPMS, Class.getResource() and ClassLoader.getResources() do not return the same URLs + URL url = cls.getResource(name); + if (url != null && maxLength == 1) { + return new URL[] {url}; + } + + String path = ""; if (!name.startsWith("/")) { String s = cls.getName().replace('.', '/'); int n = s.lastIndexOf('/'); if (n >= 0) { - name = s.substring(0, n + 1) + name; + path = s.substring(0, n + 1); } } else { name = name.substring(1); } - Enumeration urls = cls.getClassLoader().getResources(name); + Enumeration urls = cls.getClassLoader().getResources(path + name); ArrayList array = new ArrayList(); - while (urls.hasMoreElements()) { - array.add(urls.nextElement()); + if (url != null) { + array.add(url); + } + while (url == null && !urls.hasMoreElements() && path.length() > 0) { + int n = path.lastIndexOf('/', path.length() - 2); + if (n >= 0) { + path = path.substring(0, n + 1); + } else { + path = ""; + } + urls = cls.getClassLoader().getResources(path + name); + } + while (urls.hasMoreElements() && array.size() < maxLength) { + url = urls.nextElement(); + if (!array.contains(url)) { + array.add(url); + } } return array.toArray(new URL[array.size()]); } @@ -1098,18 +1133,18 @@ public static URL[] findLibrary(Class cls, ClassProperties properties, String li } String subdir = (resource == null ? "" : "/" + resource) + platform + (extension == null ? "" : extension) + "/"; - URL u = cls.getResource(subdir + styles[i]); - if (u != null) { - if (reference) { - try { + try { + URL u = findResource(cls, subdir + styles[i]); + if (u != null) { + if (reference) { u = new URL(u + "#" + styles2[i]); - } catch (MalformedURLException e) { - throw new RuntimeException(e); + } + if (!urls.contains(u)) { + urls.add(u); } } - if (!urls.contains(u)) { - urls.add(u); - } + } catch (IOException e) { + throw new RuntimeException(e); } } } @@ -1128,7 +1163,7 @@ public static URL[] findLibrary(Class cls, ClassProperties properties, String li if (!urls.contains(u)) { urls.add(k++, u); } - } catch (MalformedURLException ex) { + } catch (IOException ex) { throw new RuntimeException(ex); } } diff --git a/src/main/java/org/bytedeco/javacpp/tools/Builder.java b/src/main/java/org/bytedeco/javacpp/tools/Builder.java index 7494842f3..37b637c99 100644 --- a/src/main/java/org/bytedeco/javacpp/tools/Builder.java +++ b/src/main/java/org/bytedeco/javacpp/tools/Builder.java @@ -460,11 +460,13 @@ int compile(String[] sourceFilenames, String outputFilename, ClassProperties pro } /** - * Generates a C++ source file for classes, and compiles everything in + * Generates C++ source files for classes, and compiles everything in * one shared library when {@code compile == true}. * * @param classes the Class objects as input to Generator * @param outputName the output name of the shared library + * @param first of the batch, so generate jnijavacpp.cpp + * @param last of the batch, so delete jnijavacpp.cpp * @return the actual File generated, either the compiled library or its source * @throws IOException * @throws InterruptedException @@ -483,9 +485,23 @@ File[] generateAndCompile(Class[] classes, String outputName, boolean first, boo if (outputPath == null) { URI uri = null; try { - String resourceName = '/' + classes[classes.length - 1].getName().replace('.', '/') + ".class"; - String resourceURL = classes[classes.length - 1].getResource(resourceName).toString(); - uri = new URI(resourceURL.substring(0, resourceURL.lastIndexOf('/') + 1)); + String resourceName = '/' + classes[0].getName().replace('.', '/') + ".class"; + String resourceURL = Loader.findResource(classes[0], resourceName).toString(); + String packageURI = resourceURL.substring(0, resourceURL.lastIndexOf('/') + 1); + for (int i = 1; i < classes.length; i++) { + // Use shortest common package name among all classes as default output path + String resourceName2 = '/' + classes[i].getName().replace('.', '/') + ".class"; + String resourceURL2 = Loader.findResource(classes[i], resourceName2).toString(); + String packageURI2 = resourceURL2.substring(0, resourceURL2.lastIndexOf('/') + 1); + + String longest = packageURI2.length() > packageURI.length() ? packageURI2 : packageURI; + String shortest = packageURI2.length() < packageURI.length() ? packageURI2 : packageURI; + while (!longest.startsWith(shortest) && shortest.lastIndexOf('/') > 0) { + shortest = shortest.substring(0, shortest.lastIndexOf('/')); + } + packageURI = shortest; + } + uri = new URI(packageURI); boolean isFile = "file".equals(uri.getScheme()); File classPath = new File(classScanner.getClassLoader().getPaths()[0]).getCanonicalFile(); // If our class is not a file, use first path of the user class loader as base for our output path diff --git a/src/main/java/org/bytedeco/javacpp/tools/Parser.java b/src/main/java/org/bytedeco/javacpp/tools/Parser.java index 279c58e4b..5c66f1b71 100644 --- a/src/main/java/org/bytedeco/javacpp/tools/Parser.java +++ b/src/main/java/org/bytedeco/javacpp/tools/Parser.java @@ -3693,7 +3693,7 @@ public File parse(File outputDirectory, String[] classPath, Class cls) throws IO String javaText = text + "import static " + global + ".*;\n" + (prevd != null && prevd.comment ? prevd.text : "") + d.text.replace("public static class " + d.type.javaName + " ", - "@Properties(inherit = " + cls.getSimpleName() + ".class)\n" + "@Properties(inherit = " + cls.getCanonicalName() + ".class)\n" + "public class " + d.type.javaName + " "); Files.write(javaFile.toPath(), encoding != null ? javaText.getBytes(encoding) : javaText.getBytes()); prevd = null; diff --git a/src/test/java/org/bytedeco/javacpp/BuilderTest.java b/src/test/java/org/bytedeco/javacpp/BuilderTest.java index 83a039e5b..746dea28e 100644 --- a/src/test/java/org/bytedeco/javacpp/BuilderTest.java +++ b/src/test/java/org/bytedeco/javacpp/BuilderTest.java @@ -59,7 +59,7 @@ public class BuilderTest implements BuildEnabled, LoadEnabled { Class c = BuilderTest.class; String[] extensions = {"", "-ext1", "-ext2"}; for (String extension : extensions) { - URL u = c.getResource(Loader.getPlatform() + extension); + URL u = Loader.findResource(c, Loader.getPlatform() + extension); if (u != null) { for (File f : new File(u.toURI()).listFiles()) { f.delete();