Implement multi-release support to respect Archive support and loading constraints
diff --git a/platform/o.n.bootstrap/src/org/netbeans/JarClassLoader.java b/platform/o.n.bootstrap/src/org/netbeans/JarClassLoader.java
index 0663295..7a238bc 100644
--- a/platform/o.n.bootstrap/src/org/netbeans/JarClassLoader.java
+++ b/platform/o.n.bootstrap/src/org/netbeans/JarClassLoader.java
@@ -86,6 +86,7 @@
//
private static Stamps cache;
+ private static final String META_INF = "META-INF/";
private static final Name MULTI_RELEASE = new Name("Multi-Release");
private static final int BASE_VERSION = 8;
private static final int RUNTIME_VERSION;
@@ -296,14 +297,7 @@
}
}
Manifest man = new DelayedManifest();
- if (man.getMainAttributes().containsKey(MULTI_RELEASE)) {
- String multiRelease = (String) man.getMainAttributes().get(MULTI_RELEASE);
- src.setMultiRelease(multiRelease.equalsIgnoreCase("true"));
- }
- if (src.isMultiRelease() && RUNTIME_VERSION != BASE_VERSION) {
- data = src.getClassData(path);
- }
-
+
try {
definePackage(pkgName, man, src.getURL());
} catch (IllegalArgumentException x) {
@@ -368,7 +362,7 @@
private ProtectionDomain pd;
protected JarClassLoader jcl;
private static Map<String,Source> sources = new HashMap<String, Source>();
- private boolean multiRelease;
+ private Boolean multiRelease;
public Source(URL url) {
this.url = url;
@@ -445,11 +439,20 @@
return url.toString();
}
- private void setMultiRelease(boolean multiRelease) {
- this.multiRelease = multiRelease;
- }
-
protected boolean isMultiRelease() {
+ Manifest man = getManifest();
+ if(man == null) {
+ return false;
+ }
+ if(multiRelease != null) {
+ return multiRelease;
+ }
+ if (man.getMainAttributes().containsKey(MULTI_RELEASE)) {
+ String multiReleaseString = (String) man.getMainAttributes().get(MULTI_RELEASE);
+ multiRelease = Boolean.valueOf(multiReleaseString);
+ } else {
+ multiRelease = false;
+ }
return multiRelease;
}
@@ -616,21 +619,12 @@
@Override
protected byte[] readClass(String path) throws IOException {
try {
- if (isMultiRelease() && RUNTIME_VERSION > BASE_VERSION) {
+ if ((! path.startsWith(META_INF)) && isMultiRelease() && RUNTIME_VERSION > BASE_VERSION) {
int[] vers = getVersions();
- if (vers.length > 0) {
- for (int i = vers.length - 1; i >= 0; i--) {
- int version = vers[i];
- if (version > RUNTIME_VERSION) {
- continue;
- }
- if (version < BASE_VERSION) {
- break;
- }
- byte[] data = archive.getData(this, "META-INF/versions/" + version + "/" + path);
- if (data != null) {
- return data;
- }
+ for (int version: vers) {
+ byte[] data = archive.getData(this, "META-INF/versions/" + version + "/" + path);
+ if (data != null) {
+ return data;
}
}
}
@@ -641,25 +635,22 @@
}
}
+ /**
+ * @return versions for which a {@code META-INF/versions/NUMBER} entry exists.
+ * The order is from largest version to lowest. Only versions supported by
+ * the runtime VM are reported.
+ */
private int[] getVersions() {
if (versions != null) {
return versions;
}
try {
- JarFile src = getJarFile("versions");
- Set<Integer> vers = new TreeSet<>();
- Enumeration<JarEntry> en = src.entries();
- while (en.hasMoreElements()) {
- JarEntry je = en.nextElement();
- if (je.isDirectory()) {
- String itm = je.getName();
- if (itm.startsWith("META-INF/versions/")) {
- String res = itm.substring(18);
- int idx = res.indexOf('/');
- if (idx > 0 && idx == res.length() - 1) {
- vers.add(Integer.parseInt(res.substring(0, idx)));
- }
- }
+ Set<Integer> vers = new TreeSet<>(Collections.reverseOrder());
+ for(int i = BASE_VERSION; i <= RUNTIME_VERSION; i++) {
+ String directory = "META-INF/versions/" + i;
+ byte[] data = archive.getData(this, directory);
+ if (data != null && data.length == 0) {
+ vers.add(i);
}
}
int[] ret = new int[vers.size()];
@@ -669,23 +660,11 @@
}
versions = ret;
return ret;
- } catch (ZipException x) { // Unix
- if (warnedFiles.add(file)) {
- LOGGER.log(Level.INFO, "Cannot open " + file, x);
- dumpFiles(file, -1);
- }
- } catch (FileNotFoundException x) { // Windows
- if (warnedFiles.add(file)) {
- LOGGER.log(Level.INFO, "Cannot open " + file, x);
- dumpFiles(file, -1);
- }
} catch (IOException ioe) {
if (warnedFiles.add(file)) {
LOGGER.log(Level.WARNING, "problems with " + file, ioe);
dumpFiles(file, -1);
}
- } finally {
- releaseJarFile();
}
return new int[0];
}
diff --git a/platform/o.n.bootstrap/test/unit/src/org/netbeans/JarClassLoaderTest.java b/platform/o.n.bootstrap/test/unit/src/org/netbeans/JarClassLoaderTest.java
index 722cc15..cc6e36f 100644
--- a/platform/o.n.bootstrap/test/unit/src/org/netbeans/JarClassLoaderTest.java
+++ b/platform/o.n.bootstrap/test/unit/src/org/netbeans/JarClassLoaderTest.java
@@ -18,6 +18,7 @@
*/
package org.netbeans;
+import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileOutputStream;
@@ -55,6 +56,8 @@
import org.openide.util.lookup.Lookups;
import org.openide.util.test.TestFileUtils;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
/** Tests that cover some basic aspects of a Proxy/JarClassLoader.
*
* @author Petr Nejedly
@@ -428,6 +431,8 @@
public void testMultiReleaseJar() throws Exception {
clearWorkDir();
+
+ // Prepare multi-release jar file
File classes = new File(getWorkDir(), "classes");
classes.mkdirs();
ToolProvider.getSystemJavaCompiler()
@@ -453,11 +458,14 @@
throw new IllegalStateException(ex);
}
});
+ jarContent.put("test/dummy.txt", "base".getBytes(UTF_8));
+ jarContent.put("META-INF/versions/9/test/dummy.txt", "9".getBytes(UTF_8));
File jar = new File(getWorkDir(), "multi-release.jar");
try (OutputStream out = new FileOutputStream(jar)) {
TestFileUtils.writeZipFile(out, jarContent);
}
+ // Check multi release class loading
JarClassLoader jcl = new JarClassLoader(Arrays.asList(jar), new ProxyClassLoader[0]);
Class<?> api = jcl.loadClass("api.API");
Method run = api.getDeclaredMethod("run");
@@ -470,6 +478,21 @@
expected = "base";
}
assertEquals(expected, output);
+
+ // Check multi release resource loading
+ try(InputStream is = jcl.getResourceAsStream("test/dummy.txt")) {
+ assertEquals(expected, loadUTF8(is));
+ }
+ }
+
+ private static String loadUTF8(InputStream is) throws IOException {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ byte[] buffer = new byte[2048];
+ int read;
+ while ((read = is.read(buffer)) > 0) {
+ baos.write(buffer, 0, read);
+ }
+ return baos.toString("UTF-8");
}
private static final class SourceFileObject extends SimpleJavaFileObject {