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 {