# HG changeset patch # User sherman # Date 1511996476 28800 # Node ID 85ea7e83af3027e66a2645ade178e3a6b2600092 # Parent 45c5d7817e9ed83f9dd94d51d1a9fb8a35a9f25d 8189611: JarFile versioned stream and real name support Reviewed-by: psandoz, alanb, mchung, martin diff -r 45c5d7817e9e -r 85ea7e83af30 src/java.base/share/classes/java/util/jar/JarEntry.java --- 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 + * multi-release jar file 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(); + } } diff -r 45c5d7817e9e -r 85ea7e83af30 src/java.base/share/classes/java/util/jar/JarFile.java --- 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 @@ * */ 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, - Iterator - { - final Enumeration 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 asIterator() { - return this; - } + return je; } /** @@ -548,7 +518,7 @@ * may be thrown if the jar file has been closed */ public Enumeration entries() { - return new JarEntryIterator(); + return JUZFA.entries(this, JarFileEntry::new); } /** @@ -561,68 +531,100 @@ * @since 1.8 */ public Stream 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. + * + *

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 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 entryNames(CodeSource[] cs) { @@ -1077,35 +1114,37 @@ Enumeration 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 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(); } diff -r 45c5d7817e9e -r 85ea7e83af30 src/java.base/share/classes/java/util/jar/JarVerifier.java --- 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 entries2(final JarFile jar, Enumeration e) { + public Enumeration entries2(final JarFile jar, Enumeration e) { final Map map = new HashMap<>(); map.putAll(signerMap()); - final Enumeration enum_ = e; + final Enumeration enum_ = e; return new Enumeration<>() { Enumeration 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; } diff -r 45c5d7817e9e -r 85ea7e83af30 src/java.base/share/classes/java/util/zip/ZipCoder.java --- 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()); diff -r 45c5d7817e9e -r 85ea7e83af30 src/java.base/share/classes/java/util/zip/ZipFile.java --- 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 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, Iterator { + private class ZipEntryIterator + implements Enumeration, Iterator { + private int i = 0; private final int entryCount; + private final Function gen; - public ZipEntryIterator() { - synchronized (ZipFile.this) { - ensureOpen(); - this.entryCount = zsrc.total; - } + public ZipEntryIterator(int entryCount, Function 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 asIterator() { + @Override + public Iterator asIterator() { return this; } } @@ -511,11 +534,51 @@ * @throws IllegalStateException if the zip file has been closed */ public Enumeration entries() { - return new ZipEntryIterator(); + synchronized (this) { + ensureOpen(); + return new ZipEntryIterator(zsrc.total, ZipEntry::new); + } + } + + private Enumeration entries(Function func) { + synchronized (this) { + ensureOpen(); + return new ZipEntryIterator(zsrc.total, func); + } + } + + private class EntrySpliterator extends Spliterators.AbstractSpliterator { + private int index; + private final int fence; + private final IntFunction gen; + + EntrySpliterator(int index, int fence, IntFunction 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 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 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 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 stream(Function 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 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 func) { + return (JarEntry)zip.getEntry(name, func); + } + @Override + public Enumeration entries(ZipFile zip, + Function func) { + return zip.entries(func); + } + @Override + public Stream stream(ZipFile zip, + Function func) { + return zip.stream(func); + } + @Override + public Stream entryNameStream(ZipFile zip) { + return zip.entryNameStream(); + } } ); isWindows = VM.getSavedProperty("os.name").contains("Windows"); diff -r 45c5d7817e9e -r 85ea7e83af30 src/java.base/share/classes/jdk/internal/loader/URLClassPath.java --- 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; } diff -r 45c5d7817e9e -r 85ea7e83af30 src/java.base/share/classes/jdk/internal/misc/JavaUtilZipFileAccess.java --- 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 func); + public Enumeration entries(ZipFile zip, Function func); + public Stream stream(ZipFile zip, Function func); + public Stream entryNameStream(ZipFile zip); } diff -r 45c5d7817e9e -r 85ea7e83af30 src/java.base/share/classes/jdk/internal/module/ModulePath.java --- 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> map = VersionedStream.stream(jf) + Map> 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 jarPackages(JarFile jf) { - return VersionedStream.stream(jf) + return jf.versionedStream() .filter(e -> !e.isDirectory()) .map(JarEntry::getName) .map(this::toPackageName) diff -r 45c5d7817e9e -r 85ea7e83af30 src/java.base/share/classes/jdk/internal/module/ModuleReferences.java --- 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 implList() throws IOException { // take snapshot to avoid async close - List names = VersionedStream.stream(jf) + List names = jf.versionedStream() .map(JarEntry::getName) .collect(Collectors.toList()); return names.stream(); diff -r 45c5d7817e9e -r 85ea7e83af30 src/java.base/share/classes/module-info.java --- 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 diff -r 45c5d7817e9e -r 85ea7e83af30 src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JarArchive.java --- 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); } diff -r 45c5d7817e9e -r 85ea7e83af30 test/jdk/java/util/jar/JarFile/mrjar/TestVersionedStream.java --- /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 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 jes = jf.versionedStream()) + { + Assert.assertNotNull(jes); + + // put versioned entries in list so we can reuse them + List versionedEntries = jes.collect(Collectors.toList()); + + Assert.assertTrue(versionedEntries.size() > 0); + + // also keep the names + List versionedNames = new ArrayList<>(versionedEntries.size()); + + // verify the correct order while building enames + Iterator allIt = unversionedEntryNames.iterator(); + Iterator 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 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 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(" +")); + } +} diff -r 45c5d7817e9e -r 85ea7e83af30 test/jdk/jdk/internal/util/jar/TestVersionedStream.java --- 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 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 jes = jdk.internal.util.jar.VersionedStream.stream(jf)) - { - Assert.assertNotNull(jes); - - // put versioned entries in list so we can reuse them - List versionedEntries = jes.collect(Collectors.toList()); - - Assert.assertTrue(versionedEntries.size() > 0); - - // also keep the names - List versionedNames = new ArrayList<>(versionedEntries.size()); - - // verify the correct order while building enames - Iterator allIt = unversionedEntryNames.iterator(); - Iterator 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 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 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(" +")); - } -}