8114827: JDK 9 multi-release enabled jar tool
Reviewed-by: chegar
This commit is contained in:
parent
7dd146f072
commit
9f0c345f7f
@ -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/")) {
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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=\
|
||||
|
@ -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 + "]")
|
||||
);
|
||||
|
||||
|
354
jdk/test/tools/jar/multiRelease/Basic.java
Normal file
354
jdk/test/tools/jar/multiRelease/Basic.java
Normal 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; }
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
package version;
|
||||
|
||||
public class Version {
|
||||
public int getVersion() {
|
||||
return 8;
|
||||
}
|
||||
|
||||
protected void doNothing() {
|
||||
}
|
||||
|
||||
private void reallyDoNothing() {
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
package version;
|
||||
|
||||
public class Version {
|
||||
public int getVersion() {
|
||||
return 10;
|
||||
}
|
||||
|
||||
protected void doNothing() {
|
||||
}
|
||||
|
||||
private void someName() {
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
package version;
|
||||
|
||||
public class Version {
|
||||
public int getVersion() {
|
||||
return 9;
|
||||
}
|
||||
|
||||
protected void doNothing() {
|
||||
}
|
||||
|
||||
private void anyName() {
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user