8172921: Zip filesystem performance improvement and code cleanup
authorsherman
Wed, 18 Jan 2017 11:18:13 -0800
changeset 43193 a8e490921d20
parent 43192 0c05ac48a30b
child 43194 3c8c3c61ae92
8172921: Zip filesystem performance improvement and code cleanup Reviewed-by: redestad
jdk/src/jdk.zipfs/share/classes/jdk/nio/zipfs/JarFileSystem.java
jdk/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipCoder.java
jdk/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipFileSystem.java
jdk/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipPath.java
jdk/test/jdk/nio/zipfs/PathOps.java
--- a/jdk/src/jdk.zipfs/share/classes/jdk/nio/zipfs/JarFileSystem.java	Wed Jan 18 11:08:46 2017 -0800
+++ b/jdk/src/jdk.zipfs/share/classes/jdk/nio/zipfs/JarFileSystem.java	Wed Jan 18 11:18:13 2017 -0800
@@ -135,7 +135,7 @@
         TreeMap<Integer,IndexNode> map = new TreeMap<>();
         IndexNode child = metaInfVersions.child;
         while (child != null) {
-            Integer key = getVersion(child.name, metaInfVersions.name.length);
+            Integer key = getVersion(child.name, metaInfVersions.name.length + 1);
             if (key != null && key <= version) {
                 map.put(key, child);
             }
@@ -149,7 +149,7 @@
      */
     private Integer getVersion(byte[] name, int offset) {
         try {
-            return Integer.parseInt(getString(Arrays.copyOfRange(name, offset, name.length-1)));
+            return Integer.parseInt(getString(Arrays.copyOfRange(name, offset, name.length)));
         } catch (NumberFormatException x) {
             // ignore this even though it might indicate issues with the JAR structure
             return null;
@@ -176,7 +176,7 @@
      *   returns foo/bar.class
      */
     private byte[] getRootName(IndexNode prefix, IndexNode inode) {
-        int offset = prefix.name.length - 1;
+        int offset = prefix.name.length;
         byte[] fullName = inode.name;
         return Arrays.copyOfRange(fullName, offset, fullName.length);
     }
--- a/jdk/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipCoder.java	Wed Jan 18 11:08:46 2017 -0800
+++ b/jdk/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipCoder.java	Wed Jan 18 11:18:13 2017 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009, 2014, 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
@@ -34,97 +34,95 @@
 import java.nio.charset.CodingErrorAction;
 import java.util.Arrays;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.nio.charset.StandardCharsets.ISO_8859_1;
+
 /**
  * Utility class for zipfile name and comment decoding and encoding
  *
  * @author  Xueming Shen
  */
 
-final class ZipCoder {
+class ZipCoder {
+
+    static class UTF8 extends ZipCoder {
+        UTF8() {
+            super(UTF_8);
+        }
+
+        @Override
+        byte[] getBytes(String s) {        // fast pass for ascii
+            for (int i = 0; i < s.length(); i++) {
+                if (s.charAt(i) > 0x7f) return super.getBytes(s);
+            }
+            return s.getBytes(ISO_8859_1);
+        }
 
-    String toString(byte[] ba, int length) {
+        @Override
+        String toString(byte[] ba) {
+            for (byte b : ba) {
+                if (b < 0) return super.toString(ba);
+            }
+            return new String(ba, ISO_8859_1);
+        }
+    }
+
+    private static ZipCoder utf8 = new UTF8();
+
+    public static ZipCoder get(String csn) {
+        Charset cs = Charset.forName(csn);
+        if (cs.name().equals("UTF-8")) {
+            return utf8;
+        }
+        return new ZipCoder(cs);
+    }
+
+    String toString(byte[] ba) {
         CharsetDecoder cd = decoder().reset();
-        int len = (int)(length * cd.maxCharsPerByte());
-        char[] ca = new char[len];
-        if (len == 0)
-            return new String(ca);
-        ByteBuffer bb = ByteBuffer.wrap(ba, 0, length);
+        int clen = (int)(ba.length * cd.maxCharsPerByte());
+        char[] ca = new char[clen];
+        if (clen == 0)
+        return new String(ca);
+        ByteBuffer bb = ByteBuffer.wrap(ba, 0, ba.length);
         CharBuffer cb = CharBuffer.wrap(ca);
         CoderResult cr = cd.decode(bb, cb, true);
         if (!cr.isUnderflow())
-            throw new IllegalArgumentException(cr.toString());
+        throw new IllegalArgumentException(cr.toString());
         cr = cd.flush(cb);
         if (!cr.isUnderflow())
-            throw new IllegalArgumentException(cr.toString());
+        throw new IllegalArgumentException(cr.toString());
         return new String(ca, 0, cb.position());
     }
 
-    String toString(byte[] ba) {
-        return toString(ba, ba.length);
-    }
-
     byte[] getBytes(String s) {
         CharsetEncoder ce = encoder().reset();
         char[] ca = s.toCharArray();
         int len = (int)(ca.length * ce.maxBytesPerChar());
         byte[] ba = new byte[len];
         if (len == 0)
-            return ba;
+        return ba;
         ByteBuffer bb = ByteBuffer.wrap(ba);
         CharBuffer cb = CharBuffer.wrap(ca);
         CoderResult cr = ce.encode(cb, bb, true);
         if (!cr.isUnderflow())
-            throw new IllegalArgumentException(cr.toString());
+        throw new IllegalArgumentException(cr.toString());
         cr = ce.flush(bb);
         if (!cr.isUnderflow())
-            throw new IllegalArgumentException(cr.toString());
+        throw new IllegalArgumentException(cr.toString());
         if (bb.position() == ba.length)  // defensive copy?
-            return ba;
+        return ba;
         else
-            return Arrays.copyOf(ba, bb.position());
-    }
-
-    // assume invoked only if "this" is not utf8
-    byte[] getBytesUTF8(String s) {
-        if (isutf8)
-            return getBytes(s);
-        if (utf8 == null)
-            utf8 = new ZipCoder(Charset.forName("UTF-8"));
-        return utf8.getBytes(s);
-    }
-
-    String toStringUTF8(byte[] ba, int len) {
-        if (isutf8)
-            return toString(ba, len);
-        if (utf8 == null)
-            utf8 = new ZipCoder(Charset.forName("UTF-8"));
-        return utf8.toString(ba, len);
+        return Arrays.copyOf(ba, bb.position());
     }
 
     boolean isUTF8() {
-        return isutf8;
+        return cs == UTF_8;
     }
 
     private Charset cs;
-    private boolean isutf8;
-    private ZipCoder utf8;
 
     private ZipCoder(Charset cs) {
         this.cs = cs;
-        this.isutf8 = cs.name().equals("UTF-8");
-    }
-
-    static ZipCoder get(Charset charset) {
-        return new ZipCoder(charset);
-    }
-
-    static ZipCoder get(String csn) {
-        try {
-            return new ZipCoder(Charset.forName(csn));
-        } catch (Throwable t) {
-            t.printStackTrace();
-        }
-        return new ZipCoder(Charset.defaultCharset());
     }
 
     private final ThreadLocal<CharsetDecoder> decTL = new ThreadLocal<>();
@@ -133,10 +131,10 @@
     private CharsetDecoder decoder() {
         CharsetDecoder dec = decTL.get();
         if (dec == null) {
-            dec = cs.newDecoder()
-              .onMalformedInput(CodingErrorAction.REPORT)
-              .onUnmappableCharacter(CodingErrorAction.REPORT);
-            decTL.set(dec);
+        dec = cs.newDecoder()
+            .onMalformedInput(CodingErrorAction.REPORT)
+            .onUnmappableCharacter(CodingErrorAction.REPORT);
+        decTL.set(dec);
         }
         return dec;
     }
@@ -144,10 +142,10 @@
     private CharsetEncoder encoder() {
         CharsetEncoder enc = encTL.get();
         if (enc == null) {
-            enc = cs.newEncoder()
-              .onMalformedInput(CodingErrorAction.REPORT)
-              .onUnmappableCharacter(CodingErrorAction.REPORT);
-            encTL.set(enc);
+        enc = cs.newEncoder()
+            .onMalformedInput(CodingErrorAction.REPORT)
+            .onUnmappableCharacter(CodingErrorAction.REPORT);
+        encTL.set(enc);
         }
         return enc;
     }
--- a/jdk/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipFileSystem.java	Wed Jan 18 11:08:46 2017 -0800
+++ b/jdk/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipFileSystem.java	Wed Jan 18 11:18:13 2017 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009, 2015, 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
@@ -314,8 +314,8 @@
                 IndexNode inode = getInode(path);
                 if (inode == null)
                     return null;
-                e = new Entry(inode.name);       // pseudo directory
-                e.method = METHOD_STORED;        // STORED for dir
+                e = new Entry(inode.name, inode.isdir);  // pseudo directory
+                e.method = METHOD_STORED;         // STORED for dir
                 e.mtime = e.atime = e.ctime = zfsDefaultTimeStamp;
             }
         } finally {
@@ -400,7 +400,8 @@
             List<Path> list = new ArrayList<>();
             IndexNode child = inode.child;
             while (child != null) {
-                ZipPath zp = new ZipPath(this, child.name);
+                // assume all path from zip file itself is "normalized"
+                ZipPath zp = new ZipPath(this, child.name, true);
                 if (filter == null || filter.accept(zp))
                     list.add(zp);
                 child = child.sibling;
@@ -415,14 +416,14 @@
         throws IOException
     {
         checkWritable();
-        dir = toDirectoryPath(dir);
+        //  dir = toDirectoryPath(dir);
         beginWrite();
         try {
             ensureOpen();
             if (dir.length == 0 || exists(dir))  // root dir, or exiting dir
                 throw new FileAlreadyExistsException(getString(dir));
             checkParents(dir);
-            Entry e = new Entry(dir, Entry.NEW);
+            Entry e = new Entry(dir, Entry.NEW, true);
             e.method = METHOD_STORED;            // STORED for dir
             update(e);
         } finally {
@@ -463,7 +464,7 @@
             } else {
                 checkParents(dst);
             }
-            Entry u = new Entry(eSrc, Entry.COPY);    // copy eSrc entry
+            Entry u = new Entry(eSrc, Entry.COPY);  // copy eSrc entry
             u.name(dst);                              // change name
             if (eSrc.type == Entry.NEW || eSrc.type == Entry.FILECH)
             {
@@ -533,7 +534,7 @@
                 if (!hasCreate && !hasCreateNew)
                     throw new NoSuchFileException(getString(path));
                 checkParents(path);
-                return getOutputStream(new Entry(path, Entry.NEW));
+                return getOutputStream(new Entry(path, Entry.NEW, false));
             }
         } finally {
             endRead();
@@ -887,7 +888,7 @@
         int off = getParentOff(path);
         if (off <= 1)
             return ROOTPATH;
-        return Arrays.copyOf(path, off + 1);
+        return Arrays.copyOf(path, off);
     }
 
     private static int getParentOff(byte[] path) {
@@ -1075,11 +1076,9 @@
             if (pos + CENHDR + nlen > limit) {
                 zerror("invalid CEN header (bad header size)");
             }
-            byte[] name = new byte[nlen + 1];
-            System.arraycopy(cen, pos + CENHDR, name, 1, nlen);
-            name[0] = '/';
-            IndexNode inode = new IndexNode(name, pos);
+            IndexNode inode = new IndexNode(cen, pos + CENHDR, nlen, pos);
             inodes.put(inode, inode);
+
             // skip ext and comment
             pos += (CENHDR + nlen + elen + clen);
         }
@@ -1112,7 +1111,7 @@
     private boolean hasUpdate = false;
 
     // shared key. consumer guarantees the "writeLock" before use it.
-    private final IndexNode LOOKUPKEY = IndexNode.keyOf(null);
+    private final IndexNode LOOKUPKEY = new IndexNode(null, -1);
 
     private void updateDelete(IndexNode inode) {
         beginWrite();
@@ -1312,16 +1311,7 @@
     IndexNode getInode(byte[] path) {
         if (path == null)
             throw new NullPointerException("path");
-        IndexNode key = IndexNode.keyOf(path);
-        IndexNode inode = inodes.get(key);
-        if (inode == null &&
-            (path.length == 0 || path[path.length -1] != '/')) {
-            // if does not ends with a slash
-            path = Arrays.copyOf(path, path.length + 1);
-            path[path.length - 1] = '/';
-            inode = inodes.get(key.as(path));
-        }
-        return inode;
+        return inodes.get(IndexNode.keyOf(path));
     }
 
     Entry getEntry(byte[] path) throws IOException {
@@ -1782,8 +1772,11 @@
         int    hashcode;  // node is hashable/hashed by its name
         int    pos = -1;  // position in cen table, -1 menas the
                           // entry does not exists in zip file
-        IndexNode(byte[] name) {
+        boolean isdir;
+
+        IndexNode(byte[] name, boolean isdir) {
             name(name);
+            this.isdir = isdir;
             this.pos = -1;
         }
 
@@ -1792,8 +1785,28 @@
             this.pos = pos;
         }
 
+        // constructor for cenInit()
+        IndexNode(byte[] cen, int noff, int nlen, int pos) {
+            if (cen[noff + nlen - 1] == '/') {
+                isdir = true;
+                nlen--;
+            }
+            name = new byte[nlen + 1];
+            System.arraycopy(cen, pos + CENHDR, name, 1, nlen);
+            name[0] = '/';
+            name(name);
+            this.pos = pos;
+        }
+
+        private static final ThreadLocal<IndexNode> cachedKey = new ThreadLocal<>();
+
         final static IndexNode keyOf(byte[] name) { // get a lookup key;
-            return new IndexNode(name, -1);
+            IndexNode key = cachedKey.get();
+            if (key == null) {
+                key = new IndexNode(name, -1);
+                cachedKey.set(key);
+            }
+            return key.as(name);
         }
 
         final void name(byte[] name) {
@@ -1807,8 +1820,7 @@
         }
 
         boolean isDir() {
-            return name != null &&
-                   (name.length == 0 || name[name.length - 1] == '/');
+            return isdir;
         }
 
         public boolean equals(Object other) {
@@ -1865,8 +1877,9 @@
 
         Entry() {}
 
-        Entry(byte[] name) {
+        Entry(byte[] name, boolean isdir) {
             name(name);
+            this.isdir = isdir;
             this.mtime  = this.ctime = this.atime = System.currentTimeMillis();
             this.crc    = 0;
             this.size   = 0;
@@ -1874,13 +1887,14 @@
             this.method = METHOD_DEFLATED;
         }
 
-        Entry(byte[] name, int type) {
-            this(name);
+        Entry(byte[] name, int type, boolean isdir) {
+            this(name, isdir);
             this.type = type;
         }
 
         Entry (Entry e, int type) {
             name(e.name);
+            this.isdir     = e.isdir;
             this.version   = e.version;
             this.ctime     = e.ctime;
             this.atime     = e.atime;
@@ -1902,7 +1916,7 @@
         }
 
         Entry (byte[] name, Path file, int type) {
-            this(name, type);
+            this(name, type, false);
             this.file = file;
             this.method = METHOD_STORED;
         }
@@ -1948,6 +1962,7 @@
             locoff      = CENOFF(cen, pos);
             pos += CENHDR;
             this.name = inode.name;
+            this.isdir = inode.isdir;
             this.hashcode = inode.hashcode;
 
             pos += nlen;
@@ -1974,9 +1989,10 @@
             int elenEXTT = 0;                // extra for Extended Timestamp
             boolean foundExtraTime = false;  // if time stamp NTFS, EXTT present
 
-            // confirm size/length
+            byte[] zname = isdir ? toDirectoryPath(name) : name;
 
-            int nlen = (name != null) ? name.length - 1 : 0;  // name has [0] as "slash"
+            // confirm size/length
+            int nlen = (zname != null) ? zname.length - 1 : 0;  // name has [0] as "slash"
             int elen = (extra != null) ? extra.length : 0;
             int eoff = 0;
             int clen = (comment != null) ? comment.length : 0;
@@ -2037,7 +2053,7 @@
             writeShort(os, 0);              // internal file attributes (unused)
             writeInt(os, 0);                // external file attributes (unused)
             writeInt(os, locoff0);          // relative offset of local header
-            writeBytes(os, name, 1, nlen);
+            writeBytes(os, zname, 1, nlen);
             if (elen64 != 0) {
                 writeShort(os, EXTID_ZIP64);// Zip64 extra
                 writeShort(os, elen64 - 4); // size of "this" extra block
@@ -2075,87 +2091,13 @@
         }
 
         ///////////////////// LOC //////////////////////
-        static Entry readLOC(ZipFileSystem zipfs, long pos)
-            throws IOException
-        {
-            return readLOC(zipfs, pos, new byte[1024]);
-        }
-
-        static Entry readLOC(ZipFileSystem zipfs, long pos, byte[] buf)
-            throws IOException
-        {
-            return new Entry().loc(zipfs, pos, buf);
-        }
-
-        Entry loc(ZipFileSystem zipfs, long pos, byte[] buf)
-            throws IOException
-        {
-            assert (buf.length >= LOCHDR);
-            if (zipfs.readFullyAt(buf, 0, LOCHDR , pos) != LOCHDR)
-                throw new ZipException("loc: reading failed");
-            if (!locSigAt(buf, 0))
-                throw new ZipException("loc: wrong sig ->"
-                                       + Long.toString(getSig(buf, 0), 16));
-            //startPos = pos;
-            version  = LOCVER(buf);
-            flag     = LOCFLG(buf);
-            method   = LOCHOW(buf);
-            mtime    = dosToJavaTime(LOCTIM(buf));
-            crc      = LOCCRC(buf);
-            csize    = LOCSIZ(buf);
-            size     = LOCLEN(buf);
-            int nlen = LOCNAM(buf);
-            int elen = LOCEXT(buf);
-
-            name = new byte[nlen + 1];
-            name[0] = '/';
-            if (zipfs.readFullyAt(name, 1, nlen, pos + LOCHDR) != nlen) {
-                throw new ZipException("loc: name reading failed");
-            }
-            if (elen > 0) {
-                extra = new byte[elen];
-                if (zipfs.readFullyAt(extra, 0, elen, pos + LOCHDR + nlen)
-                    != elen) {
-                    throw new ZipException("loc: ext reading failed");
-                }
-            }
-            pos += (LOCHDR + nlen + elen);
-            if ((flag & FLAG_DATADESCR) != 0) {
-                // Data Descriptor
-                Entry e = zipfs.getEntry(name);  // get the size/csize from cen
-                if (e == null)
-                    throw new ZipException("loc: name not found in cen");
-                size = e.size;
-                csize = e.csize;
-                pos += (method == METHOD_STORED ? size : csize);
-                if (size >= ZIP64_MINVAL || csize >= ZIP64_MINVAL)
-                    pos += 24;
-                else
-                    pos += 16;
-            } else {
-                if (extra != null &&
-                    (size == ZIP64_MINVAL || csize == ZIP64_MINVAL)) {
-                    // zip64 ext: must include both size and csize
-                    int off = 0;
-                    while (off + 20 < elen) {    // HeaderID+DataSize+Data
-                        int sz = SH(extra, off + 2);
-                        if (SH(extra, off) == EXTID_ZIP64 && sz == 16) {
-                            size = LL(extra, off + 4);
-                            csize = LL(extra, off + 12);
-                            break;
-                        }
-                        off += (sz + 4);
-                    }
-                }
-                pos += (method == METHOD_STORED ? size : csize);
-            }
-            return this;
-        }
 
         int writeLOC(OutputStream os) throws IOException {
             writeInt(os, LOCSIG);               // LOC header signature
             int version = version();
-            int nlen = (name != null) ? name.length - 1 : 0; // [0] is slash
+
+            byte[] zname = isdir ? toDirectoryPath(name) : name;
+            int nlen = (zname != null) ? zname.length - 1 : 0; // [0] is slash
             int elen = (extra != null) ? extra.length : 0;
             boolean foundExtraTime = false;     // if extra timestamp present
             int eoff = 0;
@@ -2214,7 +2156,7 @@
             }
             writeShort(os, nlen);
             writeShort(os, elen + elen64 + elenNTFS + elenEXTT);
-            writeBytes(os, name, 1, nlen);
+            writeBytes(os, zname, 1, nlen);
             if (elen64 != 0) {
                 writeShort(os, EXTID_ZIP64);
                 writeShort(os, 16);
@@ -2551,7 +2493,7 @@
     private void buildNodeTree() throws IOException {
         beginWrite();
         try {
-            IndexNode root = new IndexNode(ROOTPATH);
+            IndexNode root = new IndexNode(ROOTPATH, true);
             IndexNode[] nodes = inodes.keySet().toArray(new IndexNode[0]);
             inodes.put(root, root);
             ParentLookup lookup = new ParentLookup();
@@ -2564,7 +2506,7 @@
                         root.child = node;
                         break;
                     }
-                    lookup = lookup.as(node.name, off + 1);
+                    lookup = lookup.as(node.name, off);
                     if (inodes.containsKey(lookup)) {
                         parent = inodes.get(lookup);
                         node.sibling = parent.child;
@@ -2572,7 +2514,7 @@
                         break;
                     }
                     // add new pseudo directory entry
-                    parent = new IndexNode(Arrays.copyOf(node.name, off + 1));
+                    parent = new IndexNode(Arrays.copyOf(node.name, off), true);
                     inodes.put(parent, parent);
                     node.sibling = parent.child;
                     parent.child = node;
--- a/jdk/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipPath.java	Wed Jan 18 11:08:46 2017 -0800
+++ b/jdk/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipPath.java	Wed Jan 18 11:18:13 2017 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009, 2014, 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
@@ -59,8 +59,7 @@
         } else {
             if (zfs.zc.isUTF8()) {
                 this.path = normalize(path);
-            } else {
-                // see normalize(String);
+            } else {    // see normalize(String);
                 this.path = normalize(zfs.getString(path));
             }
         }
@@ -68,12 +67,7 @@
 
     ZipPath(ZipFileSystem zfs, String path) {
         this.zfs = zfs;
-        if (zfs.zc.isUTF8()) {
-            this.path = normalize(zfs.getBytes(path));
-        } else {
-            // see normalize(String);
-            this.path = normalize(path);
-        }
+        this.path = normalize(path);
     }
 
     @Override
@@ -84,33 +78,31 @@
             return null;
     }
 
-    @Override
+   @Override
     public Path getFileName() {
-        initOffsets();
-        int count = offsets.length;
-        if (count == 0)
-            return null;  // no elements so no name
-        if (count == 1 && path[0] != '/')
+        int off = path.length;
+        if (off == 0 || off == 1 && path[0] == '/')
+            return null;
+        while (--off >= 0 && path[off] != '/') {}
+        if (off < 0)
             return this;
-        int lastOffset = offsets[count-1];
-        int len = path.length - lastOffset;
-        byte[] result = new byte[len];
-        System.arraycopy(path, lastOffset, result, 0, len);
-        return new ZipPath(zfs, result);
+        off++;
+        byte[] result = new byte[path.length - off];
+        System.arraycopy(path, off, result, 0, result.length);
+        return new ZipPath(getFileSystem(), result, true);
     }
 
     @Override
     public ZipPath getParent() {
-        initOffsets();
-        int count = offsets.length;
-        if (count == 0)    // no elements so no parent
+        int off = path.length;
+        if (off == 0 || off == 1 && path[0] == '/')
             return null;
-        int len = offsets[count-1] - 1;
-        if (len <= 0)      // parent is root only (may be null)
+        while (--off >= 0 && path[off] != '/') {}
+        if (off <= 0)
             return getRoot();
-        byte[] result = new byte[len];
-        System.arraycopy(path, 0, result, 0, len);
-        return new ZipPath(zfs, result);
+        byte[] result = new byte[off];
+        System.arraycopy(path, 0, result, 0, off);
+        return new ZipPath(getFileSystem(), result, true);
     }
 
     @Override
@@ -277,30 +269,36 @@
 
     @Override
     public boolean isAbsolute() {
-        return (this.path.length > 0 && path[0] == '/');
+        return path.length > 0 && path[0] == '/';
     }
 
     @Override
     public ZipPath resolve(Path other) {
-        final ZipPath o = checkPath(other);
-        int tlen = this.path.length;
-        if (tlen == 0 || o.isAbsolute())
+        ZipPath o = checkPath(other);
+        if (o.path.length == 0)
+            return this;
+        if (o.isAbsolute() || this.path.length == 0)
             return o;
-        int olen = o.path.length;
-        if (olen == 0)
-            return this;
+        return resolve(o.path);
+    }
+
+    // opath is normalized, just concat
+    private ZipPath resolve(byte[] opath) {
         byte[] resolved = null;
-        if (this.path[tlen - 1] == '/') {
+        byte[] tpath = this.path;
+        int tlen = tpath.length;
+        int olen = opath.length;
+        if (path[tlen - 1] == '/') {
             resolved = new byte[tlen + olen];
-            System.arraycopy(path, 0, resolved, 0, tlen);
-            System.arraycopy(o.path, 0, resolved, tlen, olen);
+            System.arraycopy(tpath, 0, resolved, 0, tlen);
+            System.arraycopy(opath, 0, resolved, tlen, olen);
         } else {
             resolved = new byte[tlen + 1 + olen];
-            System.arraycopy(path, 0, resolved, 0, tlen);
+            System.arraycopy(tpath, 0, resolved, 0, tlen);
             resolved[tlen] = '/';
-            System.arraycopy(o.path, 0, resolved, tlen + 1, olen);
+            System.arraycopy(opath, 0, resolved, tlen + 1, olen);
         }
-        return new ZipPath(zfs, resolved);
+        return new ZipPath(zfs, resolved, true);
     }
 
     @Override
@@ -351,7 +349,12 @@
 
     @Override
     public ZipPath resolve(String other) {
-        return resolve(zfs.getPath(other));
+        byte[] opath = normalize(other);
+        if (opath.length == 0)
+            return this;
+        if (opath[0] == '/' || this.path.length == 0)
+            return new ZipPath(zfs, opath, true);
+        return resolve(opath);
     }
 
     @Override
@@ -455,8 +458,9 @@
                 return normalize(path, i - 1);
             prevC = c;
         }
-        if (len > 1 && prevC == '/')
+        if (len > 1 && prevC == '/') {
             return Arrays.copyOf(path, len - 1);
+        }
         return path;
     }
 
@@ -490,6 +494,8 @@
     // to avoid incorrectly normalizing byte '0x5c' (as '\')
     // to '/'.
     private byte[] normalize(String path) {
+        if (zfs.zc.isUTF8())
+            return normalize(zfs.getBytes(path));
         int len = path.length();
         if (len == 0)
             return new byte[0];
@@ -533,7 +539,8 @@
     // Remove DotSlash(./) and resolve DotDot (..) components
     private byte[] getResolved() {
         for (int i = 0; i < path.length; i++) {
-            if (path[i] == (byte)'.') {
+            if (path[i] == (byte)'.' &&
+                (i + 1 == path.length || path[i + 1] == '/')) {
                 return resolve0();
             }
         }
@@ -976,5 +983,4 @@
         }
         return sb.toString();
     }
-
 }
--- a/jdk/test/jdk/nio/zipfs/PathOps.java	Wed Jan 18 11:08:46 2017 -0800
+++ b/jdk/test/jdk/nio/zipfs/PathOps.java	Wed Jan 18 11:18:13 2017 -0800
@@ -31,7 +31,7 @@
 /**
  *
  * @test
- * @bug 8038500 8040059 8139956 8146754
+ * @bug 8038500 8040059 8139956 8146754 8172921
  * @summary Tests path operations for zip provider.
  *
  * @run main PathOps
@@ -180,6 +180,13 @@
         return this;
     }
 
+    PathOps resolvePath(String other, String expected) {
+        out.format("test resolve %s\n", other);
+        checkPath();
+        check(path.resolve(fs.getPath(other)), expected);
+        return this;
+    }
+
     PathOps resolveSibling(String other, String expected) {
         out.format("test resolveSibling %s\n", other);
         checkPath();
@@ -384,6 +391,30 @@
             .resolve("", "")
             .resolve("foo", "foo")
             .resolve("/foo", "/foo");
+        test("/")
+            .resolve("", "/")
+            .resolve("foo", "/foo")
+            .resolve("/foo", "/foo")
+            .resolve("/foo/", "/foo");
+
+        // resolve(Path)
+        test("/tmp")
+            .resolvePath("foo", "/tmp/foo")
+            .resolvePath("/foo", "/foo")
+            .resolvePath("", "/tmp");
+        test("tmp")
+            .resolvePath("foo", "tmp/foo")
+            .resolvePath("/foo", "/foo")
+            .resolvePath("", "tmp");
+        test("")
+            .resolvePath("", "")
+            .resolvePath("foo", "foo")
+            .resolvePath("/foo", "/foo");
+        test("/")
+            .resolvePath("", "/")
+            .resolvePath("foo", "/foo")
+            .resolvePath("/foo", "/foo")
+            .resolvePath("/foo/", "/foo");
 
         // resolveSibling
         test("foo")