8114827: JDK 9 multi-release enabled jar tool

Reviewed-by: chegar
This commit is contained in:
Steve Drach 2016-06-10 13:57:51 -07:00 committed by Steve Drach
parent 7dd146f072
commit 9f0c345f7f
9 changed files with 602 additions and 84 deletions

View File

@ -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/")) {

View File

@ -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<String, File> entryMap = new HashMap<String, File>();
Map<String, Entry> entryMap = new HashMap<>();
// All entries need to be added/updated.
Map<String, Entry> entries = new LinkedHashMap<>();
// All files need to be added/updated.
Set<File> entries = new LinkedHashSet<File>();
// All packages.
Set<String> 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<String> jarEntries = new HashSet<>();
// Directories specified by "-C" operation.
Set<String> paths = new HashSet<String>();
// A paths Set for each version, where each Set contains directories
// specified by the "-C" operation.
Map<Integer,Set<String>> pathsMap = new HashMap<>();
// There's also a files array per version
Map<Integer,String[]> 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<String,Path> 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<String,byte[]> moduleInfos = new LinkedHashMap<>();
if (!moduleInfoPaths.isEmpty()) {
if (!checkModuleInfos(moduleInfoPaths))
@ -348,7 +412,10 @@ class Main {
(new FileInputStream(mname)) : null;
Map<String,Path> 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<String,byte[]> moduleInfos = new HashMap<>();
for (Map.Entry<String,Path> 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<Integer,String[]> filesMap) {
if (filesMap.isEmpty()) return null;
return filesMap.entrySet()
.stream()
.flatMap(this::filesToEntryNames)
.toArray(String[]::new);
}
Stream<String> filesToEntryNames(Map.Entry<Integer,String[]> 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<String,Path> moduleInfoPaths)
Map<String,Path> 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<Integer, String []> 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);
}

View File

@ -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=\

View File

@ -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 + "]")
);

View File

@ -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<String,String[]> 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<String,String[]> 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<String,String[]> 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<String,String[]> 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<String,String[]> 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<Path>() {
@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<String> 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<String> 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<Result> r) { r.accept(this); return this; }
}
}

View File

@ -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());
}
}

View File

@ -0,0 +1,13 @@
package version;
public class Version {
public int getVersion() {
return 8;
}
protected void doNothing() {
}
private void reallyDoNothing() {
}
}

View File

@ -0,0 +1,13 @@
package version;
public class Version {
public int getVersion() {
return 10;
}
protected void doNothing() {
}
private void someName() {
}
}

View File

@ -0,0 +1,13 @@
package version;
public class Version {
public int getVersion() {
return 9;
}
protected void doNothing() {
}
private void anyName() {
}
}