8189611: JarFile versioned stream and real name support

Reviewed-by: psandoz, alanb, mchung, martin
This commit is contained in:
Xueming Shen 2017-11-29 15:01:16 -08:00
parent 0780382f34
commit 97cddabb17
12 changed files with 422 additions and 175 deletions

View File

@ -128,4 +128,25 @@ class JarEntry extends ZipEntry {
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();
}
}

View File

@ -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.Enumeration;
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 @@ class JarFile extends ZipFile {
// 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 @@ class JarFile extends ZipFile {
}
private String[] getMetaInfEntryNames() {
return jdk.internal.misc.SharedSecrets.getJavaUtilZipFileAccess()
.getMetaInfEntryNames((ZipFile)this);
return JUZFA.getMetaInfEntryNames((ZipFile)this);
}
/**
@ -497,47 +503,11 @@ class JarFile extends ZipFile {
* </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 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 getVersionedEntry(name, je);
}
return je;
}
/**
@ -548,7 +518,7 @@ class JarFile extends ZipFile {
* 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 @@ class JarFile extends ZipFile {
* @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.
*
* @param entry the JarEntry
* @return the real name of the JarEntry
* @since 9
* <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.
*
* @return stream of versioned entries
* @since 10
*/
String getRealName(JarEntry entry) {
if (entry instanceof JarFileEntry) {
return ((JarFileEntry)entry).realName();
public Stream<JarEntry> versionedStream() {
if (isMultiRelease()) {
return JUZFA.entryNameStream(this).map(this::getBasename)
.filter(Objects::nonNull)
.distinct()
.map(this::getJarEntry);
}
return entry.getName();
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) {
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 @@ class JarFile extends ZipFile {
return null;
}
}
@Override
public Certificate[] getCertificates() {
try {
maybeInstantiateVerifier();
@ -642,6 +646,8 @@ class JarFile extends ZipFile {
}
return certs == null ? null : certs.clone();
}
@Override
public CodeSigner[] getCodeSigners() {
try {
maybeInstantiateVerifier();
@ -653,20 +659,30 @@ class JarFile extends ZipFile {
}
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 @@ class JarFile extends ZipFile {
}
}
/*
* Initializes the verifier object by reading all the manifest
* entries and passing them to the verifier.
@ -904,7 +919,7 @@ class JarFile extends ZipFile {
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 @@ class JarFile extends ZipFile {
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 @@ class JarFile extends ZipFile {
}
}
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 @@ class JarFile extends ZipFile {
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();
}

View File

@ -724,10 +724,10 @@ class JarVerifier {
* 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 @@ class JarVerifier {
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 @@ class JarVerifier {
}
while (signers.hasMoreElements()) {
String name = signers.nextElement();
entry = jar.newEntry(new ZipEntry(name));
entry = jar.newEntry(name);
return true;
}

View File

@ -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 @@ import sun.nio.cs.ArrayEncoder;
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 @@ final class ZipCoder {
}
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());

View File

@ -50,11 +50,15 @@ import java.util.NoSuchElementException;
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 @@ class ZipFile implements ZipConstants, Closeable {
* @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 @@ class ZipFile implements ZipConstants, Closeable {
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 @@ class ZipFile implements ZipConstants, Closeable {
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 @@ class ZipFile implements ZipConstants, Closeable {
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 @@ class ZipFile implements ZipConstants, Closeable {
* @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 @@ class ZipFile implements ZipConstants, Closeable {
* @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 @@ class ZipFile implements ZipConstants, Closeable {
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 @@ class ZipFile implements ZipConstants, Closeable {
public long skip(long n) throws IOException {
synchronized (ZipFile.this) {
ensureOpenOrZipException();
initDataOffset();
if (n > rem) {
n = rem;
@ -857,12 +970,33 @@ class ZipFile implements ZipConstants, Closeable {
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");

View File

@ -834,7 +834,7 @@ public class URLClassPath {
try {
String nm;
if (jar.isMultiRelease()) {
nm = SharedSecrets.javaUtilJarAccess().getRealName(jar, entry);
nm = entry.getRealName();
} else {
nm = name;
}

View File

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

View File

@ -66,8 +66,6 @@ import java.util.zip.ZipFile;
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 @@ public class ModulePath implements ModuleFinder {
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 @@ public class ModulePath implements ModuleFinder {
}
private Set<String> jarPackages(JarFile jf) {
return VersionedStream.stream(jf)
return jf.versionedStream()
.filter(e -> !e.isDirectory())
.map(JarEntry::getName)
.map(this::toPackageName)

View File

@ -50,9 +50,7 @@ import java.util.stream.Stream;
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 @@ class ModuleReferences {
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 @@ class ModuleReferences {
@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();

View File

@ -211,8 +211,7 @@ module java.base {
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

View File

@ -34,7 +34,6 @@ import java.util.jar.JarFile;
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 @@ public abstract class JarArchive implements Archive {
} catch (IOException ioe) {
throw new UncheckedIOException(ioe);
}
return VersionedStream.stream(jarFile)
return jarFile.versionedStream()
.filter(je -> !je.isDirectory())
.map(this::toEntry);
}

View File

@ -23,7 +23,7 @@
/*
* @test
* @bug 8163798
* @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
@ -133,7 +133,7 @@ public class TestVersionedStream {
@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))
Stream<JarEntry> jes = jf.versionedStream())
{
Assert.assertNotNull(jes);
@ -166,38 +166,52 @@ public class TestVersionedStream {
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");
// 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:
contents.put("p/Foo.class", "base/p/Foo.class");
expected.put("p/Foo.class", new String[]
{ "base/p/Foo.class", "p/Foo.class" });
break;
case 9:
contents.put("p/Foo.class", "v9/p/Foo.class");
expected.put("p/Foo.class", new String[]
{ "v9/p/Foo.class", "META-INF/versions/9/p/Foo.class" });
break;
case 10:
contents.put("p/Foo.class", "v10/p/Foo.class");
contents.put("q/Bar.class", "v10/q/Bar.class");
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:
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");
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");
}
contents.entrySet().stream().forEach(e -> {
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(), "");
Assert.assertTrue(s.endsWith(e.getValue()), s);
// 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);
}