diff --git a/jdk/src/java.base/share/classes/jdk/internal/util/jar/JarIndex.java b/jdk/src/java.base/share/classes/jdk/internal/util/jar/JarIndex.java index fd92b13bdb9..367ac31b07a 100644 --- a/jdk/src/java.base/share/classes/jdk/internal/util/jar/JarIndex.java +++ b/jdk/src/java.base/share/classes/jdk/internal/util/jar/JarIndex.java @@ -222,7 +222,8 @@ public class JarIndex { // Any files in META-INF/ will be indexed explicitly if (fileName.equals("META-INF/") || fileName.equals(INDEX_NAME) || - fileName.equals(JarFile.MANIFEST_NAME)) + fileName.equals(JarFile.MANIFEST_NAME) || + fileName.startsWith("META-INF/versions/")) continue; if (!metaInfFilenames || !fileName.startsWith("META-INF/")) { diff --git a/jdk/src/jdk.jartool/share/classes/sun/tools/jar/Main.java b/jdk/src/jdk.jartool/share/classes/sun/tools/jar/Main.java index 85381b79ecc..f33bcf711ce 100644 --- a/jdk/src/jdk.jartool/share/classes/sun/tools/jar/Main.java +++ b/jdk/src/jdk.jartool/share/classes/sun/tools/jar/Main.java @@ -48,6 +48,7 @@ import java.util.function.Function; import java.util.function.Supplier; import java.util.regex.Pattern; import java.util.stream.Collectors; +import java.util.stream.Stream; import java.util.zip.*; import java.util.jar.*; import java.util.jar.Pack200.*; @@ -77,24 +78,82 @@ class Main { PrintStream out, err; String fname, mname, ename; String zname = ""; - String[] files; String rootjar = null; - // An entryName(path)->File map generated during "expand", it helps to + private static final int BASE_VERSION = 0; + + class Entry { + final String basename; + final String entryname; + final File file; + final boolean isDir; + + Entry(int version, File file) { + this.file = file; + String path = file.getPath(); + if (file.isDirectory()) { + isDir = true; + path = path.endsWith(File.separator) ? path : + path + File.separator; + } else { + isDir = false; + } + EntryName en = new EntryName(path, version); + basename = en.baseName; + entryname = en.entryName; + } + } + + class EntryName { + final String baseName; + final String entryName; + + EntryName(String name, int version) { + name = name.replace(File.separatorChar, '/'); + String matchPath = ""; + for (String path : pathsMap.get(version)) { + if (name.startsWith(path) + && (path.length() > matchPath.length())) { + matchPath = path; + } + } + name = safeName(name.substring(matchPath.length())); + // the old implementaton doesn't remove + // "./" if it was led by "/" (?) + if (name.startsWith("./")) { + name = name.substring(2); + } + this.baseName = name; + this.entryName = (version > BASE_VERSION) + ? VERSIONS_DIR + version + "/" + this.baseName + : this.baseName; + } + } + + // An entryName(path)->Entry map generated during "expand", it helps to // decide whether or not an existing entry in a jar file needs to be // replaced, during the "update" operation. - Map entryMap = new HashMap(); + Map entryMap = new HashMap<>(); + + // All entries need to be added/updated. + Map entries = new LinkedHashMap<>(); - // All files need to be added/updated. - Set entries = new LinkedHashSet(); // All packages. Set packages = new HashSet<>(); // All actual entries added, or existing, in the jar file ( excl manifest // and module-info.class ). Populated during create or update. Set jarEntries = new HashSet<>(); - // Directories specified by "-C" operation. - Set paths = new HashSet(); + // A paths Set for each version, where each Set contains directories + // specified by the "-C" operation. + Map> pathsMap = new HashMap<>(); + + // There's also a files array per version + Map filesMap = new HashMap<>(); + + // Do we think this is a multi-release jar? Set to true + // if --release option found followed by at least file + boolean isMultiRelease; /* * cflag: create @@ -241,10 +300,15 @@ class Main { if (ename != null) { addMainClass(manifest, ename); } + if (isMultiRelease) { + addMultiRelease(manifest); + } } Map moduleInfoPaths = new HashMap<>(); - expand(null, files, false, moduleInfoPaths); - + for (int version : filesMap.keySet()) { + String[] files = filesMap.get(version); + expand(null, files, false, moduleInfoPaths, version); + } Map moduleInfos = new LinkedHashMap<>(); if (!moduleInfoPaths.isEmpty()) { if (!checkModuleInfos(moduleInfoPaths)) @@ -348,7 +412,10 @@ class Main { (new FileInputStream(mname)) : null; Map moduleInfoPaths = new HashMap<>(); - expand(null, files, true, moduleInfoPaths); + for (int version : filesMap.keySet()) { + String[] files = filesMap.get(version); + expand(null, files, true, moduleInfoPaths, version); + } Map moduleInfos = new HashMap<>(); for (Map.Entry e : moduleInfoPaths.entrySet()) @@ -381,10 +448,11 @@ class Main { tmpFile.delete(); } } else if (tflag) { - replaceFSC(files); + replaceFSC(filesMap); // For the "list table contents" action, access using the // ZipFile class is always most efficient since only a // "one-finger" scan through the central directory is required. + String[] files = filesMapToFiles(filesMap); if (fname != null) { list(fname, files); } else { @@ -396,7 +464,7 @@ class Main { } } } else if (xflag) { - replaceFSC(files); + replaceFSC(filesMap); // For the extract action, when extracting all the entries, // access using the ZipInputStream class is most efficient, // since only a single sequential scan through the zip file is @@ -406,6 +474,8 @@ class Main { // "leading garbage", we fall back from the ZipInputStream // implementation to the ZipFile implementation, since only the // latter can handle it. + + String[] files = filesMapToFiles(filesMap); if (fname != null && files != null) { extract(fname, files); } else { @@ -421,6 +491,7 @@ class Main { } } } else if (iflag) { + String[] files = filesMap.get(BASE_VERSION); // base entries only, can be null genIndex(rootjar, files); } else if (printModuleDescriptor) { boolean found; @@ -449,6 +520,20 @@ class Main { return ok; } + private String[] filesMapToFiles(Map filesMap) { + if (filesMap.isEmpty()) return null; + return filesMap.entrySet() + .stream() + .flatMap(this::filesToEntryNames) + .toArray(String[]::new); + } + + Stream filesToEntryNames(Map.Entry fileEntries) { + int version = fileEntries.getKey(); + return Stream.of(fileEntries.getValue()) + .map(f -> (new EntryName(f, version)).entryName); + } + /** * Parses command line arguments. */ @@ -579,8 +664,10 @@ class Main { /* parse file arguments */ int n = args.length - count; if (n > 0) { + int version = BASE_VERSION; int k = 0; String[] nameBuf = new String[n]; + pathsMap.put(version, new HashSet<>()); try { for (int i = count; i < args.length; i++) { if (args[i].equals("-C")) { @@ -592,8 +679,33 @@ class Main { while (dir.indexOf("//") > -1) { dir = dir.replace("//", "/"); } - paths.add(dir.replace(File.separatorChar, '/')); + pathsMap.get(version).add(dir.replace(File.separatorChar, '/')); nameBuf[k++] = dir + args[++i]; + } else if (args[i].startsWith("--release")) { + int v = BASE_VERSION; + try { + v = Integer.valueOf(args[++i]); + } catch (NumberFormatException x) { + error(formatMsg("error.release.value.notnumber", args[i])); + // this will fall into the next error, thus returning false + } + if (v < 9) { + error(formatMsg("error.release.value.toosmall", String.valueOf(v))); + usageError(); + return false; + } + // associate the files, if any, with the previous version number + if (k > 0) { + String[] files = new String[k]; + System.arraycopy(nameBuf, 0, files, 0, k); + filesMap.put(version, files); + isMultiRelease = version > BASE_VERSION; + } + // reset the counters and start with the new version number + k = 0; + nameBuf = new String[n]; + version = v; + pathsMap.put(version, new HashSet<>()); } else { nameBuf[k++] = args[i]; } @@ -602,8 +714,13 @@ class Main { usageError(); return false; } - files = new String[k]; - System.arraycopy(nameBuf, 0, files, 0, k); + // associate remaining files, if any, with a version + if (k > 0) { + String[] files = new String[k]; + System.arraycopy(nameBuf, 0, files, 0, k); + filesMap.put(version, files); + isMultiRelease = version > BASE_VERSION; + } } else if (cflag && (mname == null)) { error(getMsg("error.bad.cflag")); usageError(); @@ -651,7 +768,8 @@ class Main { void expand(File dir, String[] files, boolean isUpdate, - Map moduleInfoPaths) + Map moduleInfoPaths, + int version) throws IOException { if (files == null) @@ -664,29 +782,29 @@ class Main { else f = new File(dir, files[i]); + Entry entry = new Entry(version, f); + String entryName = entry.entryname; + if (f.isFile()) { - String path = f.getPath(); - String entryName = entryName(path); if (entryName.endsWith(MODULE_INFO)) { moduleInfoPaths.put(entryName, f.toPath()); if (isUpdate) - entryMap.put(entryName, f); - } else if (entries.add(f)) { + entryMap.put(entryName, entry); + } else if (!entries.containsKey(entryName)) { + entries.put(entryName, entry); jarEntries.add(entryName); - if (path.endsWith(".class") && !entryName.startsWith(VERSIONS_DIR)) - packages.add(toPackageName(entryName)); + if (entry.basename.endsWith(".class") && !entryName.startsWith(VERSIONS_DIR)) + packages.add(toPackageName(entry.basename)); if (isUpdate) - entryMap.put(entryName, f); + entryMap.put(entryName, entry); } } else if (f.isDirectory()) { - if (entries.add(f)) { + if (!entries.containsKey(entryName)) { + entries.put(entryName, entry); if (isUpdate) { - String dirPath = f.getPath(); - dirPath = (dirPath.endsWith(File.separator)) ? dirPath : - (dirPath + File.separator); - entryMap.put(entryName(dirPath), f); + entryMap.put(entryName, entry); } - expand(f, f.list(), isUpdate, moduleInfoPaths); + expand(f, f.list(), isUpdate, moduleInfoPaths, version); } } else { error(formatMsg("error.nosuch.fileordir", String.valueOf(f))); @@ -740,8 +858,9 @@ class Main { in.transferTo(zos); zos.closeEntry(); } - for (File file: entries) { - addFile(zos, file); + for (String entryname : entries.keySet()) { + Entry entry = entries.get(entryname); + addFile(zos, entry); } zos.close(); } @@ -823,7 +942,7 @@ class Main { || (Mflag && isManifestEntry)) { continue; } else if (isManifestEntry && ((newManifest != null) || - (ename != null))) { + (ename != null) || isMultiRelease)) { foundManifest = true; if (newManifest != null) { // Don't read from the newManifest InputStream, as we @@ -862,21 +981,21 @@ class Main { zos.putNextEntry(e2); copy(zis, zos); } else { // replace with the new files - File f = entryMap.get(name); - addFile(zos, f); + Entry ent = entryMap.get(name); + addFile(zos, ent); entryMap.remove(name); - entries.remove(f); + entries.remove(name); } jarEntries.add(name); - if (name.endsWith(".class")) + if (name.endsWith(".class") && !(name.startsWith(VERSIONS_DIR))) packages.add(toPackageName(name)); } } // add the remaining new files - for (File f: entries) { - addFile(zos, f); + for (String entryname : entries.keySet()) { + addFile(zos, entries.get(entryname)); } if (!foundManifest) { if (newManifest != null) { @@ -961,6 +1080,9 @@ class Main { if (ename != null) { addMainClass(m, ename); } + if (isMultiRelease) { + addMultiRelease(m); + } ZipEntry e = new ZipEntry(MANIFEST_NAME); e.setTime(System.currentTimeMillis()); if (flag0) { @@ -1016,24 +1138,6 @@ class Main { return name; } - private String entryName(String name) { - name = name.replace(File.separatorChar, '/'); - String matchPath = ""; - for (String path : paths) { - if (name.startsWith(path) - && (path.length() > matchPath.length())) { - matchPath = path; - } - } - name = safeName(name.substring(matchPath.length())); - // the old implementaton doesn't remove - // "./" if it was led by "/" (?) - if (name.startsWith("./")) { - name = name.substring(2); - } - return name; - } - private void addVersion(Manifest m) { Attributes global = m.getMainAttributes(); if (global.getValue(Attributes.Name.MANIFEST_VERSION) == null) { @@ -1058,6 +1162,11 @@ class Main { global.put(Attributes.Name.MAIN_CLASS, mainApp); } + private void addMultiRelease(Manifest m) { + Attributes global = m.getMainAttributes(); + global.put(Attributes.Name.MULTI_RELEASE, "true"); + } + private boolean isAmbiguousMainClass(Manifest m) { if (ename != null) { Attributes global = m.getMainAttributes(); @@ -1073,14 +1182,10 @@ class Main { /** * Adds a new file entry to the ZIP output stream. */ - void addFile(ZipOutputStream zos, File file) throws IOException { - String name = file.getPath(); - boolean isDir = file.isDirectory(); - if (isDir) { - name = name.endsWith(File.separator) ? name : - (name + File.separator); - } - name = entryName(name); + void addFile(ZipOutputStream zos, Entry entry) throws IOException { + File file = entry.file; + String name = entry.entryname; + boolean isDir = entry.isDir; if (name.equals("") || name.equals(".") || name.equals(zname)) { return; @@ -1221,12 +1326,15 @@ class Main { os.updateEntry(e); } - void replaceFSC(String files[]) { - if (files != null) { - for (int i = 0; i < files.length; i++) { - files[i] = files[i].replace(File.separatorChar, '/'); + void replaceFSC(Map filesMap) { + filesMap.keySet().forEach(version -> { + String[] files = filesMap.get(version); + if (files != null) { + for (int i = 0; i < files.length; i++) { + files[i] = files[i].replace(File.separatorChar, '/'); + } } - } + }); } @SuppressWarnings("serial") @@ -1566,7 +1674,7 @@ class Main { /** * Print an error message; like something is broken */ - protected void error(String s) { + void error(String s) { err.println(s); } diff --git a/jdk/src/jdk.jartool/share/classes/sun/tools/jar/resources/jar.properties b/jdk/src/jdk.jartool/share/classes/sun/tools/jar/resources/jar.properties index 85316dfb00b..e00ee76610d 100644 --- a/jdk/src/jdk.jartool/share/classes/sun/tools/jar/resources/jar.properties +++ b/jdk/src/jdk.jartool/share/classes/sun/tools/jar/resources/jar.properties @@ -63,24 +63,28 @@ error.unexpected.module-info=\ error.module.descriptor.not.found=\ Module descriptor not found error.versioned.info.without.root=\ - module-info.class found in versioned section without module-info.class \ + module-info.class found in a versioned directory without module-info.class \ in the root error.versioned.info.name.notequal=\ - module-info.class in versioned section contains incorrect name + module-info.class in a versioned directory contains incorrect name error.versioned.info.requires.public=\ - module-info.class in versioned section contains additional requires public + module-info.class in a versioned directory contains additional requires public error.versioned.info.requires.added=\ - module-info.class in versioned section contains additional requires + module-info.class in a versioned directory contains additional requires error.versioned.info.requires.dropped=\ - module-info.class in versioned section contains missing requires + module-info.class in a versioned directory contains missing requires error.versioned.info.exports.notequal=\ - module-info.class in versioned section contains different exports + module-info.class in a versioned directory contains different exports error.versioned.info.provides.notequal=\ - module-info.class in versioned section contains different provides + module-info.class in a versioned directory contains different provides error.invalid.versioned.module.attribute=\ Invalid module descriptor attribute {0} error.missing.provider=\ Service provider not found: {0} +error.release.value.notnumber=\ + release {0} not valid +error.release.value.toosmall=\ + release {0} not valid, must be >= 9 out.added.manifest=\ added manifest out.added.module-info=\ @@ -109,7 +113,7 @@ out.size=\ usage.compat=\ \Compatibility Interface:\ \n\ -Usage: jar {ctxui}[vfmn0PMe] [jar-file] [manifest-file] [entry-point] [-C dir] files ...\n\ +Usage: jar {ctxui}[vfmn0PMe] [jar-file] [manifest-file] [entry-point] [-C dir] files] ...\n\ Options:\n\ \ \ -c create new archive\n\ \ \ -t list table of contents for archive\n\ @@ -141,7 +145,7 @@ main.usage.summary.try=\ Try `jar --help' for more information. main.help.preopt=\ -Usage: jar [OPTION...] [-C dir] files ...\n\ +Usage: jar [OPTION...] [ [--release VERSION] [-C dir] files] ...\n\ jar creates an archive for classes and resources, and can manipulate or\n\ restore individual classes or resources from an archive.\n\ \n\ @@ -156,7 +160,9 @@ restore individual classes or resources from an archive.\n\ \ -C foo/ classes resources\n\ \ # Update an existing non-modular jar to a modular jar:\n\ \ jar --update --file foo.jar --main-class com.foo.Main --module-version 1.0\n\ -\ -C foo/ module-info.class +\ -C foo/ module-info.class\n\ +\ # Create a multi-release jar, placing some files in the META-INF/versions/9 directory:\n\ +\ jar --create --file mr.jar -C foo classes --release 9 -C foo9 classes main.help.opt.main=\ \ Main operation mode:\n main.help.opt.main.create=\ @@ -178,7 +184,9 @@ main.help.opt.any=\ \ -C DIR Change to the specified directory and include the\n\ \ following file main.help.opt.any.file=\ -\ -f, --file=FILE The archive file name +\ -f, --file=FILE The archive file name\n\ +\ --release VERSION Places all following files in a versioned directory\n\ +\ of the jar (i.e. META-INF/versions/VERSION/) main.help.opt.any.verbose=\ \ -v, --verbose Generate verbose output on standard output main.help.opt.create.update=\ diff --git a/jdk/test/tools/jar/compat/CLICompatibility.java b/jdk/test/tools/jar/compat/CLICompatibility.java index 1dd3589362c..75dd1393d99 100644 --- a/jdk/test/tools/jar/compat/CLICompatibility.java +++ b/jdk/test/tools/jar/compat/CLICompatibility.java @@ -415,14 +415,14 @@ public class CLICompatibility { jar("-h") .assertSuccess() .resultChecker(r -> - assertTrue(r.output.startsWith("Usage: jar [OPTION...] [-C dir] files"), + assertTrue(r.output.startsWith("Usage: jar [OPTION...] [ [--release VERSION] [-C dir] files]"), "Failed, got [" + r.output + "]") ); jar("--help") .assertSuccess() .resultChecker(r -> - assertTrue(r.output.startsWith("Usage: jar [OPTION...] [-C dir] files"), + assertTrue(r.output.startsWith("Usage: jar [OPTION...] [ [--release VERSION] [-C dir] files]"), "Failed, got [" + r.output + "]") ); diff --git a/jdk/test/tools/jar/multiRelease/Basic.java b/jdk/test/tools/jar/multiRelease/Basic.java new file mode 100644 index 00000000000..a1b595338e0 --- /dev/null +++ b/jdk/test/tools/jar/multiRelease/Basic.java @@ -0,0 +1,354 @@ +/* + * 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 + * @library /test/lib/share/classes + * @modules java.base/jdk.internal.misc + * @build jdk.test.lib.JDKToolFinder jdk.test.lib.Platform + * @run testng Basic + */ + +import static org.testng.Assert.*; + +import org.testng.annotations.*; + +import java.io.*; +import java.nio.file.*; +import java.nio.file.attribute.*; +import java.util.*; +import java.util.function.Consumer; +import java.util.jar.*; +import java.util.stream.Stream; +import java.util.zip.*; + +import jdk.test.lib.JDKToolFinder; + +import static java.lang.String.format; +import static java.lang.System.out; + +public class Basic { + private final String src = System.getProperty("test.src", "."); + private final String usr = System.getProperty("user.dir", "."); + + @Test + // create a regular, non-multi-release jar + public void test00() throws IOException { + String jarfile = "test.jar"; + + compile("test01"); //use same data as test01 + + Path classes = Paths.get("classes"); + jar("cf", jarfile, "-C", classes.resolve("base").toString(), ".") + .assertSuccess(); + + checkMultiRelease(jarfile, false); + + Map names = Map.of( + "version/Main.class", + new String[] {"base", "version", "Main.class"}, + + "version/Version.class", + new String[] {"base", "version", "Version.class"} + ); + + compare(jarfile, names); + + delete(jarfile); + deleteDir(Paths.get(usr, "classes")); + } + + @Test + // create a multi-release jar + public void test01() throws IOException { + String jarfile = "test.jar"; + + compile("test01"); + + Path classes = Paths.get("classes"); + jar("cf", jarfile, "-C", classes.resolve("base").toString(), ".", + "--release", "9", "-C", classes.resolve("v9").toString(), ".", + "--release", "10", "-C", classes.resolve("v10").toString(), ".") + .assertSuccess(); + + checkMultiRelease(jarfile, true); + + Map names = Map.of( + "version/Main.class", + new String[] {"base", "version", "Main.class"}, + + "version/Version.class", + new String[] {"base", "version", "Version.class"}, + + "META-INF/versions/9/version/Version.class", + new String[] {"v9", "version", "Version.class"}, + + "META-INF/versions/10/version/Version.class", + new String[] {"v10", "version", "Version.class"} + ); + + compare(jarfile, names); + + delete(jarfile); + deleteDir(Paths.get(usr, "classes")); + } + + @Test + // update a regular jar to a multi-release jar + public void test02() throws IOException { + String jarfile = "test.jar"; + + compile("test01"); //use same data as test01 + + Path classes = Paths.get("classes"); + jar("cf", jarfile, "-C", classes.resolve("base").toString(), ".") + .assertSuccess(); + + checkMultiRelease(jarfile, false); + + jar("uf", jarfile, "--release", "9", "-C", classes.resolve("v9").toString(), ".") + .assertSuccess(); + + checkMultiRelease(jarfile, true); + + Map names = Map.of( + "version/Main.class", + new String[] {"base", "version", "Main.class"}, + + "version/Version.class", + new String[] {"base", "version", "Version.class"}, + + "META-INF/versions/9/version/Version.class", + new String[] {"v9", "version", "Version.class"} + ); + + compare(jarfile, names); + + delete(jarfile); + deleteDir(Paths.get(usr, "classes")); + } + + @Test + // replace a base entry and a versioned entry + public void test03() throws IOException { + String jarfile = "test.jar"; + + compile("test01"); //use same data as test01 + + Path classes = Paths.get("classes"); + jar("cf", jarfile, "-C", classes.resolve("base").toString(), ".", + "--release", "9", "-C", classes.resolve("v9").toString(), ".") + .assertSuccess(); + + checkMultiRelease(jarfile, true); + + Map names = Map.of( + "version/Main.class", + new String[] {"base", "version", "Main.class"}, + + "version/Version.class", + new String[] {"base", "version", "Version.class"}, + + "META-INF/versions/9/version/Version.class", + new String[] {"v9", "version", "Version.class"} + ); + + compare(jarfile, names); + + // write the v9 version/Version.class entry in base and the v10 + // version/Version.class entry in versions/9 section + jar("uf", jarfile, "-C", classes.resolve("v9").toString(), "version", + "--release", "9", "-C", classes.resolve("v10").toString(), ".") + .assertSuccess(); + + checkMultiRelease(jarfile, true); + + names = Map.of( + "version/Main.class", + new String[] {"base", "version", "Main.class"}, + + "version/Version.class", + new String[] {"v9", "version", "Version.class"}, + + "META-INF/versions/9/version/Version.class", + new String[] {"v10", "version", "Version.class"} + ); + + delete(jarfile); + deleteDir(Paths.get(usr, "classes")); + } + + /* + * Test Infrastructure + */ + private void compile(String test) throws IOException { + Path classes = Paths.get(usr, "classes", "base"); + Files.createDirectories(classes); + Path source = Paths.get(src, "data", test, "base", "version"); + javac(classes, source.resolve("Main.java"), source.resolve("Version.java")); + + classes = Paths.get(usr, "classes", "v9"); + Files.createDirectories(classes); + source = Paths.get(src, "data", test, "v9", "version"); + javac(classes, source.resolve("Version.java")); + + classes = Paths.get(usr, "classes", "v10"); + Files.createDirectories(classes); + source = Paths.get(src, "data", test, "v10", "version"); + javac(classes, source.resolve("Version.java")); + } + + private void checkMultiRelease(String jarFile, boolean expected) throws IOException { + try (JarFile jf = new JarFile(new File(jarFile), true, ZipFile.OPEN_READ, + JarFile.Release.RUNTIME)) { + assertEquals(jf.isMultiRelease(), expected); + } + } + + // compares the bytes found in the jar entries with the bytes found in the + // corresponding data files used to create the entries + private void compare(String jarfile, Map names) throws IOException { + try (JarFile jf = new JarFile(jarfile)) { + for (String name : names.keySet()) { + Path path = Paths.get("classes", names.get(name)); + byte[] b1 = Files.readAllBytes(path); + byte[] b2; + JarEntry je = jf.getJarEntry(name); + try (InputStream is = jf.getInputStream(je)) { + b2 = is.readAllBytes(); + } + assertEquals(b1,b2); + } + } + } + + private void delete(String name) throws IOException { + Files.delete(Paths.get(usr, name)); + } + + private void deleteDir(Path dir) throws IOException { + Files.walkFileTree(dir, new SimpleFileVisitor() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + Files.delete(file); + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { + Files.delete(dir); + return FileVisitResult.CONTINUE; + } + }); + } + + /* + * The following methods were taken from modular jar and other jar tests + */ + + void javac(Path dest, Path... sourceFiles) throws IOException { + String javac = JDKToolFinder.getJDKTool("javac"); + + List commands = new ArrayList<>(); + commands.add(javac); + commands.add("-d"); + commands.add(dest.toString()); + Stream.of(sourceFiles).map(Object::toString).forEach(x -> commands.add(x)); + + quickFail(run(new ProcessBuilder(commands))); + } + + Result jarWithStdin(File stdinSource, String... args) { + String jar = JDKToolFinder.getJDKTool("jar"); + List commands = new ArrayList<>(); + commands.add(jar); + Stream.of(args).forEach(x -> commands.add(x)); + ProcessBuilder p = new ProcessBuilder(commands); + if (stdinSource != null) + p.redirectInput(stdinSource); + return run(p); + } + + Result jar(String... args) { + return jarWithStdin(null, args); + } + + void quickFail(Result r) { + if (r.ec != 0) + throw new RuntimeException(r.output); + } + + Result run(ProcessBuilder pb) { + Process p; + out.printf("Running: %s%n", pb.command()); + try { + p = pb.start(); + } catch (IOException e) { + throw new RuntimeException( + format("Couldn't start process '%s'", pb.command()), e); + } + + String output; + try { + output = toString(p.getInputStream(), p.getErrorStream()); + } catch (IOException e) { + throw new RuntimeException( + format("Couldn't read process output '%s'", pb.command()), e); + } + + try { + p.waitFor(); + } catch (InterruptedException e) { + throw new RuntimeException( + format("Process hasn't finished '%s'", pb.command()), e); + } + return new Result(p.exitValue(), output); + } + + String toString(InputStream in1, InputStream in2) throws IOException { + try (ByteArrayOutputStream dst = new ByteArrayOutputStream(); + InputStream concatenated = new SequenceInputStream(in1, in2)) { + concatenated.transferTo(dst); + return new String(dst.toByteArray(), "UTF-8"); + } + } + + static class Result { + final int ec; + final String output; + + private Result(int ec, String output) { + this.ec = ec; + this.output = output; + } + Result assertSuccess() { + assertTrue(ec == 0, format("ec: %d, output: %s", ec, output)); + return this; + } + Result assertFailure() { + assertTrue(ec != 0, format("ec: %d, output: %s", ec, output)); + return this; + } + Result resultChecker(Consumer r) { r.accept(this); return this; } + } +} diff --git a/jdk/test/tools/jar/multiRelease/data/test01/base/version/Main.java b/jdk/test/tools/jar/multiRelease/data/test01/base/version/Main.java new file mode 100644 index 00000000000..4fae52482a6 --- /dev/null +++ b/jdk/test/tools/jar/multiRelease/data/test01/base/version/Main.java @@ -0,0 +1,8 @@ +package version; + +public class Main { + public static void main(String[] args) { + Version v = new Version(); + System.out.println("I am running on version " + v.getVersion()); + } +} diff --git a/jdk/test/tools/jar/multiRelease/data/test01/base/version/Version.java b/jdk/test/tools/jar/multiRelease/data/test01/base/version/Version.java new file mode 100644 index 00000000000..71bd392e821 --- /dev/null +++ b/jdk/test/tools/jar/multiRelease/data/test01/base/version/Version.java @@ -0,0 +1,13 @@ +package version; + +public class Version { + public int getVersion() { + return 8; + } + + protected void doNothing() { + } + + private void reallyDoNothing() { + } +} diff --git a/jdk/test/tools/jar/multiRelease/data/test01/v10/version/Version.java b/jdk/test/tools/jar/multiRelease/data/test01/v10/version/Version.java new file mode 100644 index 00000000000..571d5b4914f --- /dev/null +++ b/jdk/test/tools/jar/multiRelease/data/test01/v10/version/Version.java @@ -0,0 +1,13 @@ +package version; + +public class Version { + public int getVersion() { + return 10; + } + + protected void doNothing() { + } + + private void someName() { + } +} diff --git a/jdk/test/tools/jar/multiRelease/data/test01/v9/version/Version.java b/jdk/test/tools/jar/multiRelease/data/test01/v9/version/Version.java new file mode 100644 index 00000000000..db45a5c8b58 --- /dev/null +++ b/jdk/test/tools/jar/multiRelease/data/test01/v9/version/Version.java @@ -0,0 +1,13 @@ +package version; + +public class Version { + public int getVersion() { + return 9; + } + + protected void doNothing() { + } + + private void anyName() { + } +}