8189611: JarFile versioned stream and real name support
Reviewed-by: psandoz, alanb, mchung, martin
--- a/src/java.base/share/classes/java/util/jar/JarEntry.java Wed Nov 29 22:23:21 2017 +0100
+++ b/src/java.base/share/classes/java/util/jar/JarEntry.java Wed Nov 29 15:01:16 2017 -0800
@@ -128,4 +128,25 @@
public CodeSigner[] getCodeSigners() {
return signers == null ? null : signers.clone();
}
+
+ /**
+ * Returns the real name of this {@code JarEntry}.
+ *
+ * If this {@code JarEntry} is an entry of a
+ * <a href="JarFile.html#multirelease">multi-release jar file</a> and the
+ * {@code JarFile} is configured to be processed as such, the name returned
+ * by this method is the path name of the versioned entry that the
+ * {@code JarEntry} represents, rather than the path name of the base entry
+ * that {@link #getName()} returns. If the {@code JarEntry} does not represent
+ * a versioned entry of a multi-release {@code JarFile} or the {@code JarFile}
+ * is not configured for processing a multi-release jar file, this method
+ * returns the same name that {@link #getName()} returns.
+ *
+ * @return the real name of the JarEntry
+ *
+ * @since 10
+ */
+ public String getRealName() {
+ return super.getName();
+ }
}
--- a/src/java.base/share/classes/java/util/jar/JarFile.java Wed Nov 29 22:23:21 2017 +0100
+++ b/src/java.base/share/classes/java/util/jar/JarFile.java Wed Nov 29 15:01:16 2017 -0800
@@ -26,6 +26,7 @@
package java.util.jar;
import jdk.internal.misc.SharedSecrets;
+import jdk.internal.misc.JavaUtilZipFileAccess;
import sun.security.action.GetPropertyAction;
import sun.security.util.ManifestEntryVerifier;
import sun.security.util.SignatureFileVerifier;
@@ -45,10 +46,12 @@
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
+import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Spliterator;
import java.util.Spliterators;
+import java.util.stream.Collector;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import java.util.zip.ZipEntry;
@@ -163,9 +166,13 @@
// true if manifest checked for special attributes
private volatile boolean hasCheckedSpecialAttributes;
+ private static final JavaUtilZipFileAccess JUZFA;
+
static {
// Set up JavaUtilJarAccess in SharedSecrets
SharedSecrets.setJavaUtilJarAccess(new JavaUtilJarAccessImpl());
+ // Get JavaUtilZipFileAccess from SharedSecrets
+ JUZFA = jdk.internal.misc.SharedSecrets.getJavaUtilZipFileAccess();
// multi-release jar file versions >= 9
BASE_VERSION = Runtime.Version.parse(Integer.toString(8));
BASE_VERSION_MAJOR = BASE_VERSION.major();
@@ -424,8 +431,7 @@
}
private String[] getMetaInfEntryNames() {
- return jdk.internal.misc.SharedSecrets.getJavaUtilZipFileAccess()
- .getMetaInfEntryNames((ZipFile)this);
+ return JUZFA.getMetaInfEntryNames((ZipFile)this);
}
/**
@@ -497,47 +503,11 @@
* </div>
*/
public ZipEntry getEntry(String name) {
- ZipEntry ze = super.getEntry(name);
- if (ze != null) {
- return new JarFileEntry(ze);
- }
- // no matching base entry, but maybe there is a versioned entry,
- // like a new private class
+ JarFileEntry je = getEntry0(name);
if (isMultiRelease()) {
- ze = new ZipEntry(name);
- ZipEntry vze = getVersionedEntry(ze);
- if (ze != vze) {
- return new JarFileEntry(name, vze);
- }
+ return getVersionedEntry(name, je);
}
- return null;
- }
-
- private class JarEntryIterator implements Enumeration<JarEntry>,
- Iterator<JarEntry>
- {
- final Enumeration<? extends ZipEntry> e = JarFile.super.entries();
-
- public boolean hasNext() {
- return e.hasMoreElements();
- }
-
- public JarEntry next() {
- ZipEntry ze = e.nextElement();
- return new JarFileEntry(ze.getName(), ze);
- }
-
- public boolean hasMoreElements() {
- return hasNext();
- }
-
- public JarEntry nextElement() {
- return next();
- }
-
- public Iterator<JarEntry> asIterator() {
- return this;
- }
+ return je;
}
/**
@@ -548,7 +518,7 @@
* may be thrown if the jar file has been closed
*/
public Enumeration<JarEntry> entries() {
- return new JarEntryIterator();
+ return JUZFA.entries(this, JarFileEntry::new);
}
/**
@@ -561,68 +531,100 @@
* @since 1.8
*/
public Stream<JarEntry> stream() {
- return StreamSupport.stream(Spliterators.spliterator(
- new JarEntryIterator(), size(),
- Spliterator.ORDERED | Spliterator.DISTINCT |
- Spliterator.IMMUTABLE | Spliterator.NONNULL), false);
- }
-
- private ZipEntry searchForVersionedEntry(final int version, String name) {
- ZipEntry vze = null;
- String sname = "/" + name;
- int i = version;
- while (i > BASE_VERSION_MAJOR) {
- vze = super.getEntry(META_INF_VERSIONS + i + sname);
- if (vze != null) break;
- i--;
- }
- return vze;
- }
-
- private ZipEntry getVersionedEntry(ZipEntry ze) {
- ZipEntry vze = null;
- if (BASE_VERSION_MAJOR < versionMajor) {
- String name = ze.getName();
- if (!name.startsWith(META_INF)) {
- vze = searchForVersionedEntry(versionMajor, name);
- }
- }
- return vze == null ? ze : vze;
+ return JUZFA.stream(this, JarFileEntry::new);
}
/**
- * Returns the real name of a {@code JarEntry}. If this {@code JarFile} is
- * a multi-release jar file and is configured to be processed as such, the
- * name returned by this method is the path name of the versioned entry
- * that the {@code JarEntry} represents, rather than the path name of the
- * base entry that {@link JarEntry#getName()} returns. If the
- * {@code JarEntry} does not represent a versioned entry, or the
- * jar file is not a multi-release jar file or {@code JarFile} is not
- * configured for processing a multi-release jar file, this method returns
- * the same name that {@link JarEntry#getName()} returns.
+ * Returns a {@code Stream} of the versioned jar file entries.
+ *
+ * <p>If this {@code JarFile} is a multi-release jar file and is configured to
+ * be processed as such, then an entry in the stream is the latest versioned entry
+ * associated with the corresponding base entry name. The maximum version of the
+ * latest versioned entry is the version returned by {@link #getVersion()}.
+ * The returned stream may include an entry that only exists as a versioned entry.
+ *
+ * If the jar file is not a multi-release jar file or the {@code JarFile} is not
+ * configured for processing a multi-release jar file, this method returns the
+ * same stream that {@link #stream()} returns.
*
- * @param entry the JarEntry
- * @return the real name of the JarEntry
- * @since 9
+ * @return stream of versioned entries
+ * @since 10
+ */
+ public Stream<JarEntry> versionedStream() {
+
+ if (isMultiRelease()) {
+ return JUZFA.entryNameStream(this).map(this::getBasename)
+ .filter(Objects::nonNull)
+ .distinct()
+ .map(this::getJarEntry);
+ }
+ return stream();
+ }
+
+ /*
+ * Invokes {@ZipFile}'s getEntry to Return a {@code JarFileEntry} for the
+ * given entry name or {@code null} if not found.
*/
+ private JarFileEntry getEntry0(String name) {
+ return (JarFileEntry)JUZFA.getEntry(this, name, JarFileEntry::new);
+ }
+
+ private String getBasename(String name) {
+ if (name.startsWith(META_INF_VERSIONS)) {
+ int off = META_INF_VERSIONS.length();
+ int index = name.indexOf('/', off);
+ try {
+ // filter out dir META-INF/versions/ and META-INF/versions/*/
+ // and any entry with version > 'version'
+ if (index == -1 || index == (name.length() - 1) ||
+ Integer.parseInt(name, off, index, 10) > versionMajor) {
+ return null;
+ }
+ } catch (NumberFormatException x) {
+ return null; // remove malformed entries silently
+ }
+ // map to its base name
+ return name.substring(index + 1);
+ }
+ return name;
+ }
+
+ private JarEntry getVersionedEntry(String name, JarEntry je) {
+ if (BASE_VERSION_MAJOR < versionMajor) {
+ if (!name.startsWith(META_INF)) {
+ // search for versioned entry
+ int v = versionMajor;
+ while (v > BASE_VERSION_MAJOR) {
+ JarFileEntry vje = getEntry0(META_INF_VERSIONS + v + "/" + name);
+ if (vje != null) {
+ return vje.withBasename(name);
+ }
+ v--;
+ }
+ }
+ }
+ return je;
+ }
+
+ // placeholder for now
String getRealName(JarEntry entry) {
- if (entry instanceof JarFileEntry) {
- return ((JarFileEntry)entry).realName();
- }
- return entry.getName();
+ return entry.getRealName();
}
private class JarFileEntry extends JarEntry {
- final private String name;
+ private String basename;
- JarFileEntry(ZipEntry ze) {
- super(isMultiRelease() ? getVersionedEntry(ze) : ze);
- this.name = ze.getName();
+ JarFileEntry(String name) {
+ super(name);
+ this.basename = name;
}
+
JarFileEntry(String name, ZipEntry vze) {
super(vze);
- this.name = name;
+ this.basename = name;
}
+
+ @Override
public Attributes getAttributes() throws IOException {
Manifest man = JarFile.this.getManifest();
if (man != null) {
@@ -631,6 +633,8 @@
return null;
}
}
+
+ @Override
public Certificate[] getCertificates() {
try {
maybeInstantiateVerifier();
@@ -642,6 +646,8 @@
}
return certs == null ? null : certs.clone();
}
+
+ @Override
public CodeSigner[] getCodeSigners() {
try {
maybeInstantiateVerifier();
@@ -653,20 +659,30 @@
}
return signers == null ? null : signers.clone();
}
- JarFileEntry realEntry() {
- if (isMultiRelease() && versionMajor != BASE_VERSION_MAJOR) {
- String entryName = super.getName();
- return entryName.equals(this.name) ? this : new JarFileEntry(entryName, this);
- }
- return this;
- }
- String realName() {
+
+ @Override
+ public String getRealName() {
return super.getName();
}
@Override
public String getName() {
- return name;
+ return basename;
+ }
+
+ JarFileEntry realEntry() {
+ if (isMultiRelease() && versionMajor != BASE_VERSION_MAJOR) {
+ String entryName = super.getName();
+ return entryName == basename || entryName.equals(basename) ?
+ this : new JarFileEntry(entryName, this);
+ }
+ return this;
+ }
+
+ // changes the basename, returns "this"
+ JarFileEntry withBasename(String name) {
+ basename = name;
+ return this;
}
}
@@ -704,7 +720,6 @@
}
}
-
/*
* Initializes the verifier object by reading all the manifest
* entries and passing them to the verifier.
@@ -904,7 +919,7 @@
private JarEntry getManEntry() {
if (manEntry == null) {
// First look up manifest entry using standard name
- ZipEntry manEntry = super.getEntry(MANIFEST_NAME);
+ JarEntry manEntry = getEntry0(MANIFEST_NAME);
if (manEntry == null) {
// If not found, then iterate through all the "META-INF/"
// entries to find a match.
@@ -912,15 +927,13 @@
if (names != null) {
for (String name : names) {
if (MANIFEST_NAME.equals(name.toUpperCase(Locale.ENGLISH))) {
- manEntry = super.getEntry(name);
+ manEntry = getEntry0(name);
break;
}
}
}
}
- this.manEntry = (manEntry == null)
- ? null
- : new JarFileEntry(manEntry.getName(), manEntry);
+ this.manEntry = manEntry;
}
return manEntry;
}
@@ -1032,8 +1045,32 @@
}
}
- JarEntry newEntry(ZipEntry ze) {
- return new JarFileEntry(ze);
+ /*
+ * Returns a versioned {@code JarFileEntry} for the given entry,
+ * if there is one. Otherwise returns the original entry. This
+ * is invoked by the {@code entries2} for verifier.
+ */
+ JarEntry newEntry(JarEntry je) {
+ if (isMultiRelease()) {
+ return getVersionedEntry(je.getName(), je);
+ }
+ return je;
+ }
+
+ /*
+ * Returns a versioned {@code JarFileEntry} for the given entry
+ * name, if there is one. Otherwise returns a {@code JarFileEntry}
+ * with the given name. It is invoked from JarVerifier's entries2
+ * for {@code singers}.
+ */
+ JarEntry newEntry(String name) {
+ if (isMultiRelease()) {
+ JarEntry vje = getVersionedEntry(name, (JarEntry)null);
+ if (vje != null) {
+ return vje;
+ }
+ }
+ return new JarFileEntry(name);
}
Enumeration<String> entryNames(CodeSource[] cs) {
@@ -1077,35 +1114,37 @@
Enumeration<JarEntry> entries2() {
ensureInitialization();
if (jv != null) {
- return jv.entries2(this, super.entries());
+ return jv.entries2(this, JUZFA.entries(JarFile.this,
+ JarFileEntry::new));
}
// screen out entries which are never signed
- final Enumeration<? extends ZipEntry> enum_ = super.entries();
+ final var unfilteredEntries = JUZFA.entries(JarFile.this, JarFileEntry::new);
+
return new Enumeration<>() {
- ZipEntry entry;
+ JarEntry entry;
public boolean hasMoreElements() {
if (entry != null) {
return true;
}
- while (enum_.hasMoreElements()) {
- ZipEntry ze = enum_.nextElement();
- if (JarVerifier.isSigningRelated(ze.getName())) {
+ while (unfilteredEntries.hasMoreElements()) {
+ JarEntry je = unfilteredEntries.nextElement();
+ if (JarVerifier.isSigningRelated(je.getName())) {
continue;
}
- entry = ze;
+ entry = je;
return true;
}
return false;
}
- public JarFileEntry nextElement() {
+ public JarEntry nextElement() {
if (hasMoreElements()) {
- ZipEntry ze = entry;
+ JarEntry je = entry;
entry = null;
- return new JarFileEntry(ze);
+ return newEntry(je);
}
throw new NoSuchElementException();
}
--- a/src/java.base/share/classes/java/util/jar/JarVerifier.java Wed Nov 29 22:23:21 2017 +0100
+++ b/src/java.base/share/classes/java/util/jar/JarVerifier.java Wed Nov 29 15:01:16 2017 -0800
@@ -724,10 +724,10 @@
* Like entries() but screens out internal JAR mechanism entries
* and includes signed entries with no ZIP data.
*/
- public Enumeration<JarEntry> entries2(final JarFile jar, Enumeration<? extends ZipEntry> e) {
+ public Enumeration<JarEntry> entries2(final JarFile jar, Enumeration<JarEntry> e) {
final Map<String, CodeSigner[]> map = new HashMap<>();
map.putAll(signerMap());
- final Enumeration<? extends ZipEntry> enum_ = e;
+ final Enumeration<JarEntry> enum_ = e;
return new Enumeration<>() {
Enumeration<String> signers = null;
@@ -738,11 +738,11 @@
return true;
}
while (enum_.hasMoreElements()) {
- ZipEntry ze = enum_.nextElement();
- if (JarVerifier.isSigningRelated(ze.getName())) {
+ JarEntry je = enum_.nextElement();
+ if (JarVerifier.isSigningRelated(je.getName())) {
continue;
}
- entry = jar.newEntry(ze);
+ entry = jar.newEntry(je);
return true;
}
if (signers == null) {
@@ -750,7 +750,7 @@
}
while (signers.hasMoreElements()) {
String name = signers.nextElement();
- entry = jar.newEntry(new ZipEntry(name));
+ entry = jar.newEntry(name);
return true;
}
--- a/src/java.base/share/classes/java/util/zip/ZipCoder.java Wed Nov 29 22:23:21 2017 +0100
+++ b/src/java.base/share/classes/java/util/zip/ZipCoder.java Wed Nov 29 15:01:16 2017 -0800
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2009, 2011, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2009, 2017, 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
@@ -43,7 +43,34 @@
final class ZipCoder {
+ private static boolean isASCII(byte[] ba, int off, int len) {
+ for (int i = off; i < off + len; i++) {
+ if (ba[i] < 0)
+ return false;
+ }
+ return true;
+ }
+
+ private static boolean hasReplaceChar(byte[] ba) {
+ for (int i = 0; i < ba.length; i++) {
+ if (ba[i] == (byte)'?')
+ return true;
+ }
+ return false;
+ }
+
String toString(byte[] ba, int off, int length) {
+
+ // fastpath for UTF-8 cs and ascii only name, leverage the
+ // compact string impl to avoid the unnecessary char[] copy/
+ // paste. A temporary workaround before we have better approach,
+ // such as a String constructor that throws exception for
+ // malformed and/or unmappable characters, instead of silently
+ // replacing with repl char
+ if (isUTF8 && isASCII(ba, off, length)) {
+ return new String(ba, off, length, cs);
+ }
+
CharsetDecoder cd = decoder().reset();
int len = (int)(length * cd.maxCharsPerByte());
char[] ca = new char[len];
@@ -78,6 +105,15 @@
}
byte[] getBytes(String s) {
+ if (isUTF8) {
+ // fastpath for UTF8. should only occur when the string
+ // has malformed surrogates. A postscan should still be
+ // faster and use less memory.
+ byte[] ba = s.getBytes(cs);
+ if (!hasReplaceChar(ba)) {
+ return ba;
+ }
+ }
CharsetEncoder ce = encoder().reset();
char[] ca = s.toCharArray();
int len = (int)(ca.length * ce.maxBytesPerChar());
--- a/src/java.base/share/classes/java/util/zip/ZipFile.java Wed Nov 29 22:23:21 2017 +0100
+++ b/src/java.base/share/classes/java/util/zip/ZipFile.java Wed Nov 29 15:01:16 2017 -0800
@@ -50,11 +50,15 @@
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.WeakHashMap;
+
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.IntFunction;
+import java.util.jar.JarEntry;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import jdk.internal.misc.JavaUtilZipFileAccess;
import jdk.internal.misc.SharedSecrets;
-import jdk.internal.misc.JavaIORandomAccessFileAccess;
import jdk.internal.misc.VM;
import jdk.internal.perf.PerfCounter;
@@ -296,13 +300,27 @@
* @throws IllegalStateException if the zip file has been closed
*/
public ZipEntry getEntry(String name) {
+ return getEntry(name, ZipEntry::new);
+ }
+
+ /*
+ * Returns the zip file entry for the specified name, or null
+ * if not found.
+ *
+ * @param name the name of the entry
+ * @param func the function that creates the returned entry
+ *
+ * @return the zip file entry, or null if not found
+ * @throws IllegalStateException if the zip file has been closed
+ */
+ private ZipEntry getEntry(String name, Function<String, ? extends ZipEntry> func) {
Objects.requireNonNull(name, "name");
synchronized (this) {
ensureOpen();
byte[] bname = zc.getBytes(name);
int pos = zsrc.getEntryPos(bname, true);
if (pos != -1) {
- return getZipEntry(name, bname, pos);
+ return getZipEntry(name, bname, pos, func);
}
}
return null;
@@ -374,12 +392,10 @@
private class ZipFileInflaterInputStream extends InflaterInputStream {
private volatile boolean closeRequested;
private boolean eof = false;
- private final ZipFileInputStream zfin;
ZipFileInflaterInputStream(ZipFileInputStream zfin, Inflater inf,
int size) {
super(zfin, inf, size);
- this.zfin = zfin;
}
public void close() throws IOException {
@@ -416,7 +432,7 @@
public int available() throws IOException {
if (closeRequested)
return 0;
- long avail = zfin.size() - inf.getBytesWritten();
+ long avail = ((ZipFileInputStream)in).size() - inf.getBytesWritten();
return (avail > (long) Integer.MAX_VALUE ?
Integer.MAX_VALUE : (int) avail);
}
@@ -466,41 +482,48 @@
return name;
}
- private class ZipEntryIterator implements Enumeration<ZipEntry>, Iterator<ZipEntry> {
+ private class ZipEntryIterator<T extends ZipEntry>
+ implements Enumeration<T>, Iterator<T> {
+
private int i = 0;
private final int entryCount;
+ private final Function<String, T> gen;
- public ZipEntryIterator() {
- synchronized (ZipFile.this) {
- ensureOpen();
- this.entryCount = zsrc.total;
- }
+ public ZipEntryIterator(int entryCount, Function<String, T> gen) {
+ this.entryCount = entryCount;
+ this.gen = gen;
}
+ @Override
public boolean hasMoreElements() {
return hasNext();
}
+ @Override
public boolean hasNext() {
return i < entryCount;
}
- public ZipEntry nextElement() {
+ @Override
+ public T nextElement() {
return next();
}
- public ZipEntry next() {
+ @Override
+ @SuppressWarnings("unchecked")
+ public T next() {
synchronized (ZipFile.this) {
ensureOpen();
if (!hasNext()) {
throw new NoSuchElementException();
}
// each "entry" has 3 ints in table entries
- return getZipEntry(null, null, zsrc.getEntryPos(i++ * 3));
+ return (T)getZipEntry(null, null, zsrc.getEntryPos(i++ * 3), gen);
}
}
- public Iterator<ZipEntry> asIterator() {
+ @Override
+ public Iterator<T> asIterator() {
return this;
}
}
@@ -511,11 +534,51 @@
* @throws IllegalStateException if the zip file has been closed
*/
public Enumeration<? extends ZipEntry> entries() {
- return new ZipEntryIterator();
+ synchronized (this) {
+ ensureOpen();
+ return new ZipEntryIterator<ZipEntry>(zsrc.total, ZipEntry::new);
+ }
+ }
+
+ private Enumeration<JarEntry> entries(Function<String, JarEntry> func) {
+ synchronized (this) {
+ ensureOpen();
+ return new ZipEntryIterator<JarEntry>(zsrc.total, func);
+ }
+ }
+
+ private class EntrySpliterator<T> extends Spliterators.AbstractSpliterator<T> {
+ private int index;
+ private final int fence;
+ private final IntFunction<T> gen;
+
+ EntrySpliterator(int index, int fence, IntFunction<T> gen) {
+ super((long)fence,
+ Spliterator.ORDERED | Spliterator.DISTINCT | Spliterator.IMMUTABLE |
+ Spliterator.NONNULL);
+ this.index = index;
+ this.fence = fence;
+ this.gen = gen;
+ }
+
+ @Override
+ public boolean tryAdvance(Consumer<? super T> action) {
+ if (action == null)
+ throw new NullPointerException();
+ if (index >= 0 && index < fence) {
+ synchronized (ZipFile.this) {
+ ensureOpen();
+ action.accept(gen.apply(zsrc.getEntryPos(index++ * 3)));
+ }
+ return true;
+ }
+ return false;
+ }
}
/**
* Returns an ordered {@code Stream} over the ZIP file entries.
+ *
* Entries appear in the {@code Stream} in the order they appear in
* the central directory of the ZIP file.
*
@@ -524,17 +587,68 @@
* @since 1.8
*/
public Stream<? extends ZipEntry> stream() {
- return StreamSupport.stream(Spliterators.spliterator(
- new ZipEntryIterator(), size(),
- Spliterator.ORDERED | Spliterator.DISTINCT |
- Spliterator.IMMUTABLE | Spliterator.NONNULL), false);
+ synchronized (this) {
+ ensureOpen();
+ return StreamSupport.stream(new EntrySpliterator<>(0, zsrc.total,
+ pos -> getZipEntry(null, null, pos, ZipEntry::new)), false);
+ }
+ }
+
+ private String getEntryName(int pos) {
+ byte[] cen = zsrc.cen;
+ int nlen = CENNAM(cen, pos);
+ int clen = CENCOM(cen, pos);
+ int flag = CENFLG(cen, pos);
+ if (!zc.isUTF8() && (flag & EFS) != 0) {
+ return zc.toStringUTF8(cen, pos + CENHDR, nlen);
+ } else {
+ return zc.toString(cen, pos + CENHDR, nlen);
+ }
+ }
+
+ /*
+ * Returns an ordered {@code Stream} over the zip file entry names.
+ *
+ * Entry names appear in the {@code Stream} in the order they appear in
+ * the central directory of the ZIP file.
+ *
+ * @return an ordered {@code Stream} of entry names in this zip file
+ * @throws IllegalStateException if the zip file has been closed
+ * @since 10
+ */
+ private Stream<String> entryNameStream() {
+ synchronized (this) {
+ ensureOpen();
+ return StreamSupport.stream(
+ new EntrySpliterator<>(0, zsrc.total, this::getEntryName), false);
+ }
+ }
+
+ /*
+ * Returns an ordered {@code Stream} over the zip file entries.
+ *
+ * Entries appear in the {@code Stream} in the order they appear in
+ * the central directory of the jar file.
+ *
+ * @param func the function that creates the returned entry
+ * @return an ordered {@code Stream} of entries in this zip file
+ * @throws IllegalStateException if the zip file has been closed
+ * @since 10
+ */
+ private Stream<JarEntry> stream(Function<String, JarEntry> func) {
+ synchronized (this) {
+ ensureOpen();
+ return StreamSupport.stream(new EntrySpliterator<>(0, zsrc.total,
+ pos -> (JarEntry)getZipEntry(null, null, pos, func)), false);
+ }
}
private String lastEntryName;
private int lastEntryPos;
/* Checks ensureOpen() before invoke this method */
- private ZipEntry getZipEntry(String name, byte[] bname, int pos) {
+ private ZipEntry getZipEntry(String name, byte[] bname, int pos,
+ Function<String, ? extends ZipEntry> func) {
byte[] cen = zsrc.cen;
int nlen = CENNAM(cen, pos);
int elen = CENEXT(cen, pos);
@@ -551,7 +665,7 @@
name = zc.toString(cen, pos + CENHDR, nlen);
}
}
- ZipEntry e = new ZipEntry(name);
+ ZipEntry e = func.apply(name); //ZipEntry e = new ZipEntry(name);
e.flag = flag;
e.xdostime = CENTIM(cen, pos);
e.crc = CENCRC(cen, pos);
@@ -791,7 +905,6 @@
public long skip(long n) throws IOException {
synchronized (ZipFile.this) {
- ensureOpenOrZipException();
initDataOffset();
if (n > rem) {
n = rem;
@@ -857,12 +970,33 @@
static {
SharedSecrets.setJavaUtilZipFileAccess(
new JavaUtilZipFileAccess() {
+ @Override
public boolean startsWithLocHeader(ZipFile zip) {
return zip.zsrc.startsWithLoc;
}
+ @Override
public String[] getMetaInfEntryNames(ZipFile zip) {
return zip.getMetaInfEntryNames();
}
+ @Override
+ public JarEntry getEntry(ZipFile zip, String name,
+ Function<String, JarEntry> func) {
+ return (JarEntry)zip.getEntry(name, func);
+ }
+ @Override
+ public Enumeration<JarEntry> entries(ZipFile zip,
+ Function<String, JarEntry> func) {
+ return zip.entries(func);
+ }
+ @Override
+ public Stream<JarEntry> stream(ZipFile zip,
+ Function<String, JarEntry> func) {
+ return zip.stream(func);
+ }
+ @Override
+ public Stream<String> entryNameStream(ZipFile zip) {
+ return zip.entryNameStream();
+ }
}
);
isWindows = VM.getSavedProperty("os.name").contains("Windows");
--- a/src/java.base/share/classes/jdk/internal/loader/URLClassPath.java Wed Nov 29 22:23:21 2017 +0100
+++ b/src/java.base/share/classes/jdk/internal/loader/URLClassPath.java Wed Nov 29 15:01:16 2017 -0800
@@ -834,7 +834,7 @@
try {
String nm;
if (jar.isMultiRelease()) {
- nm = SharedSecrets.javaUtilJarAccess().getRealName(jar, entry);
+ nm = entry.getRealName();
} else {
nm = name;
}
--- a/src/java.base/share/classes/jdk/internal/misc/JavaUtilZipFileAccess.java Wed Nov 29 22:23:21 2017 +0100
+++ b/src/java.base/share/classes/jdk/internal/misc/JavaUtilZipFileAccess.java Wed Nov 29 15:01:16 2017 -0800
@@ -25,10 +25,19 @@
package jdk.internal.misc;
+import java.io.IOException;
+import java.util.Enumeration;
+import java.util.function.Function;
+import java.util.jar.JarEntry;
+import java.util.stream.Stream;
import java.util.zip.ZipFile;
public interface JavaUtilZipFileAccess {
public boolean startsWithLocHeader(ZipFile zip);
public String[] getMetaInfEntryNames(ZipFile zip);
+ public JarEntry getEntry(ZipFile zip, String name, Function<String, JarEntry> func);
+ public Enumeration<JarEntry> entries(ZipFile zip, Function<String, JarEntry> func);
+ public Stream<JarEntry> stream(ZipFile zip, Function<String, JarEntry> func);
+ public Stream<String> entryNameStream(ZipFile zip);
}
--- a/src/java.base/share/classes/jdk/internal/module/ModulePath.java Wed Nov 29 22:23:21 2017 +0100
+++ b/src/java.base/share/classes/jdk/internal/module/ModulePath.java Wed Nov 29 15:01:16 2017 -0800
@@ -66,8 +66,6 @@
import jdk.internal.jmod.JmodFile;
import jdk.internal.jmod.JmodFile.Section;
import jdk.internal.perf.PerfCounter;
-import jdk.internal.util.jar.VersionedStream;
-
/**
* A {@code ModuleFinder} that locates modules on the file system by searching
@@ -515,7 +513,7 @@
builder.version(vs);
// scan the names of the entries in the JAR file
- Map<Boolean, Set<String>> map = VersionedStream.stream(jf)
+ Map<Boolean, Set<String>> map = jf.versionedStream()
.filter(e -> !e.isDirectory())
.map(JarEntry::getName)
.filter(e -> (e.endsWith(".class") ^ e.startsWith(SERVICES_PREFIX)))
@@ -615,7 +613,7 @@
}
private Set<String> jarPackages(JarFile jf) {
- return VersionedStream.stream(jf)
+ return jf.versionedStream()
.filter(e -> !e.isDirectory())
.map(JarEntry::getName)
.map(this::toPackageName)
--- a/src/java.base/share/classes/jdk/internal/module/ModuleReferences.java Wed Nov 29 22:23:21 2017 +0100
+++ b/src/java.base/share/classes/jdk/internal/module/ModuleReferences.java Wed Nov 29 15:01:16 2017 -0800
@@ -50,9 +50,7 @@
import java.util.zip.ZipFile;
import jdk.internal.jmod.JmodFile;
-import jdk.internal.misc.SharedSecrets;
import jdk.internal.module.ModuleHashes.HashSupplier;
-import jdk.internal.util.jar.VersionedStream;
import sun.net.www.ParseUtil;
@@ -250,7 +248,7 @@
JarEntry je = getEntry(name);
if (je != null) {
if (jf.isMultiRelease())
- name = SharedSecrets.javaUtilJarAccess().getRealName(jf, je);
+ name = je.getRealName();
if (je.isDirectory() && !name.endsWith("/"))
name += "/";
String encodedPath = ParseUtil.encodePath(name, false);
@@ -274,7 +272,7 @@
@Override
Stream<String> implList() throws IOException {
// take snapshot to avoid async close
- List<String> names = VersionedStream.stream(jf)
+ List<String> names = jf.versionedStream()
.map(JarEntry::getName)
.collect(Collectors.toList());
return names.stream();
--- a/src/java.base/share/classes/module-info.java Wed Nov 29 22:23:21 2017 +0100
+++ b/src/java.base/share/classes/module-info.java Wed Nov 29 15:01:16 2017 -0800
@@ -211,8 +211,7 @@
jdk.incubator.httpclient;
exports jdk.internal.util.jar to
jdk.jartool,
- jdk.jdeps,
- jdk.jlink;
+ jdk.jdeps;
exports sun.net to
jdk.incubator.httpclient;
exports sun.net.ext to
--- a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JarArchive.java Wed Nov 29 22:23:21 2017 +0100
+++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JarArchive.java Wed Nov 29 15:01:16 2017 -0800
@@ -34,7 +34,6 @@
import java.util.stream.Stream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
-import jdk.internal.util.jar.VersionedStream;
import jdk.tools.jlink.internal.Archive.Entry.EntryType;
/**
@@ -105,7 +104,7 @@
} catch (IOException ioe) {
throw new UncheckedIOException(ioe);
}
- return VersionedStream.stream(jarFile)
+ return jarFile.versionedStream()
.filter(je -> !je.isDirectory())
.map(this::toEntry);
}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/util/jar/JarFile/mrjar/TestVersionedStream.java Wed Nov 29 15:01:16 2017 -0800
@@ -0,0 +1,242 @@
+/*
+ * Copyright (c) 2016, 2017, 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 8163798 8189611
+ * @summary basic tests for multi-release jar versioned streams
+ * @library /test/lib
+ * @modules jdk.jartool/sun.tools.jar java.base/jdk.internal.util.jar
+ * @build jdk.test.lib.Platform
+ * jdk.test.lib.util.FileUtils
+ * @run testng TestVersionedStream
+ */
+
+import org.testng.Assert;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UncheckedIOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import java.util.zip.ZipFile;
+
+import jdk.test.lib.util.FileUtils;
+
+public class TestVersionedStream {
+ private final Path userdir;
+ private final Set<String> unversionedEntryNames;
+
+ public TestVersionedStream() throws IOException {
+ userdir = Paths.get(System.getProperty("user.dir", "."));
+
+ // These are not real class files even though they end with .class.
+ // They are resource files so jar tool validation won't reject them.
+ // But they are what we want to test, especially q/Bar.class that
+ // could be in a concealed package if this was a modular multi-release
+ // jar.
+ createFiles(
+ "base/p/Bar.class",
+ "base/p/Foo.class",
+ "base/p/Main.class",
+ "v9/p/Foo.class",
+ "v10/p/Foo.class",
+ "v10/q/Bar.class",
+ "v11/p/Bar.class",
+ "v11/p/Foo.class"
+ );
+
+ jar("cf mmr.jar -C base . --release 9 -C v9 . " +
+ "--release 10 -C v10 . --release 11 -C v11 .");
+
+ System.out.println("Contents of mmr.jar\n=======");
+
+ try(JarFile jf = new JarFile("mmr.jar")) {
+ unversionedEntryNames = jf.stream()
+ .map(je -> je.getName())
+ .peek(System.out::println)
+ .map(nm -> nm.startsWith("META-INF/versions/")
+ ? nm.replaceFirst("META-INF/versions/\\d+/", "")
+ : nm)
+ .collect(Collectors.toCollection(LinkedHashSet::new));
+ }
+
+ System.out.println("=======");
+ }
+
+ @AfterClass
+ public void close() throws IOException {
+ Files.walk(userdir, 1)
+ .filter(p -> !p.equals(userdir))
+ .forEach(p -> {
+ try {
+ if (Files.isDirectory(p)) {
+ FileUtils.deleteFileTreeWithRetry(p);
+ } else {
+ FileUtils.deleteFileIfExistsWithRetry(p);
+ }
+ } catch (IOException x) {
+ throw new UncheckedIOException(x);
+ }
+ });
+ }
+
+ @DataProvider
+ public Object[][] data() {
+ return new Object[][] {
+ {Runtime.Version.parse("8")},
+ {Runtime.Version.parse("9")},
+ {Runtime.Version.parse("10")},
+ {Runtime.Version.parse("11")},
+ {JarFile.baseVersion()},
+ {JarFile.runtimeVersion()}
+ };
+ }
+
+ @Test(dataProvider="data")
+ public void test(Runtime.Version version) throws Exception {
+ try (JarFile jf = new JarFile(new File("mmr.jar"), false, ZipFile.OPEN_READ, version);
+ Stream<JarEntry> jes = jf.versionedStream())
+ {
+ Assert.assertNotNull(jes);
+
+ // put versioned entries in list so we can reuse them
+ List<JarEntry> versionedEntries = jes.collect(Collectors.toList());
+
+ Assert.assertTrue(versionedEntries.size() > 0);
+
+ // also keep the names
+ List<String> versionedNames = new ArrayList<>(versionedEntries.size());
+
+ // verify the correct order while building enames
+ Iterator<String> allIt = unversionedEntryNames.iterator();
+ Iterator<JarEntry> verIt = versionedEntries.iterator();
+ boolean match = false;
+
+ while (verIt.hasNext()) {
+ match = false;
+ if (!allIt.hasNext()) break;
+ String name = verIt.next().getName();
+ versionedNames.add(name);
+ while (allIt.hasNext()) {
+ if (name.equals(allIt.next())) {
+ match = true;
+ break;
+ }
+ }
+ }
+ if (!match) {
+ Assert.fail("versioned entries not in same order as unversioned entries");
+ }
+
+ // verify the contents:
+ // value.[0] end of the path
+ // value.[1] versioned path/real name
+ Map<String,String[]> expected = new HashMap<>();
+
+ expected.put("p/Bar.class", new String[] { "base/p/Bar.class", "p/Bar.class" });
+ expected.put("p/Main.class", new String[] { "base/p/Main.class", "p/Main.class" });
+ switch (version.major()) {
+ case 8:
+ expected.put("p/Foo.class", new String[]
+ { "base/p/Foo.class", "p/Foo.class" });
+ break;
+ case 9:
+ expected.put("p/Foo.class", new String[]
+ { "v9/p/Foo.class", "META-INF/versions/9/p/Foo.class" });
+ break;
+ case 10:
+ expected.put("p/Foo.class", new String[]
+ { "v10/p/Foo.class", "META-INF/versions/10/p/Foo.class" });
+
+ expected.put("q/Bar.class", new String[]
+ { "v10/q/Bar.class", "META-INF/versions/10/q/Bar.class" });
+ break;
+ case 11:
+ expected.put("p/Bar.class", new String[]
+ { "v11/p/Bar.class", "META-INF/versions/11/p/Bar.class"});
+ expected.put("p/Foo.class", new String[]
+ { "v11/p/Foo.class", "META-INF/versions/11/p/Foo.class"});
+ expected.put("q/Bar.class", new String[]
+ { "q/Bar.class", "META-INF/versions/10/q/Bar.class"});
+ break;
+ default:
+ Assert.fail("Test out of date, please add more cases");
+ }
+
+ expected.entrySet().stream().forEach(e -> {
+ String name = e.getKey();
+ int i = versionedNames.indexOf(name);
+ Assert.assertTrue(i != -1, name + " not in enames");
+ JarEntry je = versionedEntries.get(i);
+ try (InputStream is = jf.getInputStream(je)) {
+ String s = new String(is.readAllBytes()).replaceAll(System.lineSeparator(), "");
+ // end of the path
+ Assert.assertTrue(s.endsWith(e.getValue()[0]), s);
+ // getRealName()
+ Assert.assertTrue(je.getRealName().equals(e.getValue()[1]));
+ } catch (IOException x) {
+ throw new UncheckedIOException(x);
+ }
+ });
+ }
+ }
+
+ private void createFiles(String... files) {
+ ArrayList<String> list = new ArrayList();
+ Arrays.stream(files)
+ .map(f -> Paths.get(userdir.toAbsolutePath().toString(), f))
+ .forEach(p -> {
+ try {
+ Files.createDirectories(p.getParent());
+ Files.createFile(p);
+ list.clear();
+ list.add(p.toString().replace(File.separatorChar, '/'));
+ Files.write(p, list);
+ } catch (IOException x) {
+ throw new UncheckedIOException(x);
+ }});
+ }
+
+ private void jar(String args) {
+ new sun.tools.jar.Main(System.out, System.err, "jar")
+ .run(args.split(" +"));
+ }
+}
--- a/test/jdk/jdk/internal/util/jar/TestVersionedStream.java Wed Nov 29 22:23:21 2017 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,228 +0,0 @@
-/*
- * Copyright (c) 2016, 2017, 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 8163798
- * @summary basic tests for multi-release jar versioned streams
- * @library /test/lib
- * @modules jdk.jartool/sun.tools.jar java.base/jdk.internal.util.jar
- * @build jdk.test.lib.Platform
- * jdk.test.lib.util.FileUtils
- * @run testng TestVersionedStream
- */
-
-import org.testng.Assert;
-import org.testng.annotations.AfterClass;
-import org.testng.annotations.DataProvider;
-import org.testng.annotations.Test;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.UncheckedIOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.jar.JarEntry;
-import java.util.jar.JarFile;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-import java.util.zip.ZipFile;
-
-import jdk.test.lib.util.FileUtils;
-
-public class TestVersionedStream {
- private final Path userdir;
- private final Set<String> unversionedEntryNames;
-
- public TestVersionedStream() throws IOException {
- userdir = Paths.get(System.getProperty("user.dir", "."));
-
- // These are not real class files even though they end with .class.
- // They are resource files so jar tool validation won't reject them.
- // But they are what we want to test, especially q/Bar.class that
- // could be in a concealed package if this was a modular multi-release
- // jar.
- createFiles(
- "base/p/Bar.class",
- "base/p/Foo.class",
- "base/p/Main.class",
- "v9/p/Foo.class",
- "v10/p/Foo.class",
- "v10/q/Bar.class",
- "v11/p/Bar.class",
- "v11/p/Foo.class"
- );
-
- jar("cf mmr.jar -C base . --release 9 -C v9 . " +
- "--release 10 -C v10 . --release 11 -C v11 .");
-
- System.out.println("Contents of mmr.jar\n=======");
-
- try(JarFile jf = new JarFile("mmr.jar")) {
- unversionedEntryNames = jf.stream()
- .map(je -> je.getName())
- .peek(System.out::println)
- .map(nm -> nm.startsWith("META-INF/versions/")
- ? nm.replaceFirst("META-INF/versions/\\d+/", "")
- : nm)
- .collect(Collectors.toCollection(LinkedHashSet::new));
- }
-
- System.out.println("=======");
- }
-
- @AfterClass
- public void close() throws IOException {
- Files.walk(userdir, 1)
- .filter(p -> !p.equals(userdir))
- .forEach(p -> {
- try {
- if (Files.isDirectory(p)) {
- FileUtils.deleteFileTreeWithRetry(p);
- } else {
- FileUtils.deleteFileIfExistsWithRetry(p);
- }
- } catch (IOException x) {
- throw new UncheckedIOException(x);
- }
- });
- }
-
- @DataProvider
- public Object[][] data() {
- return new Object[][] {
- {Runtime.Version.parse("8")},
- {Runtime.Version.parse("9")},
- {Runtime.Version.parse("10")},
- {Runtime.Version.parse("11")},
- {JarFile.baseVersion()},
- {JarFile.runtimeVersion()}
- };
- }
-
- @Test(dataProvider="data")
- public void test(Runtime.Version version) throws Exception {
- try (JarFile jf = new JarFile(new File("mmr.jar"), false, ZipFile.OPEN_READ, version);
- Stream<JarEntry> jes = jdk.internal.util.jar.VersionedStream.stream(jf))
- {
- Assert.assertNotNull(jes);
-
- // put versioned entries in list so we can reuse them
- List<JarEntry> versionedEntries = jes.collect(Collectors.toList());
-
- Assert.assertTrue(versionedEntries.size() > 0);
-
- // also keep the names
- List<String> versionedNames = new ArrayList<>(versionedEntries.size());
-
- // verify the correct order while building enames
- Iterator<String> allIt = unversionedEntryNames.iterator();
- Iterator<JarEntry> verIt = versionedEntries.iterator();
- boolean match = false;
-
- while (verIt.hasNext()) {
- match = false;
- if (!allIt.hasNext()) break;
- String name = verIt.next().getName();
- versionedNames.add(name);
- while (allIt.hasNext()) {
- if (name.equals(allIt.next())) {
- match = true;
- break;
- }
- }
- }
- if (!match) {
- Assert.fail("versioned entries not in same order as unversioned entries");
- }
-
- // verify the contents
- Map<String,String> contents = new HashMap<>();
- contents.put("p/Bar.class", "base/p/Bar.class");
- contents.put("p/Main.class", "base/p/Main.class");
- switch (version.major()) {
- case 8:
- contents.put("p/Foo.class", "base/p/Foo.class");
- break;
- case 9:
- contents.put("p/Foo.class", "v9/p/Foo.class");
- break;
- case 10:
- contents.put("p/Foo.class", "v10/p/Foo.class");
- contents.put("q/Bar.class", "v10/q/Bar.class");
- break;
- case 11:
- contents.put("p/Bar.class", "v11/p/Bar.class");
- contents.put("p/Foo.class", "v11/p/Foo.class");
- contents.put("q/Bar.class", "v10/q/Bar.class");
- break;
- default:
- Assert.fail("Test out of date, please add more cases");
- }
-
- contents.entrySet().stream().forEach(e -> {
- String name = e.getKey();
- int i = versionedNames.indexOf(name);
- Assert.assertTrue(i != -1, name + " not in enames");
- JarEntry je = versionedEntries.get(i);
- try (InputStream is = jf.getInputStream(je)) {
- String s = new String(is.readAllBytes()).replaceAll(System.lineSeparator(), "");
- Assert.assertTrue(s.endsWith(e.getValue()), s);
- } catch (IOException x) {
- throw new UncheckedIOException(x);
- }
- });
- }
- }
-
- private void createFiles(String... files) {
- ArrayList<String> list = new ArrayList();
- Arrays.stream(files)
- .map(f -> Paths.get(userdir.toAbsolutePath().toString(), f))
- .forEach(p -> {
- try {
- Files.createDirectories(p.getParent());
- Files.createFile(p);
- list.clear();
- list.add(p.toString().replace(File.separatorChar, '/'));
- Files.write(p, list);
- } catch (IOException x) {
- throw new UncheckedIOException(x);
- }});
- }
-
- private void jar(String args) {
- new sun.tools.jar.Main(System.out, System.err, "jar")
- .run(args.split(" +"));
- }
-}