< prev index next >

src/java.base/share/classes/java/util/jar/JarFile.java

Print this page
rev 13123 : 8132734: JDK 9 runtime changes to support multi-release jar files
Summary: JEP 238 Multi-Release JAR Files runtime support
Contributed-by: steve.drach@oracle.com
rev 12882 : 8139706: JarFile.getBytes could use InputStream.readNBytes
Reviewed-by: sherman, chegar, alanb
rev 12860 : 8138978: Examine usages of sun.misc.IOUtils
Reviewed-by: alanb, mullan, psandoz, rriggs, weijun
rev 12807 : 8137056: Move SharedSecrets and interface friends out of sun.misc
Reviewed-by: alanb, mchung, psandoz, rriggs
rev 12522 : 8133115: docs: replace <tt> tags (obsolete in html5) for java.util.logging, java.util.prefs, java.util.zip, java.util.jar
Reviewed-by: lancea
rev 12298 : 8081678: Add Stream returning methods to classes where there currently exist only Enumeration returning methods
Reviewed-by: lancea, alanb, chegar, dfuchs, mullan, smarks
rev 12022 : 8064736: Part of java.util.jar.JarFile spec looks confusing with references to Zip
Summary: update the api doc for entries()/stream() accordingly
Reviewed-by: alanb
rev 11804 : 8078467: Update core libraries to use diamond with anonymous classes
Reviewed-by: mchung, alanb
rev 11051 : 8049367: Modular Run-Time Images
Reviewed-by: chegar, dfuchs, ihse, joehw, mullan, psandoz, wetmore
Contributed-by: alan.bateman@oracle.com, alex.buckley@oracle.com, bradford.wetmore@oracle.com, chris.hegarty@oracle.com, erik.joelsson@oracle.com, james.laskey@oracle.com, jonathan.gibbons@oracle.com, karen.kinnear@oracle.com, magnus.ihse.bursie@oracle.com, mandy.chung@oracle.com, mark.reinhold@oracle.com, paul.sandoz@oracle.com, sundararajan.athijegannathan@oracle.com
rev 10469 : 8054834: Modular Source Code
Reviewed-by: alanb, chegar, ihse, mduigou
Contributed-by: alan.bateman@oracle.com, alex.buckley@oracle.com, chris.hegarty@oracle.com, erik.joelsson@oracle.com, jonathan.gibbons@oracle.com, karen.kinnear@oracle.com, magnus.ihse.bursie@oracle.com, mandy.chung@oracle.com, mark.reinhold@oracle.com, paul.sandoz@oracle.com

@@ -26,70 +26,200 @@
 package java.util.jar;
 
 import java.io.*;
 import java.lang.ref.SoftReference;
 import java.net.URL;
+import java.security.PrivilegedAction;
 import java.util.*;
 import java.util.stream.Stream;
 import java.util.stream.StreamSupport;
 import java.util.zip.*;
 import java.security.CodeSigner;
 import java.security.cert.Certificate;
 import java.security.AccessController;
 import java.security.CodeSource;
 import jdk.internal.misc.SharedSecrets;
-import sun.security.action.GetPropertyAction;
 import sun.security.util.ManifestEntryVerifier;
 import sun.security.util.SignatureFileVerifier;
 
+import static java.util.jar.Attributes.Name.MULTI_RELEASE;
+
 /**
  * The {@code JarFile} class is used to read the contents of a jar file
  * from any file that can be opened with {@code java.io.RandomAccessFile}.
  * It extends the class {@code java.util.zip.ZipFile} with support
- * for reading an optional {@code Manifest} entry. The
- * {@code Manifest} can be used to specify meta-information about the
- * jar file and its entries.
+ * for reading an optional {@code Manifest} entry, and support for
+ * processing multi-release jar files.  The {@code Manifest} can be used
+ * to specify meta-information about the jar file and its entries.
  *
- * <p> Unless otherwise noted, passing a {@code null} argument to a constructor
- * or method in this class will cause a {@link NullPointerException} to be
- * thrown.
+ * <p>A multi-release jar file is a jar file that contains a manifest with a
+ * main attribute named "Multi-Release", and also contains a directory
+ * "META-INF/versions" with subdirectories that contain versioned entries
+ * segregated by the major version of Java platform releases, starting with
+ * release 9.  A versioned entry, with a version {@code n}, {@code 8 < n}, in
+ * the "META-INF/versions/{n}" directory overrides the unversioned root entry
+ * as well as any entry with a version number {@code i} where
+ * {@code 8 < i < n}.
  *
- * If the verify flag is on when opening a signed jar file, the content of the
- * file is verified against its signature embedded inside the file. Please note
- * that the verification process does not include validating the signer's
+ * <p>By default, a {@code JarFile} for a multi-release jar file is configured
+ * to process the multi-release jar file as if it were a plain (unversioned) jar
+ * file, and as such an entry name is associated with at most one root entry.
+ * The {@code JarFile} may be configured to process a
+ * multi-release jar file by setting a maximum version used when searching for
+ * versioned entries (see methods {@link JarFile#setVersioned(int)} and
+ * {@link JarFile#setRuntimeVersioned()}).  When so configured, an entry name
+ * can correspond with at most one root entry and zero or more versioned
+ * entries. A search is required to associate the entry name with the latest
+ * versioned entry whose version is less than or equal to the maximum version
+ * (see {@link #getEntry(String)}).
+ *
+ * <p>Class loaders that utilize {@code JarFile} to load classes from the
+ * contents of {@code JarFile} entries should, after construction of a
+ * {@code JarFile}, call {@link JarFile#setRuntimeVersioned()} prior to any
+ * subsequent operations.  This assures that classes compatible with the major
+ * version of the running JVM are loaded from multi-release jar files.
+ *
+ * <p>If the verify flag is on when opening a signed jar file, the content of
+ * the file is verified against its signature embedded inside the file. Please
+ * note that the verification process does not include validating the signer's
  * certificate. A caller should inspect the return value of
  * {@link JarEntry#getCodeSigners()} to further determine if the signature
  * can be trusted.
  *
+ * <p> Unless otherwise noted, passing a {@code null} argument to a constructor
+ * or method in this class will cause a {@link NullPointerException} to be
+ * thrown.
+ *
+ * @implNote
+ * <div class="block">
+ * If the API can not be used to configure a {@code JarFile} (e.g. to override
+ * the configuration of a compiled application or library), two {@code System}
+ * properties are available.
+ * <ul>
+ * <li>
+ * {@code jdk.util.jar.version} can be assigned a value that is the
+ * {@code String} representation of a non-negative integer
+ * {@code <= Version.current().major()}.  The value is used to set the effective
+ * runtime version to something other than the default value obtained by
+ * evaluating {@code Version.current().major()}. The effective runtime version
+ * is the version that the method {@link JarFile#setRuntimeVersioned()} uses.
+ * This makes the following two method invocations equivalent:
+ * {@code jf.setRuntimeVersioned()} and {@code jf.setVersioned(n)} where
+ * {@code jf} is a reference to a {@code JarFile} and {@code n} is the
+ * value assigned to the {@code jdk.util.jar.version} property.
+ * </li>
+ * <li>
+ * {@code jdk.util.jar.enableMultiRelease} can be assigned one of the three
+ * {@code String} values <em>true</em>, <em>false</em>, or <em>force</em>.  The
+ * value <em>true</em>, the default value, enables multi-release jar file
+ * processing.  The value <em>false</em> disables multi-release jar processing,
+ * ignoring the "Multi-Release" manifest attribute, and the versioned
+ * directories in a multi-release jar file if they exist.  Furthermore,
+ * the method {@link JarFile#isMultiRelease()} returns <em>false</em> and the
+ * method {@link JarFile#setVersioned(int)} does nothing.  The value
+ * <em>force</em> causes the {@code JarFile} to be initialized to runtime
+ * versioning after construction.  It effectively does the same as this code:
+ * {@code (new JarFile(param)).setRuntimeVersioned()}.
+ * </li>
+ * </ul>
+ * </div>
+ *
  * @author  David Connelly
  * @see     Manifest
  * @see     java.util.zip.ZipFile
  * @see     java.util.jar.JarEntry
  * @since   1.2
  */
 public
 class JarFile extends ZipFile {
+    private final static int BASE_VERSION;
+    private final static int RUNTIME_VERSION;
+    private final static boolean MULTI_RELEASE_ENABLED;
+    private final static boolean MULTI_RELEASE_FORCED;
+    private volatile int version;
     private SoftReference<Manifest> manRef;
     private JarEntry manEntry;
     private JarVerifier jv;
     private boolean jvInitialized;
     private boolean verify;
 
     // indicates if Class-Path attribute present (only valid if hasCheckedSpecialAttributes true)
     private boolean hasClassPathAttribute;
     // true if manifest checked for special attributes
     private volatile boolean hasCheckedSpecialAttributes;
+    // configuration lock for multi-release jars, prevent version changes after reading an entry
+    private boolean configured;
+
+    /*
+     * Temporary place holder until java.util.Version (JEP 223) is integrated.
+     */
+    private static class Version {
+        private static Version instance;
+
+        private Version() {
+            super();
+        }
+
+        public static Version current() {
+            if (instance == null) {
+                instance = new Version();
+            }
+            return instance;
+        }
+
+        public int major() {
+            return sun.misc.Version.jdkMinorVersion();
+        }
+    }
 
-    // Set up JavaUtilJarAccess in SharedSecrets
     static {
+        // Set up JavaUtilJarAccess in SharedSecrets
         SharedSecrets.setJavaUtilJarAccess(new JavaUtilJarAccessImpl());
+
+        BASE_VERSION = 8;  // current JDK implementation version minus 1
+        RUNTIME_VERSION = AccessController.doPrivileged(
+                new PrivilegedAction<Integer>() {
+                    public Integer run() {
+                        Integer v = Version.current().major();
+                        Integer i = Integer.getInteger("jdk.util.jar.version", v);
+                        i = i < 0 ? 0 : i;
+                        return i > v ? v : i;
+                    }
+                }
+        );
+        String multi_release = AccessController.doPrivileged(
+                new PrivilegedAction<String>() {
+                    public String run() {
+                        return System.getProperty("jdk.util.jar.enableMultiRelease", "true");
+                    }
+                }
+        );
+        switch (multi_release) {
+            case "true":
+            default:
+                MULTI_RELEASE_ENABLED = true;
+                MULTI_RELEASE_FORCED = false;
+                break;
+            case "false":
+                MULTI_RELEASE_ENABLED = false;
+                MULTI_RELEASE_FORCED = false;
+                break;
+            case "force":
+                MULTI_RELEASE_ENABLED = true;
+                MULTI_RELEASE_FORCED = true;
+                break;
+        }
     }
 
+    private static final String META_INF = "META-INF/";
+
+    private static final String META_INF_VERSIONS = META_INF + "versions/";
+
     /**
      * The JAR manifest file name.
      */
-    public static final String MANIFEST_NAME = "META-INF/MANIFEST.MF";
+    public static final String MANIFEST_NAME = META_INF + "MANIFEST.MF";
 
     /**
      * Creates a new {@code JarFile} to read from the specified
      * file {@code name}. The {@code JarFile} will be verified if
      * it is signed.

@@ -162,13 +292,138 @@
      * @since 1.3
      */
     public JarFile(File file, boolean verify, int mode) throws IOException {
         super(file, mode);
         this.verify = verify;
+        this.version = MULTI_RELEASE_FORCED ? RUNTIME_VERSION : BASE_VERSION;
+    }
+
+    /**
+     * If this {@code JarFile} is a multi-release jar file, sets the maximum
+     * version used when searching for versioned entries to the specified
+     * version.  If the specified version is less than 9, then this
+     * {@code JarFile} is configured to be processed as if it is an unversioned
+     * jar file.
+     *
+     * <p>This method may be called one or more times after construction and
+     * before the first operation that obtains an entry, after which the
+     * {@code JarFile} configuration is considered immutable, and subsequent
+     * calls to this method will result in an {@code IllegalStateException}.
+     *
+     * <p>This method has no effect if this {@code JarFile} is not a
+     * multi-release jar file.
+     *
+     * @param version the maximum version
+     *
+     * @return this {@code JarFile} configured so that the search for versioned
+     *         entries starts with the specified version
+     *
+     * @throws IllegalStateException if called after an entry has been obtained
+     *
+     * @see #getVersioned()
+     * @see #setRuntimeVersioned()
+     * @since 9
+     **/
+    public JarFile setVersioned(int version) {
+        if (isMultiRelease()) {
+            synchronized (this) {  // see getVersionedEntry
+                if (configured)
+                    throw new IllegalStateException("multi-release configuration locked");
+                this.version = (version > BASE_VERSION) ? version : BASE_VERSION;
+            }
+        }
+        return this;
+    }
+
+    /**
+     * If this {@code JarFile} is a multi-release jar file, sets the maximum
+     * version used when searching for versioned entries to the major version
+     * of the running JVM.
+     *
+     * <p>This method may be called one or more times after construction and
+     * before the first operation that obtains an entry, after which the
+     * {@code JarFile} configuration is considered immutable, and subsequent
+     * calls to this method will result in an {@code IllegalStateException}.
+     *
+     * <p>This method has no effect if this {@code JarFile} is not a
+     * multi-release jar file.
+     *
+     * @apiNote
+     * This method behaves the same as
+     * {@code JarFile.setVersioned(Version.current().major())}.
+     *
+     * @return this {@code JarFile} configured so that the search for versioned
+     *         entries starts with the major version of the running JVM
+     *
+     * @throws IllegalStateException if called after an entry has been obtained
+     *
+     * @see #getVersioned()
+     * @see #setVersioned(int)
+     * @since 9
+     */
+    public JarFile setRuntimeVersioned() {
+        return setVersioned(RUNTIME_VERSION);
+    }
+
+    /**
+     * Returns the maximum version used when searching for versioned entries.
+     *
+     * @return the maximum version, or {@code 0} if this jar file is
+     *         processed as if it is an unversioned jar file or is not a
+     *         multi-release jar file
+     *
+     * @see #setVersioned(int)
+     * @see #setRuntimeVersioned()
+     * @since 9
+     */
+    public int getVersioned() {
+        if (!isMultiRelease()) {
+            version = BASE_VERSION;  // guard against inappropriate use of MULTI_RELEASE_FORCED
+        }
+        return version <= BASE_VERSION ? 0 : version;
     }
 
     /**
+     * Indicates whether or not this jar file is a multi-release jar file.
+     *
+     * @return true if this JarFile is a multi-release jar file
+     * @since 9
+     */
+    public boolean isMultiRelease() {
+        // do not call this code in a constructor because some subclasses use
+        // lazy loading of manifest so it won't be available at construction time
+        if (MULTI_RELEASE_ENABLED) {
+            // Doubled-checked locking pattern
+            Boolean result = isMultiRelease;
+            if (result == null) {
+                synchronized (this) {
+                    result = isMultiRelease;
+                    if (result == null) {
+                        Manifest man = null;
+                        try {
+                            man = getManifest();
+                        } catch (IOException e) {
+                            //Ignored, manifest cannot be read
+                        }
+                        isMultiRelease = result = (man != null)
+                                && man.getMainAttributes().containsKey(MULTI_RELEASE)
+                                ? Boolean.TRUE : Boolean.FALSE;
+                    }
+                }
+            }
+            return result == Boolean.TRUE;
+        } else {
+            return false;
+        }
+    }
+    // the following field, isMultiRelease, should only be used in the method
+    // isMultiRelease(), like a static local
+    private volatile Boolean isMultiRelease;    // is jar multi-release?
+
+
+
+    /**
      * Returns the jar file manifest, or {@code null} if none.
      *
      * @return the jar file manifest, or {@code null} if none
      *
      * @throws IllegalStateException

@@ -204,16 +459,28 @@
     }
 
     private native String[] getMetaInfEntryNames();
 
     /**
-     * Returns the {@code JarEntry} for the given entry name or
+     * Returns the {@code JarEntry} for the given root entry name or
      * {@code null} if not found.
      *
+     * <p>If this {@code JarFile} is a multi-release jar file and is configured
+     * to be processed as such, then a search is performed to find and return
+     * a {@code JarEntry} that is the latest versioned entry associated with the
+     * given entry name.  The returned {@code JarEntry} is the versioned entry
+     * corresponding to the given root entry name prefixed with the string
+     * {@code "META-INF/versions/{n}/"}, for the largest value of {@code n} for
+     * which an entry exists.  If such a versioned entry does not exist, then
+     * the {@code JarEntry} for the root entry is returned, otherwise
+     * {@code null} is returned if no entries are found.  The initial value for
+     * the version {@code n} is the maximum version as returned by the method
+     * {@link JarFile#getVersioned()}.
+     *
      * @param name the jar file entry name
-     * @return the {@code JarEntry} for the given entry name or
-     *         {@code null} if not found.
+     * @return the {@code JarEntry} for the given entry name, or
+     *         the versioned entry name, or {@code null} if not found
      *
      * @throws IllegalStateException
      *         may be thrown if the jar file has been closed
      *
      * @see java.util.jar.JarEntry

@@ -221,16 +488,28 @@
     public JarEntry getJarEntry(String name) {
         return (JarEntry)getEntry(name);
     }
 
     /**
-     * Returns the {@code ZipEntry} for the given entry name or
+     * Returns the {@code ZipEntry} for the given root entry name or
      * {@code null} if not found.
      *
+     * <p>If this {@code JarFile} is a multi-release jar file and is configured
+     * to be processed as such, then a search is performed to find and return
+     * a {@code ZipEntry} that is the latest versioned entry associated with the
+     * given entry name.  The returned {@code ZipEntry} is the versioned entry
+     * corresponding to the given root entry name prefixed with the string
+     * {@code "META-INF/versions/{n}/"}, for the largest value of {@code n} for
+     * which an entry exists.  If such a versioned entry does not exist, then
+     * the {@code ZipEntry} for the root entry is returned, otherwise
+     * {@code null} is returned if no entries are found.  The initial value for
+     * the version {@code n} is the maximum version as returned by the method
+     * {@link JarFile#getVersioned()}.
+     *
      * @param name the jar file entry name
      * @return the {@code ZipEntry} for the given entry name or
-     *         {@code null} if not found
+     *         the versioned entry name or {@code null} if not found
      *
      * @throws IllegalStateException
      *         may be thrown if the jar file has been closed
      *
      * @see java.util.zip.ZipEntry

@@ -238,10 +517,18 @@
     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
+        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>

@@ -295,18 +582,92 @@
                 new JarEntryIterator(), size(),
                 Spliterator.ORDERED | Spliterator.DISTINCT |
                         Spliterator.IMMUTABLE | Spliterator.NONNULL), false);
     }
 
+    private ZipEntry searchForVersionedEntry(int version, String name) {
+        ZipEntry vze = null;
+        String sname = "/" + name;
+        int i = version;
+        do {
+            vze = JarFile.super.getEntry(META_INF_VERSIONS + i + sname);
+            if (vze != null) break;
+        } while (--i > BASE_VERSION);
+        return vze;
+    }
+
+    /**
+     * Returns the runtime versioned {@code JarEntry} for the given root entry
+     * name or {@code null} if not found.
+     *
+     * <p>If this {@code JarFile} is a multi-release jar file, then a
+     * search is performed to find and return a {@code JarEntry} that
+     * is the runtime versioned entry associated with the
+     * given entry name.  The runtime version is obtained by invoking
+     * {@code Version.current().major()}, perhaps modified by the two
+     * {@code System} properties described in the {@code JarFile} overview.
+     * The returned {@code JarEntry} is the versioned entry corresponding to the
+     * given root entry name prefixed with the string
+     * {@code "META-INF/versions/{n}/"}, for the largest value of
+     * {@code n, 8 < n <= runtime version}, for which an entry exists.  If such a
+     * versioned entry does not exist, then the root {@code JarEntry} is returned
+     * if it exists, else {@code null} is returned.
+     *
+     * @param name the jar file entry name
+     * @return the {@code JarEntry} for the given versioned entry name, or
+     *         {@code null} if not found
+     *
+     * @throws IllegalStateException
+     *         may be thrown if the jar file has been closed
+     *
+     * @see java.util.jar.JarEntry
+     * @since 9
+     */
+    public JarEntry getRuntimeVersionedEntry(String name) {
+        ZipEntry vze = null;
+        if (RUNTIME_VERSION > BASE_VERSION && isMultiRelease() && !name.startsWith(META_INF)) {
+            vze = searchForVersionedEntry(RUNTIME_VERSION, name);
+        }
+        if (vze == null) {
+            vze = JarFile.super.getEntry(name);
+        }
+        return vze == null ? null : new JarFileEntry(name, vze);
+    }
+
+    private ZipEntry getVersionedEntry(ZipEntry ze) {
+        ZipEntry vze = null;
+        if (!configured) {
+            synchronized (this) {  // see setVersioned
+                configured = true;
+            }
+        }
+        // we know that version will not change at this point, but don't read it twice
+        int v = version;
+        if (v > BASE_VERSION && !ze.isDirectory()) {
+            String name = ze.getName();
+            if (!name.startsWith(META_INF)) {
+                vze = searchForVersionedEntry(v, name);
+            }
+        }
+        return vze == null ? ze : vze;
+    }
+
     private class JarFileEntry extends JarEntry {
+        final private String name;
+
         JarFileEntry(ZipEntry ze) {
-            super(ze);
+            super(isMultiRelease() ? getVersionedEntry(ze) : ze);
+            this.name = ze.getName();
+        }
+        JarFileEntry(String name, ZipEntry vze) {
+            super(vze);
+            this.name = name;
         }
         public Attributes getAttributes() throws IOException {
             Manifest man = JarFile.this.getManifest();
             if (man != null) {
-                return man.getAttributes(getName());
+                return man.getAttributes(super.getName());
             } else {
                 return null;
             }
         }
         public Certificate[] getCertificates() {

@@ -314,25 +675,37 @@
                 maybeInstantiateVerifier();
             } catch (IOException e) {
                 throw new RuntimeException(e);
             }
             if (certs == null && jv != null) {
-                certs = jv.getCerts(JarFile.this, this);
+                certs = jv.getCerts(JarFile.this, reifiedEntry());
             }
             return certs == null ? null : certs.clone();
         }
         public CodeSigner[] getCodeSigners() {
             try {
                 maybeInstantiateVerifier();
             } catch (IOException e) {
                 throw new RuntimeException(e);
             }
             if (signers == null && jv != null) {
-                signers = jv.getCodeSigners(JarFile.this, this);
+                signers = jv.getCodeSigners(JarFile.this, reifiedEntry());
             }
             return signers == null ? null : signers.clone();
         }
+        JarFileEntry reifiedEntry() {
+            if (isMultiRelease()) {
+                String entryName = super.getName();
+                return entryName.equals(this.name) ? this : new JarFileEntry(entryName, this);
+            }
+            return this;
+        }
+
+        @Override
+        public String getName() {
+            return name;
+        }
     }
 
     /*
      * Ensures that the JarVerifier has been created if one is
      * necessary (i.e., the jar appears to be signed.) This is done as

@@ -486,16 +859,23 @@
         }
 
         // wrap a verifier stream around the real stream
         return new JarVerifier.VerifierStream(
             getManifestFromReference(),
-            ze instanceof JarFileEntry ?
-            (JarEntry) ze : getJarEntry(ze.getName()),
+            verifiableEntry(ze),
             super.getInputStream(ze),
             jv);
     }
 
+    private JarEntry verifiableEntry(ZipEntry ze) {
+        if (!(ze instanceof JarFileEntry)) {
+            ze = getJarEntry(ze.getName());
+        }
+        // assure the name and entry match for verification
+        return ze == null ? null : ((JarFileEntry)ze).reifiedEntry();
+    }
+
     // Statics for hand-coded Boyer-Moore search
     private static final char[] CLASSPATH_CHARS = {'c','l','a','s','s','-','p','a','t','h'};
     // The bad character shift for "class-path"
     private static final int[] CLASSPATH_LASTOCC;
     // The good suffix shift for "class-path"

@@ -518,24 +898,25 @@
     }
 
     private JarEntry getManEntry() {
         if (manEntry == null) {
             // First look up manifest entry using standard name
-            manEntry = getJarEntry(MANIFEST_NAME);
+            ZipEntry manEntry = super.getEntry(MANIFEST_NAME);
             if (manEntry == null) {
                 // If not found, then iterate through all the "META-INF/"
                 // entries to find a match.
                 String[] names = getMetaInfEntryNames();
                 if (names != null) {
                     for (String name : names) {
                         if (MANIFEST_NAME.equals(name.toUpperCase(Locale.ENGLISH))) {
-                            manEntry = getJarEntry(name);
+                            manEntry = super.getEntry(name);
                             break;
                         }
                     }
                 }
             }
+            this.manEntry = manEntry == null ? null : new JarFileEntry(manEntry.getName(), manEntry);
         }
         return manEntry;
     }
 
    /**
< prev index next >