changeset 14178:60fdfede6d80

8147460: Clean-up jrtfs implementation Reviewed-by: alanb, jlaskey, sundar
author sherman
date Fri, 15 Apr 2016 13:05:52 -0700
parents 6512923d64e5
children e8b09982c198
files src/java.base/share/classes/jdk/internal/jimage/ImageReader.java src/java.base/share/classes/jdk/internal/jrtfs/AbstractJrtFileAttributes.java src/java.base/share/classes/jdk/internal/jrtfs/AbstractJrtFileSystem.java src/java.base/share/classes/jdk/internal/jrtfs/AbstractJrtPath.java src/java.base/share/classes/jdk/internal/jrtfs/ExplodedImage.java src/java.base/share/classes/jdk/internal/jrtfs/JrtDirectoryStream.java src/java.base/share/classes/jdk/internal/jrtfs/JrtExplodedFileAttributes.java src/java.base/share/classes/jdk/internal/jrtfs/JrtExplodedFileSystem.java src/java.base/share/classes/jdk/internal/jrtfs/JrtExplodedPath.java src/java.base/share/classes/jdk/internal/jrtfs/JrtFileAttributeView.java src/java.base/share/classes/jdk/internal/jrtfs/JrtFileAttributes.java src/java.base/share/classes/jdk/internal/jrtfs/JrtFileStore.java src/java.base/share/classes/jdk/internal/jrtfs/JrtFileSystem.java src/java.base/share/classes/jdk/internal/jrtfs/JrtFileSystemProvider.java src/java.base/share/classes/jdk/internal/jrtfs/JrtPath.java src/java.base/share/classes/jdk/internal/jrtfs/SystemImage.java src/java.base/share/classes/jdk/internal/jrtfs/SystemImages.java test/jdk/internal/jrtfs/PathOps.java
diffstat 18 files changed, 1817 insertions(+), 2604 deletions(-) [+]
line wrap: on
line diff
--- a/src/java.base/share/classes/jdk/internal/jimage/ImageReader.java	Fri Apr 15 10:14:57 2016 -0700
+++ b/src/java.base/share/classes/jdk/internal/jimage/ImageReader.java	Fri Apr 15 13:05:52 2016 -0700
@@ -136,7 +136,7 @@
         private final BasicFileAttributes fileAttrs;
         private boolean completed;
 
-        Node(String name, BasicFileAttributes fileAttrs) {
+        protected Node(String name, BasicFileAttributes fileAttrs) {
             this.name = Objects.requireNonNull(name);
             this.fileAttrs = Objects.requireNonNull(fileAttrs);
         }
--- a/src/java.base/share/classes/jdk/internal/jrtfs/AbstractJrtFileAttributes.java	Fri Apr 15 10:14:57 2016 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,84 +0,0 @@
-/*
- * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-package jdk.internal.jrtfs;
-
-import java.nio.file.attribute.BasicFileAttributes;
-import java.util.Formatter;
-
-/**
- * Base class for file attributes supported by jrt file systems.
- *
- * @implNote This class needs to maintain JDK 8 source compatibility.
- *
- * It is used internally in the JDK to implement jimage/jrtfs access,
- * but also compiled and delivered as part of the jrtfs.jar to support access
- * to the jimage file provided by the shipped JDK by tools running on JDK 8.
- */
-public abstract class AbstractJrtFileAttributes implements BasicFileAttributes {
-
-    // jrt fs specific attributes
-    /**
-     * Compressed resource file. If not available or not applicable, 0L is
-     * returned.
-     *
-     * @return the compressed resource size for compressed resources.
-     */
-    public abstract long compressedSize();
-
-    /**
-     * "file" extension of a file resource.
-     *
-     * @return extension string for the file resource
-     */
-    public abstract String extension();
-
-    @Override
-    public final String toString() {
-        StringBuilder sb = new StringBuilder(1024);
-        try (Formatter fm = new Formatter(sb)) {
-            if (creationTime() != null) {
-                fm.format("    creationTime    : %tc%n", creationTime().toMillis());
-            } else {
-                fm.format("    creationTime    : null%n");
-            }
-
-            if (lastAccessTime() != null) {
-                fm.format("    lastAccessTime  : %tc%n", lastAccessTime().toMillis());
-            } else {
-                fm.format("    lastAccessTime  : null%n");
-            }
-            fm.format("    lastModifiedTime: %tc%n", lastModifiedTime().toMillis());
-            fm.format("    isRegularFile   : %b%n", isRegularFile());
-            fm.format("    isDirectory     : %b%n", isDirectory());
-            fm.format("    isSymbolicLink  : %b%n", isSymbolicLink());
-            fm.format("    isOther         : %b%n", isOther());
-            fm.format("    fileKey         : %s%n", fileKey());
-            fm.format("    size            : %d%n", size());
-            fm.format("    compressedSize  : %d%n", compressedSize());
-            fm.format("    extension       : %s%n", extension());
-        }
-        return sb.toString();
-    }
-}
--- a/src/java.base/share/classes/jdk/internal/jrtfs/AbstractJrtFileSystem.java	Fri Apr 15 10:14:57 2016 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,372 +0,0 @@
-/*
- * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-package jdk.internal.jrtfs;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.nio.ByteBuffer;
-import java.nio.channels.Channels;
-import java.nio.channels.FileChannel;
-import java.nio.channels.NonWritableChannelException;
-import java.nio.channels.ReadableByteChannel;
-import java.nio.channels.SeekableByteChannel;
-import java.nio.charset.Charset;
-import java.nio.file.ClosedFileSystemException;
-import java.nio.file.CopyOption;
-import java.nio.file.FileStore;
-import java.nio.file.FileSystem;
-import java.nio.file.FileSystemNotFoundException;
-import java.nio.file.Files;
-import java.nio.file.LinkOption;
-import java.nio.file.OpenOption;
-import java.nio.file.Path;
-import java.nio.file.PathMatcher;
-import java.nio.file.ReadOnlyFileSystemException;
-import java.nio.file.StandardOpenOption;
-import java.nio.file.WatchService;
-import java.nio.file.attribute.FileAttribute;
-import java.nio.file.attribute.FileTime;
-import java.nio.file.attribute.UserPrincipalLookupService;
-import java.nio.file.spi.FileSystemProvider;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.Map;
-import java.util.Set;
-import java.util.regex.Pattern;
-
-/**
- * Base class for jrt file systems. jrt filesystem implementations are currently
- * available on top of .jimage file and on top "exploded" build directories.
- *
- * @implNote This class needs to maintain JDK 8 source compatibility.
- *
- * It is used internally in the JDK to implement jimage/jrtfs access,
- * but also compiled and delivered as part of the jrtfs.jar to support access
- * to the jimage file provided by the shipped JDK by tools running on JDK 8.
- */
-abstract class AbstractJrtFileSystem extends FileSystem {
-
-    private final JrtFileSystemProvider provider;
-
-    AbstractJrtFileSystem(JrtFileSystemProvider provider, Map<String, ?> options) {
-        this.provider = provider;
-    }
-
-    private static final Charset UTF_8 = Charset.forName("UTF-8");
-
-    // static utility methods
-    static ReadOnlyFileSystemException readOnly() {
-        return new ReadOnlyFileSystemException();
-    }
-
-    // if a Path does not exist, throw exception
-    static void checkExists(Path path) {
-        if (Files.notExists(path)) {
-            throw new FileSystemNotFoundException(path.toString());
-        }
-    }
-
-    static byte[] getBytes(String name) {
-        return name.getBytes(UTF_8);
-    }
-
-    static String getString(byte[] name) {
-        return new String(name, UTF_8);
-    }
-
-    // do the supplied options imply that we have to chase symlinks?
-    static boolean followLinks(LinkOption... options) {
-        if (options != null) {
-            for (LinkOption lo : options) {
-                if (lo == LinkOption.NOFOLLOW_LINKS) {
-                    return false;
-                } else if (lo == null) {
-                    throw new NullPointerException();
-                } else {
-                    throw new AssertionError("should not reach here");
-                }
-            }
-        }
-        return true;
-    }
-
-    // check that the options passed are supported by (read-only) jrt file system
-    static void checkOptions(Set<? extends OpenOption> options) {
-        // check for options of null type and option is an intance of StandardOpenOption
-        for (OpenOption option : options) {
-            if (option == null) {
-                throw new NullPointerException();
-            }
-            if (!(option instanceof StandardOpenOption)) {
-                throw new IllegalArgumentException();
-            }
-        }
-
-        if (options.contains(StandardOpenOption.WRITE)
-                || options.contains(StandardOpenOption.APPEND)) {
-            throw readOnly();
-        }
-    }
-
-    // FileSystem method implementations
-    @Override
-    public FileSystemProvider provider() {
-        return provider;
-    }
-
-    @Override
-    public Iterable<Path> getRootDirectories() {
-        ArrayList<Path> pathArr = new ArrayList<>();
-        pathArr.add(getRootPath());
-        return pathArr;
-    }
-
-    @Override
-    public AbstractJrtPath getPath(String first, String... more) {
-        String path;
-        if (more.length == 0) {
-            path = first;
-        } else {
-            StringBuilder sb = new StringBuilder();
-            sb.append(first);
-            for (String segment : more) {
-                if (segment.length() > 0) {
-                    if (sb.length() > 0) {
-                        sb.append('/');
-                    }
-                    sb.append(segment);
-                }
-            }
-            path = sb.toString();
-        }
-        return getRootPath().newJrtPath(getBytes(path));
-    }
-
-    @Override
-    public final boolean isReadOnly() {
-        return true;
-    }
-
-    @Override
-    public final UserPrincipalLookupService getUserPrincipalLookupService() {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public final WatchService newWatchService() {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public final Iterable<FileStore> getFileStores() {
-        ArrayList<FileStore> list = new ArrayList<>(1);
-        list.add(getFileStore(getRootPath()));
-        return list;
-    }
-
-    private static final Set<String> supportedFileAttributeViews
-            = Collections.unmodifiableSet(
-                    new HashSet<String>(Arrays.asList("basic", "jrt")));
-
-    @Override
-    public final Set<String> supportedFileAttributeViews() {
-        return supportedFileAttributeViews;
-    }
-
-    @Override
-    public final String toString() {
-        return "jrt:/";
-    }
-
-    @Override
-    public final String getSeparator() {
-        return "/";
-    }
-
-    private static final String GLOB_SYNTAX = "glob";
-    private static final String REGEX_SYNTAX = "regex";
-
-    @Override
-    public PathMatcher getPathMatcher(String syntaxAndInput) {
-        int pos = syntaxAndInput.indexOf(':');
-        if (pos <= 0 || pos == syntaxAndInput.length()) {
-            throw new IllegalArgumentException();
-        }
-        String syntax = syntaxAndInput.substring(0, pos);
-        String input = syntaxAndInput.substring(pos + 1);
-        String expr;
-        if (syntax.equalsIgnoreCase(GLOB_SYNTAX)) {
-            expr = JrtUtils.toRegexPattern(input);
-        } else {
-            if (syntax.equalsIgnoreCase(REGEX_SYNTAX)) {
-                expr = input;
-            } else {
-                throw new UnsupportedOperationException("Syntax '" + syntax
-                        + "' not recognized");
-            }
-        }
-        // return matcher
-        final Pattern pattern = Pattern.compile(expr);
-        return (Path path) -> pattern.matcher(path.toString()).matches();
-    }
-
-    // These methods throw read only file system exception
-    final void setTimes(AbstractJrtPath jrtPath, FileTime mtime, FileTime atime, FileTime ctime)
-            throws IOException {
-        throw readOnly();
-    }
-
-    final void createDirectory(AbstractJrtPath jrtPath, FileAttribute<?>... attrs) throws IOException {
-        throw readOnly();
-    }
-
-    final void deleteFile(AbstractJrtPath jrtPath, boolean failIfNotExists)
-            throws IOException {
-        throw readOnly();
-    }
-
-    final OutputStream newOutputStream(AbstractJrtPath jrtPath, OpenOption... options)
-            throws IOException {
-        throw readOnly();
-    }
-
-    final void copyFile(boolean deletesrc, AbstractJrtPath srcPath, AbstractJrtPath dstPath, CopyOption... options)
-            throws IOException {
-        throw readOnly();
-    }
-
-    final FileChannel newFileChannel(AbstractJrtPath jrtPath,
-            Set<? extends OpenOption> options,
-            FileAttribute<?>... attrs)
-            throws IOException {
-        throw new UnsupportedOperationException("newFileChannel");
-    }
-
-    final InputStream newInputStream(AbstractJrtPath jrtPath) throws IOException {
-        return new ByteArrayInputStream(getFileContent(jrtPath));
-    }
-
-    final SeekableByteChannel newByteChannel(AbstractJrtPath jrtPath,
-            Set<? extends OpenOption> options,
-            FileAttribute<?>... attrs)
-            throws IOException {
-        checkOptions(options);
-
-        byte[] buf = getFileContent(jrtPath);
-        final ReadableByteChannel rbc
-                = Channels.newChannel(new ByteArrayInputStream(buf));
-        final long size = buf.length;
-        return new SeekableByteChannel() {
-            long read = 0;
-
-            @Override
-            public boolean isOpen() {
-                return rbc.isOpen();
-            }
-
-            @Override
-            public long position() throws IOException {
-                return read;
-            }
-
-            @Override
-            public SeekableByteChannel position(long pos)
-                    throws IOException {
-                throw new UnsupportedOperationException();
-            }
-
-            @Override
-            public int read(ByteBuffer dst) throws IOException {
-                int n = rbc.read(dst);
-                if (n > 0) {
-                    read += n;
-                }
-                return n;
-            }
-
-            @Override
-            public SeekableByteChannel truncate(long size)
-                    throws IOException {
-                throw new NonWritableChannelException();
-            }
-
-            @Override
-            public int write(ByteBuffer src) throws IOException {
-                throw new NonWritableChannelException();
-            }
-
-            @Override
-            public long size() throws IOException {
-                return size;
-            }
-
-            @Override
-            public void close() throws IOException {
-                rbc.close();
-            }
-        };
-    }
-
-    final JrtFileStore getFileStore(AbstractJrtPath jrtPath) {
-        return new JrtFileStore(jrtPath);
-    }
-
-    final void ensureOpen() throws IOException {
-        if (!isOpen()) {
-            throw new ClosedFileSystemException();
-        }
-    }
-
-    // abstract methods to be implemented by a particular jrt file system
-    abstract AbstractJrtPath getRootPath();
-
-    abstract boolean isSameFile(AbstractJrtPath jrtPath1, AbstractJrtPath jrtPath2) throws IOException;
-
-    abstract boolean isLink(AbstractJrtPath jrtPath) throws IOException;
-
-    abstract AbstractJrtPath resolveLink(AbstractJrtPath jrtPath) throws IOException;
-
-    abstract AbstractJrtFileAttributes getFileAttributes(AbstractJrtPath jrtPath, LinkOption... options) throws IOException;
-
-    abstract boolean exists(AbstractJrtPath jrtPath) throws IOException;
-
-    abstract boolean isDirectory(AbstractJrtPath jrtPath, boolean resolveLinks) throws IOException;
-
-    /**
-     * returns the list of child paths of the given directory "path"
-     *
-     * @param path name of the directory whose content is listed
-     * @return iterator for child paths of the given directory path
-     */
-    abstract Iterator<Path> iteratorOf(AbstractJrtPath jrtPath) throws IOException;
-
-    // returns the content of the file resource specified by the path
-    abstract byte[] getFileContent(AbstractJrtPath jrtPath) throws IOException;
-}
--- a/src/java.base/share/classes/jdk/internal/jrtfs/AbstractJrtPath.java	Fri Apr 15 10:14:57 2016 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,935 +0,0 @@
-/*
- * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-package jdk.internal.jrtfs;
-
-import java.io.*;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.nio.channels.*;
-import java.nio.file.*;
-import java.nio.file.DirectoryStream.Filter;
-import java.nio.file.attribute.*;
-import java.util.*;
-import static java.nio.file.StandardOpenOption.*;
-import static java.nio.file.StandardCopyOption.*;
-
-/**
- * Base class for Path implementation of jrt file systems.
- *
- * @implNote This class needs to maintain JDK 8 source compatibility.
- *
- * It is used internally in the JDK to implement jimage/jrtfs access,
- * but also compiled and delivered as part of the jrtfs.jar to support access
- * to the jimage file provided by the shipped JDK by tools running on JDK 8.
- */
-abstract class AbstractJrtPath implements Path {
-
-    protected final AbstractJrtFileSystem jrtfs;
-    private final byte[] path;
-    private volatile int[] offsets;
-    private int hashcode = 0;  // cached hashcode (created lazily)
-
-    AbstractJrtPath(AbstractJrtFileSystem jrtfs, byte[] path) {
-        this(jrtfs, path, false);
-        this.resolved = null;
-    }
-
-    AbstractJrtPath(AbstractJrtFileSystem jrtfs, byte[] path, boolean normalized) {
-        this.resolved = null;
-        this.jrtfs = jrtfs;
-        if (normalized) {
-            this.path = path;
-        } else {
-            this.path = normalize(path);
-        }
-    }
-
-    // factory methods to create subtypes of AbstractJrtPath
-    protected abstract AbstractJrtPath newJrtPath(byte[] path);
-
-    protected abstract AbstractJrtPath newJrtPath(byte[] path, boolean normalized);
-
-    final byte[] getName() {
-        return path;
-    }
-
-    @Override
-    public final AbstractJrtPath getRoot() {
-        if (this.isAbsolute()) {
-            return jrtfs.getRootPath();
-        } else {
-            return null;
-        }
-    }
-
-    @Override
-    public final AbstractJrtPath getFileName() {
-        initOffsets();
-        int count = offsets.length;
-        if (count == 0) {
-            return null;  // no elements so no name
-        }
-        if (count == 1 && path[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 newJrtPath(result);
-    }
-
-    @Override
-    public final AbstractJrtPath getParent() {
-        initOffsets();
-        int count = offsets.length;
-        if (count == 0) // no elements so no parent
-        {
-            return null;
-        }
-        int len = offsets[count - 1] - 1;
-        if (len <= 0) // parent is root only (may be null)
-        {
-            return getRoot();
-        }
-        byte[] result = new byte[len];
-        System.arraycopy(path, 0, result, 0, len);
-        return newJrtPath(result);
-    }
-
-    @Override
-    public final int getNameCount() {
-        initOffsets();
-        return offsets.length;
-    }
-
-    @Override
-    public final AbstractJrtPath getName(int index) {
-        initOffsets();
-        if (index < 0 || index >= offsets.length) {
-            throw new IllegalArgumentException();
-        }
-        int begin = offsets[index];
-        int len;
-        if (index == (offsets.length - 1)) {
-            len = path.length - begin;
-        } else {
-            len = offsets[index + 1] - begin - 1;
-        }
-        // construct result
-        byte[] result = new byte[len];
-        System.arraycopy(path, begin, result, 0, len);
-        return newJrtPath(result);
-    }
-
-    @Override
-    public final AbstractJrtPath subpath(int beginIndex, int endIndex) {
-        initOffsets();
-        if (beginIndex < 0
-                || beginIndex >= offsets.length
-                || endIndex > offsets.length
-                || beginIndex >= endIndex) {
-            throw new IllegalArgumentException();
-        }
-
-        // starting offset and length
-        int begin = offsets[beginIndex];
-        int len;
-        if (endIndex == offsets.length) {
-            len = path.length - begin;
-        } else {
-            len = offsets[endIndex] - begin - 1;
-        }
-        // construct result
-        byte[] result = new byte[len];
-        System.arraycopy(path, begin, result, 0, len);
-        return newJrtPath(result);
-    }
-
-    @Override
-    public final AbstractJrtPath toRealPath(LinkOption... options) throws IOException {
-        AbstractJrtPath realPath = newJrtPath(getResolvedPath()).toAbsolutePath();
-        realPath = JrtFileSystem.followLinks(options) ? jrtfs.resolveLink(this) : realPath;
-        realPath.checkAccess();
-        return realPath;
-    }
-
-    final AbstractJrtPath readSymbolicLink() throws IOException {
-        if (!jrtfs.isLink(this)) {
-            throw new IOException("not a symbolic link");
-        }
-
-        return jrtfs.resolveLink(this);
-    }
-
-    final boolean isHidden() {
-        return false;
-    }
-
-    @Override
-    public final AbstractJrtPath toAbsolutePath() {
-        if (isAbsolute()) {
-            return this;
-        } else {
-            //add / bofore the existing path
-            byte[] tmp = new byte[path.length + 1];
-            tmp[0] = '/';
-            System.arraycopy(path, 0, tmp, 1, path.length);
-            return newJrtPath(tmp).normalize();
-        }
-    }
-
-    @Override
-    public final URI toUri() {
-        try {
-            return new URI("jrt",
-                    JrtFileSystem.getString(toAbsolutePath().path),
-                    null);
-        } catch (URISyntaxException ex) {
-            throw new AssertionError(ex);
-        }
-    }
-
-    private boolean equalsNameAt(AbstractJrtPath other, int index) {
-        int mbegin = offsets[index];
-        int mlen;
-        if (index == (offsets.length - 1)) {
-            mlen = path.length - mbegin;
-        } else {
-            mlen = offsets[index + 1] - mbegin - 1;
-        }
-        int obegin = other.offsets[index];
-        int olen;
-        if (index == (other.offsets.length - 1)) {
-            olen = other.path.length - obegin;
-        } else {
-            olen = other.offsets[index + 1] - obegin - 1;
-        }
-        if (mlen != olen) {
-            return false;
-        }
-        int n = 0;
-        while (n < mlen) {
-            if (path[mbegin + n] != other.path[obegin + n]) {
-                return false;
-            }
-            n++;
-        }
-        return true;
-    }
-
-    @Override
-    public final AbstractJrtPath relativize(Path other) {
-        final AbstractJrtPath o = checkPath(other);
-        if (o.equals(this)) {
-            return newJrtPath(new byte[0], true);
-        }
-        if (/* this.getFileSystem() != o.getFileSystem() || */this.isAbsolute() != o.isAbsolute()) {
-            throw new IllegalArgumentException();
-        }
-        int mc = this.getNameCount();
-        int oc = o.getNameCount();
-        int n = Math.min(mc, oc);
-        int i = 0;
-        while (i < n) {
-            if (!equalsNameAt(o, i)) {
-                break;
-            }
-            i++;
-        }
-        int dotdots = mc - i;
-        int len = dotdots * 3 - 1;
-        if (i < oc) {
-            len += (o.path.length - o.offsets[i] + 1);
-        }
-        byte[] result = new byte[len];
-
-        int pos = 0;
-        while (dotdots > 0) {
-            result[pos++] = (byte) '.';
-            result[pos++] = (byte) '.';
-            if (pos < len) // no tailing slash at the end
-            {
-                result[pos++] = (byte) '/';
-            }
-            dotdots--;
-        }
-        if (i < oc) {
-            System.arraycopy(o.path, o.offsets[i],
-                    result, pos,
-                    o.path.length - o.offsets[i]);
-        }
-        return newJrtPath(result);
-    }
-
-    @Override
-    public AbstractJrtFileSystem getFileSystem() {
-        return jrtfs;
-    }
-
-    @Override
-    public final boolean isAbsolute() {
-        return (this.path.length > 0 && path[0] == '/');
-    }
-
-    @Override
-    public final AbstractJrtPath resolve(Path other) {
-        final AbstractJrtPath o = checkPath(other);
-        if (o.isAbsolute()) {
-            return o;
-        }
-        byte[] res;
-        if (this.path[path.length - 1] == '/') {
-            res = new byte[path.length + o.path.length];
-            System.arraycopy(path, 0, res, 0, path.length);
-            System.arraycopy(o.path, 0, res, path.length, o.path.length);
-        } else {
-            res = new byte[path.length + 1 + o.path.length];
-            System.arraycopy(path, 0, res, 0, path.length);
-            res[path.length] = '/';
-            System.arraycopy(o.path, 0, res, path.length + 1, o.path.length);
-        }
-        return newJrtPath(res);
-    }
-
-    @Override
-    public final Path resolveSibling(Path other) {
-        if (other == null) {
-            throw new NullPointerException();
-        }
-        Path parent = getParent();
-        return (parent == null) ? other : parent.resolve(other);
-    }
-
-    @Override
-    public final boolean startsWith(Path other) {
-        final AbstractJrtPath o = checkPath(other);
-        if (o.isAbsolute() != this.isAbsolute()
-                || o.path.length > this.path.length) {
-            return false;
-        }
-        int olast = o.path.length;
-        for (int i = 0; i < olast; i++) {
-            if (o.path[i] != this.path[i]) {
-                return false;
-            }
-        }
-        olast--;
-        return o.path.length == this.path.length
-                || o.path[olast] == '/'
-                || this.path[olast + 1] == '/';
-    }
-
-    @Override
-    public final boolean endsWith(Path other) {
-        final AbstractJrtPath o = checkPath(other);
-        int olast = o.path.length - 1;
-        if (olast > 0 && o.path[olast] == '/') {
-            olast--;
-        }
-        int last = this.path.length - 1;
-        if (last > 0 && this.path[last] == '/') {
-            last--;
-        }
-        if (olast == -1) // o.path.length == 0
-        {
-            return last == -1;
-        }
-        if ((o.isAbsolute() && (!this.isAbsolute() || olast != last))
-                || (last < olast)) {
-            return false;
-        }
-        for (; olast >= 0; olast--, last--) {
-            if (o.path[olast] != this.path[last]) {
-                return false;
-            }
-        }
-        return o.path[olast + 1] == '/'
-                || last == -1 || this.path[last] == '/';
-    }
-
-    @Override
-    public final AbstractJrtPath resolve(String other) {
-        return resolve(getFileSystem().getPath(other));
-    }
-
-    @Override
-    public final Path resolveSibling(String other) {
-        return resolveSibling(getFileSystem().getPath(other));
-    }
-
-    @Override
-    public final boolean startsWith(String other) {
-        return startsWith(getFileSystem().getPath(other));
-    }
-
-    @Override
-    public final boolean endsWith(String other) {
-        return endsWith(getFileSystem().getPath(other));
-    }
-
-    @Override
-    public final AbstractJrtPath normalize() {
-        byte[] res = getResolved();
-        if (res == path) // no change
-        {
-            return this;
-        }
-        return newJrtPath(res, true);
-    }
-
-    private AbstractJrtPath checkPath(Path path) {
-        if (path == null) {
-            throw new NullPointerException();
-        }
-        if (!(path instanceof AbstractJrtPath)) {
-            throw new ProviderMismatchException();
-        }
-        return (AbstractJrtPath) path;
-    }
-
-    // create offset list if not already created
-    private void initOffsets() {
-        if (offsets == null) {
-            int count, index;
-            // count names
-            count = 0;
-            index = 0;
-            while (index < path.length) {
-                byte c = path[index++];
-                if (c != '/') {
-                    count++;
-                    while (index < path.length && path[index] != '/') {
-                        index++;
-                    }
-                }
-            }
-            // populate offsets
-            int[] result = new int[count];
-            count = 0;
-            index = 0;
-            while (index < path.length) {
-                byte c = path[index];
-                if (c == '/') {
-                    index++;
-                } else {
-                    result[count++] = index++;
-                    while (index < path.length && path[index] != '/') {
-                        index++;
-                    }
-                }
-            }
-            synchronized (this) {
-                if (offsets == null) {
-                    offsets = result;
-                }
-            }
-        }
-    }
-
-    private volatile byte[] resolved;
-
-    final byte[] getResolvedPath() {
-        byte[] r = resolved;
-        if (r == null) {
-            if (isAbsolute()) {
-                r = getResolved();
-            } else {
-                r = toAbsolutePath().getResolvedPath();
-            }
-            resolved = r;
-        }
-        return resolved;
-    }
-
-    // removes redundant slashs, replace "\" to separator "/"
-    // and check for invalid characters
-    private static byte[] normalize(byte[] path) {
-        if (path.length == 0) {
-            return path;
-        }
-        byte prevC = 0;
-        for (int i = 0; i < path.length; i++) {
-            byte c = path[i];
-            if (c == '\\') {
-                return normalize(path, i);
-            }
-            if (c == (byte) '/' && prevC == '/') {
-                return normalize(path, i - 1);
-            }
-            if (c == '\u0000') {
-                throw new InvalidPathException(JrtFileSystem.getString(path),
-                        "Path: nul character not allowed");
-            }
-            prevC = c;
-        }
-
-        if (path.length > 1 && path[path.length - 1] == '/') {
-            return Arrays.copyOf(path, path.length - 1);
-        }
-
-        return path;
-    }
-
-    private static byte[] normalize(byte[] path, int off) {
-        byte[] to = new byte[path.length];
-        int n = 0;
-        while (n < off) {
-            to[n] = path[n];
-            n++;
-        }
-        int m = n;
-        byte prevC = 0;
-        while (n < path.length) {
-            byte c = path[n++];
-            if (c == (byte) '\\') {
-                c = (byte) '/';
-            }
-            if (c == (byte) '/' && prevC == (byte) '/') {
-                continue;
-            }
-            if (c == '\u0000') {
-                throw new InvalidPathException(JrtFileSystem.getString(path),
-                        "Path: nul character not allowed");
-            }
-            to[m++] = c;
-            prevC = c;
-        }
-        if (m > 1 && to[m - 1] == '/') {
-            m--;
-        }
-        return (m == to.length) ? to : Arrays.copyOf(to, m);
-    }
-
-    // Remove DotSlash(./) and resolve DotDot (..) components
-    private byte[] getResolved() {
-        if (path.length == 0) {
-            return path;
-        }
-        for (int i = 0; i < path.length; i++) {
-            byte c = path[i];
-            if (c == (byte) '.') {
-                return resolve0();
-            }
-        }
-
-        return path;
-    }
-
-    // TBD: performance, avoid initOffsets
-    private byte[] resolve0() {
-        byte[] to = new byte[path.length];
-        int nc = getNameCount();
-        int[] lastM = new int[nc];
-        int lastMOff = -1;
-        int m = 0;
-        for (int i = 0; i < nc; i++) {
-            int n = offsets[i];
-            int len = (i == offsets.length - 1)
-                    ? (path.length - n) : (offsets[i + 1] - n - 1);
-            if (len == 1 && path[n] == (byte) '.') {
-                if (m == 0 && path[0] == '/') // absolute path
-                {
-                    to[m++] = '/';
-                }
-                continue;
-            }
-            if (len == 2 && path[n] == '.' && path[n + 1] == '.') {
-                if (lastMOff >= 0) {
-                    m = lastM[lastMOff--];  // retreat
-                    continue;
-                }
-                if (path[0] == '/') {  // "/../xyz" skip
-                    if (m == 0) {
-                        to[m++] = '/';
-                    }
-                } else {               // "../xyz" -> "../xyz"
-                    if (m != 0 && to[m - 1] != '/') {
-                        to[m++] = '/';
-                    }
-                    while (len-- > 0) {
-                        to[m++] = path[n++];
-                    }
-                }
-                continue;
-            }
-            if (m == 0 && path[0] == '/' || // absolute path
-                    m != 0 && to[m - 1] != '/') {   // not the first name
-                to[m++] = '/';
-            }
-            lastM[++lastMOff] = m;
-            while (len-- > 0) {
-                to[m++] = path[n++];
-            }
-        }
-        if (m > 1 && to[m - 1] == '/') {
-            m--;
-        }
-        return (m == to.length) ? to : Arrays.copyOf(to, m);
-    }
-
-    @Override
-    public final String toString() {
-        return JrtFileSystem.getString(path);
-    }
-
-    @Override
-    public final int hashCode() {
-        int h = hashcode;
-        if (h == 0) {
-            hashcode = h = Arrays.hashCode(path);
-        }
-        return h;
-    }
-
-    @Override
-    public final boolean equals(Object obj) {
-        return obj != null
-                && obj instanceof AbstractJrtPath
-                && this.jrtfs == ((AbstractJrtPath) obj).jrtfs
-                && compareTo((Path) obj) == 0;
-    }
-
-    @Override
-    public final int compareTo(Path other) {
-        final AbstractJrtPath o = checkPath(other);
-        int len1 = this.path.length;
-        int len2 = o.path.length;
-
-        int n = Math.min(len1, len2);
-        byte v1[] = this.path;
-        byte v2[] = o.path;
-
-        int k = 0;
-        while (k < n) {
-            int c1 = v1[k] & 0xff;
-            int c2 = v2[k] & 0xff;
-            if (c1 != c2) {
-                return c1 - c2;
-            }
-            k++;
-        }
-        return len1 - len2;
-    }
-
-    @Override
-    public final WatchKey register(
-            WatchService watcher,
-            WatchEvent.Kind<?>[] events,
-            WatchEvent.Modifier... modifiers) {
-        if (watcher == null || events == null || modifiers == null) {
-            throw new NullPointerException();
-        }
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public final WatchKey register(WatchService watcher, WatchEvent.Kind<?>... events) {
-        return register(watcher, events, new WatchEvent.Modifier[0]);
-    }
-
-    @Override
-    public final File toFile() {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public final Iterator<Path> iterator() {
-        return new Iterator<Path>() {
-            private int i = 0;
-
-            @Override
-            public boolean hasNext() {
-                return (i < getNameCount());
-            }
-
-            @Override
-            public Path next() {
-                if (i < getNameCount()) {
-                    Path result = getName(i);
-                    i++;
-                    return result;
-                } else {
-                    throw new NoSuchElementException();
-                }
-            }
-
-            @Override
-            public void remove() {
-                throw new ReadOnlyFileSystemException();
-            }
-        };
-    }
-
-    /////////////////////////////////////////////////////////////////////
-    // Helpers for JrtFileSystemProvider and JrtFileSystem
-    final int getPathLength() {
-        return path.length;
-    }
-
-    final void createDirectory(FileAttribute<?>... attrs)
-            throws IOException {
-        jrtfs.createDirectory(this, attrs);
-    }
-
-    final InputStream newInputStream(OpenOption... options) throws IOException {
-        if (options.length > 0) {
-            for (OpenOption opt : options) {
-                if (opt != READ) {
-                    throw new UnsupportedOperationException("'" + opt + "' not allowed");
-                }
-            }
-        }
-        return jrtfs.newInputStream(this);
-    }
-
-    final DirectoryStream<Path> newDirectoryStream(Filter<? super Path> filter)
-            throws IOException {
-        return new JrtDirectoryStream(this, filter);
-    }
-
-    final void delete() throws IOException {
-        jrtfs.deleteFile(this, true);
-    }
-
-    final void deleteIfExists() throws IOException {
-        jrtfs.deleteFile(this, false);
-    }
-
-    final AbstractJrtFileAttributes getAttributes(LinkOption... options) throws IOException {
-        AbstractJrtFileAttributes zfas = jrtfs.getFileAttributes(this, options);
-        if (zfas == null) {
-            throw new NoSuchFileException(toString());
-        }
-        return zfas;
-    }
-
-    final void setAttribute(String attribute, Object value, LinkOption... options)
-            throws IOException {
-        String type;
-        String attr;
-        int colonPos = attribute.indexOf(':');
-        if (colonPos == -1) {
-            type = "basic";
-            attr = attribute;
-        } else {
-            type = attribute.substring(0, colonPos++);
-            attr = attribute.substring(colonPos);
-        }
-        JrtFileAttributeView view = JrtFileAttributeView.get(this, type, options);
-        if (view == null) {
-            throw new UnsupportedOperationException("view <" + view + "> is not supported");
-        }
-        view.setAttribute(attr, value);
-    }
-
-    final void setTimes(FileTime mtime, FileTime atime, FileTime ctime)
-            throws IOException {
-        jrtfs.setTimes(this, mtime, atime, ctime);
-    }
-
-    final Map<String, Object> readAttributes(String attributes, LinkOption... options)
-            throws IOException {
-        String view;
-        String attrs;
-        int colonPos = attributes.indexOf(':');
-        if (colonPos == -1) {
-            view = "basic";
-            attrs = attributes;
-        } else {
-            view = attributes.substring(0, colonPos++);
-            attrs = attributes.substring(colonPos);
-        }
-        JrtFileAttributeView jrtfv = JrtFileAttributeView.get(this, view, options);
-        if (jrtfv == null) {
-            throw new UnsupportedOperationException("view not supported");
-        }
-        return jrtfv.readAttributes(attrs);
-    }
-
-    final FileStore getFileStore() throws IOException {
-        // each JrtFileSystem only has one root (as requested for now)
-        if (exists()) {
-            return jrtfs.getFileStore(this);
-        }
-        throw new NoSuchFileException(JrtFileSystem.getString(path));
-    }
-
-    final boolean isSameFile(Path other) throws IOException {
-        if (this.equals(other)) {
-            return true;
-        }
-        if (other == null
-                || this.getFileSystem() != other.getFileSystem()) {
-            return false;
-        }
-        this.checkAccess();
-        AbstractJrtPath target = (AbstractJrtPath) other;
-        target.checkAccess();
-        return Arrays.equals(this.getResolvedPath(), target.getResolvedPath())
-                || jrtfs.isSameFile(this, target);
-    }
-
-    final SeekableByteChannel newByteChannel(Set<? extends OpenOption> options,
-            FileAttribute<?>... attrs)
-            throws IOException {
-        return jrtfs.newByteChannel(this, options, attrs);
-    }
-
-    final FileChannel newFileChannel(Set<? extends OpenOption> options,
-            FileAttribute<?>... attrs)
-            throws IOException {
-        return jrtfs.newFileChannel(this, options, attrs);
-    }
-
-    final void checkAccess(AccessMode... modes) throws IOException {
-        boolean w = false;
-        boolean x = false;
-        for (AccessMode mode : modes) {
-            switch (mode) {
-                case READ:
-                    break;
-                case WRITE:
-                    w = true;
-                    break;
-                case EXECUTE:
-                    x = true;
-                    break;
-                default:
-                    throw new UnsupportedOperationException();
-            }
-        }
-
-        BasicFileAttributes attrs = jrtfs.getFileAttributes(this);
-        if (attrs == null && (path.length != 1 || path[0] != '/')) {
-            throw new NoSuchFileException(toString());
-        }
-        if (w) {
-//            if (jrtfs.isReadOnly())
-            throw new AccessDeniedException(toString());
-        }
-        if (x) {
-            throw new AccessDeniedException(toString());
-        }
-    }
-
-    final boolean exists() {
-        try {
-            return jrtfs.exists(this);
-        } catch (IOException x) {
-        }
-        return false;
-    }
-
-    final OutputStream newOutputStream(OpenOption... options) throws IOException {
-        if (options.length == 0) {
-            return jrtfs.newOutputStream(this,
-                    CREATE_NEW, WRITE);
-        }
-        return jrtfs.newOutputStream(this, options);
-    }
-
-    final void move(AbstractJrtPath target, CopyOption... options)
-            throws IOException {
-        if (this.jrtfs == target.jrtfs) {
-            jrtfs.copyFile(true,
-                    this, target,
-                    options);
-        } else {
-            copyToTarget(target, options);
-            delete();
-        }
-    }
-
-    final void copy(AbstractJrtPath target, CopyOption... options)
-            throws IOException {
-        if (this.jrtfs == target.jrtfs) {
-            jrtfs.copyFile(false,
-                    this, target,
-                    options);
-        } else {
-            copyToTarget(target, options);
-        }
-    }
-
-    private void copyToTarget(AbstractJrtPath target, CopyOption... options)
-            throws IOException {
-        boolean replaceExisting = false;
-        boolean copyAttrs = false;
-        for (CopyOption opt : options) {
-            if (opt == REPLACE_EXISTING) {
-                replaceExisting = true;
-            } else if (opt == COPY_ATTRIBUTES) {
-                copyAttrs = true;
-            }
-        }
-        // attributes of source file
-        BasicFileAttributes jrtfas = getAttributes();
-        // check if target exists
-        boolean exists;
-        if (replaceExisting) {
-            try {
-                target.deleteIfExists();
-                exists = false;
-            } catch (DirectoryNotEmptyException x) {
-                exists = true;
-            }
-        } else {
-            exists = target.exists();
-        }
-        if (exists) {
-            throw new FileAlreadyExistsException(target.toString());
-        }
-
-        if (jrtfas.isDirectory()) {
-            // create directory or file
-            target.createDirectory();
-        } else {
-            try (InputStream is = jrtfs.newInputStream(this); OutputStream os = target.newOutputStream()) {
-                byte[] buf = new byte[8192];
-                int n;
-                while ((n = is.read(buf)) != -1) {
-                    os.write(buf, 0, n);
-                }
-            }
-        }
-        if (copyAttrs) {
-            BasicFileAttributeView view
-                    = JrtFileAttributeView.get(target, BasicFileAttributeView.class);
-            try {
-                view.setTimes(jrtfas.lastModifiedTime(),
-                        jrtfas.lastAccessTime(),
-                        jrtfas.creationTime());
-            } catch (IOException x) {
-                // rollback?
-                try {
-                    target.delete();
-                } catch (IOException ignore) {
-                }
-                throw x;
-            }
-        }
-    }
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/share/classes/jdk/internal/jrtfs/ExplodedImage.java	Fri Apr 15 13:05:52 2016 -0700
@@ -0,0 +1,295 @@
+/*
+ * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.internal.jrtfs;
+
+import java.io.IOException;
+import java.nio.file.DirectoryStream;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystemException;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import jdk.internal.jimage.ImageReader.Node;
+
+/**
+ * A jrt file system built on $JAVA_HOME/modules directory ('exploded modules
+ * build')
+ *
+ * @implNote This class needs to maintain JDK 8 source compatibility.
+ *
+ * It is used internally in the JDK to implement jimage/jrtfs access,
+ * but also compiled and delivered as part of the jrtfs.jar to support access
+ * to the jimage file provided by the shipped JDK by tools running on JDK 8.
+ */
+class ExplodedImage extends SystemImage {
+
+    private static final String MODULES = "/modules/";
+    private static final String PACKAGES = "/packages/";
+    private static final int PACKAGES_LEN = PACKAGES.length();
+
+    private final FileSystem defaultFS;
+    private final String separator;
+    private final Map<String, PathNode> nodes = Collections.synchronizedMap(new HashMap<>());
+    private final BasicFileAttributes modulesDirAttrs;
+
+    ExplodedImage(Path modulesDir) throws IOException {
+        defaultFS = FileSystems.getDefault();
+        String str = defaultFS.getSeparator();
+        separator = str.equals("/") ? null : str;
+        modulesDirAttrs = Files.readAttributes(modulesDir, BasicFileAttributes.class);
+        initNodes();
+    }
+
+    // A Node that is backed by actual default file system Path
+    private final class PathNode extends Node {
+
+        // Path in underlying default file system
+        private Path path;
+        private PathNode link;
+        private List<Node> children;
+
+        PathNode(String name, Path path, BasicFileAttributes attrs) {  // path
+            super(name, attrs);
+            this.path = path;
+        }
+
+        PathNode(String name, Node link) {              // link
+            super(name, link.getFileAttributes());
+            this.link = (PathNode)link;
+        }
+
+        PathNode(String name, List<Node> children) {    // dir
+            super(name, modulesDirAttrs);
+            this.children = children;
+        }
+
+        @Override
+        public boolean isDirectory() {
+            return children != null ||
+                   (link == null && getFileAttributes().isDirectory());
+        }
+
+        @Override
+        public boolean isLink() {
+            return link != null;
+        }
+
+        @Override
+        public PathNode resolveLink(boolean recursive) {
+            if (link == null)
+                return this;
+            return recursive && link.isLink() ? link.resolveLink(true) : link;
+        }
+
+        byte[] getContent() throws IOException {
+            if (!getFileAttributes().isRegularFile())
+                throw new FileSystemException(getName() + " is not file");
+            return Files.readAllBytes(path);
+        }
+
+        @Override
+        public List<Node> getChildren() {
+            if (!isDirectory())
+                throw new IllegalArgumentException("not a directory: " + getNameString());
+            if (children == null) {
+                List<Node> list = new ArrayList<>();
+                try (DirectoryStream<Path> stream = Files.newDirectoryStream(path)) {
+                    for (Path p : stream) {
+                        p = explodedModulesDir.relativize(p);
+                        String pName = MODULES + nativeSlashToFrontSlash(p.toString());
+                        Node node = findNode(pName);
+                        if (node != null) {  // findNode may choose to hide certain files!
+                            list.add(node);
+                        }
+                    }
+                } catch (IOException x) {
+                    return null;
+                }
+                children = list;
+            }
+            return children;
+        }
+    }
+
+    @Override
+    public void close() throws IOException {
+        nodes.clear();
+    }
+
+    @Override
+    public byte[] getResource(Node node) throws IOException {
+        return ((PathNode)node).getContent();
+    }
+
+    // find Node for the given Path
+    @Override
+    public synchronized Node findNode(String str) {
+        Node node = findModulesNode(str);
+        if (node != null) {
+            return node;
+        }
+        // lazily created for paths like /packages/<package>/<module>/xyz
+        // For example /packages/java.lang/java.base/java/lang/
+        if (str.startsWith(PACKAGES)) {
+            // pkgEndIdx marks end of <package> part
+            int pkgEndIdx = str.indexOf('/', PACKAGES_LEN);
+            if (pkgEndIdx != -1) {
+                // modEndIdx marks end of <module> part
+                int modEndIdx = str.indexOf('/', pkgEndIdx + 1);
+                if (modEndIdx != -1) {
+                    // make sure we have such module link!
+                    // ie., /packages/<package>/<module> is valid
+                    Node linkNode = nodes.get(str.substring(0, modEndIdx));
+                    if (linkNode == null || !linkNode.isLink()) {
+                        return null;
+                    }
+                    // map to "/modules/zyz" path and return that node
+                    // For example, "/modules/java.base/java/lang" for
+                    // "/packages/java.lang/java.base/java/lang".
+                    String mod = MODULES + str.substring(pkgEndIdx + 1);
+                    return findModulesNode(mod);
+                }
+            }
+        }
+        return null;
+    }
+
+    // find a Node for a path that starts like "/modules/..."
+    Node findModulesNode(String str) {
+        PathNode node = nodes.get(str);
+        if (node != null) {
+            return node;
+        }
+        // lazily created "/modules/xyz/abc/" Node
+        // This is mapped to default file system path "<JDK_MODULES_DIR>/xyz/abc"
+        Path p = underlyingPath(str);
+        if (p != null) {
+            try {
+                BasicFileAttributes attrs = Files.readAttributes(p, BasicFileAttributes.class);
+                if (attrs.isRegularFile()) {
+                    Path f = p.getFileName();
+                    if (f.toString().startsWith("_the."))
+                        return null;
+                }
+                node = new PathNode(str, p, attrs);
+                nodes.put(str, node);
+                return node;
+            } catch (IOException x) {
+                // does not exists or unable to determine
+            }
+        }
+        return null;
+    }
+
+    Path underlyingPath(String str) {
+        if (str.startsWith(MODULES)) {
+            str = frontSlashToNativeSlash(str.substring("/modules".length()));
+            return defaultFS.getPath(explodedModulesDir.toString(), str);
+        }
+        return null;
+    }
+
+    // convert "/" to platform path separator
+    private String frontSlashToNativeSlash(String str) {
+        return separator == null ? str : str.replace("/", separator);
+    }
+
+    // convert platform path separator to "/"
+    private String nativeSlashToFrontSlash(String str) {
+        return separator == null ? str : str.replace(separator, "/");
+    }
+
+    // convert "/"s to "."s
+    private String slashesToDots(String str) {
+        return str.replace(separator != null ? separator : "/", ".");
+    }
+
+    // initialize file system Nodes
+    private void initNodes() throws IOException {
+        // same package prefix may exist in mutliple modules. This Map
+        // is filled by walking "jdk modules" directory recursively!
+        Map<String, List<String>> packageToModules = new HashMap<>();
+        try (DirectoryStream<Path> stream = Files.newDirectoryStream(explodedModulesDir)) {
+            for (Path module : stream) {
+                if (Files.isDirectory(module)) {
+                    String moduleName = module.getFileName().toString();
+                    // make sure "/modules/<moduleName>" is created
+                    findModulesNode(MODULES + moduleName);
+                    Files.walk(module).filter(Files::isDirectory).forEach((p) -> {
+                        p = module.relativize(p);
+                        String pkgName = slashesToDots(p.toString());
+                        // skip META-INFO and empty strings
+                        if (!pkgName.isEmpty() && !pkgName.startsWith("META-INF")) {
+                            List<String> moduleNames = packageToModules.get(pkgName);
+                            if (moduleNames == null) {
+                                moduleNames = new ArrayList<>();
+                                packageToModules.put(pkgName, moduleNames);
+                            }
+                            moduleNames.add(moduleName);
+                        }
+                    });
+                }
+            }
+        }
+        // create "/modules" directory
+        // "nodes" map contains only /modules/<foo> nodes only so far and so add all as children of /modules
+        PathNode modulesDir = new PathNode("/modules", new ArrayList<>(nodes.values()));
+        nodes.put(modulesDir.getName(), modulesDir);
+
+        // create children under "/packages"
+        List<Node> packagesChildren = new ArrayList<>(packageToModules.size());
+        for (Map.Entry<String, List<String>> entry : packageToModules.entrySet()) {
+            String pkgName = entry.getKey();
+            List<String> moduleNameList = entry.getValue();
+            List<Node> moduleLinkNodes = new ArrayList<>(moduleNameList.size());
+            for (String moduleName : moduleNameList) {
+                Node moduleNode = findModulesNode(MODULES + moduleName);
+                PathNode linkNode = new PathNode(PACKAGES + pkgName + "/" + moduleName, moduleNode);
+                nodes.put(linkNode.getName(), linkNode);
+                moduleLinkNodes.add(linkNode);
+            }
+            PathNode pkgDir = new PathNode(PACKAGES + pkgName, moduleLinkNodes);
+            nodes.put(pkgDir.getName(), pkgDir);
+            packagesChildren.add(pkgDir);
+        }
+        // "/packages" dir
+        PathNode packagesDir = new PathNode("/packages", packagesChildren);
+        nodes.put(packagesDir.getName(), packagesDir);
+
+        // finally "/" dir!
+        List<Node> rootChildren = new ArrayList<>();
+        rootChildren.add(packagesDir);
+        rootChildren.add(modulesDir);
+        PathNode root = new PathNode("/", rootChildren);
+        nodes.put(root.getName(), root);
+    }
+}
--- a/src/java.base/share/classes/jdk/internal/jrtfs/JrtDirectoryStream.java	Fri Apr 15 10:14:57 2016 -0700
+++ b/src/java.base/share/classes/jdk/internal/jrtfs/JrtDirectoryStream.java	Fri Apr 15 13:05:52 2016 -0700
@@ -30,6 +30,7 @@
 import java.nio.file.NotDirectoryException;
 import java.nio.file.Path;
 import java.util.Iterator;
+import java.util.Objects;
 import java.util.NoSuchElementException;
 import java.io.IOException;
 
@@ -44,115 +45,47 @@
  */
 final class JrtDirectoryStream implements DirectoryStream<Path> {
 
-    private final AbstractJrtFileSystem jrtfs;
-    private final AbstractJrtPath dir;
+    private final JrtPath dir;
     private final DirectoryStream.Filter<? super Path> filter;
     private volatile boolean isClosed;
     private volatile Iterator<Path> itr;
 
-    JrtDirectoryStream(AbstractJrtPath jrtPath,
+    JrtDirectoryStream(JrtPath dir,
             DirectoryStream.Filter<? super java.nio.file.Path> filter)
-            throws IOException {
-        this.jrtfs = jrtPath.getFileSystem();
-        this.dir = jrtPath;
-        // sanity check
-        if (!jrtfs.isDirectory(dir, true)) {
-            throw new NotDirectoryException(jrtPath.toString());
+            throws IOException
+    {
+        this.dir = dir;
+        if (!dir.jrtfs.isDirectory(dir, true)) {  // sanity check
+            throw new NotDirectoryException(dir.toString());
         }
-
         this.filter = filter;
     }
 
     @Override
     public synchronized Iterator<Path> iterator() {
-        if (isClosed) {
+        if (isClosed)
             throw new ClosedDirectoryStreamException();
-        }
-        if (itr != null) {
+        if (itr != null)
             throw new IllegalStateException("Iterator has already been returned");
-        }
-
         try {
-            itr = jrtfs.iteratorOf(dir);
+            itr = dir.jrtfs.iteratorOf(dir, filter);
         } catch (IOException e) {
             throw new IllegalStateException(e);
         }
         return new Iterator<Path>() {
-            /*
-             * next Path value to return from this iterator.
-             * null value means hasNext() not called yet
-             * or last hasNext() returned false or resulted
-             * in exception. If last hasNext() returned true,
-             * then this field has non-null value.
-             */
             private Path next;
-
-            // get-and-clear and set-next by these methods
-            private Path getAndClearNext() {
-                assert next != null;
-                Path result = this.next;
-                this.next = null;
-                return result;
-            }
-
-            private void setNext(Path path) {
-                assert path != null;
-                this.next = path;
-            }
-
-            // if hasNext() returns true, 'next' field has non-null Path
             @Override
             public synchronized boolean hasNext() {
-                if (next != null) {
-                    return true;
-                }
-
-                if (isClosed) {
+                if (isClosed)
                     return false;
-                }
-
-                if (filter == null) {
-                    if (itr.hasNext()) {
-                        setNext(itr.next());
-                        return true;
-                    } else {
-                        return false;
-                    }
-                } else {
-                    while (itr.hasNext()) {
-                        Path tmpPath = itr.next();
-                        try {
-                            if (filter.accept(tmpPath)) {
-                                setNext(tmpPath);
-                                return true;
-                            }
-                        } catch (IOException ioe) {
-                            throw new DirectoryIteratorException(ioe);
-                        }
-                    }
-
-                    return false;
-                }
+                return itr.hasNext();
             }
 
             @Override
             public synchronized Path next() {
-                if (next != null) {
-                    return getAndClearNext();
-                }
-
-                if (isClosed) {
+                if (isClosed)
                     throw new NoSuchElementException();
-                }
-
-                if (next == null && itr.hasNext()) {
-                    // missing hasNext() between next() calls.
-                    if (hasNext()) {
-                        return getAndClearNext();
-                    }
-                }
-
-                throw new NoSuchElementException();
+                return itr.next();
             }
 
             @Override
--- a/src/java.base/share/classes/jdk/internal/jrtfs/JrtExplodedFileAttributes.java	Fri Apr 15 10:14:57 2016 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,106 +0,0 @@
-/*
- * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-package jdk.internal.jrtfs;
-
-import java.io.IOException;
-import java.nio.file.attribute.BasicFileAttributes;
-import java.nio.file.attribute.FileTime;
-import jdk.internal.jrtfs.JrtExplodedFileSystem.Node;
-
-/**
- * jrt file system attributes implementation on top of 'exploded file system'
- * Node.
- *
- * @implNote This class needs to maintain JDK 8 source compatibility.
- *
- * It is used internally in the JDK to implement jimage/jrtfs access,
- * but also compiled and delivered as part of the jrtfs.jar to support access
- * to the jimage file provided by the shipped JDK by tools running on JDK 8.
- */
-final class JrtExplodedFileAttributes extends AbstractJrtFileAttributes {
-
-    private final Node node;
-    private final BasicFileAttributes attrs;
-
-    JrtExplodedFileAttributes(Node node) throws IOException {
-        this.node = node;
-        this.attrs = node.getBasicAttrs();
-    }
-
-    @Override
-    public FileTime creationTime() {
-        return attrs.creationTime();
-    }
-
-    @Override
-    public boolean isDirectory() {
-        return node.isDirectory();
-    }
-
-    @Override
-    public boolean isOther() {
-        return false;
-    }
-
-    @Override
-    public boolean isRegularFile() {
-        return node.isFile();
-    }
-
-    @Override
-    public FileTime lastAccessTime() {
-        return attrs.lastAccessTime();
-    }
-
-    @Override
-    public FileTime lastModifiedTime() {
-        return attrs.lastModifiedTime();
-    }
-
-    @Override
-    public long size() {
-        return isRegularFile() ? attrs.size() : 0L;
-    }
-
-    @Override
-    public boolean isSymbolicLink() {
-        return node.isLink();
-    }
-
-    @Override
-    public Object fileKey() {
-        return node.resolveLink(true);
-    }
-
-    @Override
-    public long compressedSize() {
-        return 0L;
-    }
-
-    @Override
-    public String extension() {
-        return node.getExtension();
-    }
-}
--- a/src/java.base/share/classes/jdk/internal/jrtfs/JrtExplodedFileSystem.java	Fri Apr 15 10:14:57 2016 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,528 +0,0 @@
-/*
- * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-package jdk.internal.jrtfs;
-
-import java.io.IOException;
-import java.nio.file.DirectoryStream;
-import java.nio.file.FileSystem;
-import java.nio.file.FileSystemException;
-import java.nio.file.FileSystems;
-import java.nio.file.Files;
-import java.nio.file.LinkOption;
-import java.nio.file.NoSuchFileException;
-import java.nio.file.NotDirectoryException;
-import java.nio.file.Path;
-import java.nio.file.attribute.BasicFileAttributes;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.function.Function;
-import static java.util.stream.Collectors.toList;
-import static jdk.internal.jrtfs.AbstractJrtFileSystem.getString;
-
-/**
- * A jrt file system built on $JAVA_HOME/modules directory ('exploded modules
- * build')
- *
- * @implNote This class needs to maintain JDK 8 source compatibility.
- *
- * It is used internally in the JDK to implement jimage/jrtfs access,
- * but also compiled and delivered as part of the jrtfs.jar to support access
- * to the jimage file provided by the shipped JDK by tools running on JDK 8.
- */
-class JrtExplodedFileSystem extends AbstractJrtFileSystem {
-
-    private static final String MODULES = "/modules/";
-    private static final String PACKAGES = "/packages/";
-    private static final int PACKAGES_LEN = PACKAGES.length();
-
-    // root path
-    private final JrtExplodedPath rootPath;
-    private volatile boolean isOpen;
-    private final FileSystem defaultFS;
-    private final String separator;
-    private final Map<String, Node> nodes = Collections.synchronizedMap(new HashMap<>());
-    private final BasicFileAttributes modulesDirAttrs;
-
-    JrtExplodedFileSystem(JrtFileSystemProvider provider,
-            Map<String, ?> env)
-            throws IOException {
-
-        super(provider, env);
-        checkExists(SystemImages.explodedModulesDir());
-        byte[] root = new byte[]{'/'};
-        rootPath = new JrtExplodedPath(this, root);
-        isOpen = true;
-        defaultFS = FileSystems.getDefault();
-        String str = defaultFS.getSeparator();
-        separator = str.equals(getSeparator()) ? null : str;
-        modulesDirAttrs = Files.readAttributes(SystemImages.explodedModulesDir(), BasicFileAttributes.class);
-        initNodes();
-    }
-
-    @Override
-    public void close() throws IOException {
-        cleanup();
-    }
-
-    @Override
-    public boolean isOpen() {
-        return isOpen;
-    }
-
-    @Override
-    protected void finalize() throws Throwable {
-        cleanup();
-        super.finalize();
-    }
-
-    private synchronized void cleanup() {
-        isOpen = false;
-        nodes.clear();
-    }
-
-    @Override
-    JrtExplodedPath getRootPath() {
-        return rootPath;
-    }
-
-    // Base class for Nodes of this file system
-    abstract class Node {
-
-        private final String name;
-
-        Node(String name) {
-            this.name = name;
-        }
-
-        final String getName() {
-            return name;
-        }
-
-        final String getExtension() {
-            if (isFile()) {
-                final int index = name.lastIndexOf(".");
-                if (index != -1) {
-                    return name.substring(index + 1);
-                }
-            }
-
-            return null;
-        }
-
-        BasicFileAttributes getBasicAttrs() throws IOException {
-            return modulesDirAttrs;
-        }
-
-        boolean isLink() {
-            return false;
-        }
-
-        boolean isDirectory() {
-            return false;
-        }
-
-        boolean isFile() {
-            return false;
-        }
-
-        byte[] getContent() throws IOException {
-            if (!isFile()) {
-                throw new FileSystemException(name + " is not file");
-            }
-
-            throw new AssertionError("ShouldNotReachHere");
-        }
-
-        List<Node> getChildren() throws IOException {
-            if (!isDirectory()) {
-                throw new NotDirectoryException(name);
-            }
-
-            throw new AssertionError("ShouldNotReachHere");
-        }
-
-        final Node resolveLink() {
-            return resolveLink(false);
-        }
-
-        Node resolveLink(boolean recursive) {
-            return this;
-        }
-    }
-
-    // A Node that is backed by actual default file system Path
-    private final class PathNode extends Node {
-
-        // Path in underlying default file system
-        private final Path path;
-        private final boolean file;
-        // lazily initialized, don't read attributes unless required!
-        private BasicFileAttributes attrs;
-
-        PathNode(String name, Path path) {
-            super(name);
-            this.path = path;
-            this.file = Files.isRegularFile(path);
-        }
-
-        @Override
-        synchronized BasicFileAttributes getBasicAttrs() throws IOException {
-            if (attrs == null) {
-                attrs = Files.readAttributes(path, BasicFileAttributes.class);
-            }
-            return attrs;
-        }
-
-        @Override
-        boolean isDirectory() {
-            return !file;
-        }
-
-        @Override
-        boolean isFile() {
-            return file;
-        }
-
-        @Override
-        byte[] getContent() throws IOException {
-            if (!isFile()) {
-                throw new FileSystemException(getName() + " is not file");
-            }
-
-            return Files.readAllBytes(path);
-        }
-
-        @Override
-        List<Node> getChildren() throws IOException {
-            if (!isDirectory()) {
-                throw new NotDirectoryException(getName());
-            }
-
-            List<Node> children = new ArrayList<>();
-            try (DirectoryStream<Path> stream = Files.newDirectoryStream(path)) {
-                for (Path cp : stream) {
-                    cp = SystemImages.explodedModulesDir().relativize(cp);
-                    String cpName = MODULES + nativeSlashToFrontSlash(cp.toString());
-                    try {
-                        children.add(findNode(cpName));
-                    } catch (NoSuchFileException nsfe) {
-                        // findNode may choose to hide certain files!
-                    }
-                }
-            }
-
-            return children;
-        }
-    }
-
-    // A Node that links to another Node
-    private final class LinkNode extends Node {
-
-        // underlying linked Node
-        private final Node link;
-
-        LinkNode(String name, Node link) {
-            super(name);
-            this.link = link;
-        }
-
-        @Override
-        BasicFileAttributes getBasicAttrs() throws IOException {
-            return link.getBasicAttrs();
-        }
-
-        @Override
-        public boolean isLink() {
-            return true;
-        }
-
-        @Override
-        Node resolveLink(boolean recursive) {
-            return recursive && (link instanceof LinkNode) ? ((LinkNode) link).resolveLink(true) : link;
-        }
-    }
-
-    // A directory Node with it's children Nodes
-    private final class DirNode extends Node {
-
-        // children Nodes of this Node.
-        private final List<Node> children;
-
-        DirNode(String name, List<Node> children) {
-            super(name);
-            this.children = children;
-        }
-
-        @Override
-        boolean isDirectory() {
-            return true;
-        }
-
-        @Override
-        List<Node> getChildren() throws IOException {
-            return children;
-        }
-    }
-
-    private JrtExplodedPath toJrtExplodedPath(String path) {
-        return toJrtExplodedPath(getBytes(path));
-    }
-
-    private JrtExplodedPath toJrtExplodedPath(byte[] path) {
-        return new JrtExplodedPath(this, path);
-    }
-
-    @Override
-    boolean isSameFile(AbstractJrtPath jrtPath1, AbstractJrtPath jrtPath2) throws IOException {
-        Node n1 = checkNode(jrtPath1);
-        Node n2 = checkNode(jrtPath2);
-        return n1 == n2;
-    }
-
-    @Override
-    boolean isLink(AbstractJrtPath jrtPath) throws IOException {
-        return checkNode(jrtPath).isLink();
-    }
-
-    @Override
-    AbstractJrtPath resolveLink(AbstractJrtPath jrtPath) throws IOException {
-        String name = checkNode(jrtPath).resolveLink().getName();
-        return toJrtExplodedPath(name);
-    }
-
-    @Override
-    AbstractJrtFileAttributes getFileAttributes(AbstractJrtPath jrtPath, LinkOption... options) throws IOException {
-        Node node = checkNode(jrtPath);
-        if (node.isLink() && followLinks(options)) {
-            node = node.resolveLink(true);
-        }
-        return new JrtExplodedFileAttributes(node);
-    }
-
-    @Override
-    boolean exists(AbstractJrtPath jrtPath) throws IOException {
-        try {
-            checkNode(jrtPath);
-            return true;
-        } catch (NoSuchFileException nsfe) {
-            return false;
-        }
-    }
-
-    @Override
-    boolean isDirectory(AbstractJrtPath jrtPath, boolean resolveLinks) throws IOException {
-        Node node = checkNode(jrtPath);
-        return resolveLinks && node.isLink()
-                ? node.resolveLink(true).isDirectory()
-                : node.isDirectory();
-    }
-
-    @Override
-    Iterator<Path> iteratorOf(AbstractJrtPath dir) throws IOException {
-        Node node = checkNode(dir).resolveLink(true);
-        if (!node.isDirectory()) {
-            throw new NotDirectoryException(getString(dir.getName()));
-        }
-
-        Function<Node, Path> nodeToPath =
-            child -> dir.resolve(
-                toJrtExplodedPath(child.getName()).
-                getFileName());
-
-        return node.getChildren().stream().
-                   map(nodeToPath).collect(toList()).
-                   iterator();
-    }
-
-    @Override
-    byte[] getFileContent(AbstractJrtPath jrtPath) throws IOException {
-        return checkNode(jrtPath).getContent();
-    }
-
-    private Node checkNode(AbstractJrtPath jrtPath) throws IOException {
-        return checkNode(jrtPath.getResolvedPath());
-    }
-
-    private Node checkNode(byte[] path) throws IOException {
-        ensureOpen();
-        return findNode(path);
-    }
-
-    synchronized Node findNode(byte[] path) throws IOException {
-        return findNode(getString(path));
-    }
-
-    // find Node for the given Path
-    synchronized Node findNode(String str) throws IOException {
-        Node node = findModulesNode(str);
-        if (node != null) {
-            return node;
-        }
-
-        // lazily created for paths like /packages/<package>/<module>/xyz
-        // For example /packages/java.lang/java.base/java/lang/
-        if (str.startsWith(PACKAGES)) {
-            // pkgEndIdx marks end of <package> part
-            int pkgEndIdx = str.indexOf('/', PACKAGES_LEN);
-            if (pkgEndIdx != -1) {
-                // modEndIdx marks end of <module> part
-                int modEndIdx = str.indexOf('/', pkgEndIdx + 1);
-                if (modEndIdx != -1) {
-                    // make sure we have such module link!
-                    // ie., /packages/<package>/<module> is valid
-                    Node linkNode = nodes.get(str.substring(0, modEndIdx));
-                    if (linkNode == null || !linkNode.isLink()) {
-                        throw new NoSuchFileException(str);
-                    }
-
-                    // map to "/modules/zyz" path and return that node
-                    // For example, "/modules/java.base/java/lang" for
-                    // "/packages/java.lang/java.base/java/lang".
-                    String mod = MODULES + str.substring(pkgEndIdx + 1);
-                    return findNode(mod);
-                }
-            }
-        }
-
-        throw new NoSuchFileException(str);
-    }
-
-    // find a Node for a path that starts like "/modules/..."
-    synchronized Node findModulesNode(String str) throws IOException {
-        Node node = nodes.get(str);
-        if (node != null) {
-            return node;
-        }
-
-        // lazily created "/modules/xyz/abc/" Node
-        // This is mapped to default file system path "<JDK_MODULES_DIR>/xyz/abc"
-        Path p = underlyingPath(str);
-        if (p != null) {
-            if (Files.isRegularFile(p)) {
-                Path file = p.getFileName();
-                if (file.toString().startsWith("_the.")) {
-                    return null;
-                }
-            }
-            node = new PathNode(str, p);
-            nodes.put(str, node);
-            return node;
-        }
-
-        return null;
-    }
-
-    Path underlyingPath(String str) {
-        if (str.startsWith(MODULES)) {
-            str = frontSlashToNativeSlash(str.substring("/modules".length()));
-            return defaultFS.getPath(SystemImages.explodedModulesDir().toString(), str);
-        }
-        return null;
-    }
-
-    // convert "/" to platform path separator
-    private String frontSlashToNativeSlash(String str) {
-        return separator == null ? str : str.replace("/", separator);
-    }
-
-    // convert platform path separator to "/"
-    private String nativeSlashToFrontSlash(String str) {
-        return separator == null ? str : str.replace(separator, "/");
-    }
-
-    // convert "/"s to "."s
-    private String slashesToDots(String str) {
-        return str.replace(separator != null ? separator : "/", ".");
-    }
-
-    // initialize file system Nodes
-    private void initNodes() throws IOException {
-        // same package prefix may exist in mutliple modules. This Map
-        // is filled by walking "jdk modules" directory recursively!
-        Map<String, List<String>> packageToModules = new HashMap<>();
-
-        try (DirectoryStream<Path> stream = Files.newDirectoryStream(SystemImages.explodedModulesDir())) {
-            for (Path module : stream) {
-                if (Files.isDirectory(module)) {
-                    String moduleName = module.getFileName().toString();
-                    // make sure "/modules/<moduleName>" is created
-                    findModulesNode(MODULES + moduleName);
-
-                    Files.walk(module).filter(Files::isDirectory).forEach((p) -> {
-                        p = module.relativize(p);
-                        String pkgName = slashesToDots(p.toString());
-                        // skip META-INFO and empty strings
-                        if (!pkgName.isEmpty() && !pkgName.startsWith("META-INF")) {
-                            List<String> moduleNames = packageToModules.get(pkgName);
-                            if (moduleNames == null) {
-                                moduleNames = new ArrayList<>();
-                                packageToModules.put(pkgName, moduleNames);
-                            }
-                            moduleNames.add(moduleName);
-                        }
-                    });
-                }
-            }
-        }
-
-        // create "/modules" directory
-        // "nodes" map contains only /modules/<foo> nodes only so far and so add all as children of /modules
-        DirNode modulesDir = new DirNode("/modules", new ArrayList<>(nodes.values()));
-        nodes.put(modulesDir.getName(), modulesDir);
-
-        // create children under "/packages"
-        List<Node> packagesChildren = new ArrayList<>(packageToModules.size());
-        for (Map.Entry<String, List<String>> entry : packageToModules.entrySet()) {
-            String pkgName = entry.getKey();
-            List<String> moduleNameList = entry.getValue();
-            List<Node> moduleLinkNodes = new ArrayList<>(moduleNameList.size());
-            for (String moduleName : moduleNameList) {
-                Node moduleNode = findModulesNode(MODULES + moduleName);
-                LinkNode linkNode = new LinkNode(PACKAGES + pkgName + "/" + moduleName, moduleNode);
-                nodes.put(linkNode.getName(), linkNode);
-                moduleLinkNodes.add(linkNode);
-            }
-
-            DirNode pkgDir = new DirNode(PACKAGES + pkgName, moduleLinkNodes);
-            nodes.put(pkgDir.getName(), pkgDir);
-            packagesChildren.add(pkgDir);
-        }
-
-        // "/packages" dir
-        DirNode packagesDir = new DirNode("/packages", packagesChildren);
-        nodes.put(packagesDir.getName(), packagesDir);
-
-        // finally "/" dir!
-        List<Node> rootChildren = new ArrayList<>();
-        rootChildren.add(modulesDir);
-        rootChildren.add(packagesDir);
-        DirNode root = new DirNode("/", rootChildren);
-        nodes.put(root.getName(), root);
-    }
-}
--- a/src/java.base/share/classes/jdk/internal/jrtfs/JrtExplodedPath.java	Fri Apr 15 10:14:57 2016 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,60 +0,0 @@
-/*
- * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-package jdk.internal.jrtfs;
-
-/**
- * Path implementation for jrt file system on JDK exploded modules build.
- *
- * @implNote This class needs to maintain JDK 8 source compatibility.
- *
- * It is used internally in the JDK to implement jimage/jrtfs access,
- * but also compiled and delivered as part of the jrtfs.jar to support access
- * to the jimage file provided by the shipped JDK by tools running on JDK 8.
- */
-final class JrtExplodedPath extends AbstractJrtPath {
-
-    JrtExplodedPath(AbstractJrtFileSystem jrtfs, byte[] path) {
-        super(jrtfs, path);
-    }
-
-    JrtExplodedPath(AbstractJrtFileSystem jrtfs, byte[] path, boolean normalized) {
-        super(jrtfs, path, normalized);
-    }
-
-    @Override
-    protected AbstractJrtPath newJrtPath(byte[] path) {
-        return new JrtExplodedPath(jrtfs, path);
-    }
-
-    @Override
-    protected AbstractJrtPath newJrtPath(byte[] path, boolean normalized) {
-        return new JrtExplodedPath(jrtfs, path, normalized);
-    }
-
-    @Override
-    public JrtExplodedFileSystem getFileSystem() {
-        return (JrtExplodedFileSystem) jrtfs;
-    }
-}
--- a/src/java.base/share/classes/jdk/internal/jrtfs/JrtFileAttributeView.java	Fri Apr 15 10:14:57 2016 -0700
+++ b/src/java.base/share/classes/jdk/internal/jrtfs/JrtFileAttributeView.java	Fri Apr 15 13:05:52 2016 -0700
@@ -29,6 +29,7 @@
 import java.io.IOException;
 import java.util.LinkedHashMap;
 import java.util.Map;
+import java.util.Objects;
 
 /**
  * File attribute view for jrt file system.
@@ -42,7 +43,6 @@
 final class JrtFileAttributeView implements BasicFileAttributeView {
 
     private static enum AttrID {
-
         size,
         creationTime,
         lastAccessTime,
@@ -56,21 +56,19 @@
         extension
     };
 
-    private final AbstractJrtPath path;
+    private final JrtPath path;
     private final boolean isJrtView;
     private final LinkOption[] options;
 
-    private JrtFileAttributeView(AbstractJrtPath path, boolean isJrtView, LinkOption... options) {
+    private JrtFileAttributeView(JrtPath path, boolean isJrtView, LinkOption... options) {
         this.path = path;
         this.isJrtView = isJrtView;
         this.options = options;
     }
 
     @SuppressWarnings("unchecked") // Cast to V
-    static <V extends FileAttributeView> V get(AbstractJrtPath path, Class<V> type, LinkOption... options) {
-        if (type == null) {
-            throw new NullPointerException();
-        }
+    static <V extends FileAttributeView> V get(JrtPath path, Class<V> type, LinkOption... options) {
+        Objects.requireNonNull(type);
         if (type == BasicFileAttributeView.class) {
             return (V) new JrtFileAttributeView(path, false, options);
         }
@@ -80,10 +78,8 @@
         return null;
     }
 
-    static JrtFileAttributeView get(AbstractJrtPath path, String type, LinkOption... options) {
-        if (type == null) {
-            throw new NullPointerException();
-        }
+    static JrtFileAttributeView get(JrtPath path, String type, LinkOption... options) {
+        Objects.requireNonNull(type);
         if (type.equals("basic")) {
             return new JrtFileAttributeView(path, false, options);
         }
@@ -99,61 +95,74 @@
     }
 
     @Override
-    public AbstractJrtFileAttributes readAttributes() throws IOException {
+    public JrtFileAttributes readAttributes() throws IOException {
         return path.getAttributes(options);
     }
 
     @Override
     public void setTimes(FileTime lastModifiedTime,
-            FileTime lastAccessTime,
-            FileTime createTime)
-            throws IOException {
+                         FileTime lastAccessTime,
+                         FileTime createTime) throws IOException {
         path.setTimes(lastModifiedTime, lastAccessTime, createTime);
     }
 
-    void setAttribute(String attribute, Object value)
+    static void setAttribute(JrtPath path, String attribute, Object value)
             throws IOException {
+        int colonPos = attribute.indexOf(':');
+        if (colonPos != -1) {    // type = "basic", if no ":"
+            String type = attribute.substring(0, colonPos++);
+            if (!type.equals("basic") && !type.equals("jrt")) {
+                throw new UnsupportedOperationException(
+                    "view <" + type + "> is not supported");
+            }
+            attribute = attribute.substring(colonPos);
+        }
         try {
-            if (AttrID.valueOf(attribute) == AttrID.lastModifiedTime) {
-                setTimes((FileTime) value, null, null);
-            }
-            if (AttrID.valueOf(attribute) == AttrID.lastAccessTime) {
-                setTimes(null, (FileTime) value, null);
-            }
-            if (AttrID.valueOf(attribute) == AttrID.creationTime) {
-                setTimes(null, null, (FileTime) value);
+            AttrID id = AttrID.valueOf(attribute);
+            if (id == AttrID.lastModifiedTime) {
+                path.setTimes((FileTime) value, null, null);
+            } else if (id == AttrID.lastAccessTime) {
+                path.setTimes(null, (FileTime) value, null);
+            } else if (id == AttrID.creationTime) {
+                path.setTimes(null, null, (FileTime) value);
             }
             return;
-        } catch (IllegalArgumentException x) {
-        }
+        } catch (IllegalArgumentException x) {}
         throw new UnsupportedOperationException("'" + attribute
                 + "' is unknown or read-only attribute");
     }
 
-    Map<String, Object> readAttributes(String attributes)
+    static Map<String, Object> readAttributes(JrtPath path, String attributes,
+                                              LinkOption... options)
             throws IOException {
-        AbstractJrtFileAttributes jrtfas = readAttributes();
+        int colonPos = attributes.indexOf(':');
+        boolean isJrtView = false;
+        if (colonPos != -1) {    // type = "basic", if no ":"
+            String type = attributes.substring(0, colonPos++);
+            if (!type.equals("basic") && !type.equals("jrt")) {
+                throw new UnsupportedOperationException("view <" + type +
+                                                        "> is not supported");
+            }
+            isJrtView = true;
+            attributes = attributes.substring(colonPos);
+        }
+        JrtFileAttributes jrtfas = path.getAttributes();
         LinkedHashMap<String, Object> map = new LinkedHashMap<>();
         if ("*".equals(attributes)) {
             for (AttrID id : AttrID.values()) {
-                try {
-                    map.put(id.name(), attribute(id, jrtfas));
-                } catch (IllegalArgumentException x) {
-                }
+                map.put(id.name(), attribute(id, jrtfas, isJrtView));
             }
         } else {
             String[] as = attributes.split(",");
             for (String a : as) {
-                try {
-                    map.put(a, attribute(AttrID.valueOf(a), jrtfas));
-                } catch (IllegalArgumentException x) {
-                }
+                //throw IllegalArgumentException
+                map.put(a, attribute(AttrID.valueOf(a), jrtfas, isJrtView));
             }
         }
         return map;
     }
 
-    Object attribute(AttrID id, AbstractJrtFileAttributes jrtfas) {
+    static Object attribute(AttrID id, JrtFileAttributes jrtfas, boolean isJrtView) {
         switch (id) {
             case size:
                 return jrtfas.size();
--- a/src/java.base/share/classes/jdk/internal/jrtfs/JrtFileAttributes.java	Fri Apr 15 10:14:57 2016 -0700
+++ b/src/java.base/share/classes/jdk/internal/jrtfs/JrtFileAttributes.java	Fri Apr 15 13:05:52 2016 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016, 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
@@ -24,7 +24,9 @@
  */
 package jdk.internal.jrtfs;
 
+import java.nio.file.attribute.BasicFileAttributes;
 import java.nio.file.attribute.FileTime;
+import java.util.Formatter;
 import jdk.internal.jimage.ImageReader.Node;
 
 /**
@@ -36,7 +38,7 @@
  * but also compiled and delivered as part of the jrtfs.jar to support access
  * to the jimage file provided by the shipped JDK by tools running on JDK 8.
  */
-final class JrtFileAttributes extends AbstractJrtFileAttributes {
+final class JrtFileAttributes  implements BasicFileAttributes {
 
     private final Node node;
 
@@ -90,14 +92,50 @@
         return node.resolveLink(true);
     }
 
-    ///////// jrt entry attributes ///////////
-    @Override
+    ///////// jrtfs specific attributes ///////////
+    /**
+     * Compressed resource file. If not available or not applicable, 0L is
+     * returned.
+     *
+     * @return the compressed resource size for compressed resources.
+     */
     public long compressedSize() {
         return node.compressedSize();
     }
 
-    @Override
+    /**
+     * "file" extension of a file resource.
+     *
+     * @return extension string for the file resource
+     */
     public String extension() {
         return node.extension();
     }
+
+    @Override
+    public final String toString() {
+        StringBuilder sb = new StringBuilder(1024);
+        try (Formatter fm = new Formatter(sb)) {
+            if (creationTime() != null) {
+                fm.format("    creationTime    : %tc%n", creationTime().toMillis());
+            } else {
+                fm.format("    creationTime    : null%n");
+            }
+            if (lastAccessTime() != null) {
+                fm.format("    lastAccessTime  : %tc%n", lastAccessTime().toMillis());
+            } else {
+                fm.format("    lastAccessTime  : null%n");
+            }
+            fm.format("    lastModifiedTime: %tc%n", lastModifiedTime().toMillis());
+            fm.format("    isRegularFile   : %b%n", isRegularFile());
+            fm.format("    isDirectory     : %b%n", isDirectory());
+            fm.format("    isSymbolicLink  : %b%n", isSymbolicLink());
+            fm.format("    isOther         : %b%n", isOther());
+            fm.format("    fileKey         : %s%n", fileKey());
+            fm.format("    size            : %d%n", size());
+            fm.format("    compressedSize  : %d%n", compressedSize());
+            fm.format("    extension       : %s%n", extension());
+        }
+        return sb.toString();
+    }
 }
--- a/src/java.base/share/classes/jdk/internal/jrtfs/JrtFileStore.java	Fri Apr 15 10:14:57 2016 -0700
+++ b/src/java.base/share/classes/jdk/internal/jrtfs/JrtFileStore.java	Fri Apr 15 13:05:52 2016 -0700
@@ -30,6 +30,7 @@
 import java.nio.file.attribute.FileAttributeView;
 import java.nio.file.attribute.BasicFileAttributeView;
 import java.nio.file.attribute.FileStoreAttributeView;
+import java.util.Objects;
 
 /**
  * File store implementation for jrt file systems.
@@ -44,7 +45,7 @@
 
     protected final FileSystem jrtfs;
 
-    JrtFileStore(AbstractJrtPath jrtPath) {
+    JrtFileStore(JrtPath jrtPath) {
         this.jrtfs = jrtPath.getFileSystem();
     }
 
@@ -71,9 +72,7 @@
     @Override
     @SuppressWarnings("unchecked")
     public <V extends FileStoreAttributeView> V getFileStoreAttributeView(Class<V> type) {
-        if (type == null) {
-            throw new NullPointerException();
-        }
+        Objects.requireNonNull(type, "type");
         return (V) null;
     }
 
@@ -99,7 +98,7 @@
 
     @Override
     public boolean supportsFileAttributeView(Class<? extends FileAttributeView> type) {
-        return (type == BasicFileAttributeView.class
-                || type == JrtFileAttributeView.class);
+        return type == BasicFileAttributeView.class ||
+               type == JrtFileAttributeView.class;
     }
 }
--- a/src/java.base/share/classes/jdk/internal/jrtfs/JrtFileSystem.java	Fri Apr 15 10:14:57 2016 -0700
+++ b/src/java.base/share/classes/jdk/internal/jrtfs/JrtFileSystem.java	Fri Apr 15 13:05:52 2016 -0700
@@ -24,23 +24,47 @@
  */
 package jdk.internal.jrtfs;
 
+import java.io.ByteArrayInputStream;
 import java.io.IOException;
-import java.nio.file.LinkOption;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.nio.channels.Channels;
+import java.nio.channels.FileChannel;
+import java.nio.channels.NonWritableChannelException;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.channels.SeekableByteChannel;
+import java.nio.file.ClosedFileSystemException;
+import java.nio.file.CopyOption;
+import java.nio.file.DirectoryStream;
+import java.nio.file.FileStore;
+import java.nio.file.FileSystem;
 import java.nio.file.FileSystemException;
 import java.nio.file.InvalidPathException;
+import java.nio.file.LinkOption;
 import java.nio.file.NoSuchFileException;
 import java.nio.file.NotDirectoryException;
+import java.nio.file.OpenOption;
 import java.nio.file.Path;
+import java.nio.file.PathMatcher;
+import java.nio.file.ReadOnlyFileSystemException;
+import java.nio.file.StandardOpenOption;
+import java.nio.file.WatchService;
+import java.nio.file.attribute.FileAttribute;
+import java.nio.file.attribute.FileTime;
+import java.nio.file.attribute.UserPrincipalLookupService;
+import java.nio.file.spi.FileSystemProvider;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
 import java.util.Iterator;
-import java.util.List;
 import java.util.Map;
-import java.util.function.Function;
+import java.util.Objects;
+import java.util.Set;
+import java.util.regex.Pattern;
+import jdk.internal.jimage.ImageReader.Node;
 import static java.util.stream.Collectors.toList;
-import jdk.internal.jimage.ImageReader;
-import jdk.internal.jimage.ImageReader.Node;
-
 
 /**
  * jrt file system implementation built on System jimage files.
@@ -51,33 +75,21 @@
  * but also compiled and delivered as part of the jrtfs.jar to support access
  * to the jimage file provided by the shipped JDK by tools running on JDK 8.
  */
-class JrtFileSystem extends AbstractJrtFileSystem {
+class JrtFileSystem extends FileSystem {
 
-    // System image reader
-    private ImageReader bootImage;
-    // root path
-    private final JrtPath rootPath;
+    private final JrtFileSystemProvider provider;
+    private final JrtPath rootPath = new JrtPath(this, "/");
     private volatile boolean isOpen;
+    private volatile boolean isClosable;
+    private SystemImage image;
 
-    // open a .jimage and build directory structure
-    private static ImageReader openImage(Path path) throws IOException {
-        ImageReader image = ImageReader.open(path);
-        image.getRootDirectory();
-        return image;
-    }
-
-    JrtFileSystem(JrtFileSystemProvider provider,
-            Map<String, ?> env)
-            throws IOException {
-        super(provider, env);
-        checkExists(SystemImages.moduleImageFile());
-
-        // open image file
-        this.bootImage = openImage(SystemImages.moduleImageFile());
-
-        byte[] root = new byte[]{'/'};
-        rootPath = new JrtPath(this, root);
-        isOpen = true;
+    JrtFileSystem(JrtFileSystemProvider provider, Map<String, ?> env)
+            throws IOException
+    {
+        this.provider = provider;
+        this.image = SystemImage.open();  // open image file
+        this.isOpen = true;
+        this.isClosable = env != null;
     }
 
     // FileSystem method implementations
@@ -88,6 +100,8 @@
 
     @Override
     public void close() throws IOException {
+        if (!isClosable)
+            throw new UnsupportedOperationException();
         cleanup();
     }
 
@@ -95,237 +109,397 @@
     protected void finalize() throws Throwable {
         try {
             cleanup();
-        } catch (IOException ignored) {
-        }
-        super.finalize();
-    }
-
-    // AbstractJrtFileSystem method implementations
-    @Override
-    JrtPath getRootPath() {
-        return rootPath;
+        } catch (IOException ignored) {}
     }
 
     @Override
-    boolean isSameFile(AbstractJrtPath p1, AbstractJrtPath p2) throws IOException {
-        ensureOpen();
-        Node node1 = findNode(p1);
-        Node node2 = findNode(p2);
-        return node1.equals(node2);
+    public FileSystemProvider provider() {
+        return provider;
     }
 
     @Override
-    boolean isLink(AbstractJrtPath jrtPath) throws IOException {
-        return checkNode(jrtPath).isLink();
+    public Iterable<Path> getRootDirectories() {
+        ArrayList<Path> dirs = new ArrayList<>();
+        dirs.add(getRootPath());
+        return dirs;
     }
 
     @Override
-    AbstractJrtPath resolveLink(AbstractJrtPath jrtPath) throws IOException {
-        Node node = checkNode(jrtPath);
-        if (node.isLink()) {
-            node = node.resolveLink();
-            return toJrtPath(getBytes(node.getName()));
+    public JrtPath getPath(String first, String... more) {
+        if (more.length == 0) {
+            return new JrtPath(this, first);
         }
-
-        return jrtPath;
+        StringBuilder sb = new StringBuilder();
+        sb.append(first);
+        for (String path : more) {
+            if (path.length() > 0) {
+                if (sb.length() > 0) {
+                    sb.append('/');
+                }
+                sb.append(path);
+            }
+        }
+        return new JrtPath(this, sb.toString());
     }
 
     @Override
-    JrtFileAttributes getFileAttributes(AbstractJrtPath jrtPath, LinkOption... options)
+    public final boolean isReadOnly() {
+        return true;
+    }
+
+    @Override
+    public final UserPrincipalLookupService getUserPrincipalLookupService() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public final WatchService newWatchService() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public final Iterable<FileStore> getFileStores() {
+        ArrayList<FileStore> list = new ArrayList<>(1);
+        list.add(getFileStore(getRootPath()));
+        return list;
+    }
+
+    private static final Set<String> supportedFileAttributeViews
+            = Collections.unmodifiableSet(
+                    new HashSet<String>(Arrays.asList("basic", "jrt")));
+
+    @Override
+    public final Set<String> supportedFileAttributeViews() {
+        return supportedFileAttributeViews;
+    }
+
+    @Override
+    public final String toString() {
+        return "jrt:/";
+    }
+
+    @Override
+    public final String getSeparator() {
+        return "/";
+    }
+
+    @Override
+    public PathMatcher getPathMatcher(String syntaxAndInput) {
+        int pos = syntaxAndInput.indexOf(':');
+        if (pos <= 0 || pos == syntaxAndInput.length()) {
+            throw new IllegalArgumentException();
+        }
+        String syntax = syntaxAndInput.substring(0, pos);
+        String input = syntaxAndInput.substring(pos + 1);
+        String expr;
+        if (syntax.equalsIgnoreCase("glob")) {
+            expr = JrtUtils.toRegexPattern(input);
+        } else if (syntax.equalsIgnoreCase("regex")) {
+            expr = input;
+        } else {
+                throw new UnsupportedOperationException("Syntax '" + syntax
+                        + "' not recognized");
+        }
+        // return matcher
+        final Pattern pattern = Pattern.compile(expr);
+        return (Path path) -> pattern.matcher(path.toString()).matches();
+    }
+
+    JrtPath resolveLink(JrtPath path) throws IOException {
+        Node node = checkNode(path);
+        if (node.isLink()) {
+            node = node.resolveLink();
+            return new JrtPath(this, node.getName());  // TBD, normalized?
+        }
+        return path;
+    }
+
+    JrtFileAttributes getFileAttributes(JrtPath path, LinkOption... options)
             throws IOException {
-        Node node = checkNode(jrtPath);
+        Node node = checkNode(path);
         if (node.isLink() && followLinks(options)) {
             return new JrtFileAttributes(node.resolveLink(true));
         }
         return new JrtFileAttributes(node);
     }
 
-    @Override
-    boolean exists(AbstractJrtPath jrtPath) throws IOException {
+    /**
+     * returns the list of child paths of the given directory "path"
+     *
+     * @param path name of the directory whose content is listed
+     * @return iterator for child paths of the given directory path
+     */
+    Iterator<Path> iteratorOf(JrtPath path, DirectoryStream.Filter<? super Path> filter)
+            throws IOException {
+        Node node = checkNode(path).resolveLink(true);
+        if (!node.isDirectory()) {
+            throw new NotDirectoryException(path.getName());
+        }
+        if (filter == null) {
+            return node.getChildren()
+                       .stream()
+                       .map(child -> (Path)(path.resolve(new JrtPath(this, child.getNameString()).getFileName())))
+                       .iterator();
+        }
+        return node.getChildren()
+                   .stream()
+                   .map(child -> (Path)(path.resolve(new JrtPath(this, child.getNameString()).getFileName())))
+                   .filter(p ->  { try { return filter.accept(p);
+                                   } catch (IOException x) {}
+                                   return false;
+                                  })
+                   .iterator();
+    }
+
+    // returns the content of the file resource specified by the path
+    byte[] getFileContent(JrtPath path) throws IOException {
+        Node node = checkNode(path);
+        if (node.isDirectory()) {
+            throw new FileSystemException(path + " is a directory");
+        }
+        //assert node.isResource() : "resource node expected here";
+        return image.getResource(node);
+    }
+
+    /////////////// Implementation details below this point //////////
+
+    // static utility methods
+    static ReadOnlyFileSystemException readOnly() {
+        return new ReadOnlyFileSystemException();
+    }
+
+    // do the supplied options imply that we have to chase symlinks?
+    static boolean followLinks(LinkOption... options) {
+        if (options != null) {
+            for (LinkOption lo : options) {
+                Objects.requireNonNull(lo);
+                if (lo == LinkOption.NOFOLLOW_LINKS) {
+                    return false;
+                } else {
+                    throw new AssertionError("should not reach here");
+                }
+            }
+        }
+        return true;
+    }
+
+    // check that the options passed are supported by (read-only) jrt file system
+    static void checkOptions(Set<? extends OpenOption> options) {
+        // check for options of null type and option is an intance of StandardOpenOption
+        for (OpenOption option : options) {
+            Objects.requireNonNull(option);
+            if (!(option instanceof StandardOpenOption)) {
+                throw new IllegalArgumentException();
+            }
+        }
+        if (options.contains(StandardOpenOption.WRITE) ||
+            options.contains(StandardOpenOption.APPEND)) {
+            throw readOnly();
+        }
+    }
+
+    // clean up this file system - called from finalize and close
+    void cleanup() throws IOException {
+        if (!isOpen) {
+            return;
+        }
+        synchronized (this) {
+            isOpen = false;
+            // close image reader and null out
+            image.close();
+            image = null;
+        }
+    }
+
+    // These methods throw read only file system exception
+    final void setTimes(JrtPath jrtPath, FileTime mtime, FileTime atime, FileTime ctime)
+            throws IOException {
+        throw readOnly();
+    }
+
+    // These methods throw read only file system exception
+    final void createDirectory(JrtPath jrtPath, FileAttribute<?>... attrs) throws IOException {
+        throw readOnly();
+    }
+
+    final void deleteFile(JrtPath jrtPath, boolean failIfNotExists)
+            throws IOException {
+        throw readOnly();
+    }
+
+    final OutputStream newOutputStream(JrtPath jrtPath, OpenOption... options)
+            throws IOException {
+        throw readOnly();
+    }
+
+    final void copyFile(boolean deletesrc, JrtPath srcPath, JrtPath dstPath, CopyOption... options)
+            throws IOException {
+        throw readOnly();
+    }
+
+    final FileChannel newFileChannel(JrtPath path,
+            Set<? extends OpenOption> options,
+            FileAttribute<?>... attrs)
+            throws IOException {
+        throw new UnsupportedOperationException("newFileChannel");
+    }
+
+    final InputStream newInputStream(JrtPath path) throws IOException {
+        return new ByteArrayInputStream(getFileContent(path));
+    }
+
+    final SeekableByteChannel newByteChannel(JrtPath path,
+            Set<? extends OpenOption> options,
+            FileAttribute<?>... attrs)
+            throws IOException {
+        checkOptions(options);
+
+        byte[] buf = getFileContent(path);
+        final ReadableByteChannel rbc
+                = Channels.newChannel(new ByteArrayInputStream(buf));
+        final long size = buf.length;
+        return new SeekableByteChannel() {
+            long read = 0;
+
+            @Override
+            public boolean isOpen() {
+                return rbc.isOpen();
+            }
+
+            @Override
+            public long position() throws IOException {
+                return read;
+            }
+
+            @Override
+            public SeekableByteChannel position(long pos)
+                    throws IOException {
+                throw new UnsupportedOperationException();
+            }
+
+            @Override
+            public int read(ByteBuffer dst) throws IOException {
+                int n = rbc.read(dst);
+                if (n > 0) {
+                    read += n;
+                }
+                return n;
+            }
+
+            @Override
+            public SeekableByteChannel truncate(long size)
+                    throws IOException {
+                throw new NonWritableChannelException();
+            }
+
+            @Override
+            public int write(ByteBuffer src) throws IOException {
+                throw new NonWritableChannelException();
+            }
+
+            @Override
+            public long size() throws IOException {
+                return size;
+            }
+
+            @Override
+            public void close() throws IOException {
+                rbc.close();
+            }
+        };
+    }
+
+    final JrtFileStore getFileStore(JrtPath path) {
+        return new JrtFileStore(path);
+    }
+
+    final void ensureOpen() throws IOException {
+        if (!isOpen()) {
+            throw new ClosedFileSystemException();
+        }
+    }
+
+    final JrtPath getRootPath() {
+        return rootPath;
+    }
+
+    boolean isSameFile(JrtPath path1, JrtPath path2) throws IOException {
+        return checkNode(path1) == checkNode(path2);
+    }
+
+    boolean isLink(JrtPath path) throws IOException {
+        return checkNode(path).isLink();
+    }
+
+    boolean exists(JrtPath path) throws IOException {
         try {
-            checkNode(jrtPath);
+            checkNode(path);
         } catch (NoSuchFileException exp) {
             return false;
         }
         return true;
     }
 
-    @Override
-    boolean isDirectory(AbstractJrtPath jrtPath, boolean resolveLinks)
+    boolean isDirectory(JrtPath path, boolean resolveLinks)
             throws IOException {
-        Node node = checkNode(jrtPath);
+        Node node = checkNode(path);
         return resolveLinks && node.isLink()
                 ? node.resolveLink(true).isDirectory()
                 : node.isDirectory();
     }
 
-    @Override
-    Iterator<Path> iteratorOf(AbstractJrtPath jrtPath) throws IOException {
-        Node node = checkNode(jrtPath).resolveLink(true);
-        if (!node.isDirectory()) {
-            throw new NotDirectoryException(getString(jrtPath.getName()));
+    JrtPath toRealPath(JrtPath path, LinkOption... options)
+            throws IOException {
+        Node node = checkNode(path);
+        if (followLinks(options) && node.isLink()) {
+            node = node.resolveLink();
         }
-
-        if (node.isRootDir()) {
-            return rootDirIterator(jrtPath);
-        } else if (node.isModulesDir()) {
-            return modulesDirIterator(jrtPath);
-        } else if (node.isPackagesDir()) {
-            return packagesDirIterator(jrtPath);
-        }
-
-        return nodesToIterator(jrtPath, node.getChildren());
+        // image node holds the real/absolute path name
+        return new JrtPath(this, node.getName(), true);
     }
 
-    @Override
-    byte[] getFileContent(AbstractJrtPath jrtPath) throws IOException {
-        final Node node = checkResource(jrtPath);
-        return bootImage.getResource(node);
-    }
-
-    // Implementation details below this point
-    // clean up this file system - called from finalize and close
-    private void cleanup() throws IOException {
-        if (!isOpen) {
-            return;
-        }
-
-        synchronized (this) {
-            isOpen = false;
-
-            // close all image reader and null out
-            bootImage.close();
-            bootImage = null;
+    private Node lookup(String path) {
+        try {
+            return image.findNode(path);
+        } catch (RuntimeException re) {
+            throw new InvalidPathException(path, re.toString());
         }
     }
 
-    private Node lookup(byte[] path) {
-        Node node = null;
-        try {
-            node = bootImage.findNode(getString(path));
-        } catch (RuntimeException re) {
-            throw new InvalidPathException(getString(path), re.toString());
+    private Node lookupSymbolic(String path) {
+        int i = 1;
+        while (i < path.length()) {
+            i = path.indexOf('/', i);
+            if (i == -1) {
+                break;
+            }
+            String prefix = path.substring(0, i);
+            Node node = lookup(prefix);
+            if (node == null) {
+                break;
+            }
+            if (node.isLink()) {
+                Node link = node.resolveLink(true);
+                // resolved symbolic path concatenated to the rest of the path
+                String resPath = link.getName() + path.substring(i);
+                node = lookup(resPath);
+                return node != null ? node : lookupSymbolic(resPath);
+            }
+            i++;
         }
-        return node;
-    }
-
-    private Node lookupSymbolic(byte[] path) {
-        for (int i = 1; i < path.length; i++) {
-            if (path[i] == (byte) '/') {
-                byte[] prefix = Arrays.copyOfRange(path, 0, i);
-                Node node = lookup(prefix);
-                if (node == null) {
-                    break;
-                }
-
-                if (node.isLink()) {
-                    Node link = node.resolveLink(true);
-                    // resolved symbolic path concatenated to the rest of the path
-                    String resPath = link.getName() + getString(path).substring(i);
-                    byte[] resPathBytes = getBytes(resPath);
-                    node = lookup(resPathBytes);
-                    return node != null ? node : lookupSymbolic(resPathBytes);
-                }
-            }
-        }
-
         return null;
     }
 
-    private Node findNode(AbstractJrtPath jrtPath) throws IOException {
-        return findNode(jrtPath.getResolvedPath());
-    }
-
-    private Node findNode(byte[] path) throws IOException {
-        Node node = lookup(path);
+    Node checkNode(JrtPath path) throws IOException {
+        ensureOpen();
+        String p = path.getResolvedPath();
+        Node node = lookup(p);
         if (node == null) {
-            node = lookupSymbolic(path);
+            node = lookupSymbolic(p);
             if (node == null) {
-                throw new NoSuchFileException(getString(path));
+                throw new NoSuchFileException(p);
             }
         }
         return node;
     }
-
-    private Node checkNode(AbstractJrtPath jrtPath) throws IOException {
-        return checkNode(jrtPath.getResolvedPath());
-    }
-
-    private Node checkNode(byte[] path) throws IOException {
-        ensureOpen();
-        return findNode(path);
-    }
-
-    private Node checkResource(AbstractJrtPath jrtPath) throws IOException {
-        return checkResource(jrtPath.getResolvedPath());
-    }
-
-    private Node checkResource(byte[] path) throws IOException {
-        Node node = checkNode(path);
-        if (node.isDirectory()) {
-            throw new FileSystemException(getString(path) + " is a directory");
-        }
-
-        assert node.isResource() : "resource node expected here";
-        return node;
-    }
-
-    private JrtPath toJrtPath(String path) {
-        return toJrtPath(getBytes(path));
-    }
-
-    private JrtPath toJrtPath(byte[] path) {
-        return new JrtPath(this, path);
-    }
-
-    private Iterator<Path> nodesToIterator(AbstractJrtPath dir, List<Node> childNodes) {
-        Function<Node, Path> nodeToPath =
-            child -> dir.resolve(
-                toJrtPath(child.getNameString()).getFileName());
-        return childNodes.stream().
-                map(nodeToPath).collect(toList()).
-                iterator();
-    }
-
-    private List<Node> rootChildren;
-
-    private synchronized void initRootChildren(AbstractJrtPath jrtPath) throws IOException {
-        if (rootChildren == null) {
-            rootChildren = new ArrayList<>();
-            rootChildren.addAll(findNode(jrtPath).getChildren());
-        }
-    }
-
-    private Iterator<Path> rootDirIterator(AbstractJrtPath jrtPath) throws IOException {
-        initRootChildren(jrtPath);
-        return nodesToIterator(jrtPath, rootChildren);
-    }
-
-    private List<Node> modulesChildren;
-
-    private synchronized void initModulesChildren(AbstractJrtPath jrtPath) throws IOException {
-        if (modulesChildren == null) {
-            modulesChildren = new ArrayList<>();
-            modulesChildren.addAll(findNode(jrtPath).getChildren());
-        }
-    }
-
-    private Iterator<Path> modulesDirIterator(AbstractJrtPath jrtPath) throws IOException {
-        initModulesChildren(jrtPath);
-        return nodesToIterator(jrtPath, modulesChildren);
-    }
-
-    private List<Node> packagesChildren;
-
-    private synchronized void initPackagesChildren(AbstractJrtPath jrtPath) throws IOException {
-        if (packagesChildren == null) {
-            packagesChildren = new ArrayList<>();
-            packagesChildren.addAll(findNode(jrtPath).getChildren());
-        }
-    }
-
-    private Iterator<Path> packagesDirIterator(AbstractJrtPath jrtPath) throws IOException {
-        initPackagesChildren(jrtPath);
-        return nodesToIterator(jrtPath, packagesChildren);
-    }
 }
--- a/src/java.base/share/classes/jdk/internal/jrtfs/JrtFileSystemProvider.java	Fri Apr 15 10:14:57 2016 -0700
+++ b/src/java.base/share/classes/jdk/internal/jrtfs/JrtFileSystemProvider.java	Fri Apr 15 13:05:52 2016 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016, 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
@@ -70,7 +70,7 @@
     private void checkPermission() {
         SecurityManager sm = System.getSecurityManager();
         if (sm != null) {
-            String home = SystemImages.RUNTIME_HOME;
+            String home = SystemImage.RUNTIME_HOME;
             FilePermission perm
                     = new FilePermission(home + File.separator + "-", "read");
             sm.checkPermission(perm);
@@ -107,9 +107,7 @@
         if (env != null && env.containsKey("java.home")) {
             return newFileSystem((String)env.get("java.home"), uri, env);
         } else {
-            return SystemImages.hasModulesImage()
-                    ? new JrtFileSystem(this, env)
-                    : new JrtExplodedFileSystem(this, env);
+            return new JrtFileSystem(this, env);
         }
     }
 
@@ -121,7 +119,6 @@
         if (Files.notExists(jrtfs)) {
             throw new IOException(jrtfs.toString() + " not exist");
         }
-
         Map<String,?> newEnv = new HashMap<>(env);
         newEnv.remove("java.home");
         ClassLoader cl = newJrtFsLoader(jrtfs);
@@ -139,7 +136,6 @@
         JrtFsLoader(URL[] urls) {
             super(urls);
         }
-
         @Override
         protected Class<?> loadClass(String cn, boolean resolve)
                 throws ClassNotFoundException
@@ -208,21 +204,7 @@
                 fs = this.theFileSystem;
                 if (fs == null) {
                     try {
-                        if (SystemImages.hasModulesImage()) {
-                            this.theFileSystem = fs = new JrtFileSystem(this, null) {
-                                @Override
-                                public void close() {
-                                    throw new UnsupportedOperationException();
-                                }
-                            };
-                        } else {
-                            this.theFileSystem = fs = new JrtExplodedFileSystem(this, null) {
-                                @Override
-                                public void close() {
-                                    throw new UnsupportedOperationException();
-                                }
-                            };
-                        }
+                        this.theFileSystem = fs = new JrtFileSystem(this, null);
                     } catch (IOException ioe) {
                         throw new InternalError(ioe);
                     }
@@ -240,69 +222,67 @@
     }
 
     // Checks that the given file is a JrtPath
-    static final AbstractJrtPath toAbstractJrtPath(Path path) {
-        if (path == null) {
-            throw new NullPointerException();
-        }
-        if (!(path instanceof AbstractJrtPath)) {
+    static final JrtPath toJrtPath(Path path) {
+        Objects.requireNonNull(path, "path");
+        if (!(path instanceof JrtPath)) {
             throw new ProviderMismatchException();
         }
-        return (AbstractJrtPath) path;
+        return (JrtPath) path;
     }
 
     @Override
     public void checkAccess(Path path, AccessMode... modes) throws IOException {
-        toAbstractJrtPath(path).checkAccess(modes);
+        toJrtPath(path).checkAccess(modes);
     }
 
     @Override
     public Path readSymbolicLink(Path link) throws IOException {
-        return toAbstractJrtPath(link).readSymbolicLink();
+        return toJrtPath(link).readSymbolicLink();
     }
 
     @Override
     public void copy(Path src, Path target, CopyOption... options)
             throws IOException {
-        toAbstractJrtPath(src).copy(toAbstractJrtPath(target), options);
+        toJrtPath(src).copy(toJrtPath(target), options);
     }
 
     @Override
     public void createDirectory(Path path, FileAttribute<?>... attrs)
             throws IOException {
-        toAbstractJrtPath(path).createDirectory(attrs);
+        toJrtPath(path).createDirectory(attrs);
     }
 
     @Override
     public final void delete(Path path) throws IOException {
-        toAbstractJrtPath(path).delete();
+        toJrtPath(path).delete();
     }
 
     @Override
     @SuppressWarnings("unchecked")
     public <V extends FileAttributeView> V
             getFileAttributeView(Path path, Class<V> type, LinkOption... options) {
-        return JrtFileAttributeView.get(toAbstractJrtPath(path), type, options);
+        return JrtFileAttributeView.get(toJrtPath(path), type, options);
     }
 
     @Override
     public FileStore getFileStore(Path path) throws IOException {
-        return toAbstractJrtPath(path).getFileStore();
+        return toJrtPath(path).getFileStore();
     }
 
     @Override
     public boolean isHidden(Path path) {
-        return toAbstractJrtPath(path).isHidden();
+        return toJrtPath(path).isHidden();
     }
 
     @Override
     public boolean isSameFile(Path path, Path other) throws IOException {
-        return toAbstractJrtPath(path).isSameFile(other);
+        return toJrtPath(path).isSameFile(other);
     }
 
     @Override
     public void move(Path src, Path target, CopyOption... options)
             throws IOException {
-        toAbstractJrtPath(src).move(toAbstractJrtPath(target), options);
+        toJrtPath(src).move(toJrtPath(target), options);
     }
 
     @Override
@@ -319,13 +299,13 @@
             Set<? extends OpenOption> options,
             FileAttribute<?>... attrs)
             throws IOException {
-        return toAbstractJrtPath(path).newByteChannel(options, attrs);
+        return toJrtPath(path).newByteChannel(options, attrs);
     }
 
     @Override
     public DirectoryStream<Path> newDirectoryStream(
             Path path, Filter<? super Path> filter) throws IOException {
-        return toAbstractJrtPath(path).newDirectoryStream(filter);
+        return toJrtPath(path).newDirectoryStream(filter);
     }
 
     @Override
@@ -333,19 +313,19 @@
             Set<? extends OpenOption> options,
             FileAttribute<?>... attrs)
             throws IOException {
-        return toAbstractJrtPath(path).newFileChannel(options, attrs);
+        return toJrtPath(path).newFileChannel(options, attrs);
     }
 
     @Override
     public InputStream newInputStream(Path path, OpenOption... options)
             throws IOException {
-        return toAbstractJrtPath(path).newInputStream(options);
+        return toJrtPath(path).newInputStream(options);
     }
 
     @Override
     public OutputStream newOutputStream(Path path, OpenOption... options)
             throws IOException {
-        return toAbstractJrtPath(path).newOutputStream(options);
+        return toJrtPath(path).newOutputStream(options);
     }
 
     @Override
@@ -354,7 +334,7 @@
             readAttributes(Path path, Class<A> type, LinkOption... options)
             throws IOException {
         if (type == BasicFileAttributes.class || type == JrtFileAttributes.class) {
-            return (A) toAbstractJrtPath(path).getAttributes(options);
+            return (A) toJrtPath(path).getAttributes(options);
         }
         return null;
     }
@@ -363,13 +343,13 @@
     public Map<String, Object>
             readAttributes(Path path, String attribute, LinkOption... options)
             throws IOException {
-        return toAbstractJrtPath(path).readAttributes(attribute, options);
+        return toJrtPath(path).readAttributes(attribute, options);
     }
 
     @Override
     public void setAttribute(Path path, String attribute,
             Object value, LinkOption... options)
             throws IOException {
-        toAbstractJrtPath(path).setAttribute(attribute, value, options);
+        toJrtPath(path).setAttribute(attribute, value, options);
     }
 }
--- a/src/java.base/share/classes/jdk/internal/jrtfs/JrtPath.java	Fri Apr 15 10:14:57 2016 -0700
+++ b/src/java.base/share/classes/jdk/internal/jrtfs/JrtPath.java	Fri Apr 15 13:05:52 2016 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 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
@@ -24,8 +24,30 @@
  */
 package jdk.internal.jrtfs;
 
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.channels.FileChannel;
+import java.nio.channels.SeekableByteChannel;
+import java.nio.file.*;
+import java.nio.file.DirectoryStream.Filter;;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.nio.file.attribute.BasicFileAttributeView;
+import java.nio.file.attribute.FileAttribute;
+import java.nio.file.attribute.FileTime;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Objects;
+import java.util.Set;
+import static java.nio.file.StandardOpenOption.*;
+import static java.nio.file.StandardCopyOption.*;
+
 /**
- * jrt Path implementation for jrt on .jimage files.
+ * Base class for Path implementation of jrt file systems.
  *
  * @implNote This class needs to maintain JDK 8 source compatibility.
  *
@@ -33,23 +55,756 @@
  * but also compiled and delivered as part of the jrtfs.jar to support access
  * to the jimage file provided by the shipped JDK by tools running on JDK 8.
  */
-final class JrtPath extends AbstractJrtPath {
+final class JrtPath implements Path {
 
-    JrtPath(AbstractJrtFileSystem jrtfs, byte[] path) {
-        this(jrtfs, path, false);
+    final JrtFileSystem jrtfs;
+    private final String path;
+    private volatile int[] offsets;
+
+    JrtPath(JrtFileSystem jrtfs, String path) {
+        this.jrtfs = jrtfs;
+        this.path = normalize(path);
+        this.resolved = null;
     }
 
-    JrtPath(AbstractJrtFileSystem jrtfs, byte[] path, boolean normalized) {
-        super(jrtfs, path, normalized);
+    JrtPath(JrtFileSystem jrtfs, String path, boolean normalized) {
+        this.jrtfs = jrtfs;
+        this.path = normalized ? path : normalize(path);
+        this.resolved = null;
+    }
+
+    final String getName() {
+        return path;
     }
 
     @Override
-    protected JrtPath newJrtPath(byte[] path) {
-        return new JrtPath(jrtfs, path);
+    public final JrtPath getRoot() {
+        if (this.isAbsolute()) {
+            return jrtfs.getRootPath();
+        } else {
+            return null;
+        }
     }
 
     @Override
-    protected JrtPath newJrtPath(byte[] path, boolean normalized) {
-        return new JrtPath(jrtfs, path, normalized);
+    public final JrtPath getFileName() {
+        if (path.length() == 0)
+            return this;
+        if (path.length() == 1 && path.charAt(0) == '/')
+            return null;
+        int off = path.lastIndexOf('/');
+        if (off == -1)
+            return this;
+        return new JrtPath(jrtfs, path.substring(off + 1), true);
+    }
+
+    @Override
+    public final JrtPath getParent() {
+        initOffsets();
+        int count = offsets.length;
+        if (count == 0) {     // no elements so no parent
+            return null;
+        }
+        int off = offsets[count - 1] - 1;
+        if (off <= 0) {       // parent is root only (may be null)
+            return getRoot();
+        }
+        return new JrtPath(jrtfs, path.substring(0, off));
+    }
+
+    @Override
+    public final int getNameCount() {
+        initOffsets();
+        return offsets.length;
+    }
+
+    @Override
+    public final JrtPath getName(int index) {
+        initOffsets();
+        if (index < 0 || index >= offsets.length) {
+            throw new IllegalArgumentException();
+        }
+        int begin = offsets[index];
+        int end;
+        if (index == (offsets.length - 1)) {
+            end = path.length();
+        } else {
+            end = offsets[index + 1];
+        }
+        return new JrtPath(jrtfs, path.substring(begin, end));
+    }
+
+    @Override
+    public final JrtPath subpath(int beginIndex, int endIndex) {
+        initOffsets();
+        if (beginIndex < 0 || endIndex > offsets.length ||
+            beginIndex >= endIndex) {
+            throw new IllegalArgumentException();
+        }
+        // starting/ending offsets
+        int begin = offsets[beginIndex];
+        int end;
+        if (endIndex == offsets.length) {
+            end = path.length();
+        } else {
+            end = offsets[endIndex];
+        }
+        return new JrtPath(jrtfs, path.substring(begin, end));
+    }
+
+    @Override
+    public final JrtPath toRealPath(LinkOption... options) throws IOException {
+        return jrtfs.toRealPath(this, options);
+    }
+
+    @Override
+    public final JrtPath toAbsolutePath() {
+        if (isAbsolute())
+            return this;
+        return new JrtPath(jrtfs, "/" + path, true);
+    }
+
+    @Override
+    public final URI toUri() {
+        try {
+            return new URI("jrt", toAbsolutePath().path, null);
+        } catch (URISyntaxException ex) {
+            throw new AssertionError(ex);
+        }
+    }
+
+    private boolean equalsNameAt(JrtPath other, int index) {
+        int mbegin = offsets[index];
+        int mlen;
+        if (index == (offsets.length - 1)) {
+            mlen = path.length() - mbegin;
+        } else {
+            mlen = offsets[index + 1] - mbegin - 1;
+        }
+        int obegin = other.offsets[index];
+        int olen;
+        if (index == (other.offsets.length - 1)) {
+            olen = other.path.length() - obegin;
+        } else {
+            olen = other.offsets[index + 1] - obegin - 1;
+        }
+        if (mlen != olen) {
+            return false;
+        }
+        int n = 0;
+        while (n < mlen) {
+            if (path.charAt(mbegin + n) != other.path.charAt(obegin + n)) {
+                return false;
+            }
+            n++;
+        }
+        return true;
+    }
+
+    @Override
+    public final JrtPath relativize(Path other) {
+        final JrtPath o = checkPath(other);
+        if (o.equals(this)) {
+            return new JrtPath(jrtfs, "", true);
+        }
+        if (path.length() == 0) {
+            return o;
+        }
+        if (jrtfs != o.jrtfs || isAbsolute() != o.isAbsolute()) {
+            throw new IllegalArgumentException();
+        }
+        final String tp = this.path;
+        final String op = o.path;
+        if (op.startsWith(tp)) {    // fast path
+            int off = tp.length();
+            if (op.charAt(off - 1) == '/')
+                return new JrtPath(jrtfs, op.substring(off), true);
+            if (op.charAt(off) == '/')
+                return new JrtPath(jrtfs, op.substring(off + 1), true);
+        }
+        int mc = this.getNameCount();
+        int oc = o.getNameCount();
+        int n = Math.min(mc, oc);
+        int i = 0;
+        while (i < n) {
+            if (!equalsNameAt(o, i)) {
+                break;
+            }
+            i++;
+        }
+        int dotdots = mc - i;
+        int len = dotdots * 3 - 1;
+        if (i < oc) {
+            len += (o.path.length() - o.offsets[i] + 1);
+        }
+        StringBuilder sb  = new StringBuilder(len);
+        while (dotdots > 0) {
+            sb.append("..");
+            if (sb.length() < len) {  // no tailing slash at the end
+                sb.append('/');
+            }
+            dotdots--;
+        }
+        if (i < oc) {
+            sb.append(o.path, o.offsets[i], o.path.length());
+        }
+        return new JrtPath(jrtfs, sb.toString(), true);
+    }
+
+    @Override
+    public JrtFileSystem getFileSystem() {
+        return jrtfs;
+    }
+
+    @Override
+    public final boolean isAbsolute() {
+        return path.length() > 0 && path.charAt(0) == '/';
+    }
+
+    @Override
+    public final JrtPath resolve(Path other) {
+        final JrtPath o = checkPath(other);
+        if (this.path.length() == 0 || o.isAbsolute()) {
+            return o;
+        }
+        if (o.path.length() == 0) {
+            return this;
+        }
+        StringBuilder sb = new StringBuilder(path.length() + o.path.length());
+        sb.append(path);
+        if (path.charAt(path.length() - 1) != '/')
+            sb.append('/');
+        sb.append(o.path);
+        return new JrtPath(jrtfs, sb.toString(), true);
+    }
+
+    @Override
+    public final Path resolveSibling(Path other) {
+        Objects.requireNonNull(other, "other");
+        Path parent = getParent();
+        return (parent == null) ? other : parent.resolve(other);
+    }
+
+    @Override
+    public final boolean startsWith(Path other) {
+        if (!(Objects.requireNonNull(other) instanceof JrtPath))
+            return false;
+        final JrtPath o = (JrtPath)other;
+        final String tp = this.path;
+        final String op = o.path;
+        if (isAbsolute() != o.isAbsolute() || !tp.startsWith(op)) {
+            return false;
+        }
+        int off = op.length();
+        if (off == 0) {
+            return tp.length() == 0;
+        }
+        // check match is on name boundary
+        return tp.length() == off || tp.charAt(off) == '/' ||
+               off == 0 || op.charAt(off - 1) == '/';
+    }
+
+    @Override
+    public final boolean endsWith(Path other) {
+        if (!(Objects.requireNonNull(other) instanceof JrtPath))
+            return false;
+        final JrtPath o = (JrtPath)other;
+        final JrtPath t = this;
+        int olast = o.path.length() - 1;
+        if (olast > 0 && o.path.charAt(olast) == '/') {
+            olast--;
+        }
+        int last = t.path.length() - 1;
+        if (last > 0 && t.path.charAt(last) == '/') {
+            last--;
+        }
+        if (olast == -1) {  // o.path.length == 0
+            return last == -1;
+        }
+        if ((o.isAbsolute() && (!t.isAbsolute() || olast != last))
+            || last < olast) {
+            return false;
+        }
+        for (; olast >= 0; olast--, last--) {
+            if (o.path.charAt(olast) != t.path.charAt(last)) {
+                return false;
+            }
+        }
+        return o.path.charAt(olast + 1) == '/' ||
+               last == -1 || t.path.charAt(last) == '/';
+    }
+
+    @Override
+    public final JrtPath resolve(String other) {
+        return resolve(getFileSystem().getPath(other));
+    }
+
+    @Override
+    public final Path resolveSibling(String other) {
+        return resolveSibling(getFileSystem().getPath(other));
+    }
+
+    @Override
+    public final boolean startsWith(String other) {
+        return startsWith(getFileSystem().getPath(other));
+    }
+
+    @Override
+    public final boolean endsWith(String other) {
+        return endsWith(getFileSystem().getPath(other));
+    }
+
+    @Override
+    public final JrtPath normalize() {
+        String res = getResolved();
+        if (res == path) {  // no change
+            return this;
+        }
+        return new JrtPath(jrtfs, res, true);
+    }
+
+    private JrtPath checkPath(Path path) {
+        Objects.requireNonNull(path);
+        if (!(path instanceof JrtPath))
+            throw new ProviderMismatchException();
+        return (JrtPath) path;
+    }
+
+    // create offset list if not already created
+    private void initOffsets() {
+        if (this.offsets == null) {
+            int len = path.length();
+            // count names
+            int count = 0;
+            int off = 0;
+            while (off < len) {
+                char c = path.charAt(off++);
+                if (c != '/') {
+                    count++;
+                    off = path.indexOf('/', off);
+                    if (off == -1)
+                        break;
+                }
+            }
+            // populate offsets
+            int[] offsets = new int[count];
+            count = 0;
+            off = 0;
+            while (off < len) {
+                char c = path.charAt(off);
+                if (c == '/') {
+                    off++;
+                } else {
+                    offsets[count++] = off++;
+                    off = path.indexOf('/', off);
+                    if (off == -1)
+                        break;
+                }
+            }
+            this.offsets = offsets;
+        }
+    }
+
+    private volatile String resolved;
+
+    final String getResolvedPath() {
+        String r = resolved;
+        if (r == null) {
+            if (isAbsolute()) {
+                r = getResolved();
+            } else {
+                r = toAbsolutePath().getResolvedPath();
+            }
+            resolved = r;
+        }
+        return r;
+    }
+
+    // removes redundant slashs, replace "\" to separator "/"
+    // and check for invalid characters
+    private static String normalize(String path) {
+        int len = path.length();
+        if (len == 0) {
+            return path;
+        }
+        char prevC = 0;
+        for (int i = 0; i < len; i++) {
+            char c = path.charAt(i);
+            if (c == '\\' || c == '\u0000') {
+                return normalize(path, i);
+            }
+            if (c == '/' && prevC == '/') {
+                return normalize(path, i - 1);
+            }
+            prevC = c;
+        }
+        if (prevC == '/' && len > 1) {
+            return path.substring(0, len - 1);
+        }
+        return path;
+    }
+
+    private static String normalize(String path, int off) {
+        int len = path.length();
+        StringBuilder to = new StringBuilder(len);
+        to.append(path, 0, off);
+        char prevC = 0;
+        while (off < len) {
+            char c = path.charAt(off++);
+            if (c == '\\') {
+                c = '/';
+            }
+            if (c == '/' && prevC == '/') {
+                continue;
+            }
+            if (c == '\u0000') {
+                throw new InvalidPathException(path,
+                        "Path: nul character not allowed");
+            }
+            to.append(c);
+            prevC = c;
+        }
+        len = to.length();
+        if (len > 1 && to.charAt(len - 1) == '/') {
+            to.deleteCharAt(len - 1);
+        }
+        return to.toString();
+    }
+
+    // Remove DotSlash(./) and resolve DotDot (..) components
+    private String getResolved() {
+        if (path.length() == 0) {
+            return path;
+        }
+        if (path.indexOf('.') == -1) {
+            return path;
+        }
+        int length = path.length();
+        char[] to = new char[length];
+        int nc = getNameCount();
+        int[] lastM = new int[nc];
+        int lastMOff = -1;
+        int m = 0;
+        for (int i = 0; i < nc; i++) {
+            int n = offsets[i];
+            int len = (i == offsets.length - 1) ? length - n
+                                                : offsets[i + 1] - n - 1;
+            if (len == 1 && path.charAt(n) == '.') {
+                if (m == 0 && path.charAt(0) == '/')   // absolute path
+                    to[m++] = '/';
+                continue;
+            }
+            if (len == 2 && path.charAt(n) == '.' && path.charAt(n + 1) == '.') {
+                if (lastMOff >= 0) {
+                    m = lastM[lastMOff--];    // retreat
+                    continue;
+                }
+                if (path.charAt(0) == '/') {  // "/../xyz" skip
+                    if (m == 0)
+                        to[m++] = '/';
+                } else {                      // "../xyz" -> "../xyz"
+                    if (m != 0 && to[m-1] != '/')
+                        to[m++] = '/';
+                    while (len-- > 0)
+                        to[m++] = path.charAt(n++);
+                }
+                continue;
+            }
+            if (m == 0 && path.charAt(0) == '/' ||   // absolute path
+                m != 0 && to[m-1] != '/') {   // not the first name
+                to[m++] = '/';
+            }
+            lastM[++lastMOff] = m;
+            while (len-- > 0)
+                to[m++] = path.charAt(n++);
+        }
+        if (m > 1 && to[m - 1] == '/')
+            m--;
+        return (m == to.length) ? new String(to) : new String(to, 0, m);
+    }
+
+    @Override
+    public final String toString() {
+        return path;
+    }
+
+    @Override
+    public final int hashCode() {
+        return path.hashCode();
+    }
+
+    @Override
+    public final boolean equals(Object obj) {
+        return obj instanceof JrtPath &&
+               this.path.equals(((JrtPath) obj).path);
+    }
+
+    @Override
+    public final int compareTo(Path other) {
+        final JrtPath o = checkPath(other);
+        return path.compareTo(o.path);
+    }
+
+    @Override
+    public final WatchKey register(
+            WatchService watcher,
+            WatchEvent.Kind<?>[] events,
+            WatchEvent.Modifier... modifiers) {
+        Objects.requireNonNull(watcher, "watcher");
+        Objects.requireNonNull(events, "events");
+        Objects.requireNonNull(modifiers, "modifiers");
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public final WatchKey register(WatchService watcher, WatchEvent.Kind<?>... events) {
+        return register(watcher, events, new WatchEvent.Modifier[0]);
+    }
+
+    @Override
+    public final File toFile() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public final Iterator<Path> iterator() {
+        return new Iterator<Path>() {
+            private int i = 0;
+
+            @Override
+            public boolean hasNext() {
+                return (i < getNameCount());
+            }
+
+            @Override
+            public Path next() {
+                if (i < getNameCount()) {
+                    Path result = getName(i);
+                    i++;
+                    return result;
+                } else {
+                    throw new NoSuchElementException();
+                }
+            }
+
+            @Override
+            public void remove() {
+                throw new ReadOnlyFileSystemException();
+            }
+        };
+    }
+
+    // Helpers for JrtFileSystemProvider and JrtFileSystem
+
+    final JrtPath readSymbolicLink() throws IOException {
+        if (!jrtfs.isLink(this)) {
+            throw new IOException("not a symbolic link");
+        }
+        return jrtfs.resolveLink(this);
+    }
+
+    final boolean isHidden() {
+        return false;
+    }
+
+    final void createDirectory(FileAttribute<?>... attrs)
+            throws IOException {
+        jrtfs.createDirectory(this, attrs);
+    }
+
+    final InputStream newInputStream(OpenOption... options) throws IOException {
+        if (options.length > 0) {
+            for (OpenOption opt : options) {
+                if (opt != READ) {
+                    throw new UnsupportedOperationException("'" + opt + "' not allowed");
+                }
+            }
+        }
+        return jrtfs.newInputStream(this);
+    }
+
+    final DirectoryStream<Path> newDirectoryStream(Filter<? super Path> filter)
+            throws IOException {
+        return new JrtDirectoryStream(this, filter);
+    }
+
+    final void delete() throws IOException {
+        jrtfs.deleteFile(this, true);
+    }
+
+    final void deleteIfExists() throws IOException {
+        jrtfs.deleteFile(this, false);
+    }
+
+    final JrtFileAttributes getAttributes(LinkOption... options) throws IOException {
+        JrtFileAttributes zfas = jrtfs.getFileAttributes(this, options);
+        if (zfas == null) {
+            throw new NoSuchFileException(toString());
+        }
+        return zfas;
+    }
+
+    final void setAttribute(String attribute, Object value, LinkOption... options)
+            throws IOException {
+        JrtFileAttributeView.setAttribute(this, attribute, value);
+    }
+
+    final Map<String, Object> readAttributes(String attributes, LinkOption... options)
+            throws IOException {
+        return JrtFileAttributeView.readAttributes(this, attributes, options);
+    }
+
+    final void setTimes(FileTime mtime, FileTime atime, FileTime ctime)
+            throws IOException {
+        jrtfs.setTimes(this, mtime, atime, ctime);
+    }
+
+    final FileStore getFileStore() throws IOException {
+        // each JrtFileSystem only has one root (as requested for now)
+        if (exists()) {
+            return jrtfs.getFileStore(this);
+        }
+        throw new NoSuchFileException(path);
+    }
+
+    final boolean isSameFile(Path other) throws IOException {
+        if (this == other || this.equals(other)) {
+            return true;
+        }
+        if (other == null || this.getFileSystem() != other.getFileSystem()) {
+            return false;
+        }
+        this.checkAccess();
+        JrtPath o = (JrtPath) other;
+        o.checkAccess();
+        return this.getResolvedPath().equals(o.getResolvedPath()) ||
+               jrtfs.isSameFile(this, o);
+    }
+
+    final SeekableByteChannel newByteChannel(Set<? extends OpenOption> options,
+                                             FileAttribute<?>... attrs)
+            throws IOException
+    {
+        return jrtfs.newByteChannel(this, options, attrs);
+    }
+
+    final FileChannel newFileChannel(Set<? extends OpenOption> options,
+            FileAttribute<?>... attrs)
+            throws IOException {
+        return jrtfs.newFileChannel(this, options, attrs);
+    }
+
+    final void checkAccess(AccessMode... modes) throws IOException {
+        if (modes.length == 0) {    // check if the path exists
+            jrtfs.checkNode(this);  // no need to follow link. the "link" node
+                                    // is built from real node under "/module"
+        } else {
+            boolean w = false;
+            for (AccessMode mode : modes) {
+                switch (mode) {
+                    case READ:
+                        break;
+                    case WRITE:
+                        w = true;
+                        break;
+                    case EXECUTE:
+                        throw new AccessDeniedException(toString());
+                    default:
+                        throw new UnsupportedOperationException();
+                }
+            }
+            jrtfs.checkNode(this);
+            if (w && jrtfs.isReadOnly()) {
+                throw new AccessDeniedException(toString());
+            }
+        }
+    }
+
+    final boolean exists() {
+        try {
+            return jrtfs.exists(this);
+        } catch (IOException x) {}
+        return false;
+    }
+
+    final OutputStream newOutputStream(OpenOption... options) throws IOException {
+        if (options.length == 0) {
+            return jrtfs.newOutputStream(this, CREATE_NEW, WRITE);
+        }
+        return jrtfs.newOutputStream(this, options);
+    }
+
+    final void move(JrtPath target, CopyOption... options) throws IOException {
+        if (this.jrtfs == target.jrtfs) {
+            jrtfs.copyFile(true, this, target, options);
+        } else {
+            copyToTarget(target, options);
+            delete();
+        }
+    }
+
+    final void copy(JrtPath target, CopyOption... options) throws IOException {
+        if (this.jrtfs == target.jrtfs) {
+            jrtfs.copyFile(false, this, target, options);
+        } else {
+            copyToTarget(target, options);
+        }
+    }
+
+    private void copyToTarget(JrtPath target, CopyOption... options)
+            throws IOException {
+        boolean replaceExisting = false;
+        boolean copyAttrs = false;
+        for (CopyOption opt : options) {
+            if (opt == REPLACE_EXISTING) {
+                replaceExisting = true;
+            } else if (opt == COPY_ATTRIBUTES) {
+                copyAttrs = true;
+            }
+        }
+        // attributes of source file
+        BasicFileAttributes jrtfas = getAttributes();
+        // check if target exists
+        boolean exists;
+        if (replaceExisting) {
+            try {
+                target.deleteIfExists();
+                exists = false;
+            } catch (DirectoryNotEmptyException x) {
+                exists = true;
+            }
+        } else {
+            exists = target.exists();
+        }
+        if (exists) {
+            throw new FileAlreadyExistsException(target.toString());
+        }
+        if (jrtfas.isDirectory()) {
+            // create directory or file
+            target.createDirectory();
+        } else {
+            try (InputStream is = jrtfs.newInputStream(this);
+                 OutputStream os = target.newOutputStream()) {
+                byte[] buf = new byte[8192];
+                int n;
+                while ((n = is.read(buf)) != -1) {
+                    os.write(buf, 0, n);
+                }
+            }
+        }
+        if (copyAttrs) {
+            BasicFileAttributeView view =
+                Files.getFileAttributeView(target, BasicFileAttributeView.class);
+            try {
+                view.setTimes(jrtfas.lastModifiedTime(),
+                              jrtfas.lastAccessTime(),
+                              jrtfas.creationTime());
+            } catch (IOException x) {
+                try {
+                    target.delete();  // rollback?
+                } catch (IOException ignore) {}
+                throw x;
+            }
+        }
     }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/share/classes/jdk/internal/jrtfs/SystemImage.java	Fri Apr 15 13:05:52 2016 -0700
@@ -0,0 +1,126 @@
+/*
+ * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.internal.jrtfs;
+
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystems;
+import java.nio.file.FileSystemNotFoundException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.security.AccessController;
+import java.security.CodeSource;
+import java.security.PrivilegedAction;
+
+import jdk.internal.jimage.ImageReader;
+import jdk.internal.jimage.ImageReader.Node;
+
+/**
+ * @implNote This class needs to maintain JDK 8 source compatibility.
+ *
+ * It is used internally in the JDK to implement jimage/jrtfs access,
+ * but also compiled and delivered as part of the jrtfs.jar to support access
+ * to the jimage file provided by the shipped JDK by tools running on JDK 8.
+ */
+abstract class SystemImage {
+
+    abstract Node findNode(String path);
+    abstract byte[] getResource(Node node) throws IOException;
+    abstract void close() throws IOException;
+
+    static SystemImage open() throws IOException {
+        if (modulesImageExists) {
+            // open a .jimage and build directory structure
+            final ImageReader image = ImageReader.open(moduleImageFile);
+            image.getRootDirectory();
+            return new SystemImage() {
+                @Override
+                Node findNode(String path) {
+                    return image.findNode(path);
+                }
+                @Override
+                byte[] getResource(Node node) throws IOException {
+                    return image.getResource(node);
+                }
+                @Override
+                void close() throws IOException {
+                    image.close();
+                }
+            };
+        }
+        if (Files.notExists(explodedModulesDir))
+            throw new FileSystemNotFoundException(explodedModulesDir.toString());
+        return new ExplodedImage(explodedModulesDir);
+    }
+
+    static final String RUNTIME_HOME;
+    // "modules" jimage file Path
+    final static Path moduleImageFile;
+    // "modules" jimage exists or not?
+    final static boolean modulesImageExists;
+    // <JAVA_HOME>/modules directory Path
+    static final Path explodedModulesDir;
+
+    static {
+        PrivilegedAction<String> pa = SystemImage::findHome;
+        RUNTIME_HOME = AccessController.doPrivileged(pa);
+
+        FileSystem fs = FileSystems.getDefault();
+        moduleImageFile = fs.getPath(RUNTIME_HOME, "lib", "modules");
+        explodedModulesDir = fs.getPath(RUNTIME_HOME, "modules");
+
+        modulesImageExists = AccessController.doPrivileged(
+            new PrivilegedAction<Boolean>() {
+                @Override
+                public Boolean run() {
+                    return Files.isRegularFile(moduleImageFile);
+                }
+            });
+    }
+
+    /**
+     * Returns the appropriate JDK home for this usage of the FileSystemProvider.
+     * When the CodeSource is null (null loader) then jrt:/ is the current runtime,
+     * otherwise the JDK home is located relative to jrt-fs.jar.
+     */
+    private static String findHome() {
+        CodeSource cs = SystemImage.class.getProtectionDomain().getCodeSource();
+        if (cs == null)
+            return System.getProperty("java.home");
+
+        // assume loaded from $TARGETJDK/jrt-fs.jar
+        URL url = cs.getLocation();
+        if (!url.getProtocol().equalsIgnoreCase("file"))
+            throw new RuntimeException(url + " loaded in unexpected way");
+        try {
+            return Paths.get(url.toURI()).getParent().toString();
+        } catch (URISyntaxException e) {
+            throw new InternalError(e);
+        }
+    }
+}
--- a/src/java.base/share/classes/jdk/internal/jrtfs/SystemImages.java	Fri Apr 15 10:14:57 2016 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,106 +0,0 @@
-/*
- * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-package jdk.internal.jrtfs;
-
-import java.net.URISyntaxException;
-import java.net.URL;
-import java.nio.file.Files;
-import java.nio.file.FileSystem;
-import java.nio.file.FileSystems;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.security.AccessController;
-import java.security.CodeSource;
-import java.security.PrivilegedAction;
-
-/**
- * @implNote This class needs to maintain JDK 8 source compatibility.
- *
- * It is used internally in the JDK to implement jimage/jrtfs access,
- * but also compiled and delivered as part of the jrtfs.jar to support access
- * to the jimage file provided by the shipped JDK by tools running on JDK 8.
- */
-final class SystemImages {
-    private SystemImages() {}
-
-
-    static final String RUNTIME_HOME;
-    // "modules" jimage file Path
-    private static final Path moduleImageFile;
-    // "modules" jimage exists or not?
-    private static final boolean modulesImageExists;
-    // <JAVA_HOME>/modules directory Path
-    private static final Path explodedModulesDir;
-
-    static {
-        PrivilegedAction<String> pa = SystemImages::findHome;
-        RUNTIME_HOME = AccessController.doPrivileged(pa);
-
-        FileSystem fs = FileSystems.getDefault();
-        moduleImageFile = fs.getPath(RUNTIME_HOME, "lib", "modules");
-        explodedModulesDir = fs.getPath(RUNTIME_HOME, "modules");
-
-        modulesImageExists = AccessController.doPrivileged(
-            new PrivilegedAction<Boolean>() {
-                @Override
-                public Boolean run() {
-                    return Files.isRegularFile(moduleImageFile);
-                }
-            });
-    }
-
-    static boolean hasModulesImage() {
-        return modulesImageExists;
-    }
-
-    static Path moduleImageFile() {
-        return moduleImageFile;
-    }
-
-    static Path explodedModulesDir() {
-        return explodedModulesDir;
-    }
-
-    /**
-     * Returns the appropriate JDK home for this usage of the FileSystemProvider.
-     * When the CodeSource is null (null loader) then jrt:/ is the current runtime,
-     * otherwise the JDK home is located relative to jrt-fs.jar.
-     */
-    private static String findHome() {
-        CodeSource cs = SystemImages.class.getProtectionDomain().getCodeSource();
-        if (cs == null)
-            return System.getProperty("java.home");
-
-        // assume loaded from $TARGETJDK/jrt-fs.jar
-        URL url = cs.getLocation();
-        if (!url.getProtocol().equalsIgnoreCase("file"))
-            throw new RuntimeException(url + " loaded in unexpected way");
-        try {
-            return Paths.get(url.toURI()).getParent().toString();
-        } catch (URISyntaxException e) {
-            throw new InternalError(e);
-        }
-    }
-}
--- a/test/jdk/internal/jrtfs/PathOps.java	Fri Apr 15 10:14:57 2016 -0700
+++ b/test/jdk/internal/jrtfs/PathOps.java	Fri Apr 15 13:05:52 2016 -0700
@@ -43,15 +43,15 @@
     private Path path;
     private Exception exc;
 
-    private PathOps(String s) {
+    private PathOps(String first, String... more) {
         out.println();
-        input = s;
+        input = first;
         try {
-            path = fs.getPath(s);
-            out.format("%s -> %s", s, path);
+            path = fs.getPath(first, more);
+            out.format("%s -> %s", first, path);
         } catch (Exception x) {
             exc = x;
-            out.format("%s -> %s", s, x);
+            out.format("%s -> %s", first, x);
         }
         out.println();
     }
@@ -175,6 +175,13 @@
         return this;
     }
 
+    PathOps resolveSibling(String other, String expected) {
+        out.format("test resolveSibling %s\n", other);
+        checkPath();
+        check(path.resolveSibling(other), expected);
+        return this;
+    }
+
     PathOps relativize(String other, String expected) {
         out.format("test relativize %s\n", other);
         checkPath();
@@ -220,6 +227,10 @@
         return new PathOps(s);
     }
 
+    static PathOps test(String first, String... more) {
+        return new PathOps(first, more);
+    }
+
     // -- PathOpss --
 
     static void header(String s) {
@@ -231,6 +242,26 @@
     static void doPathOpTests() {
         header("Path operations");
 
+        // construction
+        test("/")
+            .string("/");
+        test("/", "")
+            .string("/");
+        test("/", "foo")
+            .string("/foo");
+        test("/", "/foo")
+            .string("/foo");
+        test("/", "foo/")
+            .string("/foo");
+        test("foo", "bar", "gus")
+            .string("foo/bar/gus");
+        test("")
+            .string("");
+        test("", "/")
+            .string("/");
+        test("", "foo", "", "bar", "", "/gus")
+            .string("foo/bar/gus");
+
         // all components
         test("/a/b/c")
             .root("/")
@@ -254,6 +285,10 @@
              .root(null)
              .parent(null)
              .name("foo");
+        test("")
+             .root(null)
+             .parent(null)
+             .name("");
 
         // startsWith
         test("")
@@ -261,6 +296,7 @@
             .notStarts("/");
         test("/")
             .starts("/")
+            .notStarts("")
             .notStarts("/foo");
         test("/foo")
             .starts("/")
@@ -278,6 +314,7 @@
             .notStarts("");
         test("foo")
             .starts("foo")
+            .notStarts("")
             .notStarts("f");
         test("foo/bar")
             .starts("foo")
@@ -293,12 +330,14 @@
             .notEnds("/");
         test("/")
             .ends("/")
+            .notEnds("")
             .notEnds("foo")
             .notEnds("/foo");
         test("/foo")
             .ends("foo")
             .ends("/foo")
-            .notEnds("/");
+            .notEnds("/")
+            .notEnds("fool");
         test("/foo/bar")
             .ends("bar")
             .ends("foo/bar")
@@ -312,13 +351,31 @@
             .ends("/foo/bar")
             .notEnds("/bar");
         test("foo")
-            .ends("foo");
+            .ends("foo")
+            .notEnds("")
+            .notEnds("oo")
+            .notEnds("oola");
         test("foo/bar")
             .ends("bar")
             .ends("bar/")
             .ends("foo/bar/")
-            .ends("foo/bar");
-
+            .ends("foo/bar")
+            .notEnds("r")
+            .notEnds("barmaid")
+            .notEnds("/bar")
+            .notEnds("ar")
+            .notEnds("barack")
+            .notEnds("/bar")
+            .notEnds("o/bar");
+        test("foo/bar/gus")
+            .ends("gus")
+            .ends("bar/gus")
+            .ends("foo/bar/gus")
+            .notEnds("g")
+            .notEnds("/gus")
+            .notEnds("r/gus")
+            .notEnds("barack/gus")
+            .notEnds("bar/gust");
 
         // elements
         test("a/b/c")
@@ -339,16 +396,54 @@
         // resolve
         test("/tmp")
             .resolve("foo", "/tmp/foo")
-            .resolve("/foo", "/foo");
+            .resolve("/foo", "/foo")
+            .resolve("", "/tmp");
         test("tmp")
             .resolve("foo", "tmp/foo")
+            .resolve("/foo", "/foo")
+            .resolve("", "tmp");
+        test("")
+            .resolve("", "")
+            .resolve("foo", "foo")
             .resolve("/foo", "/foo");
 
+        // resolveSibling
+        test("foo")
+            .resolveSibling("bar", "bar")
+            .resolveSibling("/bar", "/bar")
+            .resolveSibling("", "");
+        test("foo/bar")
+            .resolveSibling("gus", "foo/gus")
+            .resolveSibling("/gus", "/gus")
+            .resolveSibling("", "foo");
+        test("/foo")
+            .resolveSibling("gus", "/gus")
+            .resolveSibling("/gus", "/gus")
+            .resolveSibling("", "/");
+        test("/foo/bar")
+            .resolveSibling("gus", "/foo/gus")
+            .resolveSibling("/gus", "/gus")
+            .resolveSibling("", "/foo");
+        test("")
+            .resolveSibling("foo", "foo")
+            .resolveSibling("/foo", "/foo")
+            .resolve("", "");
+
         // relativize
         test("/a/b/c")
             .relativize("/a/b/c", "")
             .relativize("/a/b/c/d/e", "d/e")
-            .relativize("/a/x", "../../x");
+            .relativize("/a/x", "../../x")
+            .relativize("/x", "../../../x");
+        test("a/b/c")
+            .relativize("a/b/c/d", "d")
+            .relativize("a/x", "../../x")
+            .relativize("x", "../../../x")
+            .relativize("", "../../..");
+        test("")
+            .relativize("a", "a")
+            .relativize("a/b/c", "a/b/c")
+            .relativize("", "");
 
         // normalize
         test("/")