8153654: Update jdeps to be multi-release jar aware
authorsdrach
Mon, 26 Sep 2016 13:39:50 -0700
changeset 41164 69167c89e68f
parent 41163 05d896ec6618
child 41165 4b05dadfb69d
8153654: Update jdeps to be multi-release jar aware Reviewed-by: mchung
langtools/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/Analyzer.java
langtools/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/Archive.java
langtools/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/ClassFileReader.java
langtools/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/JdepsConfiguration.java
langtools/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/JdepsTask.java
langtools/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/MultiReleaseException.java
langtools/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/VersionHelper.java
langtools/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/resources/jdeps.properties
langtools/test/tools/jdeps/MultiReleaseJar.java
langtools/test/tools/jdeps/mrjar/10/test/Version.java
langtools/test/tools/jdeps/mrjar/9/test/NonPublic.java
langtools/test/tools/jdeps/mrjar/9/test/Version.java
langtools/test/tools/jdeps/mrjar/base/p/Foo.java
langtools/test/tools/jdeps/mrjar/base/test/Version.java
langtools/test/tools/jdeps/mrjar/test/Main.java
langtools/test/tools/jdeps/mrjar/v10/q/Bar.java
langtools/test/tools/jdeps/mrjar/v10/q/Gee.java
langtools/test/tools/jdeps/mrjar/v9/p/Foo.java
langtools/test/tools/jdeps/mrjar/v9/q/Bar.java
--- a/langtools/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/Analyzer.java	Mon Sep 26 13:18:11 2016 -0700
+++ b/langtools/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/Analyzer.java	Mon Sep 26 13:39:50 2016 -0700
@@ -256,7 +256,7 @@
         // return classname or package name depending on the level
         private String getLocationName(Location o) {
             if (level == Type.CLASS || level == Type.VERBOSE) {
-                return o.getClassName();
+                return VersionHelper.get(o.getClassName());
             } else {
                 String pkg = o.getPackageName();
                 return pkg.isEmpty() ? "<unnamed>" : pkg;
--- a/langtools/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/Archive.java	Mon Sep 26 13:18:11 2016 -0700
+++ b/langtools/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/Archive.java	Mon Sep 26 13:39:50 2016 -0700
@@ -45,9 +45,9 @@
  * Represents the source of the class files.
  */
 public class Archive implements Closeable {
-    public static Archive getInstance(Path p) {
+    public static Archive getInstance(Path p, Runtime.Version version) {
         try {
-            return new Archive(p, ClassFileReader.newInstance(p));
+            return new Archive(p, ClassFileReader.newInstance(p, version));
         } catch (IOException e) {
             throw new UncheckedIOException(e);
         }
--- a/langtools/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/ClassFileReader.java	Mon Sep 26 13:18:11 2016 -0700
+++ b/langtools/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/ClassFileReader.java	Mon Sep 26 13:39:50 2016 -0700
@@ -29,6 +29,8 @@
 import com.sun.tools.classfile.ConstantPoolException;
 import com.sun.tools.classfile.Dependencies.ClassFileError;
 
+import jdk.internal.util.jar.VersionedStream;
+
 import java.io.Closeable;
 import java.io.File;
 import java.io.FileNotFoundException;
@@ -50,6 +52,7 @@
 import java.util.jar.JarFile;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
+import java.util.zip.ZipFile;
 
 /**
  * ClassFileReader reads ClassFile(s) of a given path that can be
@@ -60,6 +63,13 @@
      * Returns a ClassFileReader instance of a given path.
      */
     public static ClassFileReader newInstance(Path path) throws IOException {
+        return newInstance(path, JarFile.baseVersion());
+    }
+
+    /**
+     * Returns a ClassFileReader instance of a given path.
+     */
+    public static ClassFileReader newInstance(Path path, Runtime.Version version) throws IOException {
         if (Files.notExists(path)) {
             throw new FileNotFoundException(path.toString());
         }
@@ -67,20 +77,13 @@
         if (Files.isDirectory(path)) {
             return new DirectoryReader(path);
         } else if (path.getFileName().toString().endsWith(".jar")) {
-            return new JarFileReader(path);
+            return new JarFileReader(path, version);
         } else {
             return new ClassFileReader(path);
         }
     }
 
     /**
-     * Returns a ClassFileReader instance of a given JarFile.
-     */
-    public static ClassFileReader newInstance(Path path, JarFile jf) throws IOException {
-        return new JarFileReader(path, jf);
-    }
-
-    /**
      * Returns a ClassFileReader instance of a given FileSystem and path.
      *
      * This method is used for reading classes from jrtfs.
@@ -302,13 +305,16 @@
 
     static class JarFileReader extends ClassFileReader {
         private final JarFile jarfile;
-        JarFileReader(Path path) throws IOException {
-            this(path, new JarFile(path.toFile(), false));
+        private final Runtime.Version version;
+
+        JarFileReader(Path path, Runtime.Version version) throws IOException {
+            this(path, openJarFile(path.toFile(), version), version);
         }
 
-        JarFileReader(Path path, JarFile jf) throws IOException {
+        JarFileReader(Path path, JarFile jf, Runtime.Version version) throws IOException {
             super(path);
             this.jarfile = jf;
+            this.version = version;
         }
 
         @Override
@@ -316,9 +322,26 @@
             jarfile.close();
         }
 
+        private static JarFile openJarFile(File f, Runtime.Version version)
+                throws IOException {
+            JarFile jf;
+            if (version == null) {
+                jf = new JarFile(f, false);
+                if (jf.isMultiRelease()) {
+                    throw new MultiReleaseException("err.multirelease.option.notfound", f.getName());
+                }
+            } else {
+                jf = new JarFile(f, false, ZipFile.OPEN_READ, version);
+                if (!jf.isMultiRelease()) {
+                    throw new MultiReleaseException("err.multirelease.option.exists", f.getName());
+                }
+            }
+            return jf;
+        }
+
         protected Set<String> scan() {
-            try (JarFile jf = new JarFile(path.toFile())) {
-                return jf.stream().map(JarEntry::getName)
+            try (JarFile jf = openJarFile(path.toFile(), version)) {
+                return VersionedStream.stream(jf).map(JarEntry::getName)
                          .filter(n -> n.endsWith(".class"))
                          .collect(Collectors.toSet());
             } catch (IOException e) {
@@ -348,15 +371,14 @@
         }
 
         protected ClassFile readClassFile(JarFile jarfile, JarEntry e) throws IOException {
-            InputStream is = null;
-            try {
-                is = jarfile.getInputStream(e);
-                return ClassFile.read(is);
+            try (InputStream is = jarfile.getInputStream(e)) {
+                ClassFile cf = ClassFile.read(is);
+                if (jarfile.isMultiRelease()) {
+                    VersionHelper.add(jarfile, e, cf);
+                }
+                return cf;
             } catch (ConstantPoolException ex) {
                 throw new ClassFileError(ex);
-            } finally {
-                if (is != null)
-                    is.close();
             }
         }
 
@@ -370,6 +392,21 @@
         }
     }
 
+    Enumeration<JarEntry> versionedEntries(JarFile jf) {
+        Iterator<JarEntry> it = VersionedStream.stream(jf).iterator();
+        return new Enumeration<>() {
+            @Override
+            public boolean hasMoreElements() {
+                return it.hasNext();
+            }
+
+            @Override
+            public JarEntry nextElement() {
+                return it.next();
+            }
+        };
+    }
+
     class JarFileIterator implements Iterator<ClassFile> {
         protected final JarFileReader reader;
         protected Enumeration<JarEntry> entries;
@@ -388,7 +425,7 @@
             if (jarfile == null) return;
 
             this.jf = jarfile;
-            this.entries = jf.entries();
+            this.entries = versionedEntries(jf);
             this.nextEntry = nextEntry();
         }
 
--- a/langtools/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/JdepsConfiguration.java	Mon Sep 26 13:18:11 2016 -0700
+++ b/langtools/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/JdepsConfiguration.java	Mon Sep 26 13:39:50 2016 -0700
@@ -81,19 +81,22 @@
     private final List<Archive> initialArchives = new ArrayList<>();
     private final Set<Module> rootModules = new HashSet<>();
     private final Configuration configuration;
+    private final Runtime.Version version;
 
     private JdepsConfiguration(SystemModuleFinder systemModulePath,
                                ModuleFinder finder,
                                Set<String> roots,
                                List<Path> classpaths,
                                List<Archive> initialArchives,
-                               boolean allDefaultModules)
+                               boolean allDefaultModules,
+                               Runtime.Version version)
         throws IOException
     {
         trace("root: %s%n", roots);
 
         this.system = systemModulePath;
         this.finder = finder;
+        this.version = version;
 
         // build root set for resolution
         Set<String> mods = new HashSet<>(roots);
@@ -121,7 +124,7 @@
         // classpath archives
         for (Path p : classpaths) {
             if (Files.exists(p)) {
-                Archive archive = Archive.getInstance(p);
+                Archive archive = Archive.getInstance(p, version);
                 addPackagesInUnnamedModule(archive);
                 classpathArchives.add(archive);
             }
@@ -292,7 +295,7 @@
             if (location.getScheme().equals("jrt")) {
                 reader = system.getClassReader(mn);
             } else {
-                reader = ClassFileReader.newInstance(Paths.get(location));
+                reader = ClassFileReader.newInstance(Paths.get(location), version);
             }
 
             builder.classes(reader);
@@ -304,6 +307,10 @@
         }
     }
 
+    public Runtime.Version getVersion() {
+        return version;
+    }
+
     /*
      * Close all archives e.g. JarFile
      */
@@ -476,6 +483,7 @@
         ModuleFinder appModulePath;
         boolean addAllApplicationModules;
         boolean addAllDefaultModules;
+        Runtime.Version version;
 
         public Builder() {
             this.systemModulePath = new SystemModuleFinder();
@@ -526,8 +534,13 @@
             return this;
         }
 
+        public Builder multiRelease(Runtime.Version version) {
+            this.version = version;
+            return this;
+        }
+
         public Builder addRoot(Path path) {
-            Archive archive = Archive.getInstance(path);
+            Archive archive = Archive.getInstance(path, version);
             if (archive.contains(MODULE_INFO)) {
                 paths.add(path);
             } else {
@@ -569,7 +582,8 @@
                                           rootModules,
                                           classPaths,
                                           initialArchives,
-                                          addAllDefaultModules);
+                                          addAllDefaultModules,
+                                          version);
         }
 
         private static ModuleFinder createModulePathFinder(String mpaths) {
--- a/langtools/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/JdepsTask.java	Mon Sep 26 13:18:11 2016 -0700
+++ b/langtools/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/JdepsTask.java	Mon Sep 26 13:39:50 2016 -0700
@@ -36,6 +36,7 @@
 import java.nio.file.Paths;
 import java.text.MessageFormat;
 import java.util.*;
+import java.util.jar.JarFile;
 import java.util.regex.Pattern;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
@@ -389,6 +390,23 @@
                 }
             }
         },
+        new Option(true, "--multi-release") {
+            void process(JdepsTask task, String opt, String arg) throws BadArgs {
+                if (arg.equalsIgnoreCase("base")) {
+                    task.options.multiRelease = JarFile.baseVersion();
+                } else {
+                    try {
+                        int v = Integer.parseInt(arg);
+                        if (v < 9) {
+                            throw new BadArgs("err.invalid.arg.for.option", arg);
+                        }
+                    } catch (NumberFormatException x) {
+                        throw new BadArgs("err.invalid.arg.for.option", arg);
+                    }
+                    task.options.multiRelease = Runtime.Version.parse(arg);
+                }
+            }
+        },
     };
 
     private static final String PROGNAME = "jdeps";
@@ -481,6 +499,9 @@
         } catch (IOException e) {
             e.printStackTrace();
             return EXIT_CMDERR;
+        } catch (MultiReleaseException e) {
+            reportError(e.getKey(), (Object)e.getMsg());
+            return EXIT_CMDERR;  // could be EXIT_ABNORMAL sometimes
         } finally {
             log.flush();
         }
@@ -541,11 +562,16 @@
         if (options.classpath != null)
             builder.addClassPath(options.classpath);
 
+        if (options.multiRelease != null)
+            builder.multiRelease(options.multiRelease);
+
         // build the root set of archives to be analyzed
         for (String s : inputArgs) {
             Path p = Paths.get(s);
             if (Files.exists(p)) {
                 builder.addRoot(p);
+            } else {
+                warning("warn.invalid.arg", s);
             }
         }
 
@@ -839,6 +865,7 @@
         String modulePath;
         String rootModule;
         Set<String> addmods = new HashSet<>();
+        Runtime.Version multiRelease;
 
         boolean hasFilter() {
             return numFilters() > 0;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/MultiReleaseException.java	Mon Sep 26 13:39:50 2016 -0700
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.sun.tools.jdeps;
+
+/**
+ * Signals that an exception of some sort has occurred while processing
+ * a multi-release jar file.
+ *
+ * @since   9
+ */
+class MultiReleaseException extends RuntimeException {
+    private static final long serialVersionUID = 4474870142461654108L;
+    private final String key;
+    private final String[] msg;
+
+    /**
+     * Constructs an {@code MultiReleaseException} with the specified detail
+     * error message array.
+     *
+     * @param key
+     *        The key that identifies the message in the jdeps.properties file
+     * @param msg
+     *        The detail message array
+     */
+    public MultiReleaseException(String key, String... msg) {
+        super();
+        this.key = key;
+        this.msg = msg;
+    }
+
+    /**
+     * Returns the resource message key
+     */
+    public String getKey() {
+        return key;
+    }
+
+    /**
+     * Returns the detailed error message array.
+     *
+     * @return the detailed error message array
+     */
+    public String[] getMsg() {
+        return msg;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/VersionHelper.java	Mon Sep 26 13:39:50 2016 -0700
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.sun.tools.jdeps;
+
+import com.sun.tools.classfile.ClassFile;
+import com.sun.tools.classfile.ConstantPoolException;
+import jdk.internal.misc.SharedSecrets;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+
+public class VersionHelper {
+    private static final String META_INF_VERSIONS = "META-INF/versions/";
+    private static final Map<String,String> nameToVersion = new ConcurrentHashMap<>();
+
+    public static String get(String classname) {
+        if (nameToVersion.containsKey(classname)) {
+            return nameToVersion.get(classname) + "/" + classname;
+        }
+        return classname;
+    }
+
+    public static void add(JarFile jarfile, JarEntry e, ClassFile cf)
+            throws ConstantPoolException
+    {
+        String realName = SharedSecrets.javaUtilJarAccess().getRealName(jarfile, e);
+        if (realName.startsWith(META_INF_VERSIONS)) {
+            int len = META_INF_VERSIONS.length();
+            int n = realName.indexOf('/', len);
+            if (n > 0) {
+                String version = realName.substring(len, n);
+                assert (Integer.parseInt(version) > 8);
+                String name = cf.getName().replace('/', '.');
+                if (nameToVersion.containsKey(name)) {
+                    if (!version.equals(nameToVersion.get(name))) {
+                        throw new MultiReleaseException(
+                                "err.multirelease.version.associated",
+                                name, nameToVersion.get(name), version
+                        );
+                    }
+                } else {
+                    nameToVersion.put(name, version);
+                }
+            } else {
+                throw new MultiReleaseException("err.multirelease.jar.malformed",
+                        jarfile.getName(), realName);
+            }
+        }
+    }
+}
--- a/langtools/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/resources/jdeps.properties	Mon Sep 26 13:18:11 2016 -0700
+++ b/langtools/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/resources/jdeps.properties	Mon Sep 26 13:39:50 2016 -0700
@@ -150,6 +150,11 @@
 \  -q           -quiet               Do not show missing dependencies from \n\
 \                                    -genmoduleinfo output.
 
+main.opt.multi-release=\
+\  --multi-release <version>         Specifies the version when processing\n\
+\                                    multi-release jar files.  <version> should\n\
+\                                    be integer >= 9 or base.
+
 err.unknown.option=unknown option: {0}
 err.missing.arg=no value given for {0}
 err.invalid.arg.for.option=invalid argument for option: {0}
@@ -164,7 +169,11 @@
 err.root.module.not.set=root module set empty
 err.invalid.inverse.option={0} cannot be used with -inverse option
 err.inverse.filter.not.set={0} cannot be used with -inverse option
-warn.invalid.arg=Path not exist: {0}
+err.multirelease.option.exists={0} is not a multi-release jar file, but the --multi-release option is set
+err.multirelease.option.notfound={0} is a multi-release jar file, but the --multi-release option is not set
+err.multirelease.version.associated=class {0} already associated with version {1}, trying to add version {2}
+err.multirelease.jar.malformed=malformed multi-release jar, {0}, bad entry: {1}
+warn.invalid.arg=Path does not exist: {0}
 warn.split.package=package {0} defined in {1} {2}
 warn.replace.useJDKInternals=\
 JDK internal APIs are unsupported and private to JDK implementation that are\n\
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/tools/jdeps/MultiReleaseJar.java	Mon Sep 26 13:39:50 2016 -0700
@@ -0,0 +1,274 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @bug 8153654
+ * @summary Tests for jdeps tool with multi-release jar files
+ * @modules jdk.jdeps/com.sun.tools.jdeps
+ * @library mrjar mrjar/base mrjar/9 mrjar/10 mrjar/v9 mrjar/v10
+ * @build test.* p.* q.*
+ * @run testng MultiReleaseJar
+ */
+
+import org.testng.Assert;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.concurrent.TimeUnit;
+
+public class MultiReleaseJar {
+    Path mrjar;
+    String testJdk;
+    String fileSep;
+    Path cmdPath;
+
+    @BeforeClass
+    public void initialize() throws Exception {
+        mrjar = Paths.get(System.getProperty("test.classes", "."), "mrjar");
+        testJdk = System.getProperty("test.jdk");
+        fileSep = System.getProperty("file.separator");
+        cmdPath = Paths.get(testJdk, "bin");
+    }
+
+    @Test
+    public void basic() throws Exception {
+        // build the jar file
+        Result r = run("jar -cf Version.jar -C base test --release 9 -C 9 test --release 10 -C 10 test");
+        checkResult(r);
+
+        // try out a bunch of things
+        r = run("jdeps --multi-release 9  -v missing.jar");
+        checkResult(r, false, "Warning: Path does not exist: missing.jar");
+
+        r = run("jdeps -v Version.jar");
+        checkResult(r, false, "the --multi-release option is not set");
+
+        r = run("jdeps --multi-release base  -v Version.jar");
+        checkResult(r, true,
+                "Version.jar ->",
+                "test.Version",
+                "test.Version"
+        );
+
+        r = run("jdeps --multi-release 9  -v Version.jar");
+        checkResult(r, true,
+                "Version.jar ->",
+                "9/test.NonPublic",
+                "9/test.NonPublic",
+                "9/test.Version",
+                "9/test.Version",
+                "9/test.Version",
+                "9/test.Version"
+        );
+
+        r = run("jdeps --multi-release 10  -v Version.jar");
+        checkResult(r, true,
+                "Version.jar ->",
+                "10/test.Version",
+                "10/test.Version",
+                "10/test.Version",
+                "10/test.Version",
+                "9/test.NonPublic",
+                "9/test.NonPublic"
+        );
+
+        r = run("jdeps --multi-release 8  -v Version.jar");
+        checkResult(r, false, "Error: invalid argument for option: 8");
+
+        r = run("jdeps --multi-release 9.1  -v Version.jar");
+        checkResult(r, false, "Error: invalid argument for option: 9.1");
+
+        r = run("jdeps -v -R -cp Version.jar test/Main.class");
+        checkResult(r, false, "the --multi-release option is not set");
+
+        r = run("jdeps -v -R -cp Version.jar -multi-release 9 test/Main.class");
+        checkResult(r, false,
+                "Error: unknown option: -multi-release",
+                "Usage: jdeps <options> <path",
+                "use -h, -?, -help, or --help"
+        );
+
+        r = run("jdeps -v -R -cp Version.jar --multi-release 9 test/Main.class");
+        checkResult(r, true,
+                "Main.class ->",
+                "Main.class ->",
+                "test.Main",
+                "test.Main",
+                "test.Main",
+                "Version.jar ->",
+                "9/test.NonPublic",
+                "9/test.NonPublic",
+                "9/test.Version",
+                "9/test.Version",
+                "9/test.Version",
+                "9/test.Version"
+        );
+
+        r = run("jdeps -v -R -cp Version.jar --multi-release 10 test/Main.class");
+        checkResult(r, true,
+                "Main.class ->",
+                "Main.class ->",
+                "test.Main",
+                "test.Main",
+                "test.Main",
+                "Version.jar ->",
+                "10/test.Version",
+                "10/test.Version",
+                "10/test.Version",
+                "10/test.Version",
+                "9/test.NonPublic",
+                "9/test.NonPublic"
+        );
+
+        r = run("jdeps -v -R -cp Version.jar --multi-release base test/Main.class");
+        checkResult(r, true,
+                "Main.class ->",
+                "Main.class ->",
+                "test.Main",
+                "test.Main",
+                "test.Main",
+                "Version.jar ->",
+                "test.Version",
+                "test.Version"
+        );
+
+        r = run("jdeps -v -R -cp Version.jar --multi-release 9.1 test/Main.class");
+        checkResult(r, false, "Error: invalid argument for option: 9.1");
+
+        // Rebuild jar without version 10
+        r = run("jar -cf Version.jar -C base test --release 9 -C 9 test");
+        checkResult(r);
+
+        // but ask for version 10
+        r = run("jdeps -v -R -cp Version.jar --multi-release 10 test/Main.class");
+        checkResult(r, true,
+                "Main.class ->",
+                "Main.class ->",
+                "test.Main",
+                "test.Main",
+                "test.Main",
+                "Version.jar ->",
+                "9/test.NonPublic",
+                "9/test.NonPublic",
+                "9/test.Version",
+                "9/test.Version",
+                "9/test.Version",
+                "9/test.Version"
+        );
+    }
+
+    @Test
+    public void ps_and_qs() throws Exception {
+        // build the jar file
+        Result r = run("jar -cf PQ.jar -C base p --release 9 -C v9 p -C v9 q --release 10 -C v10 q");
+        checkResult(r);
+
+        r = run("jdeps -v -R -cp PQ.jar --multi-release base PQ.jar");
+        checkResult(r, true,
+                "PQ.jar -> java.base",
+                "p.Foo"
+        );
+
+        r = run("jdeps -v -R -cp PQ.jar --multi-release 9 PQ.jar");
+        checkResult(r, true,
+                "PQ.jar -> java.base",
+                "9/p.Foo",
+                "9/p.Foo",
+                "9/q.Bar"
+        );
+
+
+        r = run("jdeps -v -R -cp PQ.jar --multi-release 10 PQ.jar");
+        checkResult(r, true,
+                "PQ.jar -> java.base",
+                "10/q.Bar",
+                "10/q.Bar",
+                "10/q.Gee",
+                "9/p.Foo",
+                "9/p.Foo"
+        );
+    }
+
+    static class Result {
+        final String cmd;
+        final int rc;
+        final String out;
+        final String err;
+        Result(String cmd, int rc, String out, String err) {
+            this.cmd = cmd;
+            this.rc = rc;
+            this.out = out;
+            this.err = err;
+        }
+    }
+
+    Result run(String cmd) throws Exception {
+        String[] cmds = cmd.split(" +");
+        cmds[0] = cmdPath.resolve(cmds[0]).toString();
+        ProcessBuilder pb = new ProcessBuilder(cmds);
+        pb.directory(mrjar.toFile());
+        Process p = pb.start();
+        p.waitFor(10, TimeUnit.SECONDS);
+        String out;
+        try (InputStream is = p.getInputStream()) {
+            out = new String(is.readAllBytes());
+        }
+        String err;
+        try (InputStream is = p.getErrorStream()) {
+            err = new String(is.readAllBytes());
+        }
+        return new Result(cmd, p.exitValue(), out, err);
+    }
+
+    void checkResult(Result r) throws Exception {
+        System.out.println(r.cmd);
+        System.out.println(r.out);
+        if (r.rc != 0) {
+            System.out.println(r.err);
+            throw new Exception("rc=" + r.rc);
+        }
+        System.out.println();
+    }
+
+    void checkResult(Result r, boolean checkrc, String... lines) throws Exception {
+        System.out.println(r.cmd);
+        System.out.println(r.out);
+        if (checkrc && r.rc != 0) {
+            System.out.println(r.err);
+            throw new Exception("rc=" + r.rc);
+        }
+        String[] out = r.out.split("\r?\n");
+        Assert.assertEquals(out.length, lines.length);
+        int n = 0;
+        for (String line : lines) {
+            Assert.assertTrue(out[n++].contains(line), "\"" + line + "\"");
+        }
+        System.out.println();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/tools/jdeps/mrjar/10/test/Version.java	Mon Sep 26 13:39:50 2016 -0700
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package test;
+
+public class Version {
+    public int getVersion() {
+        NonPublic np = new NonPublic();
+        String ignore = np.toString();
+        return 10;
+    }
+
+    private String getStringVersion() {
+        return "10";
+    }
+
+    private void foo() {
+        if (getStringVersion() == null) throw new NullPointerException();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/tools/jdeps/mrjar/9/test/NonPublic.java	Mon Sep 26 13:39:50 2016 -0700
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package test;
+
+class NonPublic {
+    public String toString() {
+        return "NonPublic";
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/tools/jdeps/mrjar/9/test/Version.java	Mon Sep 26 13:39:50 2016 -0700
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package test;
+
+public class Version {
+    public int getVersion() {
+        NonPublic np = new NonPublic();
+        String ignore = np.toString();
+        return 9;
+    }
+
+    private void foo() {
+        if (getVersion() != 9) throw new RuntimeException();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/tools/jdeps/mrjar/base/p/Foo.java	Mon Sep 26 13:39:50 2016 -0700
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package p;
+
+// dependencies: Object
+public class Foo {
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/tools/jdeps/mrjar/base/test/Version.java	Mon Sep 26 13:39:50 2016 -0700
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package test;
+
+public class Version {
+    public int getVersion() {
+        return 8;
+    }
+
+    private void foo() {
+        if (getVersion() != 8) throw new IllegalStateException();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/tools/jdeps/mrjar/test/Main.java	Mon Sep 26 13:39:50 2016 -0700
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package test;
+
+public class Main {
+    public void run() {
+        Version v = new Version();
+        v.getVersion();
+    }
+
+    public static void main(String[] args) {
+        (new Main()).run();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/tools/jdeps/mrjar/v10/q/Bar.java	Mon Sep 26 13:39:50 2016 -0700
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package q;
+
+// dependencies: Object, q.Gee
+class Bar {
+    Gee gee = new Gee();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/tools/jdeps/mrjar/v10/q/Gee.java	Mon Sep 26 13:39:50 2016 -0700
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package q;
+
+// dependencies: Object
+class Gee {
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/tools/jdeps/mrjar/v9/p/Foo.java	Mon Sep 26 13:39:50 2016 -0700
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package p;
+
+// dependencies: Object, q.Bar
+public class Foo {
+    void crash() {
+        throw new RuntimeException();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/tools/jdeps/mrjar/v9/q/Bar.java	Mon Sep 26 13:39:50 2016 -0700
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package q;
+
+// dependecies: Object
+class Bar {
+}