8189611: JarFile versioned stream and real name support
Reviewed-by: psandoz, alanb, mchung, martin
This commit is contained in:
parent
0780382f34
commit
97cddabb17
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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());
|
||||
|
@ -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");
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
Loading…
Reference in New Issue
Block a user