Merge
authordcubed
Fri, 03 Jul 2015 07:13:24 -0700
changeset 31676 1a415610d9dd
parent 31476 d1e837c96aba (current diff)
parent 31675 d77c79cba81d (diff)
child 31677 23b656089d20
Merge
jdk/make/src/classes/build/tools/module/ModuleArchive.java
jdk/src/java.base/share/classes/jdk/internal/jimage/ImageFile.java
jdk/src/java.base/share/classes/jdk/internal/jimage/ImageModules.java
jdk/src/java.base/share/classes/jdk/internal/jimage/PReader.java
jdk/src/java.base/share/classes/jdk/internal/jimage/PackageModuleMap.java
jdk/src/java.base/share/classes/jdk/internal/jimage/Resource.java
jdk/src/java.base/share/classes/jdk/internal/jimage/concurrent/ConcurrentPReader.java
jdk/src/java.base/unix/native/libjava/ConcurrentPReader_md.c
jdk/src/java.base/windows/native/libjava/ConcurrentPReader_md.c
--- a/jdk/make/Tools.gmk	Thu Jul 02 15:25:46 2015 +0200
+++ b/jdk/make/Tools.gmk	Fri Jul 03 07:13:24 2015 -0700
@@ -144,7 +144,6 @@
     SETUP := GENERATE_OLDBYTECODE, \
     SRC := $(JDK_TOPDIR)/src/java.base/share/classes, \
     INCLUDES := $(JIMAGE_PKGS), \
-    EXCLUDES := jdk/internal/jimage/concurrent, \
     BIN := $(BUILDTOOLS_OUTPUTDIR)/interim_jimage_classes))
 
 # Because of the explicit INCLUDES in the compilation setup above, the service provider
--- a/jdk/make/mapfiles/libjava/mapfile-vers	Thu Jul 02 15:25:46 2015 +0200
+++ b/jdk/make/mapfiles/libjava/mapfile-vers	Fri Jul 03 07:13:24 2015 -0700
@@ -239,6 +239,16 @@
 		Java_java_util_TimeZone_getSystemTimeZoneID;
 		Java_java_util_TimeZone_getSystemGMTOffsetID;
 		Java_java_util_concurrent_atomic_AtomicLong_VMSupportsCS8;
+                Java_jdk_internal_jimage_ImageNativeSubstrate_openImage;
+                Java_jdk_internal_jimage_ImageNativeSubstrate_closeImage;
+                Java_jdk_internal_jimage_ImageNativeSubstrate_getIndexAddress;
+                Java_jdk_internal_jimage_ImageNativeSubstrate_getDataAddress;
+                Java_jdk_internal_jimage_ImageNativeSubstrate_read;
+                Java_jdk_internal_jimage_ImageNativeSubstrate_readCompressed;
+                Java_jdk_internal_jimage_ImageNativeSubstrate_getStringBytes;
+                Java_jdk_internal_jimage_ImageNativeSubstrate_getAttributes;
+                Java_jdk_internal_jimage_ImageNativeSubstrate_findAttributes;
+                Java_jdk_internal_jimage_ImageNativeSubstrate_attributeOffsets;
 		Java_sun_misc_MessageUtils_toStderr;
 		Java_sun_misc_MessageUtils_toStdout;
 		Java_sun_misc_NativeSignalHandler_handle0;
@@ -281,9 +291,6 @@
 		Java_sun_misc_VMSupport_initAgentProperties;
 		Java_sun_misc_VMSupport_getVMTemporaryDirectory;
 
-                Java_jdk_internal_jimage_concurrent_ConcurrentPReader_initIDs;
-                Java_jdk_internal_jimage_concurrent_ConcurrentPReader_pread;
-
                 # ZipFile.c needs this one
 		throwFileNotFoundException;
                 # zip_util.c needs this one
--- a/jdk/make/src/classes/build/tools/module/ImageBuilder.java	Thu Jul 02 15:25:46 2015 +0200
+++ b/jdk/make/src/classes/build/tools/module/ImageBuilder.java	Fri Jul 03 07:13:24 2015 -0700
@@ -26,8 +26,6 @@
 package build.tools.module;
 
 import jdk.internal.jimage.Archive;
-import jdk.internal.jimage.ImageFile;
-import jdk.internal.jimage.ImageModules;
 
 import java.io.BufferedReader;
 import java.io.File;
@@ -35,13 +33,11 @@
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.io.PrintWriter;
-import java.io.UncheckedIOException;
 import java.nio.ByteOrder;
 import java.nio.file.Files;
 import java.nio.file.InvalidPathException;
 import java.nio.file.Path;
 import java.nio.file.Paths;
-import java.nio.file.attribute.PosixFilePermission;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
@@ -52,6 +48,7 @@
 import java.util.Optional;
 import java.util.Set;
 import java.util.stream.Collectors;
+import jdk.internal.jimage.ImageFileCreator;
 
 /**
  * A tool for building a runtime image.
@@ -84,11 +81,23 @@
         boolean showUsage;
     }
 
-    static abstract class Option {
+    static class Option {
+
+        interface Processing {
+
+            void process(ImageBuilder task, String opt, String arg) throws BadArgs;
+        }
+
         final boolean hasArg;
         final String[] aliases;
-        Option(boolean hasArg, String... aliases) {
+        final String description;
+        final Processing processing;
+
+        Option(boolean hasArg, String description, Processing processing,
+                String... aliases) {
             this.hasArg = hasArg;
+            this.description = description;
+            this.processing = processing;
             this.aliases = aliases;
         }
         boolean isHidden() {
@@ -107,8 +116,12 @@
         boolean ignoreRest() {
             return false;
         }
-        abstract void process(ImageBuilder task, String opt, String arg) throws BadArgs;
-        abstract String description();
+        void process(ImageBuilder task, String opt, String arg) throws BadArgs {
+            processing.process(task, opt, arg);
+        }
+        String description() {
+            return description;
+        }
     }
 
     private static Path CWD = Paths.get("");
@@ -133,64 +146,44 @@
     }
 
     static Option[] recognizedOptions = {
-        new Option(true, "--cmds") {
-            void process(ImageBuilder task, String opt, String arg) throws BadArgs {
-                task.options.cmds = splitPath(arg, File.pathSeparator);
-            }
-            String description() { return "Location of native commands"; }
-        },
-        new Option(true, "--configs") {
-            void process(ImageBuilder task, String opt, String arg) throws BadArgs {
-                task.options.configs = splitPath(arg, File.pathSeparator);
-            }
-            String description() { return "Location of config files"; }
-        },
-        new Option(false, "--help") {
-            void process(ImageBuilder task, String opt, String arg) {
-                task.options.help = true;
-            }
-            String description() { return "Print this usage message"; }
-        },
-        new Option(true, "--classes") {
-            void process(ImageBuilder task, String opt, String arg) throws BadArgs {
-                task.options.classes = splitPath(arg, File.pathSeparator);
-            }
-            String description() { return "Location of module classes files"; }
-        },
-        new Option(true, "--libs") {
-            void process(ImageBuilder task, String opt, String arg) throws BadArgs {
-                task.options.libs = splitPath(arg, File.pathSeparator);
+        new Option(true, "Location of native commands", (task, opt, arg) -> {
+            task.options.cmds = splitPath(arg, File.pathSeparator);
+        }, "--cmds"),
+        new Option(true, "Location of config files", (task, opt, arg) -> {
+            task.options.configs = splitPath(arg, File.pathSeparator);
+        }, "--configs"),
+        new Option(false, "Print this usage message", (task, opt, arg) -> {
+            task.options.help = true;
+        }, "--help"),
+        new Option(true, "Location of module classes files", (task, opt, arg) -> {
+            task.options.classes = splitPath(arg, File.pathSeparator);
+        }, "--classes"),
+        new Option(true, "Location of native libraries", (task, opt, arg) -> {
+            task.options.libs = splitPath(arg, File.pathSeparator);
+        }, "--libs"),
+        new Option(true, "Comma separated list of module names",
+        (task, opt, arg) -> {
+            for (String mn : arg.split(",")) {
+                if (mn.isEmpty()) {
+                    throw new BadArgs("Module not found", mn);
+                }
+                task.options.mods.add(mn);
             }
-            String description() { return "Location of native libraries"; }
-        },
-        new Option(true, "--mods") {
-            void process(ImageBuilder task, String opt, String arg) throws BadArgs {
-                for (String mn : arg.split(",")) {
-                    if (mn.isEmpty())
-                        throw new BadArgs("Module not found", mn);
-                    task.options.mods.add(mn);
-                }
+        }, "--mods"),
+        new Option(true, "Location of the output path", (task, opt, arg) -> {
+            Path path = Paths.get(arg);
+            task.options.output = path;
+        }, "--output"),
+        new Option(true, "Byte order of the target runtime; {little,big}",
+        (task, opt, arg) -> {
+            if (arg.equals("little")) {
+                task.options.endian = ByteOrder.LITTLE_ENDIAN;
+            } else if (arg.equals("big")) {
+                task.options.endian = ByteOrder.BIG_ENDIAN;
+            } else {
+                throw new BadArgs("Unknown byte order " + arg);
             }
-            String description() { return "Comma separated list of module names"; }
-        },
-        new Option(true, "--output") {
-            void process(ImageBuilder task, String opt, String arg) throws BadArgs {
-                Path path = Paths.get(arg);
-                task.options.output = path;
-            }
-            String description() { return "Location of the output path"; }
-        },
-        new Option(true, "--endian") {
-            void process(ImageBuilder task, String opt, String arg) throws BadArgs {
-                if (arg.equals("little"))
-                    task.options.endian = ByteOrder.LITTLE_ENDIAN;
-                else if (arg.equals("big"))
-                    task.options.endian = ByteOrder.BIG_ENDIAN;
-                else
-                    throw new BadArgs("Unknown byte order " + arg);
-            }
-            String description() { return "Byte order of the target runtime; {little,big}"; }
-        }
+        }, "--endian")
     };
 
     private final Options options = new Options();
@@ -370,28 +363,35 @@
         final Set<String> bootModules;
         final Set<String> extModules;
         final Set<String> appModules;
-        final ImageModules imf;
 
         ImageFileHelper(Collection<String> modules) throws IOException {
             this.modules = modules;
             this.bootModules = modulesFor(BOOT_MODULES).stream()
-                     .filter(modules::contains)
-                     .collect(Collectors.toSet());
+                    .filter(modules::contains)
+                    .collect(Collectors.toSet());
             this.extModules = modulesFor(EXT_MODULES).stream()
                     .filter(modules::contains)
                     .collect(Collectors.toSet());
             this.appModules = modules.stream()
-                    .filter(m -> !bootModules.contains(m) && !extModules.contains(m))
+                    .filter(m -> m.length() != 0 &&
+                                 !bootModules.contains(m) &&
+                                 !extModules.contains(m))
                     .collect(Collectors.toSet());
-
-            this.imf = new ImageModules(bootModules, extModules, appModules);
         }
 
         void createModularImage(Path output) throws IOException {
-            Set<Archive> archives = modules.stream()
-                                            .map(this::toModuleArchive)
-                                            .collect(Collectors.toSet());
-            ImageFile.create(output, archives, imf, options.endian);
+            Set<Archive> bootArchives = bootModules.stream()
+                    .map(this::toModuleArchive)
+                    .collect(Collectors.toSet());
+            Set<Archive> extArchives = extModules.stream()
+                    .map(this::toModuleArchive)
+                    .collect(Collectors.toSet());
+            Set<Archive> appArchives = appModules.stream()
+                    .map(this::toModuleArchive)
+                    .collect(Collectors.toSet());
+            ImageFileCreator.create(output, "bootmodules", bootArchives, options.endian);
+            ImageFileCreator.create(output, "extmodules", extArchives, options.endian);
+            ImageFileCreator.create(output, "appmodules", appArchives, options.endian);
         }
 
         ModuleArchive toModuleArchive(String mn) {
--- a/jdk/make/src/classes/build/tools/module/ModuleArchive.java	Thu Jul 02 15:25:46 2015 +0200
+++ b/jdk/make/src/classes/build/tools/module/ModuleArchive.java	Fri Jul 03 07:13:24 2015 -0700
@@ -26,15 +26,17 @@
 package build.tools.module;
 
 import jdk.internal.jimage.Archive;
-import jdk.internal.jimage.Resource;
 
 import java.io.IOException;
 import java.io.InputStream;
-import java.io.OutputStream;
 import java.io.UncheckedIOException;
 import java.nio.file.Files;
 import java.nio.file.Path;
-import java.util.function.Consumer;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import jdk.internal.jimage.Archive.Entry.EntryType;
 
 /**
  * An Archive backed by an exploded representation on disk.
@@ -46,6 +48,8 @@
     private final Path configs;
     private final String moduleName;
 
+    private final List<InputStream> opened = new ArrayList<>();
+
     public ModuleArchive(String moduleName, Path classes, Path cmds,
                          Path libs, Path configs) {
         this.moduleName = moduleName;
@@ -61,183 +65,119 @@
     }
 
     @Override
-    public void visitResources(Consumer<Resource> consumer) {
-        if (classes == null)
-            return;
-        try{
-            Files.walk(classes)
-                    .sorted()
-                    .filter(p -> !Files.isDirectory(p)
-                            && !classes.relativize(p).toString().startsWith("_the.")
-                            && !classes.relativize(p).toString().equals("javac_state"))
-                    .map(this::toResource)
-                    .forEach(consumer::accept);
-        } catch (IOException ioe) {
-            throw new UncheckedIOException(ioe);
-        }
+    public void open() throws IOException {
+        // NOOP
     }
 
-    private Resource toResource(Path path) {
-        try {
-            return new Resource(classes.relativize(path).toString().replace('\\','/'),
-                                Files.size(path),
-                                0 /* no compression support yet */);
-        } catch (IOException ioe) {
-            throw new UncheckedIOException(ioe);
+    @Override
+    public void close() throws IOException {
+        IOException e = null;
+        for (InputStream stream : opened) {
+            try {
+                stream.close();
+            } catch (IOException ex) {
+                if (e == null) {
+                    e = ex;
+                } else {
+                    e.addSuppressed(ex);
+                }
+            }
         }
-    }
-
-    private enum Section {
-        CLASSES,
-        CMDS,
-        LIBS,
-        CONFIGS
+        if (e != null) {
+            throw e;
+        }
     }
 
     @Override
-    public void visitEntries(Consumer<Entry> consumer) {
-        try{
-            if (classes != null)
-                Files.walk(classes)
-                        .sorted()
-                        .filter(p -> !Files.isDirectory(p)
-                                && !classes.relativize(p).toString().startsWith("_the.")
-                                && !classes.relativize(p).toString().equals("javac_state"))
-                        .map(p -> toEntry(p, classes, Section.CLASSES))
-                        .forEach(consumer::accept);
-            if (cmds != null)
-                Files.walk(cmds)
+    public Stream<Entry> entries() {
+        List<Entry> entries = new ArrayList<>();
+        try {
+            /*
+             * This code should be revisited to avoid buffering of the entries.
+             * 1) Do we really need sorting classes? This force buffering of entries.
+             *    libs, cmds and configs are not sorted.
+             * 2) I/O streams should be concatenated instead of buffering into
+             *    entries list.
+             * 3) Close I/O streams in a close handler.
+             */
+            if (classes != null) {
+                try (Stream<Path> stream = Files.walk(classes)) {
+                    entries.addAll(stream
+                            .filter(p -> !Files.isDirectory(p)
+                                    && !classes.relativize(p).toString().startsWith("_the.")
+                                    && !classes.relativize(p).toString().equals("javac_state"))
+                            .sorted()
+                            .map(p -> toEntry(p, classes, EntryType.CLASS_OR_RESOURCE))
+                            .collect(Collectors.toList()));
+                }
+            }
+            if (cmds != null) {
+                try (Stream<Path> stream = Files.walk(cmds)) {
+                    entries.addAll(stream
+                            .filter(p -> !Files.isDirectory(p))
+                            .map(p -> toEntry(p, cmds, EntryType.NATIVE_CMD))
+                            .collect(Collectors.toList()));
+                }
+            }
+            if (libs != null) {
+                try (Stream<Path> stream = Files.walk(libs)) {
+                    entries.addAll(stream
+                            .filter(p -> !Files.isDirectory(p))
+                            .map(p -> toEntry(p, libs, EntryType.NATIVE_LIB))
+                            .collect(Collectors.toList()));
+                }
+            }
+            if (configs != null) {
+                try (Stream<Path> stream = Files.walk(configs)) {
+                entries.addAll(stream
                         .filter(p -> !Files.isDirectory(p))
-                        .map(p -> toEntry(p, cmds, Section.CMDS))
-                        .forEach(consumer::accept);
-            if (libs != null)
-                Files.walk(libs)
-                        .filter(p -> !Files.isDirectory(p))
-                        .map(p -> toEntry(p, libs, Section.LIBS))
-                        .forEach(consumer::accept);
-            if (configs != null)
-                Files.walk(configs)
-                        .filter(p -> !Files.isDirectory(p))
-                        .map(p -> toEntry(p, configs, Section.CONFIGS))
-                        .forEach(consumer::accept);
+                        .map(p -> toEntry(p, configs, EntryType.CONFIG))
+                        .collect(Collectors.toList()));
+                }
+            }
         } catch (IOException ioe) {
             throw new UncheckedIOException(ioe);
         }
+        return entries.stream();
+    }
+
+    private class FileEntry extends Entry {
+        private final boolean isDirectory;
+        private final long size;
+        private final Path entryPath;
+        FileEntry(Path entryPath, String path, EntryType type,
+                  boolean isDirectory, long size) {
+            super(ModuleArchive.this, path, path, type);
+            this.entryPath = entryPath;
+            this.isDirectory = isDirectory;
+            this.size = size;
+        }
+
+        public boolean isDirectory() {
+            return isDirectory;
+        }
+
+        @Override
+        public long size() {
+            return size;
+        }
+
+        @Override
+        public InputStream stream() throws IOException {
+            InputStream stream = Files.newInputStream(entryPath);
+            opened.add(stream);
+            return stream;
+        }
     }
 
-    private static class FileEntry implements Entry {
-        private final String name;
-        private final InputStream is;
-        private final boolean isDirectory;
-        private final Section section;
-        FileEntry(String name, InputStream is,
-                  boolean isDirectory, Section section) {
-            this.name = name;
-            this.is = is;
-            this.isDirectory = isDirectory;
-            this.section = section;
-        }
-        public String getName() {
-            return name;
-        }
-        public Section getSection() {
-            return section;
-        }
-        public InputStream getInputStream() {
-            return is;
-        }
-        public boolean isDirectory() {
-            return isDirectory;
-        }
-    }
-
-    private Entry toEntry(Path entryPath, Path basePath, Section section) {
+    private Entry toEntry(Path entryPath, Path basePath, EntryType section) {
         try {
-            return new FileEntry(basePath.relativize(entryPath).toString().replace('\\', '/'),
-                                 Files.newInputStream(entryPath), false,
-                                 section);
+            String path = basePath.relativize(entryPath).toString().replace('\\', '/');
+            return new FileEntry(entryPath, path, section,
+                    false, Files.size(entryPath));
         } catch (IOException e) {
             throw new UncheckedIOException(e);
         }
     }
-
-    @Override
-    public Consumer<Entry> defaultImageWriter(Path path, OutputStream out) {
-        return new DefaultEntryWriter(path, out);
-    }
-
-    private static class DefaultEntryWriter implements Consumer<Archive.Entry> {
-        private final Path root;
-        private final OutputStream out;
-
-        DefaultEntryWriter(Path root, OutputStream out) {
-            this.root = root;
-            this.out = out;
-        }
-
-        @Override
-        public void accept(Archive.Entry entry) {
-            try {
-                FileEntry e = (FileEntry)entry;
-                Section section = e.getSection();
-                String filename = e.getName();
-
-                try (InputStream in = entry.getInputStream()) {
-                    switch (section) {
-                        case CLASSES:
-                            if (!filename.startsWith("_the.") && !filename.equals("javac_state"))
-                                writeEntry(in);
-                            break;
-                        case LIBS:
-                            writeEntry(in, destFile(nativeDir(filename), filename));
-                            break;
-                        case CMDS:
-                            Path path = destFile("bin", filename);
-                            writeEntry(in, path);
-                            path.toFile().setExecutable(true, false);
-                            break;
-                        case CONFIGS:
-                            writeEntry(in, destFile("conf", filename));
-                            break;
-                        default:
-                            throw new InternalError("unexpected entry: " + filename);
-                    }
-                }
-            } catch (IOException x) {
-                throw new UncheckedIOException(x);
-            }
-        }
-
-        private Path destFile(String dir, String filename) {
-            return root.resolve(dir).resolve(filename);
-        }
-
-        private static void writeEntry(InputStream in, Path dstFile) throws IOException {
-            if (Files.notExists(dstFile.getParent()))
-                Files.createDirectories(dstFile.getParent());
-            Files.copy(in, dstFile);
-        }
-
-        private void writeEntry(InputStream in) throws IOException {
-            byte[] buf = new byte[8192];
-            int n;
-            while ((n = in.read(buf)) > 0)
-                out.write(buf, 0, n);
-        }
-
-        private static String nativeDir(String filename) {
-            if (System.getProperty("os.name").startsWith("Windows")) {
-                if (filename.endsWith(".dll") || filename.endsWith(".diz")
-                    || filename.endsWith(".pdb") || filename.endsWith(".map")
-                    || filename.endsWith(".cpl")) {
-                    return "bin";
-                } else {
-                    return "lib";
-                }
-            } else {
-                return "lib";
-            }
-        }
-    }
 }
 
--- a/jdk/src/java.base/share/classes/jdk/internal/jimage/Archive.java	Thu Jul 02 15:25:46 2015 +0200
+++ b/jdk/src/java.base/share/classes/jdk/internal/jimage/Archive.java	Fri Jul 03 07:13:24 2015 -0700
@@ -24,42 +24,95 @@
  */
 package jdk.internal.jimage;
 
+import java.io.IOException;
 import java.io.InputStream;
-import java.io.OutputStream;
-import java.nio.file.Path;
-import java.util.function.Consumer;
+import java.util.stream.Stream;
 
 /**
  * An Archive of all content, classes, resources, configuration files, and
  * other, for a module.
  */
 public interface Archive {
+
+    /**
+     * Entry is contained in an Archive
+     */
+    public abstract class Entry {
+
+        public static enum EntryType {
+
+            MODULE_NAME,
+            CLASS_OR_RESOURCE,
+            NATIVE_LIB,
+            NATIVE_CMD,
+            CONFIG,
+            SERVICE;
+        }
+
+        private final String name;
+        private final EntryType type;
+        private final Archive archive;
+        private final String path;
+
+        public Entry(Archive archive, String path, String name, EntryType type) {
+            this.archive = archive;
+            this.path = path;
+            this.name = name;
+            this.type = type;
+        }
+
+        public Archive archive() {
+            return archive;
+        }
+
+        public String path() {
+            return path;
+        }
+
+        public EntryType type() {
+            return type;
+        }
+
+        /**
+         * Returns the name of this entry.
+         */
+        public String name() {
+            return name;
+        }
+
+        @Override
+        public String toString() {
+            return "type " + type.name() + " path " + path;
+        }
+
+        /**
+         * Returns the number of uncompressed bytes for this entry.
+         */
+        public abstract long size();
+
+        public abstract InputStream stream() throws IOException;
+    }
+
     /**
      * The module name.
      */
     String moduleName();
 
     /**
-     * Visits all classes and resources.
+     * Stream of Entry.
+     * The stream of entries needs to be closed after use
+     * since it might cover lazy I/O based resources.
+     * So callers need to use a try-with-resources block.
      */
-    void visitResources(Consumer<Resource> consumer);
-
-    /**
-     * Visits all entries in the Archive.
-     */
-    void visitEntries(Consumer<Entry> consumer) ;
+    Stream<Entry> entries();
 
     /**
-     * An entries in the Archive.
+     * Open the archive
      */
-    interface Entry {
-        String getName();
-        InputStream getInputStream();
-        boolean isDirectory();
-    }
+    void open() throws IOException;
 
     /**
-     * A Consumer suitable for writing Entries from this Archive.
+     * Close the archive
      */
-    Consumer<Entry> defaultImageWriter(Path path, OutputStream out);
+    void close() throws IOException;
 }
--- a/jdk/src/java.base/share/classes/jdk/internal/jimage/BasicImageReader.java	Thu Jul 02 15:25:46 2015 +0200
+++ b/jdk/src/java.base/share/classes/jdk/internal/jimage/BasicImageReader.java	Fri Jul 03 07:13:24 2015 -0700
@@ -24,63 +24,88 @@
  */
 package jdk.internal.jimage;
 
+import java.io.ByteArrayInputStream;
 import java.io.IOException;
+import java.io.File;
+import java.io.InputStream;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
 import java.nio.IntBuffer;
-import java.nio.MappedByteBuffer;
-import java.nio.channels.FileChannel;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
+import java.util.Comparator;
+import java.util.stream.IntStream;
 
 public class BasicImageReader {
     private final String imagePath;
-    private final PReader preader;
+    private final ImageSubstrate substrate;
     private final ByteOrder byteOrder;
-    private final ImageHeader header;
-    private final int indexSize;
-    private final IntBuffer redirectBuffer;
-    private final IntBuffer offsetsBuffer;
-    private final ByteBuffer locationsBuffer;
-    private final ByteBuffer stringsBuffer;
-    private final ImageStrings strings;
+    private final ImageStringsReader strings;
 
-    protected BasicImageReader(String imagePath, ByteOrder byteOrder) throws IOException {
+    protected BasicImageReader(String imagePath, ByteOrder byteOrder)
+            throws IOException {
         this.imagePath = imagePath;
-        this.preader = PReader.open(imagePath);
+        this.substrate = openImageSubstrate(imagePath, byteOrder);
         this.byteOrder = byteOrder;
-        this.header = ImageHeader.readFrom(byteOrder, getIntBuffer(0, ImageHeader.getHeaderSize()));
-        this.indexSize = header.getIndexSize();
-        this.redirectBuffer = getIntBuffer(header.getRedirectOffset(), header.getRedirectSize());
-        this.offsetsBuffer = getIntBuffer(header.getOffsetsOffset(), header.getOffsetsSize());
-        this.locationsBuffer = getByteBuffer(header.getLocationsOffset(), header.getLocationsSize());
-        this.stringsBuffer = getByteBuffer(header.getStringsOffset(), header.getStringsSize());
-        this.strings = new ImageStrings(new ImageStream(stringsBuffer));
+        this.strings = new ImageStringsReader(this);
     }
 
     protected BasicImageReader(String imagePath) throws IOException {
         this(imagePath, ByteOrder.nativeOrder());
     }
 
+    private static ImageSubstrate openImageSubstrate(String imagePath, ByteOrder byteOrder)
+            throws IOException {
+        ImageSubstrate substrate;
+
+        try {
+            substrate = ImageNativeSubstrate.openImage(imagePath, byteOrder);
+        } catch (UnsatisfiedLinkError ex) {
+            substrate = ImageJavaSubstrate.openImage(imagePath, byteOrder);
+        }
+
+        return substrate;
+    }
+
     public static BasicImageReader open(String imagePath) throws IOException {
         return new BasicImageReader(imagePath, ByteOrder.nativeOrder());
     }
 
+    public static void releaseByteBuffer(ByteBuffer buffer) {
+        ImageBufferCache.releaseBuffer(buffer);
+    }
+
+    public ByteOrder getByteOrder() {
+        return byteOrder;
+    }
+
     public String imagePath() {
         return imagePath;
     }
 
+    public String imagePathName() {
+        int slash = imagePath().lastIndexOf(File.separator);
+
+        if (slash != -1) {
+            return imagePath().substring(slash + 1);
+        }
+
+        return imagePath();
+    }
+
     public boolean isOpen() {
-        return preader.isOpen();
+        return true;
     }
 
     public void close() throws IOException {
-        preader.close();
+        substrate.close();
     }
 
-    public ImageHeader getHeader() {
-        return header;
+    public ImageHeader getHeader() throws IOException {
+        return ImageHeader.readFrom(
+                getIndexIntBuffer(0, ImageHeader.getHeaderSize()));
+    }
+
+    public ImageStringsReader getStrings() {
+        return strings;
     }
 
     public ImageLocation findLocation(String name) {
@@ -92,148 +117,147 @@
     }
 
     public synchronized ImageLocation findLocation(UTF8String name) {
-        int count = header.getLocationCount();
-        int hash = name.hashCode() % count;
-        int redirect = getRedirect(hash);
-
-        if (redirect == 0) {
-            return null;
-        }
-
-        int index;
-
-        if (redirect < 0) {
-            // If no collision.
-            index = -redirect - 1;
-        } else {
-            // If collision, recompute hash code.
-            index = name.hashCode(redirect) % count;
-        }
-
-        int offset = getOffset(index);
-
-        if (offset == 0) {
-            return null;
-        }
-
-        ImageLocation location = getLocation(offset);
-
-        return location.verify(name) ? location : null;
+        return substrate.findLocation(name, strings);
     }
 
     public String[] getEntryNames() {
-        return getEntryNames(true);
-    }
-
-    public String[] getEntryNames(boolean sorted) {
-        int count = header.getLocationCount();
-        List<String> list = new ArrayList<>();
-
-        for (int i = 0; i < count; i++) {
-            int offset = getOffset(i);
-
-            if (offset != 0) {
-                ImageLocation location = ImageLocation.readFrom(locationsBuffer, offset, strings);
-                list.add(location.getFullnameString());
-            }
-        }
-
-        String[] array = list.toArray(new String[0]);
-
-        if (sorted) {
-            Arrays.sort(array);
-        }
-
-        return array;
+        return IntStream.of(substrate.attributeOffsets())
+                        .filter(o -> o != 0)
+                        .mapToObj(o -> ImageLocation.readFrom(this, o).getFullNameString())
+                        .sorted()
+                        .toArray(String[]::new);
     }
 
     protected ImageLocation[] getAllLocations(boolean sorted) {
-        int count = header.getLocationCount();
-        List<ImageLocation> list = new ArrayList<>();
-
-        for (int i = 0; i < count; i++) {
-            int offset = getOffset(i);
-
-            if (offset != 0) {
-                ImageLocation location = ImageLocation.readFrom(locationsBuffer, offset, strings);
-                list.add(location);
-            }
-        }
-
-        ImageLocation[] array = list.toArray(new ImageLocation[0]);
-
-        if (sorted) {
-            Arrays.sort(array, (ImageLocation loc1, ImageLocation loc2) ->
-                    loc1.getFullnameString().compareTo(loc2.getFullnameString()));
-        }
-
-        return array;
+        return IntStream.of(substrate.attributeOffsets())
+                        .filter(o -> o != 0)
+                        .mapToObj(o -> ImageLocation.readFrom(this, o))
+                        .sorted(Comparator.comparing(ImageLocation::getFullNameString))
+                        .toArray(ImageLocation[]::new);
     }
 
-    private IntBuffer getIntBuffer(long offset, long size) throws IOException {
-        MappedByteBuffer buffer = preader.channel().map(FileChannel.MapMode.READ_ONLY, offset, size);
+    private IntBuffer getIndexIntBuffer(long offset, long size)
+            throws IOException {
+        ByteBuffer buffer = substrate.getIndexBuffer(offset, size);
         buffer.order(byteOrder);
 
         return buffer.asIntBuffer();
     }
 
-    private ByteBuffer getByteBuffer(long offset, long size) throws IOException {
-        MappedByteBuffer buffer = preader.channel().map(FileChannel.MapMode.READ_ONLY, offset, size);
-        // order is not copied into the readonly copy.
-        ByteBuffer readOnly = buffer.asReadOnlyBuffer();
-        readOnly.order(byteOrder);
-        return readOnly;
+    ImageLocation getLocation(int offset) {
+        return ImageLocation.readFrom(this, offset);
     }
 
-    private int getRedirect(int index) {
-        return redirectBuffer.get(index);
-    }
-
-    private int getOffset(int index) {
-        return offsetsBuffer.get(index);
-    }
-
-    private ImageLocation getLocation(int offset) {
-        return ImageLocation.readFrom(locationsBuffer, offset, strings);
+    public long[] getAttributes(int offset) {
+        return substrate.getAttributes(offset);
     }
 
     public String getString(int offset) {
-        return strings.get(offset).toString();
+        return getUTF8String(offset).toString();
+    }
+
+    public UTF8String getUTF8String(int offset) {
+        return new UTF8String(substrate.getStringBytes(offset));
+    }
+
+    private byte[] getBufferBytes(ByteBuffer buffer, long size) {
+        assert size < Integer.MAX_VALUE;
+        byte[] bytes = new byte[(int)size];
+        buffer.get(bytes);
+
+        return bytes;
+    }
+
+    private byte[] getBufferBytes(long offset, long size) {
+        ByteBuffer buffer = substrate.getDataBuffer(offset, size);
+
+        return getBufferBytes(buffer, size);
     }
 
-    public byte[] getResource(ImageLocation loc) throws IOException {
+    public byte[] getResource(ImageLocation loc) {
+        long offset = loc.getContentOffset();
         long compressedSize = loc.getCompressedSize();
+        long uncompressedSize = loc.getUncompressedSize();
         assert compressedSize < Integer.MAX_VALUE;
+        assert uncompressedSize < Integer.MAX_VALUE;
+
+        if (substrate.supportsDataBuffer() && compressedSize == 0) {
+            return getBufferBytes(offset, uncompressedSize);
+        }
+
+        ByteBuffer uncompressedBuffer = ImageBufferCache.getBuffer(uncompressedSize);
+        boolean isRead;
 
-        if (compressedSize == 0) {
-            return preader.read((int)loc.getUncompressedSize(),
-                                indexSize + loc.getContentOffset());
+        if (compressedSize != 0) {
+            ByteBuffer compressedBuffer = ImageBufferCache.getBuffer(compressedSize);
+            isRead = substrate.read(offset, compressedBuffer, compressedSize,
+                                          uncompressedBuffer, uncompressedSize);
+            ImageBufferCache.releaseBuffer(compressedBuffer);
         } else {
-            byte[] buf = preader.read((int)compressedSize,
-                                      indexSize + loc.getContentOffset());
-            return ImageFile.Compressor.decompress(buf);
+            isRead = substrate.read(offset, uncompressedBuffer, uncompressedSize);
         }
+
+        byte[] bytes = isRead ? getBufferBytes(uncompressedBuffer,
+                                               uncompressedSize) : null;
+
+        ImageBufferCache.releaseBuffer(uncompressedBuffer);
+
+        return bytes;
     }
 
-    public byte[] getResource(String name) throws IOException {
+    public byte[] getResource(String name) {
         ImageLocation location = findLocation(name);
 
         return location != null ? getResource(location) : null;
     }
 
-    public List<String> getNames(String name) throws IOException {
-        return getNames(getResource(name));
+    public ByteBuffer getResourceBuffer(ImageLocation loc) {
+        long offset = loc.getContentOffset();
+        long compressedSize = loc.getCompressedSize();
+        long uncompressedSize = loc.getUncompressedSize();
+        assert compressedSize < Integer.MAX_VALUE;
+        assert uncompressedSize < Integer.MAX_VALUE;
+
+        if (substrate.supportsDataBuffer() && compressedSize == 0) {
+            return substrate.getDataBuffer(offset, uncompressedSize);
+        }
+
+        ByteBuffer uncompressedBuffer = ImageBufferCache.getBuffer(uncompressedSize);
+        boolean isRead;
+
+        if (compressedSize != 0) {
+            ByteBuffer compressedBuffer = ImageBufferCache.getBuffer(compressedSize);
+            isRead = substrate.read(offset, compressedBuffer, compressedSize,
+                                          uncompressedBuffer, uncompressedSize);
+            ImageBufferCache.releaseBuffer(compressedBuffer);
+        } else {
+            isRead = substrate.read(offset, uncompressedBuffer, uncompressedSize);
+        }
+
+        if (isRead) {
+            return uncompressedBuffer;
+        } else {
+            ImageBufferCache.releaseBuffer(uncompressedBuffer);
+
+            return null;
+        }
     }
 
-    public List<String> getNames(byte[] bytes) {
-        IntBuffer buffer = ByteBuffer.wrap(bytes).asIntBuffer();
-        List<String> names = new ArrayList<>();
+    public ByteBuffer getResourceBuffer(String name) {
+        ImageLocation location = findLocation(name);
+
+        return location != null ? getResourceBuffer(location) : null;
+    }
 
-        while (buffer.hasRemaining()) {
-            int offset = buffer.get();
-            names.add(getString(offset));
-        }
+    public InputStream getResourceStream(ImageLocation loc) {
+        byte[] bytes = getResource(loc);
 
-        return names;
+        return new ByteArrayInputStream(bytes);
+    }
+
+    public InputStream getResourceStream(String name) {
+        ImageLocation location = findLocation(name);
+
+        return location != null ? getResourceStream(location) : null;
     }
 }
--- a/jdk/src/java.base/share/classes/jdk/internal/jimage/BasicImageWriter.java	Thu Jul 02 15:25:46 2015 +0200
+++ b/jdk/src/java.base/share/classes/jdk/internal/jimage/BasicImageWriter.java	Fri Jul 03 07:13:24 2015 -0700
@@ -25,67 +25,30 @@
 
 package jdk.internal.jimage;
 
-import java.io.PrintStream;
 import java.nio.ByteOrder;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.List;
 
 public final class BasicImageWriter {
+
+    public static final String IMAGE_EXT = ".jimage";
+    public static final String BOOT_NAME = "bootmodules";
+    public static final String BOOT_IMAGE_NAME = BOOT_NAME + IMAGE_EXT;
+
     private final static int RETRY_LIMIT = 1000;
 
     private ByteOrder byteOrder;
-    private ImageStrings strings;
-    private int count;
+    private ImageStringsWriter strings;
+    private int length;
     private int[] redirect;
-    private ImageLocation[] locations;
-    private List<ImageLocation> input;
+    private ImageLocationWriter[] locations;
+    private List<ImageLocationWriter> input;
     private ImageStream headerStream;
     private ImageStream redirectStream;
     private ImageStream locationOffsetStream;
     private ImageStream locationStream;
     private ImageStream allIndexStream;
 
-    static class ImageBucket implements Comparable<ImageBucket> {
-        final List<ImageLocation> list;
-
-        ImageBucket() {
-            this.list = new ArrayList<>();
-        }
-
-        void add(ImageLocation location) {
-            list.add(location);
-        }
-
-        int getSize() {
-            return list.size();
-        }
-
-        List<ImageLocation> getList() {
-            return list;
-        }
-
-        ImageLocation getFirst() {
-            assert !list.isEmpty() : "bucket should never be empty";
-            return list.get(0);
-        }
-
-        @Override
-        public int hashCode() {
-            return getFirst().hashCode();
-        }
-
-        @Override
-        public boolean equals(Object obj) {
-            return this == obj;
-        }
-
-        @Override
-        public int compareTo(ImageBucket o) {
-            return o.getSize() - getSize();
-        }
-    }
-
     public BasicImageWriter() {
         this(ByteOrder.nativeOrder());
     }
@@ -93,7 +56,7 @@
     public BasicImageWriter(ByteOrder byteOrder) {
         this.byteOrder = byteOrder;
         this.input = new ArrayList<>();
-        this.strings = new ImageStrings();
+        this.strings = new ImageStringsWriter();
         this.headerStream = new ImageStream(byteOrder);
         this.redirectStream = new ImageStream(byteOrder);
         this.locationOffsetStream = new ImageStream(byteOrder);
@@ -101,6 +64,10 @@
         this.allIndexStream = new ImageStream(byteOrder);
     }
 
+    public ByteOrder getByteOrder() {
+        return byteOrder;
+    }
+
     public int addString(String string) {
         return addString(new UTF8String(string));
     }
@@ -109,104 +76,48 @@
         return strings.add(string);
     }
 
-    public void addLocation(String fullname, long contentOffset, long compressedSize, long uncompressedSize) {
-        ImageLocation location = ImageLocation.newLocation(new UTF8String(fullname), strings, contentOffset, compressedSize, uncompressedSize);
+    public String getString(int offset) {
+        UTF8String utf8 = strings.get(offset);
+        return utf8 != null? utf8.toString() : null;
+    }
+
+    public void addLocation(String fullname, long contentOffset,
+            long compressedSize, long uncompressedSize) {
+        ImageLocationWriter location =
+                ImageLocationWriter.newLocation(new UTF8String(fullname), strings,
+                        contentOffset, compressedSize, uncompressedSize);
         input.add(location);
-        count++;
+        length++;
+    }
+
+    ImageLocationWriter[] getLocations() {
+        return locations;
+    }
+
+    int getLocationsCount() {
+        return input.size();
     }
 
     private void generatePerfectHash() {
-        redo:
-        while(true) {
-            redirect = new int[count];
-            locations = new ImageLocation[count];
-
-            ImageBucket[] sorted = createBuckets();
-
-            int free = 0;
-
-            for (ImageBucket bucket : sorted) {
-                if (bucket.getSize() != 1) {
-                    if (!packCollidedEntries(bucket, count)) {
-                        count = (count + 1) | 1;
+        PerfectHashBuilder<ImageLocationWriter> builder =
+            new PerfectHashBuilder<>(
+                new PerfectHashBuilder.Entry<ImageLocationWriter>().getClass(),
+                new PerfectHashBuilder.Bucket<ImageLocationWriter>().getClass());
 
-                        continue redo;
-                    }
-                } else {
-                    for ( ; free < count && locations[free] != null; free++) {}
-                    assert free < count : "no free slots";
-                    locations[free] = bucket.getFirst();
-                    redirect[bucket.hashCode() % count] = -1 - free;
-                    free++;
-                }
-            }
-
-            break;
-        }
-    }
-
-    private ImageBucket[] createBuckets() {
-        ImageBucket[] buckets = new ImageBucket[count];
-
-        input.stream().forEach((location) -> {
-            int index = location.hashCode() % count;
-            ImageBucket bucket = buckets[index];
-
-            if (bucket == null) {
-                buckets[index] = bucket = new ImageBucket();
-            }
-
-            bucket.add(location);
+        input.forEach((location) -> {
+            builder.put(location.getFullName(), location);
         });
 
-        ImageBucket[] sorted = Arrays.asList(buckets).stream()
-                .filter((bucket) -> (bucket != null))
-                .sorted()
-                .toArray(ImageBucket[]::new);
-
-        return sorted;
-    }
-
-    private boolean packCollidedEntries(ImageBucket bucket, int count) {
-        List<Integer> undo = new ArrayList<>();
-        int base = UTF8String.HASH_MULTIPLIER + 1;
-
-        int retry = 0;
-
-        redo:
-        while (true) {
-            for (ImageLocation location : bucket.getList()) {
-                int index = location.hashCode(base) % count;
-
-                if (locations[index] != null) {
-                    undo.stream().forEach((i) -> {
-                        locations[i] = null;
-                    });
+        builder.generate();
 
-                    undo.clear();
-                    base++;
-
-                    if (base == 0) {
-                        base = 1;
-                    }
-
-                    if (++retry > RETRY_LIMIT) {
-                        return false;
-                    }
+        length = builder.getCount();
+        redirect = builder.getRedirect();
+        PerfectHashBuilder.Entry<ImageLocationWriter>[] order = builder.getOrder();
+        locations = new ImageLocationWriter[length];
 
-                    continue redo;
-                }
-
-                locations[index] = location;
-                undo.add(index);
-            }
-
-            redirect[bucket.hashCode() % count] = base;
-
-            break;
+        for (int i = 0; i < length; i++) {
+            locations[i] = order[i].getValue();
         }
-
-        return true;
     }
 
     private void prepareStringBytes() {
@@ -214,17 +125,17 @@
     }
 
     private void prepareRedirectBytes() {
-        for (int i = 0; i < count; i++) {
+        for (int i = 0; i < length; i++) {
             redirectStream.putInt(redirect[i]);
         }
     }
 
     private void prepareLocationBytes() {
         // Reserve location offset zero for empty locations
-        locationStream.put(ImageLocation.ATTRIBUTE_END << 3);
+        locationStream.put(ImageLocationWriter.ATTRIBUTE_END << 3);
 
-        for (int i = 0; i < count; i++) {
-            ImageLocation location = locations[i];
+        for (int i = 0; i < length; i++) {
+            ImageLocationWriter location = locations[i];
 
             if (location != null) {
                 location.writeTo(locationStream);
@@ -235,14 +146,16 @@
     }
 
     private void prepareOffsetBytes() {
-        for (int i = 0; i < count; i++) {
-            ImageLocation location = locations[i];
-            locationOffsetStream.putInt(location != null ? location.getLocationOffset() : 0);
+        for (int i = 0; i < length; i++) {
+            ImageLocationWriter location = locations[i];
+            int offset = location != null ? location.getLocationOffset() : 0;
+            locationOffsetStream.putInt(offset);
         }
     }
 
     private void prepareHeaderBytes() {
-        ImageHeader header = new ImageHeader(count, locationStream.getSize(), strings.getSize());
+        ImageHeader header = new ImageHeader(input.size(), length,
+                locationStream.getSize(), strings.getSize());
         header.writeTo(headerStream);
     }
 
@@ -268,33 +181,15 @@
         return allIndexStream.toArray();
     }
 
-    ImageLocation find(UTF8String key) {
-        int index = key.hashCode() % count;
-        index = redirect[index];
+    ImageLocationWriter find(UTF8String key) {
+        int index = redirect[key.hashCode() % length];
 
         if (index < 0) {
             index = -index - 1;
-            ImageLocation location = locations[index];
-
-            return location;
         } else {
-            index = key.hashCode(index) % count;
-            ImageLocation location = locations[index];
-
-            return location;
+            index = key.hashCode(index) % length;
         }
-    }
 
-    public void statistics() {
-        getBytes();
-        PrintStream out = System.out;
-        out.println("Count: " + count);
-        out.println("Header bytes size: " + headerStream.getSize());
-        out.println("Redirect bytes size: " + redirectStream.getSize());
-        out.println("Offset bytes size: " + locationOffsetStream.getSize());
-        out.println("Location bytes size: " + locationStream.getSize());
-        out.println("String count: " + strings.getCount());
-        out.println("String bytes size: " + strings.getSize());
-        out.println("Total bytes size: " + allIndexStream.getSize());
+        return locations[index];
     }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.base/share/classes/jdk/internal/jimage/ExternalFilesWriter.java	Fri Jul 03 07:13:24 2015 -0700
@@ -0,0 +1,105 @@
+/*
+ * 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.jimage;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UncheckedIOException;
+import java.nio.file.FileAlreadyExistsException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.function.Consumer;
+import jdk.internal.jimage.Archive.Entry;
+
+/**
+ * A Consumer suitable for processing non resources Archive Entry and writing it to the
+ * appropriate location.
+ */
+class ExternalFilesWriter implements Consumer<Entry> {
+    private final Path root;
+
+    ExternalFilesWriter(Path root) {
+        this.root = root;
+    }
+
+    @Override
+    public void accept(Entry entry) {
+        String name = entry.path();
+        try {
+            String filename = entry.path();
+            try (InputStream in = entry.stream()) {
+                switch (entry.type()) {
+                    case NATIVE_LIB:
+                        writeEntry(in, destFile(nativeDir(filename), filename));
+                        break;
+                    case NATIVE_CMD:
+                        Path path = destFile("bin", filename);
+                        writeEntry(in, path);
+                        path.toFile().setExecutable(true);
+                        break;
+                    case CONFIG:
+                        writeEntry(in, destFile("conf", filename));
+                        break;
+                    case MODULE_NAME:
+                        // skip
+                        break;
+                    case SERVICE:
+                        //throw new UnsupportedOperationException(name + " in " + zipfile.toString()); //TODO
+                        throw new UnsupportedOperationException(name + " in " + name);
+                    default:
+                        //throw new InternalError("unexpected entry: " + name + " " + zipfile.toString()); //TODO
+                        throw new InternalError("unexpected entry: " + name + " " + name);
+                }
+            }
+        } catch (FileAlreadyExistsException x) {
+            System.err.println("File already exists (skipped) " + name);
+        } catch (IOException x) {
+            throw new UncheckedIOException(x);
+        }
+    }
+
+    private Path destFile(String dir, String filename) {
+        return root.resolve(dir).resolve(filename);
+    }
+
+    private void writeEntry(InputStream in, Path dstFile) throws IOException {
+        Files.createDirectories(dstFile.getParent());
+        Files.copy(in, dstFile);
+    }
+
+    private static String nativeDir(String filename) {
+        if (System.getProperty("os.name").startsWith("Windows")) {
+            if (filename.endsWith(".dll") || filename.endsWith(".diz")
+                || filename.endsWith(".pdb") || filename.endsWith(".map")) {
+                return "bin";
+            } else {
+                return "lib";
+            }
+        } else {
+            return "lib";
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.base/share/classes/jdk/internal/jimage/ImageBufferCache.java	Fri Jul 03 07:13:24 2015 -0700
@@ -0,0 +1,123 @@
+/*
+ * 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.jimage;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+
+class ImageBufferCache {
+    private static final int MAX_FREE_BUFFERS = 3;
+    private static final int LARGE_BUFFER = 0x10000;
+    private static final ThreadLocal<ArrayList<ImageBufferCache>>
+            threadLocal = new ThreadLocal<>();
+
+    private final ByteBuffer buffer;
+    private boolean isUsed;
+
+    static ByteBuffer getBuffer(long size) {
+        assert size < Integer.MAX_VALUE;
+        ByteBuffer buffer = null;
+
+        if (size > LARGE_BUFFER) {
+            buffer = ByteBuffer.allocateDirect((int)((size + 0xFFF) & ~0xFFF));
+        } else {
+            ArrayList<ImageBufferCache> buffers = threadLocal.get();
+
+            if (buffers == null) {
+                buffers = new ArrayList<>(MAX_FREE_BUFFERS);
+                threadLocal.set(buffers);
+            }
+
+            int i = 0, j = buffers.size();
+            for (ImageBufferCache imageBuffer : buffers) {
+                if (size <= imageBuffer.capacity()) {
+                    j = i;
+
+                    if (!imageBuffer.isUsed) {
+                        imageBuffer.isUsed = true;
+                        buffer = imageBuffer.buffer;
+
+                        break;
+                    }
+                } else {
+                    break;
+                }
+
+                i++;
+            }
+
+            if (buffer == null) {
+                ImageBufferCache imageBuffer = new ImageBufferCache((int)size);
+                buffers.add(j, imageBuffer);
+                buffer = imageBuffer.buffer;
+            }
+        }
+
+        buffer.rewind();
+        buffer.limit((int)size);
+
+        return buffer;
+    }
+
+    static void releaseBuffer(ByteBuffer buffer) {
+        ArrayList<ImageBufferCache> buffers = threadLocal.get();
+
+        if (buffers == null ) {
+            return;
+        }
+
+        if (buffer.capacity() > LARGE_BUFFER) {
+            return;
+        }
+
+        int i = 0, j = buffers.size();
+        for (ImageBufferCache imageBuffer : buffers) {
+            if (!imageBuffer.isUsed) {
+                j = Math.min(j, i);
+            }
+
+            if (imageBuffer.buffer == buffer) {
+                imageBuffer.isUsed = false;
+                j = Math.min(j, i);
+
+                break;
+            }
+        }
+
+        if (buffers.size() > MAX_FREE_BUFFERS && j != buffers.size()) {
+            buffers.remove(j);
+        }
+    }
+
+    private ImageBufferCache(int needed) {
+        this.buffer = ByteBuffer.allocateDirect((needed + 0xFFF) & ~0xFFF);
+        this.isUsed = true;
+        this.buffer.limit(needed);
+    }
+
+    private long capacity() {
+        return buffer.capacity();
+    }
+}
--- a/jdk/src/java.base/share/classes/jdk/internal/jimage/ImageFile.java	Thu Jul 02 15:25:46 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,288 +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.jimage;
-
-import java.io.BufferedOutputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.DataOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.nio.ByteOrder;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.function.Consumer;
-import java.util.function.Function;
-import java.util.stream.Collectors;
-import java.util.zip.DataFormatException;
-import java.util.zip.Deflater;
-import java.util.zip.Inflater;
-import jdk.internal.jimage.ImageModules.Loader;
-import jdk.internal.jimage.ImageModules.ModuleIndex;
-
-/**
- * An image (native endian.)
- * <pre>{@code
- * {
- *   u4 magic;
- *   u2 major_version;
- *   u2 minor_version;
- *   u4 location_count;
- *   u4 location_attributes_size;
- *   u4 strings_size;
- *   u4 redirect[location_count];
- *   u4 offsets[location_count];
- *   u1 location_attributes[location_attributes_size];
- *   u1 strings[strings_size];
- *   u1 content[if !EOF];
- * }
- * }</pre>
- */
-public final class ImageFile {
-    private static final String JAVA_BASE = "java.base";
-    private static final String IMAGE_EXT = ".jimage";
-    private static final String JAR_EXT = ".jar";
-    private final Path root;
-    private final Path mdir;
-    private final Map<String, List<Resource>> resourcesForModule = new HashMap<>();
-
-    private ImageFile(Path path) {
-        this.root = path;
-        this.mdir = root.resolve(path.getFileSystem().getPath("lib", "modules"));
-    }
-
-    public static ImageFile open(Path path) throws IOException {
-        ImageFile lib = new ImageFile(path);
-        return lib.open();
-    }
-
-    private ImageFile open() throws IOException {
-        Path path = mdir.resolve("bootmodules" + IMAGE_EXT);
-
-        ImageReader reader = new ImageReader(path.toString());
-        ImageHeader header = reader.getHeader();
-
-        if (header.getMagic() != ImageHeader.MAGIC) {
-            if (header.getMagic() == ImageHeader.BADMAGIC) {
-                throw new IOException(path + ": Image may be not be native endian");
-            } else {
-                throw new IOException(path + ": Invalid magic number");
-            }
-        }
-
-        if (header.getMajorVersion() > ImageHeader.MAJOR_VERSION ||
-            (header.getMajorVersion() == ImageHeader.MAJOR_VERSION &&
-             header.getMinorVersion() > ImageHeader.MINOR_VERSION)) {
-            throw new IOException("invalid version number");
-        }
-
-        return this;
-    }
-
-    public static ImageFile create(Path output,
-                                   Set<Archive> archives,
-                                   ImageModules modules)
-        throws IOException
-    {
-        return ImageFile.create(output, archives, modules, ByteOrder.nativeOrder());
-    }
-
-    public static ImageFile create(Path output,
-                                   Set<Archive> archives,
-                                   ImageModules modules,
-                                   ByteOrder byteOrder)
-        throws IOException
-    {
-        ImageFile lib = new ImageFile(output);
-        // get all resources
-        lib.readModuleEntries(modules, archives);
-        // write to modular image
-        lib.writeImage(modules, archives, byteOrder);
-        return lib;
-    }
-
-    private void writeImage(ImageModules modules,
-                            Set<Archive> archives,
-                            ByteOrder byteOrder)
-        throws IOException
-    {
-        // name to Archive file
-        Map<String, Archive> nameToArchive =
-            archives.stream()
-                  .collect(Collectors.toMap(Archive::moduleName, Function.identity()));
-
-        Files.createDirectories(mdir);
-        for (Loader l : Loader.values()) {
-            Set<String> mods = modules.getModules(l);
-
-            try (OutputStream fos = Files.newOutputStream(mdir.resolve(l.getName() + IMAGE_EXT));
-                    BufferedOutputStream bos = new BufferedOutputStream(fos);
-                    DataOutputStream out = new DataOutputStream(bos)) {
-                // store index in addition of the class loader map for boot loader
-                BasicImageWriter writer = new BasicImageWriter(byteOrder);
-                Set<String> duplicates = new HashSet<>();
-
-                // build package map for modules and add as resources
-                ModuleIndex mindex = modules.buildModuleIndex(l, writer);
-                long offset = mindex.size();
-
-                // the order of traversing the resources and the order of
-                // the module content being written must be the same
-                for (String mn : mods) {
-                    for (Resource res : resourcesForModule.get(mn)) {
-                        String path = res.name();
-                        long uncompressedSize = res.size();
-                        long compressedSize = res.csize();
-                        long onFileSize = compressedSize != 0 ? compressedSize : uncompressedSize;
-
-                        if (duplicates.contains(path)) {
-                            System.err.format("duplicate resource \"%s\", skipping%n", path);
-                            // TODO Need to hang bytes on resource and write from resource not zip.
-                            // Skipping resource throws off writing from zip.
-                            offset += onFileSize;
-                            continue;
-                        }
-                        duplicates.add(path);
-                        writer.addLocation(path, offset, compressedSize, uncompressedSize);
-                        offset += onFileSize;
-                    }
-                }
-
-                // write header and indices
-                byte[] bytes = writer.getBytes();
-                out.write(bytes, 0, bytes.length);
-
-                // write module table and packages
-                mindex.writeTo(out);
-
-                // write module content
-                for (String mn : mods) {
-                    writeModule(nameToArchive.get(mn), out);
-                }
-            }
-        }
-    }
-
-    private void readModuleEntries(ImageModules modules,
-                                   Set<Archive> archives)
-        throws IOException
-    {
-        for (Archive archive : archives) {
-            List<Resource> res = new ArrayList<>();
-            archive.visitResources(x-> res.add(x));
-
-            String mn = archive.moduleName();
-            resourcesForModule.put(mn, res);
-
-            Set<String> pkgs = res.stream().map(Resource::name)
-                    .filter(n -> n.endsWith(".class"))
-                    .map(this::toPackage)
-                    .distinct()
-                    .collect(Collectors.toSet());
-            modules.setPackages(mn, pkgs);
-        }
-    }
-
-    private String toPackage(String name) {
-        int index = name.lastIndexOf('/');
-        if (index > 0) {
-            return name.substring(0, index).replace('/', '.');
-        } else {
-            // ## unnamed package
-            System.err.format("Warning: %s in unnamed package%n", name);
-            return "";
-        }
-    }
-
-    private void writeModule(Archive archive,
-                             OutputStream out)
-        throws IOException
-    {
-          Consumer<Archive.Entry> consumer = archive.defaultImageWriter(root, out);
-          archive.visitEntries(consumer);
-    }
-
-
-    static class Compressor {
-        public static byte[] compress(byte[] bytesIn) {
-            Deflater deflater = new Deflater();
-            deflater.setInput(bytesIn);
-            ByteArrayOutputStream stream = new ByteArrayOutputStream(bytesIn.length);
-            byte[] buffer = new byte[1024];
-
-            deflater.finish();
-            while (!deflater.finished()) {
-                int count = deflater.deflate(buffer);
-                stream.write(buffer, 0, count);
-            }
-
-            try {
-                stream.close();
-            } catch (IOException ex) {
-                return bytesIn;
-            }
-
-            byte[] bytesOut = stream.toByteArray();
-            deflater.end();
-
-            return bytesOut;
-        }
-
-        public static byte[] decompress(byte[] bytesIn) {
-            Inflater inflater = new Inflater();
-            inflater.setInput(bytesIn);
-            ByteArrayOutputStream stream = new ByteArrayOutputStream(bytesIn.length);
-            byte[] buffer = new byte[1024];
-
-            while (!inflater.finished()) {
-                int count;
-
-                try {
-                    count = inflater.inflate(buffer);
-                } catch (DataFormatException ex) {
-                    return null;
-                }
-
-                stream.write(buffer, 0, count);
-            }
-
-            try {
-                stream.close();
-            } catch (IOException ex) {
-                return null;
-            }
-
-            byte[] bytesOut = stream.toByteArray();
-            inflater.end();
-
-            return bytesOut;
-        }
-    }
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.base/share/classes/jdk/internal/jimage/ImageFileCreator.java	Fri Jul 03 07:13:24 2015 -0700
@@ -0,0 +1,355 @@
+/*
+ * 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.jimage;
+
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import jdk.internal.jimage.Archive.Entry;
+import jdk.internal.jimage.Archive.Entry.EntryType;
+import static jdk.internal.jimage.BasicImageWriter.BOOT_NAME;
+import static jdk.internal.jimage.BasicImageWriter.IMAGE_EXT;
+
+/**
+ * An image (native endian.)
+ * <pre>{@code
+ * {
+ *   u4 magic;
+ *   u2 major_version;
+ *   u2 minor_version;
+ *   u4 resource_count;
+ *   u4 table_length;
+ *   u4 location_attributes_size;
+ *   u4 strings_size;
+ *   u4 redirect[table_length];
+ *   u4 offsets[table_length];
+ *   u1 location_attributes[location_attributes_size];
+ *   u1 strings[strings_size];
+ *   u1 content[if !EOF];
+ * }
+ * }</pre>
+ */
+public final class ImageFileCreator {
+    private final Path root;
+    private final Path mdir;
+    private final Map<String, List<Entry>> entriesForModule = new HashMap<>();
+    private ImageFileCreator(Path path) {
+        this.root = path;
+        this.mdir = root.resolve(path.getFileSystem().getPath("lib", "modules"));
+    }
+
+    public static ImageFileCreator create(Path output,
+            Set<Archive> archives)
+            throws IOException {
+        return create(output, BOOT_NAME, archives, ByteOrder.nativeOrder());
+    }
+
+    public static ImageFileCreator create(Path output,
+            Set<Archive> archives,
+            ByteOrder byteOrder)
+            throws IOException {
+        return create(output, BOOT_NAME, archives, byteOrder);
+    }
+
+    public static ImageFileCreator create(Path output,
+                                   String fileName,
+                                   Set<Archive> archives,
+                                   ByteOrder byteOrder)
+        throws IOException
+    {
+        ImageFileCreator image = new ImageFileCreator(output);
+        // get all entries
+        Map<String, Set<String>> modulePackagesMap = new HashMap<>();
+        image.readAllEntries(modulePackagesMap, archives);
+        // write to modular image
+        image.writeImage(fileName, modulePackagesMap, archives, byteOrder);
+        return image;
+    }
+
+    private void readAllEntries(Map<String, Set<String>> modulePackagesMap,
+                                  Set<Archive> archives) {
+        archives.stream().forEach((archive) -> {
+            Map<Boolean, List<Entry>> es;
+            try(Stream<Entry> entries = archive.entries()) {
+                es = entries.collect(Collectors.partitioningBy(n -> n.type()
+                        == EntryType.CLASS_OR_RESOURCE));
+            }
+            String mn = archive.moduleName();
+            List<Entry> all = new ArrayList<>();
+            all.addAll(es.get(false));
+            all.addAll(es.get(true));
+            entriesForModule.put(mn, all);
+            // Extract package names
+            Set<String> pkgs = es.get(true).stream().map(Entry::name)
+                    .filter(n -> isClassPackage(n))
+                    .map(ImageFileCreator::toPackage)
+                    .collect(Collectors.toSet());
+            modulePackagesMap.put(mn, pkgs);
+        });
+    }
+
+    public static boolean isClassPackage(String path) {
+        return path.endsWith(".class");
+    }
+
+    public static boolean isResourcePackage(String path) {
+        path = path.substring(1);
+        path = path.substring(path.indexOf("/")+1);
+        return !path.startsWith("META-INF/");
+    }
+
+    public static void recreateJimage(Path jimageFile,
+            Set<Archive> archives,
+            Map<String, Set<String>> modulePackages)
+            throws IOException {
+        Map<String, List<Entry>> entriesForModule
+                = archives.stream().collect(Collectors.toMap(
+                                Archive::moduleName,
+                                a -> {
+                                    try(Stream<Entry> entries = a.entries()) {
+                                        return entries.collect(Collectors.toList());
+                                    }
+                                }));
+        Map<String, Archive> nameToArchive
+                = archives.stream()
+                .collect(Collectors.toMap(Archive::moduleName, Function.identity()));
+        ByteOrder order = ByteOrder.nativeOrder();
+        ResourcePoolImpl resources = createResources(modulePackages, nameToArchive,
+                (Entry t) -> {
+            throw new UnsupportedOperationException("Not supported, no external file "
+                    + "in a jimage file");
+        }, entriesForModule, order);
+        String fileName = jimageFile.getRoot().toString();
+        generateJImage(jimageFile, fileName, resources, order);
+    }
+
+    private void writeImage(String fileName,
+            Map<String, Set<String>> modulePackagesMap,
+            Set<Archive> archives,
+            ByteOrder byteOrder)
+            throws IOException {
+        Files.createDirectories(mdir);
+        ExternalFilesWriter filesWriter = new ExternalFilesWriter(root);
+        // name to Archive file
+        Map<String, Archive> nameToArchive
+                = archives.stream()
+                .collect(Collectors.toMap(Archive::moduleName, Function.identity()));
+        ResourcePoolImpl resources = createResources(modulePackagesMap,
+                nameToArchive, filesWriter,
+                entriesForModule, byteOrder);
+        generateJImage(mdir.resolve(fileName + IMAGE_EXT), fileName, resources,
+                byteOrder);
+    }
+
+    private static void generateJImage(Path img,
+            String fileName,
+            ResourcePoolImpl resources,
+            ByteOrder byteOrder
+    ) throws IOException {
+        BasicImageWriter writer = new BasicImageWriter(byteOrder);
+
+        Map<String, Set<String>> modulePackagesMap = resources.getModulePackages();
+
+        try (OutputStream fos = Files.newOutputStream(img);
+                BufferedOutputStream bos = new BufferedOutputStream(fos);
+                DataOutputStream out = new DataOutputStream(bos)) {
+            Set<String> duplicates = new HashSet<>();
+            ImageModuleDataWriter moduleData =
+            ImageModuleDataWriter.buildModuleData(writer, modulePackagesMap);
+            moduleData.addLocation(fileName, writer);
+            long offset = moduleData.size();
+
+            List<ResourcePool.Resource> content = new ArrayList<>();
+            List<String> paths = new ArrayList<>();
+                 // the order of traversing the resources and the order of
+            // the module content being written must be the same
+            for (ResourcePool.Resource res : resources.getResources()) {
+                String path = res.getPath();
+                int index = path.indexOf("/META-INF/");
+                if (index != -1) {
+                    path = path.substring(index + 1);
+                }
+
+                content.add(res);
+                long uncompressedSize = res.getLength();
+                long compressedSize = 0;
+                if (res instanceof ResourcePool.CompressedResource) {
+                    ResourcePool.CompressedResource comp =
+                            (ResourcePool.CompressedResource) res;
+                    compressedSize = res.getLength();
+                    uncompressedSize = comp.getUncompressedSize();
+                }
+                long onFileSize = res.getLength();
+
+                if (duplicates.contains(path)) {
+                    System.err.format("duplicate resource \"%s\", skipping%n",
+                            path);
+                     // TODO Need to hang bytes on resource and write
+                    // from resource not zip.
+                    // Skipping resource throws off writing from zip.
+                    offset += onFileSize;
+                    continue;
+                }
+                duplicates.add(path);
+                writer.addLocation(path, offset, compressedSize, uncompressedSize);
+                paths.add(path);
+                offset += onFileSize;
+            }
+
+            ImageResourcesTree tree = new ImageResourcesTree(offset, writer, paths);
+
+            // write header and indices
+            byte[] bytes = writer.getBytes();
+            out.write(bytes, 0, bytes.length);
+
+            // write module meta data
+            moduleData.writeTo(out);
+
+            // write module content
+            for(ResourcePool.Resource res : content) {
+                byte[] buf = res.getByteArray();
+                out.write(buf, 0, buf.length);
+            }
+
+            tree.addContent(out);
+        }
+    }
+
+    private static ResourcePoolImpl createResources(Map<String, Set<String>> modulePackagesMap,
+            Map<String, Archive> nameToArchive,
+            Consumer<Entry> externalFileHandler,
+            Map<String, List<Entry>> entriesForModule,
+            ByteOrder byteOrder) throws IOException {
+        ResourcePoolImpl resources = new ResourcePoolImpl(byteOrder);
+        Set<String> mods = modulePackagesMap.keySet();
+        for (String mn : mods) {
+            for (Entry entry : entriesForModule.get(mn)) {
+                String path = entry.name();
+                if (entry.type() == EntryType.CLASS_OR_RESOURCE) {
+                    if (!entry.path().endsWith(BOOT_NAME)) {
+                        try (InputStream stream = entry.stream()) {
+                            byte[] bytes = readAllBytes(stream);
+                            path = "/" + mn + "/" + path;
+                            try {
+                                resources.addResource(new ResourcePool.Resource(path,
+                                        ByteBuffer.wrap(bytes)));
+                            } catch (Exception ex) {
+                                throw new IOException(ex);
+                            }
+                        }
+                    }
+                } else {
+                    externalFileHandler.accept(entry);
+                }
+            }
+            // Done with this archive, close it.
+            Archive archive = nameToArchive.get(mn);
+            archive.close();
+        }
+        return resources;
+    }
+
+    private static final int BUF_SIZE = 8192;
+
+    private static byte[] readAllBytes(InputStream is) throws IOException {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        byte[] buf = new byte[BUF_SIZE];
+        while (true) {
+            int n = is.read(buf);
+            if (n < 0) {
+                break;
+            }
+            baos.write(buf, 0, n);
+        }
+        return baos.toByteArray();
+    }
+
+    /**
+     * Helper method that splits a Resource path onto 3 items: module, parent
+     * and resource name.
+     *
+     * @param path
+     * @return An array containing module, parent and name.
+     */
+    public static String[] splitPath(String path) {
+        Objects.requireNonNull(path);
+        String noRoot = path.substring(1);
+        int pkgStart = noRoot.indexOf("/");
+        String module = noRoot.substring(0, pkgStart);
+        List<String> result = new ArrayList<>();
+        result.add(module);
+        String pkg = noRoot.substring(pkgStart + 1);
+        String resName;
+        int pkgEnd = pkg.lastIndexOf("/");
+        if (pkgEnd == -1) { // No package.
+            resName = pkg;
+        } else {
+            resName = pkg.substring(pkgEnd + 1);
+        }
+
+        pkg = toPackage(pkg, false);
+        result.add(pkg);
+        result.add(resName);
+
+        String[] array = new String[result.size()];
+        return result.toArray(array);
+    }
+
+    private static String toPackage(String name) {
+        String pkg = toPackage(name, true);
+        return pkg;
+    }
+
+    private static String toPackage(String name, boolean log) {
+        int index = name.lastIndexOf('/');
+        if (index > 0) {
+            return name.substring(0, index).replace('/', '.');
+        } else {
+            // ## unnamed package
+            if (log) {
+                System.err.format("Warning: %s in unnamed package%n", name);
+            }
+            return "";
+        }
+    }
+}
--- a/jdk/src/java.base/share/classes/jdk/internal/jimage/ImageHeader.java	Thu Jul 02 15:25:46 2015 +0200
+++ b/jdk/src/java.base/share/classes/jdk/internal/jimage/ImageHeader.java	Fri Jul 03 07:13:24 2015 -0700
@@ -25,67 +25,76 @@
 
 package jdk.internal.jimage;
 
-import java.nio.ByteOrder;
+import java.nio.ByteBuffer;
 import java.nio.IntBuffer;
 
 public final class ImageHeader {
     public static final int MAGIC = 0xCAFEDADA;
     public static final int BADMAGIC = 0xDADAFECA;
-    public static final short MAJOR_VERSION = 0;
-    public static final short MINOR_VERSION = 1;
+    public static final int MAJOR_VERSION = 1;
+    public static final int MINOR_VERSION = 0;
 
     private final int magic;
-    private final short majorVersion;
-    private final short minorVersion;
-    private final int locationCount;
+    private final int majorVersion;
+    private final int minorVersion;
+    private final int flags;
+    private final int resourceCount;
+    private final int tableLength;
     private final int locationsSize;
     private final int stringsSize;
 
-    ImageHeader(int locationCount, int locationsSize, int stringsSize) {
-        this(MAGIC, MAJOR_VERSION, MINOR_VERSION, locationCount, locationsSize, stringsSize);
+    public ImageHeader(int resourceCount, int tableCount,
+            int locationsSize, int stringsSize) {
+        this(MAGIC, MAJOR_VERSION, MINOR_VERSION, 0, resourceCount,
+                tableCount, locationsSize, stringsSize);
     }
 
-    ImageHeader(int magic, short majorVersion, short minorVersion, int locationCount,
-                int locationsSize, int stringsSize)
+    public ImageHeader(int magic, int majorVersion, int minorVersion,
+                int flags, int resourceCount,
+                int tableLength, int locationsSize, int stringsSize)
     {
         this.magic = magic;
         this.majorVersion = majorVersion;
         this.minorVersion = minorVersion;
-        this.locationCount = locationCount;
+        this.flags = flags;
+        this.resourceCount = resourceCount;
+        this.tableLength = tableLength;
         this.locationsSize = locationsSize;
         this.stringsSize = stringsSize;
     }
 
-    static int getHeaderSize() {
-       return 4 +
-              2 + 2 +
-              4 +
-              4 +
-              4;
+    public static int getHeaderSize() {
+       return 7 * 4;
     }
 
-    static ImageHeader readFrom(ByteOrder byteOrder, IntBuffer buffer) {
+    static ImageHeader readFrom(IntBuffer buffer) {
         int magic = buffer.get(0);
         int version = buffer.get(1);
-        short majorVersion = (short)(byteOrder == ByteOrder.BIG_ENDIAN ?
-            version >>> 16 : (version & 0xFFFF));
-        short minorVersion = (short)(byteOrder == ByteOrder.BIG_ENDIAN ?
-            (version & 0xFFFF) : version >>> 16);
-        int locationCount = buffer.get(2);
-        int locationsSize = buffer.get(3);
-        int stringsSize = buffer.get(4);
+        int majorVersion = version >>> 16;
+        int minorVersion = version & 0xFFFF;
+        int flags = buffer.get(2);
+        int resourceCount = buffer.get(3);
+        int tableLength = buffer.get(4);
+        int locationsSize = buffer.get(5);
+        int stringsSize = buffer.get(6);
 
-        return new ImageHeader(magic, majorVersion, minorVersion, locationCount,
-                               locationsSize, stringsSize);
+        return new ImageHeader(magic, majorVersion, minorVersion, flags,
+            resourceCount, tableLength, locationsSize, stringsSize);
     }
 
     void writeTo(ImageStream stream) {
-        stream.putInt(magic);
-        stream.putShort(majorVersion);
-        stream.putShort(minorVersion);
-        stream.putInt(locationCount);
-        stream.putInt(locationsSize);
-        stream.putInt(stringsSize);
+        stream.ensure(getHeaderSize());
+        writeTo(stream.getBuffer());
+    }
+
+    public void writeTo(ByteBuffer buffer) {
+        buffer.putInt(magic);
+        buffer.putInt(majorVersion << 16 | minorVersion);
+        buffer.putInt(flags);
+        buffer.putInt(resourceCount);
+        buffer.putInt(tableLength);
+        buffer.putInt(locationsSize);
+        buffer.putInt(stringsSize);
     }
 
     public int getMagic() {
@@ -100,16 +109,24 @@
         return minorVersion;
     }
 
-    public int getLocationCount() {
-        return locationCount;
+    public int getFlags() {
+        return flags;
+    }
+
+    public int getResourceCount() {
+        return resourceCount;
+    }
+
+    public int getTableLength() {
+        return tableLength;
     }
 
     public int getRedirectSize() {
-        return locationCount* 4;
+        return tableLength * 4;
     }
 
     public int getOffsetsSize() {
-        return locationCount* 4;
+        return tableLength * 4;
     }
 
     public int getLocationsSize() {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.base/share/classes/jdk/internal/jimage/ImageJavaSubstrate.java	Fri Jul 03 07:13:24 2015 -0700
@@ -0,0 +1,242 @@
+/*
+ * 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.jimage;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.IntBuffer;
+import java.nio.channels.FileChannel;
+import java.nio.file.Paths;
+import static java.nio.file.StandardOpenOption.READ;
+import jdk.internal.jimage.decompressor.Decompressor;
+
+final class ImageJavaSubstrate implements ImageSubstrate {
+
+    private final String imagePath;
+    private final ByteOrder byteOrder;
+    private final FileChannel channel;
+    private final ImageHeader header;
+    private final long indexSize;
+    private final int[] redirect;
+    private final int[] offsets;
+    private final byte[] locations;
+    private final byte[] strings;
+
+    private final Decompressor decompressor = new Decompressor();
+
+  private ImageJavaSubstrate(String imagePath, ByteOrder byteOrder)
+          throws IOException {
+        this.imagePath = imagePath;
+        this.byteOrder = byteOrder;
+        channel = FileChannel.open(Paths.get(imagePath), READ);
+
+        int headerSize = ImageHeader.getHeaderSize();
+        ByteBuffer buffer = getIndexBuffer(0, headerSize);
+        header = ImageHeader.readFrom(buffer.asIntBuffer());
+
+        if (header.getMagic() != ImageHeader.MAGIC ||
+            header.getMajorVersion() != ImageHeader.MAJOR_VERSION ||
+            header.getMinorVersion() != ImageHeader.MINOR_VERSION) {
+            throw new IOException("Image not found \"" + imagePath + "\"");
+        }
+
+        indexSize = header.getIndexSize();
+
+        redirect = readIntegers(header.getRedirectOffset(),
+                                header.getRedirectSize());
+        offsets = readIntegers(header.getOffsetsOffset(),
+                               header.getOffsetsSize());
+        locations = readBytes(header.getLocationsOffset(),
+                              header.getLocationsSize());
+        strings = readBytes(header.getStringsOffset(),
+                            header.getStringsSize());
+    }
+
+    static ImageSubstrate openImage(String imagePath, ByteOrder byteOrder)
+            throws IOException {
+        return new ImageJavaSubstrate(imagePath, byteOrder);
+    }
+
+    @Override
+    public void close() {
+        try {
+            channel.close();
+        } catch (IOException ex) {
+            // Mostly harmless
+        }
+    }
+
+    @Override
+    public boolean supportsDataBuffer() {
+        return false;
+    }
+
+    private int[] readIntegers(long offset, long size) {
+        assert size < Integer.MAX_VALUE;
+        IntBuffer buffer = readBuffer(offset, size).asIntBuffer();
+        int[] integers = new int[(int)size / 4];
+        buffer.get(integers);
+
+        return integers;
+    }
+
+    private byte[] readBytes(long offset, long size) {
+        assert size < Integer.MAX_VALUE;
+        ByteBuffer buffer = readBuffer(offset, size);
+        byte[] bytes = new byte[(int)size];
+        buffer.get(bytes);
+
+        return bytes;
+    }
+
+    private ByteBuffer readBuffer(long offset, long size) {
+        assert size < Integer.MAX_VALUE;
+        ByteBuffer buffer = ByteBuffer.allocate((int)size);
+        buffer.order(byteOrder);
+
+        if (!readBuffer(buffer, offset, size)) {
+            return null;
+        }
+
+        return buffer;
+    }
+
+    private boolean readBuffer(ByteBuffer buffer, long offset, long size) {
+        assert size < Integer.MAX_VALUE;
+        assert buffer.limit() == size;
+        int read = 0;
+
+        try {
+            read = channel.read(buffer, offset);
+            buffer.rewind();
+        } catch (IOException ex) {
+            // fall thru
+        }
+
+        return read == size;
+    }
+
+    @Override
+    public ByteBuffer getIndexBuffer(long offset, long size) {
+        assert size < Integer.MAX_VALUE;
+        return readBuffer(offset, size);
+    }
+
+    @Override
+    public ByteBuffer getDataBuffer(long offset, long size) {
+        assert size < Integer.MAX_VALUE;
+        return getIndexBuffer(indexSize + offset, size);
+    }
+
+    @Override
+    public boolean read(long offset,
+                 ByteBuffer compressedBuffer, long compressedSize,
+                 ByteBuffer uncompressedBuffer, long uncompressedSize) {
+        assert compressedSize < Integer.MAX_VALUE;
+        assert uncompressedSize < Integer.MAX_VALUE;
+        boolean isRead = readBuffer(compressedBuffer,
+                                    indexSize + offset, compressedSize);
+        if (isRead) {
+            byte[] bytesIn = new byte[(int)compressedSize];
+            compressedBuffer.get(bytesIn);
+            byte[] bytesOut;
+            try {
+                bytesOut = decompressor.decompressResource(byteOrder, (int strOffset) -> {
+                    return new UTF8String(getStringBytes(strOffset)).toString();
+                }, bytesIn);
+            } catch (IOException ex) {
+                throw new RuntimeException(ex);
+            }
+            uncompressedBuffer.put(bytesOut);
+            uncompressedBuffer.rewind();
+        }
+
+        return isRead;
+    }
+
+    @Override
+    public boolean read(long offset,
+                 ByteBuffer uncompressedBuffer, long uncompressedSize) {
+        assert uncompressedSize < Integer.MAX_VALUE;
+        boolean isRead = readBuffer(uncompressedBuffer,
+                                    indexSize + offset, uncompressedSize);
+
+        return isRead;
+    }
+
+    @Override
+    public byte[] getStringBytes(int offset) {
+        if (offset == 0) {
+            return new byte[0];
+        }
+
+        int length = strings.length - offset;
+
+        for (int i = offset; i < strings.length; i++) {
+            if (strings[i] == 0) {
+                length = i - offset;
+                break;
+            }
+        }
+
+        byte[] bytes = new byte[length];
+        System.arraycopy(strings, offset, bytes, 0, length);
+
+        return bytes;
+    }
+
+    @Override
+    public long[] getAttributes(int offset) {
+        return ImageLocationBase.decompress(locations, offset);
+    }
+
+    @Override
+    public ImageLocation findLocation(UTF8String name, ImageStringsReader strings) {
+        int count = header.getTableLength();
+        int index = redirect[name.hashCode() % count];
+
+        if (index < 0) {
+            index = -index - 1;
+        } else {
+            index = name.hashCode(index) % count;
+        }
+
+        long[] attributes = getAttributes(offsets[index]);
+
+        ImageLocation imageLocation = new ImageLocation(attributes, strings);
+
+        if (!imageLocation.verify(name)) {
+            return null;
+        }
+
+        return imageLocation;
+   }
+
+    @Override
+    public int[] attributeOffsets() {
+        return offsets;
+    }
+}
--- a/jdk/src/java.base/share/classes/jdk/internal/jimage/ImageLocation.java	Thu Jul 02 15:25:46 2015 +0200
+++ b/jdk/src/java.base/share/classes/jdk/internal/jimage/ImageLocation.java	Fri Jul 03 07:13:24 2015 -0700
@@ -25,369 +25,15 @@
 
 package jdk.internal.jimage;
 
-import java.nio.ByteBuffer;
-
-public final class ImageLocation {
-    final static int ATTRIBUTE_END = 0;
-    final static int ATTRIBUTE_BASE = 1;
-    final static int ATTRIBUTE_PARENT = 2;
-    final static int ATTRIBUTE_EXTENSION = 3;
-    final static int ATTRIBUTE_OFFSET = 4;
-    final static int ATTRIBUTE_COMPRESSED = 5;
-    final static int ATTRIBUTE_UNCOMPRESSED = 6;
-    final static int ATTRIBUTE_COUNT = 7;
-
-    private int locationOffset;
-    private long[] attributes;
-    private byte[] bytes;
-    private final ImageStrings strings;
-
-    private ImageLocation(ImageStrings strings) {
-        this.strings = strings;
-    }
-
-    void writeTo(ImageStream stream) {
-        compress();
-        locationOffset = stream.getPosition();
-        stream.put(bytes, 0, bytes.length);
-    }
-
-    static ImageLocation readFrom(ByteBuffer locationsBuffer, int offset, ImageStrings strings) {
-        final long[] attributes = new long[ATTRIBUTE_COUNT];
-
-        for (int i = offset; true; ) {
-            int data = locationsBuffer.get(i++) & 0xFF;
-            int kind = attributeKind(data);
-            assert ATTRIBUTE_END <= kind && kind < ATTRIBUTE_COUNT : "Invalid attribute kind";
-
-            if (kind == ATTRIBUTE_END) {
-                break;
-            }
-
-            int length = attributeLength(data);
-            long value = 0;
-
-            for (int j = 0; j < length; j++) {
-                value <<= 8;
-                value |= locationsBuffer.get(i++) & 0xFF;
-            }
-
-            attributes[kind] = value;
-        }
-
-        ImageLocation location =  new ImageLocation(strings);
-        location.attributes = attributes;
-
-        return location;
-    }
-
-    private static int attributeLength(int data) {
-        return (data & 0x7) + 1;
-    }
-
-    private static int attributeKind(int data) {
-        return data >>> 3;
-    }
-
-    public boolean verify(UTF8String name) {
-        UTF8String match = UTF8String.match(name, getParent());
-
-        if (match == null) {
-            return false;
-        }
-
-        match = UTF8String.match(match, getBase());
-
-        if (match == null) {
-            return false;
-        }
-
-        match = UTF8String.match(match, getExtension());
-
-        return match != null && match.length() == 0;
-    }
-
-
-    long getAttribute(int kind) {
-        assert ATTRIBUTE_END < kind && kind < ATTRIBUTE_COUNT : "Invalid attribute kind";
-        decompress();
-
-        return attributes[kind];
-    }
-
-    UTF8String getAttributeUTF8String(int kind) {
-        assert ATTRIBUTE_END < kind && kind < ATTRIBUTE_COUNT : "Invalid attribute kind";
-        decompress();
-
-        return strings.get((int)attributes[kind]);
-    }
-
-    String getAttributeString(int kind) {
-        return getAttributeUTF8String(kind).toString();
-    }
-
-    ImageLocation addAttribute(int kind, long value) {
-        assert ATTRIBUTE_END < kind && kind < ATTRIBUTE_COUNT : "Invalid attribute kind";
-        decompress();
-        attributes[kind] = value;
-        return this;
-    }
-
-    private void decompress() {
-        if (attributes == null) {
-            attributes = new long[ATTRIBUTE_COUNT];
-        }
-
-        if (bytes != null) {
-            for (int i = 0; i < bytes.length; ) {
-                int data = bytes[i++] & 0xFF;
-                int kind = attributeKind(data);
-
-                if (kind == ATTRIBUTE_END) {
-                    break;
-                }
-
-                assert ATTRIBUTE_END < kind && kind < ATTRIBUTE_COUNT : "Invalid attribute kind";
-                int length = attributeLength(data);
-                long value = 0;
-
-                for (int j = 0; j < length; j++) {
-                    value <<= 8;
-                    value |= bytes[i++] & 0xFF;
-                }
-
-                 attributes[kind] = value;
-            }
-
-            bytes = null;
-        }
-    }
-
-    private void compress() {
-        if (bytes == null) {
-            ImageStream stream = new ImageStream(16);
-
-            for (int kind = ATTRIBUTE_END + 1; kind < ATTRIBUTE_COUNT; kind++) {
-                long value = attributes[kind];
-
-                if (value != 0) {
-                    int n = (63 - Long.numberOfLeadingZeros(value)) >> 3;
-                    stream.put((kind << 3) | n);
-
-                    for (int i = n; i >= 0; i--) {
-                        stream.put((int)(value >> (i << 3)));
-                    }
-                }
-            }
-
-            stream.put(ATTRIBUTE_END << 3);
-            bytes = stream.toArray();
-            attributes = null;
-        }
+public final class ImageLocation  extends ImageLocationBase {
+    ImageLocation(long[] attributes, ImageStringsReader strings) {
+        super(attributes, strings);
     }
 
-    static ImageLocation newLocation(UTF8String fullname, ImageStrings strings, long contentOffset, long compressedSize, long uncompressedSize) {
-        UTF8String base;
-        UTF8String extension = extension(fullname);
-        int parentOffset = ImageStrings.EMPTY_OFFSET;
-        int extensionOffset = ImageStrings.EMPTY_OFFSET;
-        int baseOffset;
-
-        if (extension.length() != 0) {
-            UTF8String parent = parent(fullname);
-            base = base(fullname);
-            parentOffset = strings.add(parent);
-            extensionOffset = strings.add(extension);
-        } else {
-            base = fullname;
-        }
-
-        baseOffset = strings.add(base);
-
-        return new ImageLocation(strings)
-               .addAttribute(ATTRIBUTE_BASE, baseOffset)
-               .addAttribute(ATTRIBUTE_PARENT, parentOffset)
-               .addAttribute(ATTRIBUTE_EXTENSION, extensionOffset)
-               .addAttribute(ATTRIBUTE_OFFSET, contentOffset)
-               .addAttribute(ATTRIBUTE_COMPRESSED, compressedSize)
-               .addAttribute(ATTRIBUTE_UNCOMPRESSED, uncompressedSize);
-    }
-
-    @Override
-    public int hashCode() {
-        return getExtension().hashCode(getBase().hashCode(getParent().hashCode()));
-    }
-
-    int hashCode(int base) {
-        return getExtension().hashCode(getBase().hashCode(getParent().hashCode(base)));
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (this == obj) {
-            return true;
-        }
-
-        if (!(obj instanceof ImageLocation)) {
-            return false;
-        }
-
-        ImageLocation other = (ImageLocation)obj;
-
-        return getBaseOffset() == other.getBaseOffset() &&
-               getParentOffset() == other.getParentOffset() &&
-               getExtensionOffset() == other.getExtensionOffset();
-    }
-
-    static UTF8String parent(UTF8String fullname) {
-        int slash = fullname.lastIndexOf('/');
-
-        return slash == UTF8String.NOT_FOUND ? UTF8String.EMPTY_STRING : fullname.substring(0, slash + 1);
-    }
-
-    static UTF8String extension(UTF8String fullname) {
-        int dot = fullname.lastIndexOf('.');
-
-        return dot == UTF8String.NOT_FOUND ? UTF8String.EMPTY_STRING : fullname.substring(dot);
-    }
-
-    static UTF8String base(UTF8String fullname) {
-        int slash = fullname.lastIndexOf('/');
-
-        if (slash != UTF8String.NOT_FOUND) {
-            fullname = fullname.substring(slash + 1);
-        }
-
-        int dot = fullname.lastIndexOf('.');
-
-        if (dot != UTF8String.NOT_FOUND) {
-            fullname = fullname.substring(0, dot);
-        }
-
-        return fullname;
-    }
-
-    int getLocationOffset() {
-        return locationOffset;
-    }
-
-    UTF8String getBase() {
-        return getAttributeUTF8String(ATTRIBUTE_BASE);
-    }
-
-    public String getBaseString() {
-        return  getBase().toString();
-    }
-
-    int getBaseOffset() {
-        return (int)getAttribute(ATTRIBUTE_BASE);
-    }
-
-    UTF8String getParent() {
-        return getAttributeUTF8String(ATTRIBUTE_PARENT);
-    }
+    static ImageLocation readFrom(BasicImageReader reader, int offset) {
+        long[] attributes = reader.getAttributes(offset);
+        ImageStringsReader strings = reader.getStrings();
 
-    public String getParentString() {
-        return getParent().toString();
-    }
-
-    int getParentOffset() {
-        return (int)getAttribute(ATTRIBUTE_PARENT);
-    }
-
-    UTF8String getExtension() {
-        return getAttributeUTF8String(ATTRIBUTE_EXTENSION);
-    }
-
-    public String getExtensionString() {
-        return getExtension().toString();
-    }
-
-    int getExtensionOffset() {
-        return (int)getAttribute(ATTRIBUTE_EXTENSION);
-    }
-
-    UTF8String getName() {
-        return getBase().concat(getExtension());
-    }
-
-    String getNameString() {
-        return getName().toString();
-    }
-
-    UTF8String getFullname() {
-        return getParent().concat(getBase(), getExtension());
-    }
-
-    String getFullnameString() {
-        return getFullname().toString();
-    }
-
-    public long getContentOffset() {
-        return getAttribute(ATTRIBUTE_OFFSET);
-    }
-
-    public long getCompressedSize() {
-        return getAttribute(ATTRIBUTE_COMPRESSED);
-    }
-
-    public long getUncompressedSize() {
-        return getAttribute(ATTRIBUTE_UNCOMPRESSED);
-    }
-
-    @Override
-    public String toString() {
-        final StringBuilder sb = new StringBuilder();
-        decompress();
-
-        for (int kind = ATTRIBUTE_END + 1; kind < ATTRIBUTE_COUNT; kind++) {
-            long value = attributes[kind];
-
-            if (value == 0) {
-                continue;
-            }
-
-            switch (kind) {
-                case ATTRIBUTE_BASE:
-                    sb.append("Base: ");
-                    sb.append(value);
-                    sb.append(' ');
-                    sb.append(strings.get((int)value).toString());
-                    break;
-
-                case ATTRIBUTE_PARENT:
-                    sb.append("Parent: ");
-                    sb.append(value);
-                    sb.append(' ');
-                    sb.append(strings.get((int)value).toString());
-                    break;
-
-                case ATTRIBUTE_EXTENSION:
-                    sb.append("Extension: ");
-                    sb.append(value);
-                    sb.append(' ');
-                    sb.append(strings.get((int)value).toString());
-                    break;
-
-                case ATTRIBUTE_OFFSET:
-                    sb.append("Offset: ");
-                    sb.append(value);
-                    break;
-
-                case ATTRIBUTE_COMPRESSED:
-                    sb.append("Compressed: ");
-                    sb.append(value);
-                    break;
-
-                case ATTRIBUTE_UNCOMPRESSED:
-                    sb.append("Uncompressed: ");
-                    sb.append(value);
-                    break;
-           }
-
-           sb.append("; ");
-        }
-
-        return sb.toString();
+        return new ImageLocation(attributes, strings);
     }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.base/share/classes/jdk/internal/jimage/ImageLocationBase.java	Fri Jul 03 07:13:24 2015 -0700
@@ -0,0 +1,264 @@
+/*
+ * 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.jimage;
+
+public class ImageLocationBase {
+    final static int ATTRIBUTE_END = 0;
+    final static int ATTRIBUTE_MODULE = 1;
+    final static int ATTRIBUTE_PARENT = 2;
+    final static int ATTRIBUTE_BASE = 3;
+    final static int ATTRIBUTE_EXTENSION = 4;
+    final static int ATTRIBUTE_OFFSET = 5;
+    final static int ATTRIBUTE_COMPRESSED = 6;
+    final static int ATTRIBUTE_UNCOMPRESSED = 7;
+    final static int ATTRIBUTE_COUNT = 8;
+
+    protected final long[] attributes;
+
+    protected final ImageStrings strings;
+
+    protected ImageLocationBase(long[] attributes, ImageStrings strings) {
+        this.attributes = attributes;
+        this.strings = strings;
+    }
+
+    ImageStrings getStrings() {
+        return strings;
+    }
+
+    private static int attributeLength(int data) {
+        return (data & 0x7) + 1;
+    }
+
+    private static int attributeKind(int data) {
+        return data >>> 3;
+    }
+
+    static long[] decompress(byte[] bytes) {
+        return decompress(bytes, 0);
+    }
+
+    static long[] decompress(byte[] bytes, int offset) {
+        long[] attributes = new long[ATTRIBUTE_COUNT];
+
+        if (bytes != null) {
+            for (int i = offset; i < bytes.length; ) {
+                int data = bytes[i++] & 0xFF;
+                int kind = attributeKind(data);
+
+                if (kind == ATTRIBUTE_END) {
+                    break;
+                }
+
+                assert ATTRIBUTE_END < kind &&
+                       kind < ATTRIBUTE_COUNT : "Invalid attribute kind";
+                int length = attributeLength(data);
+                long value = 0;
+
+                for (int j = 0; j < length; j++) {
+                    value <<= 8;
+                    value |= bytes[i++] & 0xFF;
+                }
+
+                 attributes[kind] = value;
+            }
+        }
+
+        return attributes;
+    }
+
+    static byte[] compress(long[] attributes) {
+        ImageStream stream = new ImageStream(16);
+
+        for (int kind = ATTRIBUTE_END + 1; kind < ATTRIBUTE_COUNT; kind++) {
+            long value = attributes[kind];
+
+            if (value != 0) {
+                int n = (63 - Long.numberOfLeadingZeros(value)) >> 3;
+                stream.put((kind << 3) | n);
+
+                for (int i = n; i >= 0; i--) {
+                    stream.put((int)(value >> (i << 3)));
+                }
+            }
+        }
+
+        stream.put(ATTRIBUTE_END << 3);
+
+        return stream.toArray();
+     }
+
+    public boolean verify(UTF8String name) {
+        return UTF8String.equals(getFullName(), name);
+    }
+
+    protected long getAttribute(int kind) {
+        assert ATTRIBUTE_END < kind &&
+               kind < ATTRIBUTE_COUNT : "Invalid attribute kind";
+
+        return attributes[kind];
+    }
+
+    protected UTF8String getAttributeUTF8String(int kind) {
+        assert ATTRIBUTE_END < kind &&
+               kind < ATTRIBUTE_COUNT : "Invalid attribute kind";
+
+        return getStrings().get((int)attributes[kind]);
+    }
+
+    protected String getAttributeString(int kind) {
+        return getAttributeUTF8String(kind).toString();
+    }
+
+    UTF8String getModule() {
+        return getAttributeUTF8String(ATTRIBUTE_MODULE);
+    }
+
+    public String getModuleString() {
+        return getModule().toString();
+    }
+
+    int getModuleOffset() {
+        return (int)getAttribute(ATTRIBUTE_MODULE);
+    }
+
+    UTF8String getBase() {
+        return getAttributeUTF8String(ATTRIBUTE_BASE);
+    }
+
+    public String getBaseString() {
+        return  getBase().toString();
+    }
+
+    int getBaseOffset() {
+        return (int)getAttribute(ATTRIBUTE_BASE);
+    }
+
+    UTF8String getParent() {
+        return getAttributeUTF8String(ATTRIBUTE_PARENT);
+    }
+
+    public String getParentString() {
+        return getParent().toString();
+    }
+
+    int getParentOffset() {
+        return (int)getAttribute(ATTRIBUTE_PARENT);
+    }
+
+    UTF8String getExtension() {
+        return getAttributeUTF8String(ATTRIBUTE_EXTENSION);
+    }
+
+    public String getExtensionString() {
+        return getExtension().toString();
+    }
+
+    int getExtensionOffset() {
+        return (int)getAttribute(ATTRIBUTE_EXTENSION);
+    }
+
+    UTF8String getFullName() {
+        return getFullName(false);
+    }
+
+    UTF8String getFullName(boolean modulesPrefix) {
+        // Note: Consider a UTF8StringBuilder.
+        UTF8String fullName = UTF8String.EMPTY_STRING;
+
+        if (getModuleOffset() != 0) {
+            fullName = fullName.concat(
+                // TODO The use of UTF8String.MODULES_STRING does not belong here.
+                modulesPrefix? UTF8String.MODULES_STRING :
+                               UTF8String.EMPTY_STRING,
+                UTF8String.SLASH_STRING,
+                getModule(),
+                UTF8String.SLASH_STRING);
+        }
+
+        if (getParentOffset() != 0) {
+            fullName = fullName.concat(getParent(),
+                                       UTF8String.SLASH_STRING);
+        }
+
+        fullName = fullName.concat(getBase());
+
+        if (getExtensionOffset() != 0) {
+                fullName = fullName.concat(UTF8String.DOT_STRING,
+                                           getExtension());
+        }
+
+        return fullName;
+    }
+
+    UTF8String buildName(boolean includeModule, boolean includeParent,
+            boolean includeName) {
+        // Note: Consider a UTF8StringBuilder.
+        UTF8String name = UTF8String.EMPTY_STRING;
+
+        if (includeModule && getModuleOffset() != 0) {
+            name = name.concat(UTF8String.MODULES_STRING,
+                               UTF8String.SLASH_STRING,
+                               getModule());
+        }
+
+        if (includeParent && getParentOffset() != 0) {
+            name = name.concat(UTF8String.SLASH_STRING,
+                                       getParent());
+        }
+
+        if (includeName) {
+            if (includeModule || includeParent) {
+                name = name.concat(UTF8String.SLASH_STRING);
+            }
+
+            name = name.concat(getBase());
+
+            if (getExtensionOffset() != 0) {
+                name = name.concat(UTF8String.DOT_STRING,
+                                           getExtension());
+            }
+        }
+
+        return name;
+    }
+
+    String getFullNameString() {
+        return getFullName().toString();
+    }
+
+    public long getContentOffset() {
+        return getAttribute(ATTRIBUTE_OFFSET);
+    }
+
+    public long getCompressedSize() {
+        return getAttribute(ATTRIBUTE_COMPRESSED);
+    }
+
+    public long getUncompressedSize() {
+        return getAttribute(ATTRIBUTE_UNCOMPRESSED);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.base/share/classes/jdk/internal/jimage/ImageLocationWriter.java	Fri Jul 03 07:13:24 2015 -0700
@@ -0,0 +1,140 @@
+/*
+ * 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.jimage;
+
+public final class ImageLocationWriter extends ImageLocationBase {
+    private int locationOffset;
+
+    private ImageLocationWriter(ImageStringsWriter strings) {
+        super(new long[ATTRIBUTE_COUNT], strings);
+    }
+
+    void writeTo(ImageStream stream) {
+        byte[] bytes = ImageLocation.compress(attributes);
+        locationOffset = stream.getPosition();
+        stream.put(bytes, 0, bytes.length);
+    }
+
+    private ImageLocationWriter addAttribute(int kind, long value) {
+        assert ATTRIBUTE_END < kind &&
+               kind < ATTRIBUTE_COUNT : "Invalid attribute kind";
+        attributes[kind] = value;
+        return this;
+    }
+
+    private ImageLocationWriter addAttribute(int kind, UTF8String value) {
+        return addAttribute(kind, strings.add(value));
+    }
+
+    static ImageLocationWriter newLocation(UTF8String fullName,
+            ImageStringsWriter strings,
+            long contentOffset, long compressedSize, long uncompressedSize) {
+        UTF8String moduleName = UTF8String.EMPTY_STRING;
+        UTF8String parentName = UTF8String.EMPTY_STRING;
+        UTF8String baseName;
+        UTF8String extensionName = UTF8String.EMPTY_STRING;
+
+        int offset = fullName.indexOf('/', 1);
+        if (fullName.length() >= 2 && fullName.charAt(0) == '/' && offset != -1) {
+            moduleName = fullName.substring(1, offset - 1);
+            fullName = fullName.substring(offset + 1);
+        }
+
+        offset = fullName.lastIndexOf('/');
+        if (offset != -1) {
+            parentName = fullName.substring(0, offset);
+            fullName = fullName.substring(offset + 1);
+        }
+
+        offset = fullName.lastIndexOf('.');
+        if (offset != -1) {
+            baseName = fullName.substring(0, offset);
+            extensionName = fullName.substring(offset + 1);
+        } else {
+            baseName = fullName;
+        }
+
+        return new ImageLocationWriter(strings)
+               .addAttribute(ATTRIBUTE_MODULE, moduleName)
+               .addAttribute(ATTRIBUTE_PARENT, parentName)
+               .addAttribute(ATTRIBUTE_BASE, baseName)
+               .addAttribute(ATTRIBUTE_EXTENSION, extensionName)
+               .addAttribute(ATTRIBUTE_OFFSET, contentOffset)
+               .addAttribute(ATTRIBUTE_COMPRESSED, compressedSize)
+               .addAttribute(ATTRIBUTE_UNCOMPRESSED, uncompressedSize);
+    }
+
+    @Override
+    public int hashCode() {
+        return hashCode(UTF8String.HASH_MULTIPLIER);
+    }
+
+    int hashCode(int seed) {
+        int hash = seed;
+
+        if (getModuleOffset() != 0) {
+            hash = UTF8String.SLASH_STRING.hashCode(hash);
+            hash = getModule().hashCode(hash);
+            hash = UTF8String.SLASH_STRING.hashCode(hash);
+        }
+
+        if (getParentOffset() != 0) {
+            hash = getParent().hashCode(hash);
+            hash = UTF8String.SLASH_STRING.hashCode(hash);
+        }
+
+        hash = getBase().hashCode(hash);
+
+        if (getExtensionOffset() != 0) {
+            hash = UTF8String.DOT_STRING.hashCode(hash);
+            hash = getExtension().hashCode(hash);
+        }
+
+        return hash;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+
+        if (!(obj instanceof ImageLocationWriter)) {
+            return false;
+        }
+
+        ImageLocation other = (ImageLocation)obj;
+
+        return getModuleOffset() == other.getModuleOffset() &&
+               getParentOffset() == other.getParentOffset() &&
+               getBaseOffset() == other.getBaseOffset() &&
+               getExtensionOffset() == other.getExtensionOffset();
+    }
+
+    int getLocationOffset() {
+        return locationOffset;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.base/share/classes/jdk/internal/jimage/ImageModuleData.java	Fri Jul 03 07:13:24 2015 -0700
@@ -0,0 +1,288 @@
+/*
+ * 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.jimage;
+
+import java.nio.ByteBuffer;
+import java.nio.IntBuffer;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+
+/*
+ * Manage module meta data.
+ *
+ * NOTE: needs revision.
+ * Each loader requires set of module meta data to identify which modules and
+ * packages are managed by that loader.  Currently, there is one image file per
+ * loader, so only one  module meta data resource per file.
+ *
+ * Each element in the module meta data is a native endian 4 byte integer.  Note
+ * that entries with zero offsets for string table entries should be ignored (
+ * padding for hash table lookup.)
+ *
+ * Format:
+ *    Count of package to module entries
+ *    Count of module to package entries
+ *    Perfect Hash redirect table[Count of package to module entries]
+ *    Package to module entries[Count of package to module entries]
+ *        Offset to package name in string table
+ *        Offset to module name in string table
+ *    Perfect Hash redirect table[Count of module to package entries]
+ *    Module to package entries[Count of module to package entries]
+ *        Offset to module name in string table
+ *        Count of packages in module
+ *        Offset to first package in packages table
+ *    Packages[]
+ *        Offset to package name in string table
+ */
+
+final public class ImageModuleData {
+    public final static String META_DATA_EXTENSION = ".jdata";
+    public final static String SEPARATOR = "\t";
+    public final static int NOT_FOUND = -1;
+    private final static int ptmCountOffset = 0;
+    private final static int mtpCountOffset = 1;
+    private final static int ptmRedirectOffset = 2;
+    private final static int dataNameOffset = 0;
+    private final static int ptmDataWidth = 2;
+    private final static int ptmDataModuleOffset = 1;
+    private final static int mtpDataWidth = 3;
+    private final static int mtpDataCountOffset = 1;
+    private final static int mtpDataOffsetOffset = 2;
+
+    private final BasicImageReader reader;
+    private final IntBuffer intBuffer;
+    private final int ptmRedirectLength;
+    private final int mtpRedirectLength;
+    private final int ptmDataOffset;
+    private final int mtpRedirectOffset;
+    private final int mtpDataOffset;
+    private final int mtpPackagesOffset;
+
+    public ImageModuleData(BasicImageReader reader) {
+         this(reader, getBytes(reader));
+    }
+
+    public ImageModuleData(BasicImageReader reader, byte[] bytes) {
+        this.reader = reader;
+
+        ByteBuffer byteBuffer = ByteBuffer.wrap(bytes).order(reader.getByteOrder());
+        this.intBuffer = byteBuffer.asIntBuffer();
+
+        this.ptmRedirectLength = get(ptmCountOffset);
+        this.mtpRedirectLength = get(mtpCountOffset);
+
+        this.ptmDataOffset = ptmRedirectOffset + ptmRedirectLength;
+        this.mtpRedirectOffset = ptmDataOffset + ptmRedirectLength * ptmDataWidth;
+        this.mtpDataOffset = mtpRedirectOffset + mtpRedirectLength;
+        this.mtpPackagesOffset = mtpDataOffset + mtpRedirectLength * mtpDataWidth;
+    }
+
+    private static byte[] getBytes(BasicImageReader reader) {
+        String loaderName = reader.imagePathName();
+
+        if (loaderName.endsWith(BasicImageWriter.IMAGE_EXT)) {
+            loaderName = loaderName.substring(0, loaderName.length() -
+                    BasicImageWriter.IMAGE_EXT.length());
+        }
+
+        byte[] bytes = reader.getResource(getModuleDataName(loaderName));
+
+        if (bytes == null) {
+            throw new InternalError("module data missing");
+        }
+
+        return bytes;
+    }
+
+    public List<String> fromModulePackages() {
+        List<String> lines = new ArrayList<>();
+
+        for (int i = 0; i < mtpRedirectLength; i++) {
+            int index = mtpDataOffset + i * mtpDataWidth;
+            int offset = get(index + dataNameOffset);
+
+            if (offset != 0) {
+                StringBuilder sb = new StringBuilder();
+
+                sb.append(getString(offset));
+
+                int count = get(index + mtpDataCountOffset);
+                int base = get(index + mtpDataOffsetOffset) + mtpPackagesOffset;
+
+                for (int j = 0; j < count; j++) {
+                    sb.append(SEPARATOR);
+                    sb.append(stringAt(base + j));
+                }
+
+                lines.add(sb.toString());
+            }
+        }
+
+        return lines;
+    }
+
+    public static String getModuleDataName(String loaderName) {
+        return loaderName + META_DATA_EXTENSION;
+    }
+
+    private int get(int index) {
+        return intBuffer.get(index);
+    }
+
+    private String getString(int offset) {
+        return reader.getString(offset);
+    }
+
+    private String stringAt(int index) {
+        return reader.getString(get(index));
+    }
+
+    private UTF8String getUTF8String(int offset) {
+        return reader.getUTF8String(offset);
+    }
+
+    private UTF8String utf8StringAt(int index) {
+        return reader.getUTF8String(get(index));
+    }
+
+    private int find(UTF8String name, int baseOffset, int length, int width) {
+        if (length == 0) {
+            return NOT_FOUND;
+        }
+
+        int hashCode = name.hashCode();
+        int index = hashCode % length;
+        int value = get(baseOffset + index);
+
+        if (value > 0 ) {
+            hashCode = name.hashCode(value);
+            index = hashCode % length;
+        } else if (value < 0) {
+            index = -1 - value;
+        } else {
+            return NOT_FOUND;
+        }
+
+        index = baseOffset + length + index * width;
+
+        if (!utf8StringAt(index + dataNameOffset).equals(name)) {
+            return NOT_FOUND;
+        }
+
+        return index;
+    }
+
+    public String packageToModule(String packageName) {
+        UTF8String moduleName = packageToModule(new UTF8String(packageName));
+
+        return moduleName != null ? moduleName.toString() : null;
+    }
+
+    public UTF8String packageToModule(UTF8String packageName) {
+        int index = find(packageName, ptmRedirectOffset, ptmRedirectLength, ptmDataWidth);
+
+        if (index != NOT_FOUND) {
+            return utf8StringAt(index + ptmDataModuleOffset);
+        }
+
+        return null;
+    }
+
+    public List<String> moduleToPackages(String moduleName) {
+        int index = find(new UTF8String(moduleName), mtpRedirectOffset,
+                mtpRedirectLength, mtpDataWidth);
+
+        if (index != NOT_FOUND) {
+            int count = get(index + mtpDataCountOffset);
+            int base = get(index + mtpDataOffsetOffset) + mtpPackagesOffset;
+            List<String> packages = new ArrayList<>(count);
+
+            for (int i = 0; i < count; i++) {
+                packages.add(stringAt(base + i));
+            }
+
+            return packages;
+        }
+
+        return null;
+    }
+
+    public List<String> allPackageNames() {
+        List<String> packages = new ArrayList<>();
+
+        for (int i = 0; i < ptmRedirectLength; i++) {
+            int offset = get(ptmDataOffset + i * ptmDataWidth + dataNameOffset);
+
+            if (offset != 0) {
+                packages.add(getString(offset));
+            }
+        }
+
+        return packages;
+    }
+
+    public Set<String> allModuleNames() {
+        Set<String> modules = new HashSet<>();
+
+        for (int i = 0; i < mtpRedirectLength; i++) {
+            int index = mtpDataOffset + i * mtpDataWidth;
+            int offset = get(index + dataNameOffset);
+
+            if (offset != 0) {
+                modules.add(getString(offset));
+            }
+        }
+
+        return modules;
+    }
+
+    public Map<String, String> packageModuleMap() {
+        Map<String, String> map = new HashMap<>();
+
+        for (int i = 0; i < mtpRedirectLength; i++) {
+            int index = mtpDataOffset + i * mtpDataWidth;
+            int offset = get(index + dataNameOffset);
+
+            if (offset != 0) {
+                String moduleName = getString(offset);
+
+                int count = get(index + mtpDataCountOffset);
+                int base = get(index + mtpDataOffsetOffset) + mtpPackagesOffset;
+
+                for (int j = 0; j < count; j++) {
+                    map.put(stringAt(base + j), moduleName);
+                }
+            }
+        }
+
+        return map;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.base/share/classes/jdk/internal/jimage/ImageModuleDataWriter.java	Fri Jul 03 07:13:24 2015 -0700
@@ -0,0 +1,165 @@
+/*
+ * 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.jimage;
+
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+public class ImageModuleDataWriter {
+    final byte[] bytes;
+
+    public ImageModuleDataWriter(BasicImageWriter writer,
+            Map<String, List<String>> modulePackages) {
+        PerfectHashBuilder<String> packageToModule = new PerfectHashBuilder<>(
+                new PerfectHashBuilder.Entry<String>().getClass(),
+                new PerfectHashBuilder.Bucket<String>().getClass());
+        PerfectHashBuilder<List<String>> moduleToPackages = new PerfectHashBuilder<>(
+                new PerfectHashBuilder.Entry<List<String>>().getClass(),
+                new PerfectHashBuilder.Bucket<List<String>>().getClass());
+
+        modulePackages.entrySet().stream().forEach((entry) -> {
+            String moduleName = entry.getKey();
+            List<String> packages = entry.getValue();
+            packages.stream().forEach((packageName) -> {
+                packageToModule.put(packageName, moduleName);
+            });
+
+            moduleToPackages.put(moduleName, packages);
+        });
+
+        packageToModule.generate();
+        moduleToPackages.generate();
+
+        bytes = getBytes(writer, packageToModule, moduleToPackages);
+    }
+
+    public static ImageModuleDataWriter buildModuleData(BasicImageWriter writer,
+            Map<String, Set<String>> modulePackagesMap) {
+        Set<String> modules = modulePackagesMap.keySet();
+
+        Map<String, List<String>> modulePackages = new LinkedHashMap<>();
+        modules.stream().sorted().forEach((moduleName) -> {
+            List<String> localPackages = modulePackagesMap.get(moduleName).stream()
+                    .map(pn -> pn.replace('.', '/'))
+                    .sorted()
+                    .collect(Collectors.toList());
+            modulePackages.put(moduleName, localPackages);
+        });
+
+        return new ImageModuleDataWriter(writer, modulePackages);
+    }
+
+    public static Map<String, List<String>> toModulePackages(List<String> lines) {
+        Map<String, List<String>> modulePackages = new LinkedHashMap<>();
+
+        for (String line : lines) {
+            String[] parts = line.split(ImageModuleData.SEPARATOR);
+            String moduleName = parts[0];
+            List<String> packages = Arrays.asList(Arrays.copyOfRange(parts, 1, parts.length));
+            modulePackages.put(moduleName, packages);
+        }
+
+        return modulePackages;
+    }
+
+    public void addLocation(String name, BasicImageWriter writer) {
+        writer.addLocation(ImageModuleData.getModuleDataName(name), 0, 0, bytes.length);
+    }
+
+    private byte[] getBytes(BasicImageWriter writer,
+            PerfectHashBuilder<String> packageToModule,
+            PerfectHashBuilder<List<String>> moduleToPackages) {
+        ImageStream stream = new ImageStream(writer.getByteOrder());
+
+        int[] ptmRedirect = packageToModule.getRedirect();
+        int[] mtpRedirect = moduleToPackages.getRedirect();
+        PerfectHashBuilder.Entry<String>[] ptmOrder = packageToModule.getOrder();
+        PerfectHashBuilder.Entry<List<String>>[] mtpOrder = moduleToPackages.getOrder();
+
+        stream.putInt(ptmRedirect.length);
+        stream.putInt(mtpRedirect.length);
+
+        for (int value : ptmRedirect) {
+            stream.putInt(value);
+        }
+
+        for (PerfectHashBuilder.Entry<String> entry : ptmOrder) {
+            if (entry != null) {
+                stream.putInt(writer.addString(entry.getKey()));
+                stream.putInt(writer.addString(entry.getValue()));
+            } else {
+                stream.putInt(0);
+                stream.putInt(0);
+            }
+        }
+
+        for (int value : mtpRedirect) {
+            stream.putInt(value);
+        }
+
+        int index = 0;
+
+        for (PerfectHashBuilder.Entry<List<String>> entry : mtpOrder) {
+            if (entry != null) {
+                int count = entry.getValue().size();
+                stream.putInt(writer.addString(entry.getKey()));
+                stream.putInt(count);
+                stream.putInt(index);
+                index += count;
+            } else {
+                stream.putInt(0);
+                stream.putInt(0);
+                stream.putInt(0);
+            }
+        }
+
+        for (PerfectHashBuilder.Entry<List<String>> entry : mtpOrder) {
+            if (entry != null) {
+                List<String> value = entry.getValue();
+                value.stream().forEach((packageName) -> {
+                    stream.putInt(writer.addString(packageName));
+                });
+            }
+        }
+
+        return stream.toArray();
+    }
+
+    public void writeTo(DataOutputStream out) throws IOException {
+         out.write(bytes, 0, bytes.length);
+    }
+
+    public int size() {
+        return bytes.length;
+    }
+}
--- a/jdk/src/java.base/share/classes/jdk/internal/jimage/ImageModules.java	Thu Jul 02 15:25:46 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,180 +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.jimage;
-
-import java.io.DataOutputStream;
-import java.io.IOException;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.LinkedHashMap;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.stream.Collectors;
-
-import static jdk.internal.jimage.PackageModuleMap.*;
-
-public class ImageModules {
-    protected final Map<Loader, LoaderModuleData> loaders = new LinkedHashMap<>();
-    protected final Map<String, Set<String>> localPkgs = new HashMap<>();
-
-    protected ImageModules() {}
-
-    public ImageModules(Set<String> bootModules,
-                        Set<String> extModules,
-                        Set<String> appModules) throws IOException {
-        mapModulesToLoader(Loader.BOOT_LOADER, bootModules);
-        mapModulesToLoader(Loader.EXT_LOADER, extModules);
-        mapModulesToLoader(Loader.APP_LOADER, appModules);
-    }
-
-    public Map<String, Set<String>> packages() {
-        return localPkgs;
-    }
-
-    // ## FIXME: should be package-private
-    // When jlink legacy format support is removed, it should
-    // use the package table in the jimage.
-    public void setPackages(String mn, Set<String> pkgs) {
-        localPkgs.put(mn, pkgs);
-    }
-
-    /*
-     * Returns the name of modules mapped to a given class loader in the image
-     */
-    public Set<String> getModules(Loader type) {
-        if (loaders.containsKey(type)) {
-            return loaders.get(type).modules();
-        } else {
-            return Collections.emptySet();
-        }
-    }
-
-    private void mapModulesToLoader(Loader loader, Set<String> modules) {
-        if (modules.isEmpty())
-            return;
-
-        // put java.base first
-        Set<String> mods = new LinkedHashSet<>();
-        modules.stream()
-               .filter(m -> m.equals("java.base"))
-               .forEach(mods::add);
-        modules.stream().sorted()
-               .filter(m -> !m.equals("java.base"))
-               .forEach(mods::add);
-        loaders.put(loader, new LoaderModuleData(loader, mods));
-    }
-
-    enum Loader {
-        BOOT_LOADER(0, "bootmodules"),
-        EXT_LOADER(1, "extmodules"),
-        APP_LOADER(2, "appmodules");  // ## may be more than 1 loader
-
-        final int id;
-        final String name;
-        Loader(int id, String name) {
-            this.id = id;
-            this.name = name;
-        }
-
-        String getName() {
-            return name;
-        }
-        static Loader get(int id) {
-            switch (id) {
-                case 0: return BOOT_LOADER;
-                case 1: return EXT_LOADER;
-                case 2: return APP_LOADER;
-                default:
-                    throw new IllegalArgumentException("invalid loader id: " + id);
-            }
-        }
-        public int id() { return id; }
-    }
-
-    public class LoaderModuleData {
-        private final Loader loader;
-        private final Set<String> modules;
-        LoaderModuleData(Loader loader, Set<String> modules) {
-            this.loader = loader;
-            this.modules = Collections.unmodifiableSet(modules);
-        }
-
-        Set<String> modules() {
-            return modules;
-        }
-        Loader loader() { return loader; }
-    }
-
-    ModuleIndex buildModuleIndex(Loader type, BasicImageWriter writer) {
-        return new ModuleIndex(getModules(type), writer);
-    }
-
-    /*
-     * Generate module name table and the package map as resources
-     * in the modular image
-     */
-    public class ModuleIndex {
-        final Map<String, Integer> moduleOffsets = new LinkedHashMap<>();
-        final Map<String, List<Integer>> packageOffsets = new HashMap<>();
-        final int size;
-        public ModuleIndex(Set<String> mods, BasicImageWriter writer) {
-            // module name offsets
-            writer.addLocation(MODULES_ENTRY, 0, 0, mods.size() * 4);
-            long offset = mods.size() * 4;
-            for (String mn : mods) {
-                moduleOffsets.put(mn, writer.addString(mn));
-                List<Integer> poffsets = localPkgs.get(mn).stream()
-                        .map(pn -> pn.replace('.', '/'))
-                        .map(writer::addString)
-                        .collect(Collectors.toList());
-                // package name offsets per module
-                String entry = mn + "/" + PACKAGES_ENTRY;
-                int bytes = poffsets.size() * 4;
-                writer.addLocation(entry, offset, 0, bytes);
-                offset += bytes;
-                packageOffsets.put(mn, poffsets);
-            }
-            this.size = (int) offset;
-        }
-
-        void writeTo(DataOutputStream out) throws IOException {
-            for (int moffset : moduleOffsets.values()) {
-                out.writeInt(moffset);
-            }
-            for (String mn : moduleOffsets.keySet()) {
-                for (int poffset : packageOffsets.get(mn)) {
-                    out.writeInt(poffset);
-                }
-            }
-        }
-
-        int size() {
-            return size;
-        }
-    }
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.base/share/classes/jdk/internal/jimage/ImageNativeSubstrate.java	Fri Jul 03 07:13:24 2015 -0700
@@ -0,0 +1,134 @@
+/*
+ * 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.jimage;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import sun.misc.JavaNioAccess;
+import sun.misc.SharedSecrets;
+
+final class ImageNativeSubstrate implements ImageSubstrate {
+    private static final JavaNioAccess NIOACCESS =
+            SharedSecrets.getJavaNioAccess();
+
+    private final long id;
+    private final long indexAddress;
+    private final long dataAddress;
+
+    native static long openImage(String imagePath, boolean bigEndian);
+    native static void closeImage(long id);
+    native static long getIndexAddress(long id);
+    native static long getDataAddress(long id);
+    native static boolean readCompressed(long id, long offset,
+            ByteBuffer compressedBuffer, long compressedSize,
+            ByteBuffer uncompressedBuffer, long uncompressedSize);
+    native static boolean read(long id, long offset,
+            ByteBuffer uncompressedBuffer, long uncompressedSize);
+    native static byte[] getStringBytes(long id, int offset);
+    native static long[] getAttributes(long id, int offset);
+    native static long[] findAttributes(long id, byte[] path);
+    native static int[] attributeOffsets(long id);
+
+    static ByteBuffer newDirectByteBuffer(long address, long capacity) {
+        assert capacity < Integer.MAX_VALUE;
+        return NIOACCESS.newDirectByteBuffer(address, (int)capacity, null);
+    }
+
+    private ImageNativeSubstrate(long id) {
+        this.id = id;
+        this.indexAddress = getIndexAddress(id);
+        this.dataAddress = getDataAddress(id);
+    }
+
+    static ImageSubstrate openImage(String imagePath, ByteOrder byteOrder)
+            throws IOException {
+        long id = openImage(imagePath, byteOrder == ByteOrder.BIG_ENDIAN);
+
+        if (id == 0) {
+             throw new IOException("Image not found \"" + imagePath + "\"");
+        }
+
+        return new ImageNativeSubstrate(id);
+    }
+
+    @Override
+    public void close() {
+        closeImage(id);
+    }
+
+    @Override
+    public ByteBuffer getIndexBuffer(long offset, long size) {
+        return newDirectByteBuffer(indexAddress + offset, size);
+    }
+
+    @Override
+    public ByteBuffer getDataBuffer(long offset, long size) {
+        return dataAddress != 0 ?
+                newDirectByteBuffer(dataAddress + offset, size) : null;
+    }
+
+    @Override
+    public boolean supportsDataBuffer() {
+        return dataAddress != 0;
+    }
+
+    @Override
+    public boolean read(long offset,
+                 ByteBuffer compressedBuffer, long compressedSize,
+                 ByteBuffer uncompressedBuffer, long uncompressedSize) {
+        return readCompressed(id, offset,
+                    compressedBuffer, compressedSize,
+                    uncompressedBuffer, uncompressedSize);
+    }
+
+    @Override
+    public boolean read(long offset,
+                 ByteBuffer uncompressedBuffer, long uncompressedSize) {
+        return read(id, offset, uncompressedBuffer, uncompressedSize);
+    }
+
+    @Override
+    public byte[] getStringBytes(int offset) {
+        return getStringBytes(id, offset);
+    }
+
+    @Override
+    public long[] getAttributes(int offset) {
+        return getAttributes(id, offset);
+    }
+
+    @Override
+    public ImageLocation findLocation(UTF8String name, ImageStringsReader strings) {
+        long[] attributes = findAttributes(id, name.getBytes());
+
+        return attributes != null ? new ImageLocation(attributes, strings) : null;
+    }
+
+    @Override
+    public int[] attributeOffsets() {
+        return attributeOffsets(id);
+    }
+}
--- a/jdk/src/java.base/share/classes/jdk/internal/jimage/ImageReader.java	Thu Jul 02 15:25:46 2015 +0200
+++ b/jdk/src/java.base/share/classes/jdk/internal/jimage/ImageReader.java	Fri Jul 03 07:13:24 2015 -0700
@@ -26,12 +26,10 @@
 
 import java.io.IOException;
 import java.io.UncheckedIOException;
-import java.net.URI;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
 import java.nio.IntBuffer;
 import java.nio.file.Files;
-import java.nio.file.FileSystem;
 import java.nio.file.attribute.BasicFileAttributes;
 import java.nio.file.attribute.FileTime;
 import java.nio.file.Paths;
@@ -42,13 +40,11 @@
 import java.util.List;
 import java.util.Map;
 import java.util.function.Consumer;
-import java.util.function.Supplier;
+import static jdk.internal.jimage.UTF8String.*;
 
 public class ImageReader extends BasicImageReader {
     // well-known strings needed for image file system.
-    static final UTF8String ROOT = new UTF8String("/");
-    static final UTF8String META_INF = new UTF8String("/META-INF");
-    static final UTF8String PACKAGES_OFFSETS = new UTF8String("packages.offsets");
+    static final UTF8String ROOT_STRING = UTF8String.SLASH_STRING;
 
     // attributes of the .jimage file. jimage file does not contain
     // attributes for the individual resources (yet). We use attributes
@@ -56,15 +52,18 @@
     // Iniitalized lazily, see {@link #imageFileAttributes()}.
     private BasicFileAttributes imageFileAttributes;
 
-    private final Map<String, String> packageMap;
+    private final ImageModuleData moduleData;
 
     // directory management implementation
     private final Map<UTF8String, Node> nodes;
     private volatile Directory rootDir;
 
+    private Directory packagesDir;
+    private Directory modulesDir;
+
     ImageReader(String imagePath, ByteOrder byteOrder) throws IOException {
         super(imagePath, byteOrder);
-        this.packageMap = PackageModuleMap.readFrom(this);
+        this.moduleData = new ImageModuleData(this);
         this.nodes = Collections.synchronizedMap(new HashMap<>());
     }
 
@@ -89,11 +88,42 @@
         clearNodes();
     }
 
+    @Override
+    public ImageLocation findLocation(UTF8String name) {
+        ImageLocation location = super.findLocation(name);
+
+        // NOTE: This should be removed when module system is up in full.
+        if (location == null) {
+            int index = name.lastIndexOf('/');
+
+            if (index != -1) {
+                UTF8String packageName = name.substring(0, index);
+                UTF8String moduleName = moduleData.packageToModule(packageName);
+
+                if (moduleName != null) {
+                    UTF8String fullName = UTF8String.SLASH_STRING.concat(moduleName,
+                            UTF8String.SLASH_STRING, name);
+                    location = super.findLocation(fullName);
+                }
+            } else {
+                // No package, try all modules.
+                for (String mod : moduleData.allModuleNames()) {
+                    location = super.findLocation("/" + mod + "/" + name);
+                    if (location != null) {
+                        break;
+                    }
+                }
+            }
+        }
+
+        return location;
+    }
+
     /**
      * Return the module name that contains the given package name.
      */
-    public String getModule(String pkg) {
-        return packageMap.get(pkg);
+    public String getModule(String packageName) {
+        return moduleData.packageToModule(packageName);
     }
 
     // jimage file does not store directory structure. We build nodes
@@ -101,14 +131,13 @@
     // Node can be a directory or a resource
     public static abstract class Node {
         private static final int ROOT_DIR = 0b0000_0000_0000_0001;
-        private static final int MODULE_DIR = 0b0000_0000_0000_0010;
-        private static final int METAINF_DIR = 0b0000_0000_0000_0100;
-        private static final int TOPLEVEL_PKG_DIR = 0b0000_0000_0000_1000;
-        private static final int HIDDEN = 0b0000_0000_0001_0000;
+        private static final int PACKAGES_DIR = 0b0000_0000_0000_0010;
+        private static final int MODULES_DIR = 0b0000_0000_0000_0100;
 
         private int flags;
         private final UTF8String name;
         private final BasicFileAttributes fileAttrs;
+        private boolean completed;
 
         Node(UTF8String name, BasicFileAttributes fileAttrs) {
             assert name != null;
@@ -117,6 +146,19 @@
             this.fileAttrs = fileAttrs;
         }
 
+        /**
+         * A node is completed when all its direct children have been built.
+         *
+         * @return
+         */
+        public boolean isCompleted() {
+            return completed;
+        }
+
+        public void setCompleted(boolean completed) {
+            this.completed = completed;
+        }
+
         public final void setIsRootDir() {
             flags |= ROOT_DIR;
         }
@@ -125,40 +167,20 @@
             return (flags & ROOT_DIR) != 0;
         }
 
-        public final void setIsModuleDir() {
-            flags |= MODULE_DIR;
-        }
-
-        public final boolean isModuleDir() {
-            return (flags & MODULE_DIR) != 0;
-        }
-
-        public final void setIsMetaInfDir() {
-            flags |= METAINF_DIR;
-        }
-
-        public final boolean isMetaInfDir() {
-            return (flags & METAINF_DIR) != 0;
+        public final void setIsPackagesDir() {
+            flags |= PACKAGES_DIR;
         }
 
-        public final void setIsTopLevelPackageDir() {
-            flags |= TOPLEVEL_PKG_DIR;
-        }
-
-        public final boolean isTopLevelPackageDir() {
-            return (flags & TOPLEVEL_PKG_DIR) != 0;
+        public final boolean isPackagesDir() {
+            return (flags & PACKAGES_DIR) != 0;
         }
 
-        public final void setIsHidden() {
-            flags |= HIDDEN;
+        public final void setIsModulesDir() {
+            flags |= MODULES_DIR;
         }
 
-        public final boolean isHidden() {
-            return (flags & HIDDEN) != 0;
-        }
-
-        public final boolean isVisible() {
-            return !isHidden();
+        public final boolean isModulesDir() {
+            return (flags & MODULES_DIR) != 0;
         }
 
         public final UTF8String getName() {
@@ -169,6 +191,20 @@
             return fileAttrs;
         }
 
+        // resolve this Node (if this is a soft link, get underlying Node)
+        public final Node resolveLink() {
+            return resolveLink(false);
+        }
+
+        public Node resolveLink(boolean recursive) {
+            return this;
+        }
+
+        // is this a soft link Node?
+        public boolean isLink() {
+            return false;
+        }
+
         public boolean isDirectory() {
             return false;
         }
@@ -242,16 +278,20 @@
     }
 
     // directory node - directory has full path name without '/' at end.
-    public static final class Directory extends Node {
+    static final class Directory extends Node {
         private final List<Node> children;
 
-        @SuppressWarnings("LeakingThisInConstructor")
-        Directory(Directory parent, UTF8String name, BasicFileAttributes fileAttrs) {
+        private Directory(Directory parent, UTF8String name, BasicFileAttributes fileAttrs) {
             super(name, fileAttrs);
             children = new ArrayList<>();
+        }
+
+        static Directory create(Directory parent, UTF8String name, BasicFileAttributes fileAttrs) {
+            Directory dir = new Directory(parent, name, fileAttrs);
             if (parent != null) {
-                parent.addChild(this);
+                parent.addChild(dir);
             }
+            return dir;
         }
 
         @Override
@@ -259,6 +299,7 @@
             return true;
         }
 
+        @Override
         public List<Node> getChildren() {
             return Collections.unmodifiableList(children);
         }
@@ -281,19 +322,33 @@
 
     // "resource" is .class or any other resource (compressed/uncompressed) in a jimage.
     // full path of the resource is the "name" of the resource.
-    public static class Resource extends Node {
+    static class Resource extends Node {
         private final ImageLocation loc;
 
-        @SuppressWarnings("LeakingThisInConstructor")
-        Resource(Directory parent, ImageLocation loc, BasicFileAttributes fileAttrs) {
-            this(parent, ROOT.concat(loc.getFullname()), loc, fileAttrs);
+        private Resource(Directory parent, ImageLocation loc, BasicFileAttributes fileAttrs) {
+            this(parent, loc.getFullName(true), loc, fileAttrs);
         }
 
-        @SuppressWarnings("LeakingThisInConstructor")
-        Resource(Directory parent, UTF8String name, ImageLocation loc, BasicFileAttributes fileAttrs) {
+        private Resource(Directory parent, UTF8String name, ImageLocation loc, BasicFileAttributes fileAttrs) {
             super(name, fileAttrs);
             this.loc = loc;
-            parent.addChild(this);
+         }
+
+        static Resource create(Directory parent, ImageLocation loc, BasicFileAttributes fileAttrs) {
+            Resource resource = new Resource(parent, loc, fileAttrs);
+            parent.addChild(resource);
+            return resource;
+        }
+
+        static Resource create(Directory parent, UTF8String name, ImageLocation loc, BasicFileAttributes fileAttrs) {
+            Resource resource = new Resource(parent, name, loc, fileAttrs);
+            parent.addChild(resource);
+            return resource;
+        }
+
+        @Override
+        public boolean isCompleted() {
+            return true;
         }
 
         @Override
@@ -327,6 +382,37 @@
         }
     }
 
+    // represents a soft link to another Node
+    static class LinkNode extends Node {
+        private final Node link;
+
+        private LinkNode(Directory parent, UTF8String name, Node link) {
+            super(name, link.getFileAttributes());
+            this.link = link;
+        }
+
+        static LinkNode create(Directory parent, UTF8String name, Node link) {
+            LinkNode linkNode = new LinkNode(parent, name, link);
+            parent.addChild(linkNode);
+            return linkNode;
+        }
+
+        @Override
+        public boolean isCompleted() {
+            return true;
+        }
+
+        @Override
+        public Node resolveLink(boolean recursive) {
+            return recursive && (link instanceof LinkNode)? ((LinkNode)link).resolveLink(true) : link;
+        }
+
+        @Override
+        public boolean isLink() {
+            return true;
+        }
+    }
+
     // directory management interface
     public Directory getRootDirectory() {
         return buildRootDirectory();
@@ -340,9 +426,154 @@
         return findNode(new UTF8String(name));
     }
 
+    /**
+     * To visit sub tree resources.
+     */
+    interface LocationVisitor {
+
+        void visit(ImageLocation loc);
+    }
+
+    /**
+     * Lazily build a node from a name.
+     */
+    private final class NodeBuilder {
+
+        private static final int SIZE_OF_OFFSET = 4;
+
+        private final UTF8String name;
+
+        private NodeBuilder(UTF8String name) {
+            this.name = name;
+        }
+
+        private Node buildNode() {
+            Node n = null;
+            boolean isPackages = false;
+            boolean isModules = false;
+            String strName = name.toString();
+            if (strName.startsWith("" + PACKAGES_STRING)) {
+                isPackages = true;
+            } else {
+                if (strName.startsWith("" + MODULES_STRING)) {
+                    isModules = true;
+                }
+            }
+            if (!isModules && !isPackages) {
+                return null;
+            }
+
+            ImageLocation loc = findLocation(name);
+
+            if (loc != null) { // A sub tree node
+                if (isPackages) {
+                    n = handlePackages(strName, loc);
+                } else { // modules sub tree
+                    n = handleModulesSubTree(strName, loc);
+                }
+            } else { // Asking for a resource? /modules/java.base/java/lang/Object.class
+                if (isModules) {
+                    n = handleResource(strName, loc);
+                }
+            }
+            return n;
+        }
+
+        private void visitLocation(ImageLocation loc, LocationVisitor visitor) {
+            byte[] offsets = getResource(loc);
+            ByteBuffer buffer = ByteBuffer.wrap(offsets);
+            buffer.order(getByteOrder());
+            IntBuffer intBuffer = buffer.asIntBuffer();
+            for (int i = 0; i < offsets.length / SIZE_OF_OFFSET; i++) {
+                int offset = intBuffer.get(i);
+                ImageLocation pkgLoc = getLocation(offset);
+                visitor.visit(pkgLoc);
+            }
+        }
+
+        private Node handlePackages(String name, ImageLocation loc) {
+            long size = loc.getUncompressedSize();
+            Node n = null;
+            // Only possiblities are /packages, /packages/package/module
+            if (name.equals("" + PACKAGES_STRING)) {
+                visitLocation(loc, (childloc) -> {
+                    findNode(childloc.getFullName());
+                });
+                packagesDir.setCompleted(true);
+                n = packagesDir;
+            } else {
+                if (size != 0) { // children are links to module
+                    String pkgName = getBaseExt(loc);
+                    Directory pkgDir = newDirectory(packagesDir,
+                            packagesDir.getName().concat(SLASH_STRING, new UTF8String(pkgName)));
+                    visitLocation(loc, (childloc) -> {
+                        findNode(childloc.getFullName());
+                    });
+                    pkgDir.setCompleted(true);
+                    n = pkgDir;
+                } else { // Link to module
+                    String pkgName = loc.getParentString();
+                    String modName = getBaseExt(loc);
+                    Node targetNode = findNode(MODULES_STRING + "/" + modName);
+                    if (targetNode != null) {
+                        UTF8String pkgDirName = packagesDir.getName().concat(SLASH_STRING, new UTF8String(pkgName));
+                        Directory pkgDir = (Directory) nodes.get(pkgDirName);
+                        Node linkNode = newLinkNode(pkgDir,
+                                pkgDir.getName().concat(SLASH_STRING, new UTF8String(modName)), targetNode);
+                        n = linkNode;
+                    }
+                }
+            }
+            return n;
+        }
+
+        private Node handleModulesSubTree(String name, ImageLocation loc) {
+            Node n;
+            Directory dir = makeDirectories(loc.getFullName());
+            visitLocation(loc, (childloc) -> {
+                String path = childloc.getFullNameString();
+                if (path.startsWith(MODULES_STRING.toString())) { // a package
+                    makeDirectories(childloc.getFullName());
+                } else { // a resource
+                    makeDirectories(childloc.buildName(true, true, false));
+                    newResource(dir, childloc);
+                }
+            });
+            dir.setCompleted(true);
+            n = dir;
+            return n;
+        }
+
+        private Node handleResource(String name, ImageLocation loc) {
+            Node n = null;
+            String locationPath = name.substring((MODULES_STRING).length());
+            ImageLocation resourceLoc = findLocation(locationPath);
+            if (resourceLoc != null) {
+                Directory dir = makeDirectories(resourceLoc.buildName(true, true, false));
+                Resource res = newResource(dir, resourceLoc);
+                n = res;
+            }
+            return n;
+        }
+
+        private String getBaseExt(ImageLocation loc) {
+            String base = loc.getBaseString();
+            String ext = loc.getExtensionString();
+            if (ext != null && !ext.isEmpty()) {
+                base = base + "." + ext;
+            }
+            return base;
+        }
+    }
+
     public synchronized Node findNode(UTF8String name) {
         buildRootDirectory();
-        return nodes.get(name);
+        Node n = nodes.get(name);
+        if (n == null || !n.isCompleted()) {
+            NodeBuilder builder = new NodeBuilder(name);
+            n = builder.buildNode();
+        }
+        return n;
     }
 
     private synchronized void clearNodes() {
@@ -375,65 +606,61 @@
         // FIXME no time information per resource in jimage file (yet?)
         // we use file attributes of jimage itself.
         // root directory
-        rootDir = new Directory(null, ROOT, imageFileAttributes());
+        rootDir = newDirectory(null, ROOT_STRING);
         rootDir.setIsRootDir();
-        nodes.put(rootDir.getName(), rootDir);
 
-        ImageLocation[] locs = getAllLocations(true);
-        for (ImageLocation loc : locs) {
-            UTF8String parent = loc.getParent();
-            // directory where this location goes as child
-            Directory dir;
-            if (parent == null || parent.isEmpty()) {
-                // top level entry under root
-                dir = rootDir;
-            } else {
-                int idx = parent.lastIndexOf('/');
-                assert idx != -1 : "invalid parent string";
-                UTF8String name = ROOT.concat(parent.substring(0, idx));
-                dir = (Directory) nodes.get(name);
-                if (dir == null) {
-                    // make all parent directories (as needed)
-                    dir = makeDirectories(parent);
-                }
-            }
-            Resource entry = new Resource(dir, loc, imageFileAttributes());
-            nodes.put(entry.getName(), entry);
-        }
+        // /packages dir
+        packagesDir = newDirectory(rootDir, PACKAGES_STRING);
+        packagesDir.setIsPackagesDir();
 
-        Node metaInf = nodes.get(META_INF);
-        if (metaInf instanceof Directory) {
-            metaInf.setIsMetaInfDir();
-            ((Directory)metaInf).walk(Node::setIsHidden);
-        }
+        // /modules dir
+        modulesDir = newDirectory(rootDir, MODULES_STRING);
+        modulesDir.setIsModulesDir();
 
-        fillPackageModuleInfo();
-
+        rootDir.setCompleted(true);
         return rootDir;
     }
 
     private Directory newDirectory(Directory parent, UTF8String name) {
-        Directory dir = new Directory(parent, name, imageFileAttributes());
+        Directory dir = Directory.create(parent, name, imageFileAttributes());
         nodes.put(dir.getName(), dir);
         return dir;
     }
 
-    private Directory makeDirectories(UTF8String parent) {
-        assert !parent.isEmpty() : "non empty parent expected";
+    private Resource newResource(Directory parent, ImageLocation loc) {
+        Resource res = Resource.create(parent, loc, imageFileAttributes());
+        nodes.put(res.getName(), res);
+        return res;
+    }
+
+    private LinkNode newLinkNode(Directory dir, UTF8String name, Node link) {
+        LinkNode linkNode = LinkNode.create(dir, name, link);
+        nodes.put(linkNode.getName(), linkNode);
+        return linkNode;
+    }
+
+    private List<UTF8String> dirs(UTF8String parent) {
+        List<UTF8String> splits = new ArrayList<>();
 
-        int idx = parent.indexOf('/');
-        assert idx != -1 : "invalid parent string";
-        UTF8String name = ROOT.concat(parent.substring(0, idx));
-        Directory top = (Directory) nodes.get(name);
-        if (top == null) {
-            top = newDirectory(rootDir, name);
+        for (int i = 1; i < parent.length(); i++) {
+            if (parent.byteAt(i) == '/') {
+                splits.add(parent.substring(0, i));
+            }
         }
-        Directory last = top;
-        while ((idx = parent.indexOf('/', idx + 1)) != -1) {
-            name = ROOT.concat(parent.substring(0, idx));
-            Directory nextDir = (Directory) nodes.get(name);
+
+        splits.add(parent);
+
+        return splits;
+    }
+
+    private Directory makeDirectories(UTF8String parent) {
+        Directory last = rootDir;
+        List<UTF8String> dirs = dirs(parent);
+
+        for (UTF8String dir : dirs) {
+            Directory nextDir = (Directory) nodes.get(dir);
             if (nextDir == null) {
-                nextDir = newDirectory(last, name);
+                nextDir = newDirectory(last, dir);
             }
             last = nextDir;
         }
@@ -441,54 +668,6 @@
         return last;
     }
 
-    private void fillPackageModuleInfo() {
-        assert rootDir != null;
-
-        packageMap.entrySet().stream().sorted((x, y)->x.getKey().compareTo(y.getKey())).forEach((entry) -> {
-              UTF8String moduleName = new UTF8String("/" + entry.getValue());
-              UTF8String fullName = moduleName.concat(new UTF8String(entry.getKey() + "/"));
-              if (! nodes.containsKey(fullName)) {
-                  Directory module = (Directory) nodes.get(moduleName);
-                  assert module != null : "module directory missing " + moduleName;
-                  module.setIsModuleDir();
-
-                  // hide "packages.offsets" in module directories
-                  Node packagesOffsets = nodes.get(moduleName.concat(ROOT, PACKAGES_OFFSETS));
-                  if (packagesOffsets != null) {
-                      packagesOffsets.setIsHidden();
-                  }
-
-                  // package name without front '/'
-                  UTF8String pkgName = new UTF8String(entry.getKey() + "/");
-                  int idx = -1;
-                  Directory moduleSubDir = module;
-                  while ((idx = pkgName.indexOf('/', idx + 1)) != -1) {
-                      UTF8String subPkg = pkgName.substring(0, idx);
-                      UTF8String moduleSubDirName = moduleName.concat(ROOT, subPkg);
-                      Directory tmp = (Directory) nodes.get(moduleSubDirName);
-                      if (tmp == null) {
-                          moduleSubDir = newDirectory(moduleSubDir, moduleSubDirName);
-                      } else {
-                          moduleSubDir = tmp;
-                      }
-                  }
-                  // copy pkgDir "resources"
-                  Directory pkgDir = (Directory) nodes.get(ROOT.concat(pkgName.substring(0, pkgName.length() - 1)));
-                  pkgDir.setIsTopLevelPackageDir();
-                  pkgDir.walk(n -> n.setIsHidden());
-                  for (Node child : pkgDir.getChildren()) {
-                      if (child.isResource()) {
-                          ImageLocation loc = child.getLocation();
-                          BasicFileAttributes imageFileAttrs = child.getFileAttributes();
-                          UTF8String rsName = moduleName.concat(child.getName());
-                          Resource rs = new Resource(moduleSubDir, rsName, loc, imageFileAttrs);
-                          nodes.put(rs.getName(), rs);
-                      }
-                  }
-              }
-        });
-    }
-
     public byte[] getResource(Node node) throws IOException {
         if (node.isResource()) {
             return super.getResource(node.getLocation());
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.base/share/classes/jdk/internal/jimage/ImageReaderFactory.java	Fri Jul 03 07:13:24 2015 -0700
@@ -0,0 +1,79 @@
+/*
+ * 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.jimage;
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.Map;
+
+/**
+ * Factory to get ImageReader
+ */
+public class ImageReaderFactory {
+    private ImageReaderFactory() {}
+
+    private static final String JAVA_HOME = System.getProperty("java.home");
+    private static final Path BOOT_MODULES_JIMAGE =
+        Paths.get(JAVA_HOME, "lib", "modules", "bootmodules.jimage");
+
+    private static final Map<Path, ImageReader> readers = new ConcurrentHashMap<>();
+
+    /**
+     * Returns an {@code ImageReader} to read from the given image file
+     */
+    public static ImageReader get(Path jimage) throws IOException {
+        ImageReader reader = readers.get(jimage);
+        if (reader != null) {
+            return reader;
+        }
+        reader = ImageReader.open(jimage.toString());
+        // potential race with other threads opening the same URL
+        ImageReader r = readers.putIfAbsent(jimage, reader);
+        if (r == null) {
+            return reader;
+        } else {
+            reader.close();
+            return r;
+        }
+    }
+
+    /**
+     * Returns the {@code ImageReader} to read the image file in this
+     * run-time image.
+     *
+     * @throws UncheckedIOException if an I/O error occurs
+     */
+    public static ImageReader getImageReader() {
+        try {
+            return get(BOOT_MODULES_JIMAGE);
+        } catch (IOException ioe) {
+            throw new UncheckedIOException(ioe);
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.base/share/classes/jdk/internal/jimage/ImageResourcesTree.java	Fri Jul 03 07:13:24 2015 -0700
@@ -0,0 +1,344 @@
+/*
+ * 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.jimage;
+
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
+
+/**
+ * A class to build a sorted tree of Resource paths as a tree of ImageLocation.
+ *
+ */
+// XXX Public only due to the JImageTask / JImageTask code duplication
+public final class ImageResourcesTree {
+
+    private static final String MODULES = "modules";
+    private static final String PACKAGES = "packages";
+    public static final String MODULES_STRING = UTF8String.MODULES_STRING.toString();
+    public static final String PACKAGES_STRING = UTF8String.PACKAGES_STRING.toString();
+
+    public static boolean isTreeInfoResource(String path) {
+        return path.startsWith(PACKAGES_STRING) || path.startsWith(MODULES_STRING);
+    }
+
+    /**
+     * Path item tree node.
+     */
+    private static final class Node {
+
+        private final String name;
+        private final Map<String, Node> children = new TreeMap<>();
+        private final Node parent;
+        private ImageLocationWriter loc;
+
+        private Node(String name, Node parent) {
+            this.name = name;
+            this.parent = parent;
+
+            if (parent != null) {
+                parent.children.put(name, this);
+            }
+        }
+
+        public String getPath() {
+            if (parent == null) {
+                return "/";
+            }
+            return buildPath(this);
+        }
+
+        public String getName() {
+            return name;
+        }
+
+        public Node getChildren(String name) {
+            Node item = children.get(name);
+            return item;
+        }
+
+        private static String buildPath(Node item) {
+            if (item == null) {
+                return null;
+            }
+            String path = buildPath(item.parent);
+            if (path == null) {
+                return item.getName();
+            } else {
+                return path + "/" + item.getName();
+            }
+        }
+    }
+
+    /**
+     * Tree of nodes.
+     */
+    private static final class Tree {
+
+        private final Map<String, Node> directAccess = new HashMap<>();
+        private final List<String> paths;
+        private final Node root;
+        private Node modules;
+        private Node packages;
+
+        private Tree(List<String> paths) {
+            this.paths = paths;
+            root = new Node("", null);
+            buildTree();
+        }
+
+        private void buildTree() {
+            modules = new Node(MODULES, root);
+            directAccess.put(modules.getPath(), modules);
+
+            Map<String, Set<String>> moduleToPackage = new TreeMap<>();
+            Map<String, Set<String>> packageToModule = new TreeMap<>();
+
+            for (String p : paths) {
+                if (!p.startsWith("/")) {
+                    continue;
+                }
+                String[] split = p.split("/");
+                Node current = modules;
+                String module = null;
+                for (int i = 0; i < split.length; i++) {
+                    String s = split[i];
+                    if (!s.isEmpty()) {
+                        if (module == null) {
+                            module = s;
+                        }
+                        Node n = current.children.get(s);
+                        if (n == null) {
+                            n = new Node(s, current);
+                            if (i == split.length - 1) { // Leaf
+                                String pkg = toPackageName(n.parent);
+                                if (pkg != null && !pkg.startsWith("META-INF")) {
+                                    Set<String> pkgs = moduleToPackage.get(module);
+                                    if (pkgs == null) {
+                                        pkgs = new TreeSet<>();
+                                        moduleToPackage.put(module, pkgs);
+                                    }
+                                    pkgs.add(pkg);
+                                }
+                            } else { // put only sub trees, no leaf
+                                directAccess.put(n.getPath(), n);
+                                String pkg = toPackageName(n);
+                                if (pkg != null && !pkg.startsWith("META-INF")) {
+                                    Set<String> mods = packageToModule.get(pkg);
+                                    if (mods == null) {
+                                        mods = new TreeSet<>();
+                                        packageToModule.put(pkg, mods);
+                                    }
+                                    mods.add(module);
+
+                                }
+                            }
+                        }
+                        current = n;
+                    }
+                }
+            }
+            packages = new Node(PACKAGES, root);
+            directAccess.put(packages.getPath(), packages);
+            for (Map.Entry<String, Set<String>> entry : moduleToPackage.entrySet()) {
+                for (String pkg : entry.getValue()) {
+                    Node pkgNode = new Node(pkg, packages);
+                    directAccess.put(pkgNode.getPath(), pkgNode);
+
+                    Node modNode = new Node(entry.getKey(), pkgNode);
+                    directAccess.put(modNode.getPath(), modNode);
+                }
+            }
+            for (Map.Entry<String, Set<String>> entry : packageToModule.entrySet()) {
+                Node pkgNode = new Node(entry.getKey(), packages);
+                directAccess.put(pkgNode.getPath(), pkgNode);
+                for (String module : entry.getValue()) {
+                    Node modNode = new Node(module, pkgNode);
+                    directAccess.put(modNode.getPath(), modNode);
+                }
+            }
+        }
+
+        public String toResourceName(Node node) {
+            if (!node.children.isEmpty()) {
+                throw new RuntimeException("Node is not a resource");
+            }
+            return removeRadical(node);
+        }
+
+        public String getModule(Node node) {
+            if (node.parent == null || node.getName().equals(MODULES) ||
+                node.getName().startsWith(PACKAGES)) {
+                return null;
+            }
+            String path = removeRadical(node);
+            // "/xxx/...";
+            path = path.substring(1);
+            int i = path.indexOf("/");
+            if (i == -1) {
+                return path;
+            } else {
+                return path.substring(0, i);
+            }
+        }
+
+        public String toPackageName(Node node) {
+            if (node.parent == null) {
+                return null;
+            }
+            String path = removeRadical(node.getPath(), "/" + MODULES + "/");
+            String module = getModule(node);
+            if (path.equals(module)) {
+                return null;
+            }
+            String pkg = removeRadical(path, module + "/");
+            return pkg.replaceAll("/", ".");
+        }
+
+        public String removeRadical(Node node) {
+            String s = node.getPath();
+            return removeRadical(node.getPath(), "/" + MODULES);
+        }
+
+        private String removeRadical(String path, String str) {
+            return path.substring(str.length());
+        }
+
+        public Node getRoot() {
+            return root;
+        }
+
+        public Map<String, Node> getMap() {
+            return directAccess;
+        }
+
+        private boolean isPackageNode(Node node) {
+            if (!node.children.isEmpty()) {
+                throw new RuntimeException("Node is not a package");
+            }
+            return node.getPath().startsWith("/" + PACKAGES);
+        }
+    }
+
+    private static final class LocationsAdder {
+
+        private long offset;
+        private final List<byte[]> content = new ArrayList<>();
+        private final BasicImageWriter writer;
+        private final Tree tree;
+
+        LocationsAdder(Tree tree, long offset, BasicImageWriter writer) {
+            this.tree = tree;
+            this.offset = offset;
+            this.writer = writer;
+            addLocations(tree.getRoot());
+        }
+
+        private int addLocations(Node current) {
+            int[] ret = new int[current.children.size()];
+            int i = 0;
+            for (java.util.Map.Entry<String, Node> entry : current.children.entrySet()) {
+                ret[i] = addLocations(entry.getValue());
+                i += 1;
+            }
+            if (current != tree.getRoot() && (ret.length > 0 || tree.isPackageNode(current))) {
+                int size = ret.length * 4;
+                writer.addLocation(current.getPath(), offset, 0, size);
+                offset += size;
+            }
+            return 0;
+        }
+
+        private List<byte[]> computeContent() {
+            // Map used to associate Tree item with locations offset.
+            Map<String, ImageLocationWriter> outLocations = new HashMap<>();
+            for (ImageLocationWriter wr : writer.getLocations()) {
+                outLocations.put(wr.getFullNameString(), wr);
+            }
+            // Attach location to node
+            for (Map.Entry<String, ImageLocationWriter> entry : outLocations.entrySet()) {
+                Node item = tree.getMap().get(entry.getKey());
+                if (item != null) {
+                    item.loc = entry.getValue();
+                }
+            }
+            computeContent(tree.getRoot(), outLocations);
+            return content;
+        }
+
+        private int computeContent(Node current, Map<String, ImageLocationWriter> outLocations) {
+            int[] ret = new int[current.children.size()];
+            int i = 0;
+            for (java.util.Map.Entry<String, Node> entry : current.children.entrySet()) {
+                ret[i] = computeContent(entry.getValue(), outLocations);
+                i += 1;
+            }
+            if (ret.length > 0) {
+                int size = ret.length * 4;
+                ByteBuffer buff = ByteBuffer.allocate(size);
+                buff.order(writer.getByteOrder());
+                for (int val : ret) {
+                    buff.putInt(val);
+                }
+                byte[] arr = buff.array();
+                content.add(arr);
+            } else {
+                if (tree.isPackageNode(current)) {
+                    current.loc = outLocations.get(current.getPath());
+                } else {
+                    String s = tree.toResourceName(current);
+                    current.loc = outLocations.get(s);
+                }
+            }
+            return current == tree.getRoot() ? 0 : current.loc.getLocationOffset();
+        }
+    }
+
+    private final List<String> paths;
+    private final LocationsAdder adder;
+
+    public ImageResourcesTree(long offset, BasicImageWriter writer, List<String> paths) {
+        this.paths = new ArrayList<>();
+        this.paths.addAll(paths);
+        Collections.sort(this.paths);
+        Tree tree = new Tree(this.paths);
+        adder = new LocationsAdder(tree, offset, writer);
+    }
+
+    public void addContent(DataOutputStream out) throws IOException {
+        List<byte[]> content = adder.computeContent();
+        for (byte[] c : content) {
+            out.write(c, 0, c.length);
+        }
+    }
+}
--- a/jdk/src/java.base/share/classes/jdk/internal/jimage/ImageStream.java	Thu Jul 02 15:25:46 2015 +0200
+++ b/jdk/src/java.base/share/classes/jdk/internal/jimage/ImageStream.java	Fri Jul 03 07:13:24 2015 -0700
@@ -72,7 +72,7 @@
         return this;
     }
 
-    private void ensure(int needs) {
+    void ensure(int needs) {
         assert 0 <= needs : "Negative needs";
 
         if (needs > buffer.remaining()) {
--- a/jdk/src/java.base/share/classes/jdk/internal/jimage/ImageStrings.java	Thu Jul 02 15:25:46 2015 +0200
+++ b/jdk/src/java.base/share/classes/jdk/internal/jimage/ImageStrings.java	Fri Jul 03 07:13:24 2015 -0700
@@ -25,83 +25,8 @@
 
 package jdk.internal.jimage;
 
-import java.nio.ByteBuffer;
-import java.util.HashMap;
-
-class ImageStrings {
-    private static final int NOT_FOUND = -1;
-    static final int EMPTY_OFFSET = 0;
-
-    private final HashMap<UTF8String, Integer> stringToOffsetMap;
-    private final ImageStream stream;
-
-    ImageStrings() {
-        this.stringToOffsetMap = new HashMap<>();
-        this.stream = new ImageStream();
-
-        // Reserve 0 offset for empty string.
-        int offset = addString(UTF8String.EMPTY_STRING);
-        assert offset == 0 : "Empty string not zero offset";
-        // Reserve 1 offset for frequently used ".class".
-        addString(UTF8String.CLASS_STRING);
-    }
-
-    ImageStrings(ImageStream stream) {
-        this.stringToOffsetMap = new HashMap<>();
-        this.stream = stream;
-    }
-
-    private int addString(final UTF8String string) {
-        int offset = stream.getPosition();
-        string.writeTo(stream);
-        stream.put('\0');
-        stringToOffsetMap.put(string, offset);
-
-        return offset;
-    }
-
-    int add(final UTF8String string) {
-        int offset = find(string);
+interface ImageStrings {
+    public UTF8String get(int offset);
 
-        return offset == NOT_FOUND ? addString(string) : offset;
-    }
-
-    int find(final UTF8String string) {
-        Integer offset = stringToOffsetMap.get(string);
-
-        return offset != null ? offset : NOT_FOUND;
-    }
-
-    UTF8String get(int offset) {
-        ByteBuffer buffer = stream.getBuffer();
-        assert 0 <= offset && offset < buffer.capacity() : "String buffer offset out of range";
-        int zero = NOT_FOUND;
-        for (int i = offset; i < buffer.capacity(); i++) {
-            if (buffer.get(i) == '\0') {
-                zero = i;
-                break;
-            }
-        }
-        assert zero != UTF8String.NOT_FOUND;
-        int length = zero - offset;
-        byte[] bytes = new byte[length];
-        int mark = buffer.position();
-        buffer.position(offset);
-        buffer.get(bytes);
-        buffer.position(mark);
-
-        return new UTF8String(bytes, 0, length);
-    }
-
-    ImageStream getStream() {
-        return stream;
-    }
-
-    int getSize() {
-        return stream.getSize();
-    }
-
-    int getCount() {
-        return stringToOffsetMap.size();
-    }
+    public int add(final UTF8String string);
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.base/share/classes/jdk/internal/jimage/ImageStringsReader.java	Fri Jul 03 07:13:24 2015 -0700
@@ -0,0 +1,44 @@
+/*
+ * 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.jimage;
+
+class ImageStringsReader implements ImageStrings {
+    private final BasicImageReader reader;
+
+    ImageStringsReader(BasicImageReader reader) {
+        this.reader = reader;
+    }
+
+    @Override
+    public UTF8String get(int offset) {
+        return reader.getUTF8String(offset);
+    }
+
+    @Override
+    public int add(final UTF8String string) {
+        throw new InternalError("Can not add strings at runtime");
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.base/share/classes/jdk/internal/jimage/ImageStringsWriter.java	Fri Jul 03 07:13:24 2015 -0700
@@ -0,0 +1,105 @@
+/*
+ * 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.jimage;
+
+import java.nio.ByteBuffer;
+import java.util.HashMap;
+
+class ImageStringsWriter implements ImageStrings {
+    private static final int NOT_FOUND = -1;
+    static final int EMPTY_OFFSET = 0;
+    static final UTF8String CLASS_STRING = new UTF8String("class");
+
+    private final HashMap<UTF8String, Integer> stringToOffsetMap;
+    private final ImageStream stream;
+
+    ImageStringsWriter() {
+        this.stringToOffsetMap = new HashMap<>();
+        this.stream = new ImageStream();
+
+        // Reserve 0 offset for empty string.
+        int offset = addString(UTF8String.EMPTY_STRING);
+        assert offset == 0 : "Empty string not zero offset";
+        // Reserve 1 offset for frequently used ".class".
+        addString(CLASS_STRING);
+    }
+
+    private int addString(final UTF8String string) {
+        int offset = stream.getPosition();
+        string.writeTo(stream);
+        stream.put('\0');
+        stringToOffsetMap.put(string, offset);
+
+        return offset;
+    }
+
+    @Override
+    public int add(final UTF8String string) {
+        int offset = find(string);
+
+        return offset == NOT_FOUND ? addString(string) : offset;
+    }
+
+    int find(final UTF8String string) {
+        Integer offset = stringToOffsetMap.get(string);
+
+        return offset != null ? offset : NOT_FOUND;
+    }
+
+    @Override
+    public UTF8String get(int offset) {
+        ByteBuffer buffer = stream.getBuffer();
+        assert 0 <= offset && offset < buffer.capacity() : "String buffer offset out of range";
+        int zero = NOT_FOUND;
+        for (int i = offset; i < buffer.capacity(); i++) {
+            if (buffer.get(i) == '\0') {
+                zero = i;
+                break;
+            }
+        }
+        assert zero != UTF8String.NOT_FOUND;
+        int length = zero - offset;
+        byte[] bytes = new byte[length];
+        int mark = buffer.position();
+        buffer.position(offset);
+        buffer.get(bytes);
+        buffer.position(mark);
+
+        return new UTF8String(bytes, 0, length);
+    }
+
+    ImageStream getStream() {
+        return stream;
+    }
+
+    int getSize() {
+        return stream.getSize();
+    }
+
+    int getCount() {
+        return stringToOffsetMap.size();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.base/share/classes/jdk/internal/jimage/ImageSubstrate.java	Fri Jul 03 07:13:24 2015 -0700
@@ -0,0 +1,45 @@
+/*
+ * 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.jimage;
+
+import java.io.Closeable;
+import java.nio.ByteBuffer;
+
+interface ImageSubstrate extends Closeable {
+    @Override
+    void close();
+    boolean supportsDataBuffer();
+    ByteBuffer getIndexBuffer(long offset, long size);
+    ByteBuffer getDataBuffer(long offset, long size);
+    boolean read(long offset,
+                          ByteBuffer compressedBuffer, long compressedSize,
+                          ByteBuffer uncompressedBuffer, long uncompressedSize);
+    boolean read(long offset,
+                          ByteBuffer uncompressedBuffer, long uncompressedSize);
+    byte[] getStringBytes(int offset);
+    long[] getAttributes(int offset);
+    ImageLocation findLocation(UTF8String name, ImageStringsReader strings);
+    int[] attributeOffsets();
+}
--- a/jdk/src/java.base/share/classes/jdk/internal/jimage/PReader.java	Thu Jul 02 15:25:46 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,139 +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.jimage;
-
-import java.io.Closeable;
-import java.io.IOException;
-import java.io.RandomAccessFile;
-import java.nio.channels.FileChannel;
-import java.lang.reflect.Constructor;
-import java.lang.reflect.InvocationTargetException;
-
-/**
- * Supports reading a file from given positions (offsets) in the file.
- */
-
-public abstract class PReader implements Closeable {
-    private final FileChannel fc;
-
-    protected PReader(FileChannel fc) {
-        this.fc = fc;
-    }
-
-    /**
-     * Returns the {@code FileChannel}.
-     */
-    final FileChannel channel() {
-        return fc;
-    }
-
-    /**
-     * Closes this {@code PReader} and the underlying file.
-     */
-    @Override
-    public final void close() throws IOException {
-        fc.close();
-    }
-
-    /**
-     * Returns {@code true} if this {@code PReader} and the underlying file is
-     * open.
-     */
-    public final boolean isOpen() {
-        return fc.isOpen();
-    }
-
-    /**
-     * Returns {@code len} bytes from a given position in the file. The bytes
-     * are returned as a byte array.
-     *
-     * @throws IOException if an I/O error occurs
-     */
-    public abstract byte[] read(int len, long position) throws IOException;
-
-    /**
-     * Opens the given file, returning a {@code PReader} to read from the file.
-     *
-     * @implNote Returns a {@code PReader} that supports concurrent pread operations
-     * if possible, otherwise a simple {@code PReader} that doesn't support
-     * concurrent operations.
-     */
-    static PReader open(String file) throws IOException {
-        Class<?> clazz;
-        try {
-            clazz = Class.forName("jdk.internal.jimage.concurrent.ConcurrentPReader");
-        } catch (ClassNotFoundException e) {
-            return new SimplePReader(file);
-        }
-        try {
-            Constructor<?> ctor = clazz.getConstructor(String.class);
-            return (PReader) ctor.newInstance(file);
-        } catch (InvocationTargetException e) {
-            Throwable cause = e.getCause();
-            if (cause instanceof IOException)
-                throw (IOException) cause;
-            if (cause instanceof Error)
-                throw (Error) cause;
-            if (cause instanceof RuntimeException)
-                throw (RuntimeException) cause;
-            throw new Error(e);
-        } catch (NoSuchMethodException | IllegalAccessException |
-                InstantiationException e) {
-            throw new InternalError(e);
-        }
-    }
-}
-
-/**
- * Simple PReader implementation based on {@code RandomAccessFile}.
- *
- * @implNote This class cannot use FileChannel read methods to do the
- * positional reads because FileChannel is interruptible.
- */
-class SimplePReader extends PReader {
-    private final RandomAccessFile raf;
-
-    private SimplePReader(RandomAccessFile raf) throws IOException {
-        super(raf.getChannel());
-        this.raf = raf;
-    }
-
-    SimplePReader(String file) throws IOException {
-        this(new RandomAccessFile(file, "r"));
-    }
-
-    @Override
-    public byte[] read(int len, long position) throws IOException {
-        synchronized (this) {
-            byte[] bytes = new byte[len];
-            raf.seek(position);
-            int n = raf.read(bytes);
-            if (n != len)
-                throw new InternalError("short read, not handled yet");
-            return bytes;
-        }
-    }
-}
--- a/jdk/src/java.base/share/classes/jdk/internal/jimage/PackageModuleMap.java	Thu Jul 02 15:25:46 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,62 +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.jimage;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.nio.IntBuffer;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-// Utility to read module info from .jimage file.
-
-public final class PackageModuleMap {
-    private PackageModuleMap() {}
-
-    public static final String MODULES_ENTRY = "module/modules.offsets";
-    public static final String PACKAGES_ENTRY = "packages.offsets";
-
-    /*
-     * Returns a package-to-module map.
-     *
-     * The package name is in binary name format.
-     */
-    static Map<String,String> readFrom(ImageReader reader) throws IOException {
-        Map<String,String> result = new HashMap<>();
-        List<String> moduleNames = reader.getNames(MODULES_ENTRY);
-
-        for (String moduleName : moduleNames) {
-            List<String> packageNames = reader.getNames(moduleName + "/" + PACKAGES_ENTRY);
-
-            for (String packageName : packageNames) {
-                result.put(packageName, moduleName);
-            }
-        }
-        return result;
-    }
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.base/share/classes/jdk/internal/jimage/PerfectHashBuilder.java	Fri Jul 03 07:13:24 2015 -0700
@@ -0,0 +1,255 @@
+/*
+ * 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.jimage;
+
+import java.lang.reflect.Array;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+public class PerfectHashBuilder<E> {
+    private final static int RETRY_LIMIT = 1000;
+
+    private Class<?> entryComponent;
+    private Class<?> bucketComponent;
+
+    private final Map<UTF8String, Entry<E>> map = new LinkedHashMap<>();
+    private int[] redirect;
+    private Entry<E>[] order;
+    private int count = 0;
+
+    @SuppressWarnings("EqualsAndHashcode")
+    public static class Entry<E> {
+        private final UTF8String key;
+        private final E value;
+
+        Entry() {
+            this("", null);
+        }
+
+        Entry(String key, E value) {
+            this(new UTF8String(key), value);
+        }
+
+        Entry(UTF8String key, E value) {
+            this.key = key;
+            this.value = value;
+        }
+
+        UTF8String getKey() {
+            return key;
+        }
+
+        E getValue() {
+            return value;
+        }
+
+        int hashCode(int seed) {
+            return key.hashCode(seed);
+        }
+
+        @Override
+        public int hashCode() {
+            return key.hashCode();
+        }
+    }
+
+    static class Bucket<E> implements Comparable<Bucket<E>> {
+        final List<Entry<E>> list = new ArrayList<>();
+
+        void add(Entry<E> entry) {
+            list.add(entry);
+        }
+
+        int getSize() {
+            return list.size();
+        }
+
+        List<Entry<E>> getList() {
+            return list;
+        }
+
+        Entry<E> getFirst() {
+            assert !list.isEmpty() : "bucket should never be empty";
+            return list.get(0);
+        }
+
+        @Override
+        public int hashCode() {
+            return getFirst().hashCode();
+        }
+
+        @Override
+        @SuppressWarnings("EqualsWhichDoesntCheckParameterClass")
+        public boolean equals(Object obj) {
+            return this == obj;
+        }
+
+        @Override
+        public int compareTo(Bucket<E> o) {
+            return o.getSize() - getSize();
+        }
+    }
+
+    public PerfectHashBuilder(Class<?> entryComponent, Class<?> bucketComponent) {
+        this.entryComponent = entryComponent;
+        this.bucketComponent = bucketComponent;
+    }
+
+    public int getCount() {
+        return map.size();
+    }
+
+    public int[] getRedirect() {
+        return redirect;
+    }
+
+    public Entry<E>[] getOrder() {
+        return order;
+    }
+
+    public Entry<E> put(String key, E value) {
+        return put(new UTF8String(key), value);
+    }
+
+    public Entry<E> put(UTF8String key, E value) {
+        return put(new Entry<>(key, value));
+    }
+
+    public Entry<E> put(Entry<E> entry) {
+        Entry<E> old = map.put(entry.key, entry);
+
+        if (old == null) {
+            count++;
+        }
+
+        return old;
+    }
+
+    @SuppressWarnings("unchecked")
+    public void generate() {
+        boolean redo = count != 0;
+        while (redo) {
+            redo = false;
+            redirect = new int[count];
+            order = (Entry<E>[])Array.newInstance(entryComponent, count);
+
+            Bucket<E>[] sorted = createBuckets();
+            int free = 0;
+
+            for (Bucket<E> bucket : sorted) {
+                if (bucket.getSize() != 1) {
+                    if (!collidedEntries(bucket, count)) {
+                        redo = true;
+                        break;
+                    }
+                } else {
+                    for ( ; free < count && order[free] != null; free++) {}
+
+                    if (free >= count) {
+                        redo = true;
+                        break;
+                    }
+
+                    order[free] = bucket.getFirst();
+                    redirect[bucket.hashCode() % count] = -1 - free;
+                    free++;
+                }
+            }
+
+            if (redo) {
+                count = (count + 1) | 1;
+            }
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    private Bucket<E>[] createBuckets() {
+        Bucket<E>[] buckets = (Bucket<E>[])Array.newInstance(bucketComponent, count);
+
+        map.values().stream().forEach((entry) -> {
+            int index = entry.hashCode() % count;
+            Bucket<E> bucket = buckets[index];
+
+            if (bucket == null) {
+                buckets[index] = bucket = new Bucket<>();
+            }
+
+            bucket.add(entry);
+        });
+
+        Bucket<E>[] sorted = Arrays.asList(buckets).stream()
+                .filter((bucket) -> (bucket != null))
+                .sorted()
+                .toArray((length) -> {
+                    return (Bucket<E>[])Array.newInstance(bucketComponent, length);
+                });
+
+        return sorted;
+    }
+
+    private boolean collidedEntries(Bucket<E> bucket, int count) {
+        List<Integer> undo = new ArrayList<>();
+        int seed = UTF8String.HASH_MULTIPLIER + 1;
+        int retry = 0;
+
+        redo:
+        while (true) {
+            for (Entry<E> entry : bucket.getList()) {
+                int index = entry.hashCode(seed) % count;
+                if (order[index] != null) {
+                    if (++retry > RETRY_LIMIT) {
+                        return false;
+                    }
+
+                    undo.stream().forEach((i) -> {
+                        order[i] = null;
+                    });
+
+                    undo.clear();
+                    seed++;
+
+                    if (seed == 0) {
+                        seed = 1;
+                    }
+
+                    continue redo;
+                }
+
+                order[index] = entry;
+                undo.add(index);
+            }
+
+            redirect[bucket.hashCode() % count] = seed;
+
+            break;
+        }
+
+        return true;
+    }
+ }
--- a/jdk/src/java.base/share/classes/jdk/internal/jimage/Resource.java	Thu Jul 02 15:25:46 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,67 +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.jimage;
-
-/**
- * Resource is a class or resource file.
- */
-public class Resource {
-    private final String name;
-    private final long size;
-    private final long csize;
-
-    public Resource(String name, long size, long csize) {
-        this.name = name;
-        this.size = size;
-        this.csize = csize;
-    }
-
-    /**
-     * Returns the name of this entry.
-     */
-    public String name() {
-        return name;
-    }
-
-    /**
-     * Returns the number of uncompressed bytes for this entry.
-     */
-    public long size() {
-        return size;
-    }
-
-    /**
-     * Returns the number of compressed bytes for this entry; 0 if
-     * uncompressed.
-     */
-    public long csize() {
-        return csize;
-    }
-
-    @Override
-    public String toString() {
-        return String.format("%s uncompressed size %d compressed size %d", name, size, csize);
-    }
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.base/share/classes/jdk/internal/jimage/ResourcePool.java	Fri Jul 03 07:13:24 2015 -0700
@@ -0,0 +1,256 @@
+/*
+ * 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.jimage;
+
+import jdk.internal.jimage.decompressor.CompressedResourceHeader;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Collection;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Pool of resources. This class contain the content of a jimage file in the
+ * matter of Resource.
+ */
+public interface ResourcePool {
+
+    /**
+     * Resources visitor
+     */
+    public interface Visitor {
+
+        /**
+         * Called for each visited Resource.
+         *
+         * @param resource The resource to deal with.
+         * @param order Byte order
+         * @param strings
+         * @return A resource or null if the passed resource is to be removed
+         * from the jimage.
+         * @throws Exception
+         */
+        public Resource visit(Resource resource, ByteOrder order,
+                StringTable strings) throws Exception;
+    }
+
+    /**
+     * A JImage Resource. Fully identified by its path.
+     */
+    public static class Resource {
+
+        private final String path;
+        private final ByteBuffer content;
+
+        private final String module;
+
+        public Resource(String path, ByteBuffer content) {
+            Objects.requireNonNull(path);
+            Objects.requireNonNull(content);
+            this.path = path;
+            this.content = content.asReadOnlyBuffer();
+            String[] split = ImageFileCreator.splitPath(path);
+            module = split[0];
+        }
+
+        public String getPath() {
+            return path;
+        }
+
+        public String getModule() {
+            return module;
+        }
+
+        /**
+         * The resource content.
+         *
+         * @return A read only buffer.
+         */
+        public ByteBuffer getContent() {
+            return content;
+        }
+
+        public int getLength() {
+            return content.limit();
+        }
+
+        public byte[] getByteArray() {
+            content.rewind();
+            byte[] array = new byte[content.remaining()];
+            content.get(array);
+            return array;
+        }
+
+        @Override
+        public String toString() {
+            return getPath();
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (!(obj instanceof Resource)) {
+                return false;
+            }
+            Resource res = (Resource) obj;
+            return res.path.equals(path);
+        }
+
+        @Override
+        public int hashCode() {
+            int hash = 7;
+            hash = 53 * hash + Objects.hashCode(this.path);
+            return hash;
+        }
+    }
+
+    /**
+     * A resource that has been compressed.
+     */
+    public static final class CompressedResource extends Resource {
+
+        private final long uncompressed_size;
+
+        private CompressedResource(String path, ByteBuffer content,
+                long uncompressed_size) {
+            super(path, content);
+            this.uncompressed_size = uncompressed_size;
+        }
+
+        public long getUncompressedSize() {
+            return uncompressed_size;
+        }
+
+        public static CompressedResource newCompressedResource(Resource original,
+                ByteBuffer compressed,
+                String plugin, String pluginConfig, StringTable strings,
+                ByteOrder order) throws Exception {
+            Objects.requireNonNull(original);
+            Objects.requireNonNull(compressed);
+            Objects.requireNonNull(plugin);
+
+            boolean isTerminal = !(original instanceof CompressedResource);
+            long uncompressed_size = original.getLength();
+            if (original instanceof CompressedResource) {
+                CompressedResource comp = (CompressedResource) original;
+                uncompressed_size = comp.getUncompressedSize();
+            }
+            int nameOffset = strings.addString(plugin);
+            int configOffset = -1;
+            if (pluginConfig != null) {
+                configOffset = strings.addString(plugin);
+            }
+            CompressedResourceHeader rh =
+                    new CompressedResourceHeader(compressed.limit(), original.getLength(),
+                    nameOffset, configOffset, isTerminal);
+            // Merge header with content;
+            byte[] h = rh.getBytes(order);
+            ByteBuffer bb = ByteBuffer.allocate(compressed.limit() + h.length);
+            bb.order(order);
+            bb.put(h);
+            bb.put(compressed);
+            ByteBuffer contentWithHeader = ByteBuffer.wrap(bb.array());
+
+            CompressedResource compressedResource =
+                    new CompressedResource(original.getPath(),
+                    contentWithHeader, uncompressed_size);
+            return compressedResource;
+        }
+    }
+
+    /**
+     * Read only state.
+     *
+     * @return true if readonly false otherwise.
+     */
+    public boolean isReadOnly();
+
+    /**
+     * The byte order
+     *
+     * @return
+     */
+    public ByteOrder getByteOrder();
+
+    /**
+     * Add a resource.
+     *
+     * @param resource The Resource to add.
+     * @throws java.lang.Exception If the pool is read only.
+     */
+    public void addResource(Resource resource) throws Exception;
+
+    /**
+     * Check if a resource is contained in the pool.
+     *
+     * @param res The resource to check.
+     * @return true if res is contained, false otherwise.
+     */
+    public boolean contains(Resource res);
+
+    /**
+     * Get all resources contained in this pool instance.
+     *
+     * @return The collection of resources;
+     */
+    public Collection<Resource> getResources();
+
+    /**
+     * Get the resource for the passed path.
+     *
+     * @param path A resource path
+     * @return A Resource instance or null if the resource is not found
+     */
+    public Resource getResource(String path);
+
+    /**
+     * The Image modules. It is computed based on the resources contained by
+     * this ResourcePool instance.
+     *
+     * @return The Image Modules.
+     */
+    public Map<String, Set<String>> getModulePackages();
+
+    /**
+     * Check if this pool contains some resources.
+     *
+     * @return True if contains some resources.
+     */
+    public boolean isEmpty();
+
+    /**
+     * Visit the resources contained in this ResourcePool.
+     *
+     * @param visitor The visitor
+     * @param output The pool to store resources.
+     * @param strings
+     * @throws Exception
+     */
+    public void visit(Visitor visitor, ResourcePool output, StringTable strings)
+            throws Exception;
+
+    public void addTransformedResource(Resource original, ByteBuffer transformed)
+            throws Exception;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.base/share/classes/jdk/internal/jimage/ResourcePoolImpl.java	Fri Jul 03 07:13:24 2015 -0700
@@ -0,0 +1,211 @@
+/*
+ * 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.jimage;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Pool of resources. This class contain the content of a jimage file in the
+ * matter of Resource.
+ */
+public class ResourcePoolImpl implements ResourcePool {
+
+    private final Map<String, Resource> resources = new LinkedHashMap<>();
+
+    private final ByteOrder order;
+    private boolean isReadOnly;
+
+    public ResourcePoolImpl(ByteOrder order) {
+        Objects.requireNonNull(order);
+        this.order = order;
+    }
+
+    /**
+     * Make this Resources instance read-only. No resource can be added.
+     */
+    public void setReadOnly() {
+        isReadOnly = true;
+    }
+
+    /**
+     * Read only state.
+     *
+     * @return true if readonly false otherwise.
+     */
+    @Override
+    public boolean isReadOnly() {
+        return isReadOnly;
+    }
+
+    /**
+     * The byte order
+     *
+     * @return
+     */
+    @Override
+    public ByteOrder getByteOrder() {
+        return order;
+    }
+
+    /**
+     * Add a resource.
+     *
+     * @param resource The Resource to add.
+     * @throws java.lang.Exception If the pool is read only.
+     */
+    @Override
+    public void addResource(Resource resource) throws Exception {
+        if (isReadOnly()) {
+            throw new Exception("pool is readonly");
+        }
+        Objects.requireNonNull(resource);
+        if (resources.get(resource.getPath()) != null) {
+            throw new Exception("Resource" + resource.getPath() +
+                    " already present");
+        }
+        resources.put(resource.getPath(), resource);
+    }
+
+    /**
+     * Check if a resource is contained in the pool.
+     *
+     * @param res The resource to check.
+     * @return true if res is contained, false otherwise.
+     */
+    @Override
+    public boolean contains(Resource res) {
+        Objects.requireNonNull(res);
+        try {
+            getResource(res.getPath());
+            return true;
+        } catch (Exception ex) {
+            return false;
+        }
+    }
+
+    /**
+     * Get all resources contained in this pool instance.
+     *
+     * @return The collection of resources;
+     */
+    @Override
+    public Collection<Resource> getResources() {
+        return Collections.unmodifiableCollection(resources.values());
+    }
+
+/**
+     * Get the resource for the passed path.
+     *
+     * @param path A resource path
+     * @return A Resource instance or null if the resource is not found
+     */
+    @Override
+    public Resource getResource(String path) {
+        Objects.requireNonNull(path);
+        return resources.get(path);
+    }
+
+    /**
+     * The Image modules. It is computed based on the resources contained by
+     * this ResourcePool instance.
+     *
+     * @return The Image Modules.
+     */
+    @Override
+    public Map<String, Set<String>> getModulePackages() {
+        Map<String, Set<String>> moduleToPackage = new LinkedHashMap<>();
+        retrieveModulesPackages(moduleToPackage);
+        return moduleToPackage;
+    }
+
+    /**
+     * Check if this pool contains some resources.
+     *
+     * @return True if contains some resources.
+     */
+    @Override
+    public boolean isEmpty() {
+        return resources.isEmpty();
+    }
+
+    /**
+     * Visit the resources contained in this ResourcePool.
+     *
+     * @param visitor The visitor
+     * @param strings
+     * @throws Exception
+     */
+    @Override
+    public void visit(Visitor visitor, ResourcePool output, StringTable strings)
+            throws Exception {
+        for (Resource resource : getResources()) {
+            Resource res = visitor.visit(resource, order, strings);
+            if (res != null) {
+                output.addResource(res);
+            }
+        }
+    }
+
+    @Override
+    public void addTransformedResource(Resource original, ByteBuffer transformed)
+            throws Exception {
+        if (isReadOnly()) {
+            throw new Exception("Pool is readonly");
+        }
+        Objects.requireNonNull(original);
+        Objects.requireNonNull(transformed);
+        if (resources.get(original.getPath()) != null) {
+            throw new Exception("Resource already present");
+        }
+        Resource res = new Resource(original.getPath(), transformed);
+        addResource(res);
+    }
+
+    private void retrieveModulesPackages(Map<String, Set<String>> moduleToPackage) {
+        for (Resource res : resources.values()) {
+            Set<String> pkgs = moduleToPackage.get(res.getModule());
+            if (pkgs == null) {
+                pkgs = new HashSet<>();
+                moduleToPackage.put(res.getModule(), pkgs);
+            }
+            // Module metadata only contains packages with resource files
+            if (ImageFileCreator.isResourcePackage(res.getPath())) {
+                String[] split = ImageFileCreator.splitPath(res.getPath());
+                String pkg = split[1];
+                if (pkg != null && !pkg.isEmpty()) {
+                    pkgs.add(pkg);
+                }
+            }
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.base/share/classes/jdk/internal/jimage/StringTable.java	Fri Jul 03 07:13:24 2015 -0700
@@ -0,0 +1,44 @@
+/*
+ * 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.jimage;
+
+/**
+* Added strings are stored in the jimage strings table.
+*/
+public interface StringTable {
+    /**
+     * Add a string to the jimage strings table.
+     * @param str The string to add.
+     * @return a String identifier.
+     */
+    public int addString(String str);
+
+    /**
+     * Retrieve a string from the passed id.
+     * @param id The string id.
+     * @return The string referenced by the passed id.
+     */
+    public String getString(int id);
+}
--- a/jdk/src/java.base/share/classes/jdk/internal/jimage/UTF8String.java	Thu Jul 02 15:25:46 2015 +0200
+++ b/jdk/src/java.base/share/classes/jdk/internal/jimage/UTF8String.java	Fri Jul 03 07:13:24 2015 -0700
@@ -29,14 +29,18 @@
 import java.util.Arrays;
 
 public final class UTF8String implements CharSequence {
-
     // Same as StandardCharsets.UTF_8 without loading all of the standard charsets
     static final Charset UTF_8 = Charset.forName("UTF-8");
 
     static final int NOT_FOUND = -1;
     static final int HASH_MULTIPLIER = 0x01000193;
-    static final UTF8String EMPTY_STRING  = new UTF8String("");
-    static final UTF8String CLASS_STRING  = new UTF8String(".class");
+    static final UTF8String EMPTY_STRING = new UTF8String("");
+    static final UTF8String SLASH_STRING = new UTF8String("/");
+    static final UTF8String DOT_STRING = new UTF8String(".");
+
+    // TODO This strings are implementation specific and should be defined elsewhere.
+    static final UTF8String MODULES_STRING = new UTF8String("/modules");
+    static final UTF8String PACKAGES_STRING = new UTF8String("/packages");
 
     final byte[] bytes;
     final int offset;
@@ -160,8 +164,8 @@
         return seed & 0x7FFFFFFF;
     }
 
-    int hashCode(int base) {
-        return hashCode(base, bytes, offset, count);
+    int hashCode(int seed) {
+        return hashCode(seed, bytes, offset, count);
     }
 
     @Override
@@ -186,7 +190,7 @@
         return equals(this, (UTF8String)obj);
     }
 
-    private static boolean equals(UTF8String a, UTF8String b) {
+    public static boolean equals(UTF8String a, UTF8String b) {
         if (a == b) {
             return true;
         }
@@ -211,6 +215,10 @@
         return true;
     }
 
+    public byte[] getBytesCopy() {
+        return Arrays.copyOfRange(bytes, offset, offset + count);
+    }
+
     byte[] getBytes() {
         if (offset != 0 || bytes.length != count) {
             return Arrays.copyOfRange(bytes, offset, offset + count);
@@ -232,33 +240,11 @@
     public char charAt(int index) {
         int ch = byteAt(index);
 
-        return (ch & 0x80) != 0 ? (char)ch : '\0';
+        return (ch & 0x80) == 0 ? (char)ch : '\0';
     }
 
     @Override
     public CharSequence subSequence(int start, int end) {
         return (CharSequence)substring(start, end - start);
     }
-
-    static UTF8String match(UTF8String a, UTF8String b) {
-        int aCount = a.count;
-        int bCount = b.count;
-
-        if (aCount < bCount) {
-            return null;
-        }
-
-        byte[] aBytes = a.bytes;
-        byte[] bBytes = b.bytes;
-        int aOffset = a.offset;
-        int bOffset = b.offset;
-
-        for (int i = 0; i < bCount; i++) {
-            if (aBytes[aOffset + i] != bBytes[bOffset + i]) {
-                return null;
-            }
-        }
-
-        return new UTF8String(aBytes, aOffset + bCount, aCount - bCount);
-    }
 }
--- a/jdk/src/java.base/share/classes/jdk/internal/jimage/concurrent/ConcurrentPReader.java	Thu Jul 02 15:25:46 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,149 +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.jimage.concurrent;
-
-import java.io.FileDescriptor;
-import java.io.FileInputStream;
-import java.io.IOException;
-
-import jdk.internal.jimage.PReader;
-
-import sun.misc.Unsafe;
-
-/**
- * A PReader implementation that supports concurrent pread operations.
- */
-public class ConcurrentPReader extends PReader {
-
-    private static final Unsafe UNSAFE = Unsafe.getUnsafe();
-    private static final long BA_OFFSET = (long) UNSAFE.arrayBaseOffset(byte[].class);
-
-    /**
-     * A temporary buffer that is cached on a per-thread basis.
-     */
-    private static class TemporaryBuffer {
-        static final ThreadLocal<TemporaryBuffer> CACHED_BUFFER =
-             new ThreadLocal<TemporaryBuffer>() {
-                @Override
-                protected TemporaryBuffer initialValue() { return null; }
-             };
-
-        static final TemporaryBuffer NOT_AVAILABLE = new TemporaryBuffer(0L, 0);
-
-        final long address;
-        final int size;
-
-        TemporaryBuffer(long address, int size) {
-            this.address = address;
-            this.size = size;
-        }
-
-        long address() { return address; }
-        int size() { return size; }
-
-        /**
-         * Returns the {@code TemporaryBuffer} for the current thread. The buffer
-         * is guaranteed to be of at least the given size. Returns {@code null}
-         * if a buffer cannot be cached for this thread.
-         */
-        static TemporaryBuffer get(int len) {
-            TemporaryBuffer buffer = CACHED_BUFFER.get();
-
-            // cached buffer large enough?
-            if (buffer != null && buffer.size() >= len) {
-                return buffer;
-            }
-
-            // if this is an InnocuousThread then don't return anything
-            if (buffer == NOT_AVAILABLE)
-                return null;
-
-            if (buffer != null) {
-                // replace buffer in cache with a larger buffer
-                long originalAddress = buffer.address();
-                long address = UNSAFE.allocateMemory(len);
-                buffer = new TemporaryBuffer(address, len);
-                CACHED_BUFFER.set(buffer);
-                UNSAFE.freeMemory(originalAddress);
-            } else {
-                // first usage.
-                if (Thread.currentThread() instanceof sun.misc.InnocuousThread) {
-                    buffer = NOT_AVAILABLE;
-                } else {
-                    long address = UNSAFE.allocateMemory(len);
-                    buffer = new TemporaryBuffer(address, len);
-                }
-                CACHED_BUFFER.set(buffer);
-            }
-            return buffer;
-        }
-    }
-
-    private final FileDescriptor fd;
-
-    private ConcurrentPReader(FileInputStream fis) throws IOException {
-        super(fis.getChannel());
-        this.fd = fis.getFD();
-    }
-
-    public ConcurrentPReader(String file) throws IOException {
-        this(new FileInputStream(file));
-    }
-
-    @Override
-    public byte[] read(int len, long position) throws IOException {
-        // need a temporary area of memory to read into
-        TemporaryBuffer buffer = TemporaryBuffer.get(len);
-        long address;
-        if (buffer == null) {
-            address = UNSAFE.allocateMemory(len);
-        } else {
-            address = buffer.address();
-        }
-        try {
-            int n = pread(fd, address, len, position);
-            if (n != len)
-                throw new InternalError("short read, not handled yet");
-            byte[] result = new byte[n];
-            UNSAFE.copyMemory(null, address, result, BA_OFFSET, len);
-            return result;
-        } finally {
-            if (buffer == null) {
-                UNSAFE.freeMemory(address);
-            }
-        }
-    }
-
-    private static native int pread(FileDescriptor fd, long address, int len, long pos)
-        throws IOException;
-
-    private static native void initIDs();
-
-    static {
-        System.loadLibrary("java");
-        initIDs();
-    }
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.base/share/classes/jdk/internal/jimage/decompressor/CompressedResourceHeader.java	Fri Jul 03 07:13:24 2015 -0700
@@ -0,0 +1,124 @@
+/*
+ * 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.jimage.decompressor;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Objects;
+import jdk.internal.jimage.decompressor.ResourceDecompressor.StringsProvider;
+
+/**
+ *
+ * A resource header for compressed resource. This class is handled internally,
+ * you don't have to add header to the resource, headers are added automatically
+ * for compressed resources.
+ */
+public final class CompressedResourceHeader {
+
+    private static final int SIZE = 21;
+    public static final int MAGIC = 0xCAFEFAFA;
+    private final int uncompressedSize;
+    private final int compressedSize;
+    private final int decompressorNameOffset;
+    private final int contentOffset;
+    private final boolean isTerminal;
+
+    public CompressedResourceHeader(int compressedSize,
+            int uncompressedSize, int decompressorNameOffset, int contentOffset,
+            boolean isTerminal) {
+        this.compressedSize = compressedSize;
+        this.uncompressedSize = uncompressedSize;
+        this.decompressorNameOffset = decompressorNameOffset;
+        this.contentOffset = contentOffset;
+        this.isTerminal = isTerminal;
+    }
+
+    public boolean isTerminal() {
+        return isTerminal;
+    }
+
+    public int getDecompressorNameOffset() {
+        return decompressorNameOffset;
+    }
+
+    public int getContentOffset() {
+        return contentOffset;
+    }
+
+    public String getStoredContent(StringsProvider provider) {
+        Objects.nonNull(provider);
+        if(contentOffset == -1) {
+            return null;
+        }
+        return provider.getString(contentOffset);
+    }
+
+    public int getUncompressedSize() {
+        return uncompressedSize;
+    }
+
+    public int getResourceSize() {
+        return compressedSize;
+    }
+
+    public byte[] getBytes(ByteOrder order) {
+        Objects.requireNonNull(order);
+        ByteBuffer buffer = ByteBuffer.allocate(SIZE);
+        buffer.order(order);
+        buffer.putInt(MAGIC);
+        buffer.putInt(compressedSize);
+        buffer.putInt(uncompressedSize);
+        buffer.putInt(decompressorNameOffset);
+        buffer.putInt(contentOffset);
+        buffer.put(isTerminal ? (byte)1 : (byte)0);
+        return buffer.array();
+    }
+
+    public static int getSize() {
+        return SIZE;
+    }
+
+    public static CompressedResourceHeader readFromResource(ByteOrder order,
+            byte[] resource) {
+        Objects.requireNonNull(order);
+        Objects.requireNonNull(resource);
+        if (resource.length < getSize()) {
+            return null;
+        }
+        ByteBuffer buffer = ByteBuffer.wrap(resource, 0, SIZE);
+        buffer.order(order);
+        int magic = buffer.getInt();
+        if(magic != MAGIC) {
+            return null;
+        }
+        int size = buffer.getInt();
+        int uncompressedSize = buffer.getInt();
+        int decompressorNameOffset = buffer.getInt();
+        int contentIndex = buffer.getInt();
+        byte isTerminal = buffer.get();
+        return new CompressedResourceHeader(size, uncompressedSize,
+                decompressorNameOffset, contentIndex, isTerminal == 1);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.base/share/classes/jdk/internal/jimage/decompressor/Decompressor.java	Fri Jul 03 07:13:24 2015 -0700
@@ -0,0 +1,97 @@
+/*
+ * 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.jimage.decompressor;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.nio.ByteOrder;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Properties;
+import jdk.internal.jimage.decompressor.ResourceDecompressor.StringsProvider;
+
+/**
+ * Entry point to decompress resources.
+ */
+public final class Decompressor {
+
+    private final Map<Integer, ResourceDecompressor> pluginsCache = new HashMap<>();
+
+    public Decompressor() {
+    }
+
+    /**
+     * Decompress a resource.
+     * @param order Byte order.
+     * @param provider Strings provider
+     * @param content The resource content to uncompress.
+     * @return A fully uncompressed resource.
+     * @throws IOException
+     */
+    public byte[] decompressResource(ByteOrder order, StringsProvider provider,
+            byte[] content) throws IOException {
+        Objects.requireNonNull(order);
+        Objects.requireNonNull(provider);
+        Objects.requireNonNull(content);
+        CompressedResourceHeader header;
+        do {
+            header = CompressedResourceHeader.readFromResource(order, content);
+            if (header != null) {
+                ResourceDecompressor decompressor =
+                        pluginsCache.get(header.getDecompressorNameOffset());
+                if (decompressor == null) {
+                    String pluginName =
+                            provider.getString(header.getDecompressorNameOffset());
+                    if (pluginName == null) {
+                        throw new IOException("Plugin name not found");
+                    }
+                    String storedContent = header.getStoredContent(provider);
+                    Properties props = new Properties();
+                    if (storedContent != null) {
+                        try (ByteArrayInputStream stream =
+                                new ByteArrayInputStream(storedContent.getBytes());) {
+                            props.loadFromXML(stream);
+                        }
+                    }
+                    decompressor = ResourceDecompressorRepository.
+                            newResourceDecompressor(props, pluginName);
+                    if (decompressor == null) {
+                        throw new IOException("Plugin not found: " + pluginName);
+                    }
+
+                    pluginsCache.put(header.getDecompressorNameOffset(), decompressor);
+                }
+                try {
+                    content = decompressor.decompress(provider, content,
+                            CompressedResourceHeader.getSize(), header.getUncompressedSize());
+                } catch (Exception ex) {
+                    throw new IOException(ex);
+                }
+            }
+        } while (header != null);
+        return content;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.base/share/classes/jdk/internal/jimage/decompressor/ResourceDecompressor.java	Fri Jul 03 07:13:24 2015 -0700
@@ -0,0 +1,53 @@
+/*
+ * 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.jimage.decompressor;
+
+/**
+ *
+ * JImage Decompressor.
+ */
+public interface ResourceDecompressor {
+
+    public interface StringsProvider {
+        public String getString(int offset);
+    }
+    /**
+     * Decompressor unique name.
+     * @return The decompressor name.
+     */
+    public String getName();
+
+    /**
+     * Decompress a resource.
+     * @param strings The String provider
+     * @param content The resource content
+     * @param offset Resource content offset
+     * @param originalSize Uncompressed size
+     * @return Uncompressed resource
+     * @throws Exception
+     */
+    public byte[] decompress(StringsProvider strings, byte[] content, int offset,
+            int originalSize) throws Exception;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.base/share/classes/jdk/internal/jimage/decompressor/ResourceDecompressorFactory.java	Fri Jul 03 07:13:24 2015 -0700
@@ -0,0 +1,80 @@
+/*
+ * 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.jimage.decompressor;
+
+import java.io.IOException;
+import java.util.Properties;
+
+/**
+ *
+ * JImage Resource Decompressor factory
+ */
+public abstract class ResourceDecompressorFactory {
+    private final String name;
+    private final String description;
+    private final String arguments;
+
+    protected ResourceDecompressorFactory(String name, String description,
+            String arguments) {
+        this.name = name;
+        this.description = description;
+        this.arguments = arguments;
+    }
+
+    /**
+     * The Factory name.
+     * @return The name.
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * The Factory description.
+     * @return The description.
+     */
+    public String getDescription() {
+        return description;
+    }
+
+    /**
+     * The Factory arguments description.
+     * @return The arguments description.
+     */
+    public String getArgumentsDescription() {
+        return arguments;
+    }
+
+    /**
+     * To build a new decompressor.
+     * @param properties Contains configuration.
+     * @return A new decompressor.
+     * @throws IOException
+     */
+    public abstract ResourceDecompressor newDecompressor(Properties properties)
+            throws IOException;
+
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.base/share/classes/jdk/internal/jimage/decompressor/ResourceDecompressorRepository.java	Fri Jul 03 07:13:24 2015 -0700
@@ -0,0 +1,70 @@
+/*
+ * 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.jimage.decompressor;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+/**
+ *
+ * JImage Decompressors. All decompressors must be registered in the static
+ * initializer of this class.
+ */
+public final class ResourceDecompressorRepository {
+
+    private ResourceDecompressorRepository() {
+    }
+
+    private static final Map<String, ResourceDecompressorFactory> factories = new HashMap<>();
+
+    static {
+        registerReaderProvider(new ZipDecompressorFactory());
+    }
+
+    /**
+     * Build a new decompressor for the passed name.
+     * @param properties Contains plugin configuration.
+     * @param name The plugin name to build.
+     * @return A decompressor or null if not found
+     * @throws IOException
+     */
+    public static ResourceDecompressor newResourceDecompressor(Properties properties,
+            String name) throws IOException {
+
+        ResourceDecompressorFactory fact = factories.get(name);
+        if (fact != null) {
+            return fact.newDecompressor(properties);
+        }
+        return null;
+    }
+
+    private static void registerReaderProvider(ResourceDecompressorFactory factory) {
+        factories.put(factory.getName(), factory);
+    }
+
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.base/share/classes/jdk/internal/jimage/decompressor/ZipDecompressor.java	Fri Jul 03 07:13:24 2015 -0700
@@ -0,0 +1,79 @@
+/*
+ * 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.jimage.decompressor;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.zip.DataFormatException;
+import java.util.zip.Inflater;
+
+/**
+ *
+ * ZIP Decompressor
+ */
+final class ZipDecompressor implements ResourceDecompressor {
+
+    @Override
+    public String getName() {
+        return ZipDecompressorFactory.NAME;
+    }
+
+    static byte[] decompress(byte[] bytesIn, int offset) {
+        Inflater inflater = new Inflater();
+        inflater.setInput(bytesIn, offset, bytesIn.length - offset);
+        ByteArrayOutputStream stream = new ByteArrayOutputStream(bytesIn.length - offset);
+        byte[] buffer = new byte[1024];
+
+        while (!inflater.finished()) {
+            int count;
+
+            try {
+                count = inflater.inflate(buffer);
+            } catch (DataFormatException ex) {
+                return null;
+            }
+
+            stream.write(buffer, 0, count);
+        }
+
+        try {
+            stream.close();
+        } catch (IOException ex) {
+            return null;
+        }
+
+        byte[] bytesOut = stream.toByteArray();
+        inflater.end();
+
+        return bytesOut;
+    }
+
+    @Override
+    public byte[] decompress(StringsProvider reader, byte[] content, int offset,
+            int originalSize) throws Exception {
+        byte[] decompressed = decompress(content, offset);
+        return decompressed;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.base/share/classes/jdk/internal/jimage/decompressor/ZipDecompressorFactory.java	Fri Jul 03 07:13:24 2015 -0700
@@ -0,0 +1,45 @@
+/*
+ * 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.jimage.decompressor;
+
+import java.io.IOException;
+import java.util.Properties;
+
+/**
+ *
+ * ZIP decompressor factory
+ */
+public final class ZipDecompressorFactory extends ResourceDecompressorFactory {
+    public static final String NAME = "zip";
+    public ZipDecompressorFactory() {
+        super(NAME, "ZIP Decompression", null);
+    }
+
+    @Override
+    public ResourceDecompressor newDecompressor(Properties properties)
+            throws IOException {
+        return new ZipDecompressor();
+    }
+}
--- a/jdk/src/java.base/share/classes/jdk/internal/jrtfs/JrtDirectoryStream.java	Thu Jul 02 15:25:46 2015 +0200
+++ b/jdk/src/java.base/share/classes/jdk/internal/jrtfs/JrtDirectoryStream.java	Fri Jul 03 07:13:24 2015 -0700
@@ -51,7 +51,7 @@
         this.jrtfs = jrtPath.getFileSystem();
         this.path = jrtPath.getResolvedPath();
         // sanity check
-        if (!jrtfs.isDirectory(path))
+        if (!jrtfs.isDirectory(path, true))
             throw new NotDirectoryException(jrtPath.toString());
 
         // absolute path and does not have funky chars in front like /./java.base
--- a/jdk/src/java.base/share/classes/jdk/internal/jrtfs/JrtFileAttributeView.java	Thu Jul 02 15:25:46 2015 +0200
+++ b/jdk/src/java.base/share/classes/jdk/internal/jrtfs/JrtFileAttributeView.java	Fri Jul 03 07:13:24 2015 -0700
@@ -25,6 +25,7 @@
 
 package jdk.internal.jrtfs;
 
+import java.nio.file.LinkOption;
 import java.nio.file.attribute.*;
 import java.io.IOException;
 import java.util.LinkedHashMap;
@@ -48,30 +49,32 @@
 
     private final JrtPath path;
     private final boolean isJrtView;
+    private final LinkOption[] options;
 
-    private JrtFileAttributeView(JrtPath path, boolean isJrtView) {
+    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(JrtPath path, Class<V> type) {
+    static <V extends FileAttributeView> V get(JrtPath path, Class<V> type, LinkOption... options) {
         if (type == null)
             throw new NullPointerException();
         if (type == BasicFileAttributeView.class)
-            return (V)new JrtFileAttributeView(path, false);
+            return (V)new JrtFileAttributeView(path, false, options);
         if (type == JrtFileAttributeView.class)
-            return (V)new JrtFileAttributeView(path, true);
+            return (V)new JrtFileAttributeView(path, true, options);
         return null;
     }
 
-    static JrtFileAttributeView get(JrtPath path, String type) {
+    static JrtFileAttributeView get(JrtPath path, String type, LinkOption... options) {
         if (type == null)
             throw new NullPointerException();
         if (type.equals("basic"))
-            return new JrtFileAttributeView(path, false);
+            return new JrtFileAttributeView(path, false, options);
         if (type.equals("jjrt"))
-            return new JrtFileAttributeView(path, true);
+            return new JrtFileAttributeView(path, true, options);
         return null;
     }
 
@@ -83,7 +86,7 @@
     @Override
     public JrtFileAttributes readAttributes() throws IOException
     {
-        return path.getAttributes();
+        return path.getAttributes(options);
     }
 
     @Override
--- a/jdk/src/java.base/share/classes/jdk/internal/jrtfs/JrtFileAttributes.java	Thu Jul 02 15:25:46 2015 +0200
+++ b/jdk/src/java.base/share/classes/jdk/internal/jrtfs/JrtFileAttributes.java	Fri Jul 03 07:13:24 2015 -0700
@@ -76,12 +76,12 @@
 
     @Override
     public boolean isSymbolicLink() {
-        return false;
+        return node.isLink();
     }
 
     @Override
     public Object fileKey() {
-        return null;
+        return node.resolveLink(true);
     }
 
     ///////// jrt entry attributes ///////////
--- a/jdk/src/java.base/share/classes/jdk/internal/jrtfs/JrtFileSystem.java	Thu Jul 02 15:25:46 2015 +0200
+++ b/jdk/src/java.base/share/classes/jdk/internal/jrtfs/JrtFileSystem.java	Fri Jul 03 07:13:24 2015 -0700
@@ -31,9 +31,9 @@
 import java.nio.ByteBuffer;
 import java.nio.channels.*;
 import java.nio.charset.Charset;
-import java.nio.file.AccessMode;
 import java.nio.file.ClosedFileSystemException;
 import java.nio.file.CopyOption;
+import java.nio.file.LinkOption;
 import java.nio.file.FileStore;
 import java.nio.file.FileSystem;
 import java.nio.file.FileSystemException;
@@ -45,16 +45,13 @@
 import java.nio.file.Path;
 import java.nio.file.PathMatcher;
 import java.nio.file.ReadOnlyFileSystemException;
-import java.nio.file.StandardCopyOption;
 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.security.AccessController;
-import java.security.PrivilegedActionException;
-import java.security.PrivilegedExceptionAction;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -63,8 +60,9 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.function.Function;
 import java.util.regex.Pattern;
-import java.util.stream.Collectors;
+import static java.util.stream.Collectors.toList;
 import jdk.internal.jimage.ImageReader;
 import jdk.internal.jimage.ImageReader.Node;
 import jdk.internal.jimage.UTF8String;
@@ -74,6 +72,7 @@
  */
 class JrtFileSystem extends FileSystem {
     private static final Charset UTF_8 = Charset.forName("UTF-8");
+
     private final JrtFileSystemProvider provider;
     // System image readers
     private ImageReader bootImage;
@@ -109,7 +108,8 @@
         this.extImage = openImage(SystemImages.extImagePath);
         this.appImage = openImage(SystemImages.appImagePath);
 
-        rootPath = new JrtPath(this, new byte[]{'/'});
+        byte[] root = new byte[] { '/' };
+        rootPath = new JrtPath(this, root);
         isOpen = true;
     }
 
@@ -149,12 +149,12 @@
         synchronized(this) {
             isOpen = false;
 
-            // close all image readers and null out
+            // close all image reader and null out
             bootImage.close();
+            bootImage = null;
             extImage.close();
+            extImage = null;
             appImage.close();
-            bootImage = null;
-            extImage = null;
             appImage = null;
         }
     }
@@ -289,21 +289,52 @@
         }
     }
 
-    private NodeAndImage findNode(byte[] path) throws IOException {
-        ImageReader image = bootImage;
+    private NodeAndImage lookup(byte[] path) {
         Node node = bootImage.findNode(path);
+        ImageReader image = bootImage;
         if (node == null) {
+            node = extImage.findNode(path);
             image = extImage;
-            node = extImage.findNode(path);
         }
         if (node == null) {
+            node = appImage.findNode(path);
             image = appImage;
-            node = appImage.findNode(path);
         }
-        if (node == null || node.isHidden()) {
-            throw new NoSuchFileException(getString(path));
+        return node != null? new NodeAndImage(node, image) : null;
+    }
+
+    private NodeAndImage lookupSymbolic(byte[] path) {
+        for (int i = 1; i < path.length; i++) {
+            if (path[i] == (byte)'/') {
+                byte[] prefix = Arrays.copyOfRange(path, 0, i);
+                NodeAndImage ni = lookup(prefix);
+                if (ni == null) {
+                    break;
+                }
+
+                if (ni.node.isLink()) {
+                    Node link = ni.node.resolveLink(true);
+                    // resolved symbolic path concatenated to the rest of the path
+                    UTF8String resPath = link.getName().concat(new UTF8String(path, i));
+                    byte[] resPathBytes = resPath.getBytesCopy();
+                    ni = lookup(resPathBytes);
+                    return ni != null? ni : lookupSymbolic(resPathBytes);
+                }
+            }
         }
-        return new NodeAndImage(node, image);
+
+        return null;
+    }
+
+    private NodeAndImage findNode(byte[] path) throws IOException {
+        NodeAndImage ni = lookup(path);
+        if (ni == null) {
+            ni = lookupSymbolic(path);
+            if (ni == null) {
+                throw new NoSuchFileException(getString(path));
+            }
+        }
+        return ni;
     }
 
     private NodeAndImage checkNode(byte[] path) throws IOException {
@@ -321,10 +352,28 @@
         return ni;
     }
 
+    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;
+    }
+
     // package private helpers
-    JrtFileAttributes getFileAttributes(byte[] path)
+    JrtFileAttributes getFileAttributes(byte[] path, LinkOption... options)
             throws IOException {
         NodeAndImage ni = checkNode(path);
+        if (ni.node.isLink() && followLinks(options)) {
+            return new JrtFileAttributes(ni.node.resolveLink(true));
+        }
         return new JrtFileAttributes(ni.node);
     }
 
@@ -343,11 +392,13 @@
         return true;
     }
 
-    boolean isDirectory(byte[] path)
+    boolean isDirectory(byte[] path, boolean resolveLinks)
             throws IOException {
         ensureOpen();
         NodeAndImage ni = checkNode(path);
-        return ni.node.isDirectory();
+        return resolveLinks && ni.node.isLink()?
+            ni.node.resolveLink(true).isDirectory() :
+            ni.node.isDirectory();
     }
 
     JrtPath toJrtPath(String path) {
@@ -358,6 +409,28 @@
         return new JrtPath(this, path);
     }
 
+    boolean isSameFile(JrtPath p1, JrtPath p2) throws IOException {
+        NodeAndImage n1 = findNode(p1.getName());
+        NodeAndImage n2 = findNode(p2.getName());
+        return n1.node.equals(n2.node);
+    }
+
+    boolean isLink(JrtPath jrtPath) throws IOException {
+        return findNode(jrtPath.getName()).node.isLink();
+    }
+
+    JrtPath resolveLink(JrtPath jrtPath) throws IOException {
+        NodeAndImage ni = findNode(jrtPath.getName());
+        if (ni.node.isLink()) {
+            Node node = ni.node.resolveLink();
+            return toJrtPath(node.getName().getBytesCopy());
+        }
+
+        return jrtPath;
+    }
+
+    private Map<UTF8String, List<Node>> packagesTreeChildren = new ConcurrentHashMap<>();
+
     /**
      * returns the list of child paths of the given directory "path"
      *
@@ -369,49 +442,73 @@
     Iterator<Path> iteratorOf(byte[] path, String childPrefix)
             throws IOException {
         NodeAndImage ni = checkNode(path);
-        if (!ni.node.isDirectory()) {
+        Node node = ni.node.resolveLink(true);
+
+        if (!node.isDirectory()) {
             throw new NotDirectoryException(getString(path));
         }
 
-        if (ni.node.isRootDir()) {
+        if (node.isRootDir()) {
             return rootDirIterator(path, childPrefix);
+        } else if (node.isModulesDir()) {
+            return modulesDirIterator(path, childPrefix);
+        } else if (node.isPackagesDir()) {
+            return packagesDirIterator(path, childPrefix);
+        } else if (node.getNameString().startsWith("/packages/")) {
+            if (ni.image != appImage) {
+                UTF8String name = node.getName();
+                List<Node> children = packagesTreeChildren.get(name);
+                if (children != null) {
+                    return nodesToIterator(toJrtPath(path), childPrefix, children);
+                }
+
+                children = new ArrayList<>();
+                children.addAll(node.getChildren());
+                Node tmpNode = null;
+                // found in boot
+                if (ni.image == bootImage) {
+                    tmpNode = extImage.findNode(name);
+                    if (tmpNode != null) {
+                        children.addAll(tmpNode.getChildren());
+                    }
+                }
+
+                // found in ext
+                tmpNode = appImage.findNode(name);
+                if (tmpNode != null) {
+                    children.addAll(tmpNode.getChildren());
+                }
+
+                packagesTreeChildren.put(name, children);
+                return nodesToIterator(toJrtPath(path), childPrefix, children);
+            }
         }
 
-        return nodesToIterator(toJrtPath(path), childPrefix, ni.node.getChildren());
+        return nodesToIterator(toJrtPath(path), childPrefix, node.getChildren());
     }
 
     private Iterator<Path> nodesToIterator(Path path, String childPrefix, List<Node> childNodes) {
-        List<Path> childPaths;
-        if (childPrefix == null) {
-            childPaths = childNodes.stream()
-                .filter(Node::isVisible)
-                .map(child -> toJrtPath(child.getNameString()))
-                .collect(Collectors.toCollection(ArrayList::new));
-        } else {
-            childPaths = childNodes.stream()
-                .filter(Node::isVisible)
-                .map(child -> toJrtPath(childPrefix + child.getNameString().substring(1)))
-                .collect(Collectors.toCollection(ArrayList::new));
-        }
-        return childPaths.iterator();
+        Function<Node, Path> f = childPrefix == null
+                ? child -> toJrtPath(child.getNameString())
+                : child -> toJrtPath(childPrefix + child.getNameString().substring(1));
+         return childNodes.stream().map(f).collect(toList()).iterator();
     }
 
-    private List<Node> rootChildren;
-    private static void addRootDirContent(List<Node> dest, List<Node> src) {
-        for (Node n : src) {
-            // only module directories at the top level. Filter other stuff!
-            if (n.isModuleDir()) {
-                dest.add(n);
+    private void addRootDirContent(List<Node> children) {
+        for (Node child : children) {
+            if (!(child.isModulesDir() || child.isPackagesDir())) {
+                rootChildren.add(child);
             }
         }
     }
 
+    private List<Node> rootChildren;
     private synchronized void initRootChildren(byte[] path) {
         if (rootChildren == null) {
             rootChildren = new ArrayList<>();
-            addRootDirContent(rootChildren, bootImage.findNode(path).getChildren());
-            addRootDirContent(rootChildren, extImage.findNode(path).getChildren());
-            addRootDirContent(rootChildren, appImage.findNode(path).getChildren());
+            rootChildren.addAll(bootImage.findNode(path).getChildren());
+            addRootDirContent(extImage.findNode(path).getChildren());
+            addRootDirContent(appImage.findNode(path).getChildren());
         }
     }
 
@@ -420,6 +517,35 @@
         return nodesToIterator(rootPath, childPrefix, rootChildren);
     }
 
+    private List<Node> modulesChildren;
+    private synchronized void initModulesChildren(byte[] path) {
+        if (modulesChildren == null) {
+            modulesChildren = new ArrayList<>();
+            modulesChildren.addAll(bootImage.findNode(path).getChildren());
+            modulesChildren.addAll(appImage.findNode(path).getChildren());
+            modulesChildren.addAll(extImage.findNode(path).getChildren());
+        }
+    }
+
+    private Iterator<Path> modulesDirIterator(byte[] path, String childPrefix) throws IOException {
+        initModulesChildren(path);
+        return nodesToIterator(new JrtPath(this, path), childPrefix, modulesChildren);
+    }
+
+    private List<Node> packagesChildren;
+    private synchronized void initPackagesChildren(byte[] path) {
+        if (packagesChildren == null) {
+            packagesChildren = new ArrayList<>();
+            packagesChildren.addAll(bootImage.findNode(path).getChildren());
+            packagesChildren.addAll(extImage.findNode(path).getChildren());
+            packagesChildren.addAll(appImage.findNode(path).getChildren());
+        }
+    }
+    private Iterator<Path> packagesDirIterator(byte[] path, String childPrefix) throws IOException {
+        initPackagesChildren(path);
+        return nodesToIterator(new JrtPath(this, path), childPrefix, packagesChildren);
+    }
+
     void createDirectory(byte[] dir, FileAttribute<?>... attrs)
             throws IOException {
         throw readOnly();
--- a/jdk/src/java.base/share/classes/jdk/internal/jrtfs/JrtFileSystemProvider.java	Thu Jul 02 15:25:46 2015 +0200
+++ b/jdk/src/java.base/share/classes/jdk/internal/jrtfs/JrtFileSystemProvider.java	Fri Jul 03 07:13:24 2015 -0700
@@ -146,6 +146,11 @@
     }
 
     @Override
+    public Path readSymbolicLink(Path link) throws IOException {
+        return toJrtPath(link).readSymbolicLink();
+    }
+
+    @Override
     public void copy(Path src, Path target, CopyOption... options)
         throws IOException
     {
@@ -169,7 +174,7 @@
     public <V extends FileAttributeView> V
         getFileAttributeView(Path path, Class<V> type, LinkOption... options)
     {
-        return JrtFileAttributeView.get(toJrtPath(path), type);
+        return JrtFileAttributeView.get(toJrtPath(path), type, options);
     }
 
     @Override
@@ -250,7 +255,7 @@
         throws IOException
     {
         if (type == BasicFileAttributes.class || type == JrtFileAttributes.class)
-            return (A)toJrtPath(path).getAttributes();
+            return (A)toJrtPath(path).getAttributes(options);
         return null;
     }
 
--- a/jdk/src/java.base/share/classes/jdk/internal/jrtfs/JrtPath.java	Thu Jul 02 15:25:46 2015 +0200
+++ b/jdk/src/java.base/share/classes/jdk/internal/jrtfs/JrtPath.java	Fri Jul 03 07:13:24 2015 -0700
@@ -55,6 +55,10 @@
             this.path = normalize(path);
     }
 
+    byte[] getName() {
+        return path;
+    }
+
     @Override
     public JrtPath getRoot() {
         if (this.isAbsolute())
@@ -140,10 +144,19 @@
     @Override
     public JrtPath toRealPath(LinkOption... options) throws IOException {
         JrtPath realPath = new JrtPath(jrtfs, getResolvedPath()).toAbsolutePath();
+        realPath = JrtFileSystem.followLinks(options)? jrtfs.resolveLink(this) : realPath;
         realPath.checkAccess();
         return realPath;
     }
 
+    JrtPath readSymbolicLink() throws IOException {
+        if (! jrtfs.isLink(this)) {
+           throw new IOException("not a symbolic link");
+        }
+
+        return jrtfs.resolveLink(this);
+    }
+
     boolean isHidden() {
         return false;
     }
@@ -638,9 +651,9 @@
         jrtfs.deleteFile(getResolvedPath(), false);
     }
 
-    JrtFileAttributes getAttributes() throws IOException
+    JrtFileAttributes getAttributes(LinkOption... options) throws IOException
     {
-        JrtFileAttributes zfas = jrtfs.getFileAttributes(getResolvedPath());
+        JrtFileAttributes zfas = jrtfs.getFileAttributes(getResolvedPath(), options);
         if (zfas == null)
             throw new NoSuchFileException(toString());
         return zfas;
@@ -659,7 +672,7 @@
             type = attribute.substring(0, colonPos++);
             attr = attribute.substring(colonPos);
         }
-        JrtFileAttributeView view = JrtFileAttributeView.get(this, type);
+        JrtFileAttributeView view = JrtFileAttributeView.get(this, type, options);
         if (view == null)
             throw new UnsupportedOperationException("view <" + view + "> is not supported");
         view.setAttribute(attr, value);
@@ -685,7 +698,7 @@
             view = attributes.substring(0, colonPos++);
             attrs = attributes.substring(colonPos);
         }
-        JrtFileAttributeView jrtfv = JrtFileAttributeView.get(this, view);
+        JrtFileAttributeView jrtfv = JrtFileAttributeView.get(this, view, options);
         if (jrtfv == null) {
             throw new UnsupportedOperationException("view not supported");
         }
@@ -706,9 +719,10 @@
             this.getFileSystem() != other.getFileSystem())
             return false;
         this.checkAccess();
-        ((JrtPath)other).checkAccess();
-        return Arrays.equals(this.getResolvedPath(),
-                             ((JrtPath)other).getResolvedPath());
+        JrtPath path = (JrtPath)other;
+        path.checkAccess();
+        return Arrays.equals(this.getResolvedPath(), path.getResolvedPath()) ||
+            jrtfs.isSameFile(this, (JrtPath)other);
     }
 
     SeekableByteChannel newByteChannel(Set<? extends OpenOption> options,
--- a/jdk/src/java.base/share/classes/jdk/internal/jrtfs/SystemImages.java	Thu Jul 02 15:25:46 2015 +0200
+++ b/jdk/src/java.base/share/classes/jdk/internal/jrtfs/SystemImages.java	Fri Jul 03 07:13:24 2015 -0700
@@ -42,6 +42,7 @@
     static final Path bootImagePath;
     static final Path extImagePath;
     static final Path appImagePath;
+
     static {
         PrivilegedAction<String> pa = SystemImages::findHome;
         RUNTIME_HOME = AccessController.doPrivileged(pa);
--- a/jdk/src/java.base/share/native/include/jvm.h	Thu Jul 02 15:25:46 2015 +0200
+++ b/jdk/src/java.base/share/native/include/jvm.h	Fri Jul 03 07:13:24 2015 -0700
@@ -557,6 +557,48 @@
 JVM_SupportsCX8(void);
 
 /*
+ * jdk.internal.jimage
+ */
+
+JNIEXPORT jlong JNICALL
+JVM_ImageOpen(JNIEnv *env, const char *nativePath, jboolean big_endian);
+
+JNIEXPORT void JNICALL
+JVM_ImageClose(JNIEnv *env, jlong id);
+
+JNIEXPORT jlong JNICALL
+JVM_ImageGetIndexAddress(JNIEnv *env, jlong id);
+
+JNIEXPORT jlong JNICALL
+JVM_ImageGetDataAddress(JNIEnv *env,jlong id);
+
+JNIEXPORT jboolean JNICALL
+JVM_ImageRead(JNIEnv *env, jlong id, jlong offset,
+            unsigned char* uncompressedAddress, jlong uncompressed_size);
+
+JNIEXPORT jboolean JNICALL
+JVM_ImageReadCompressed(JNIEnv *env, jlong id, jlong offset,
+            unsigned char* compressedBuffer, jlong compressed_size,
+            unsigned char* uncompressedBuffer, jlong uncompressed_size);
+
+JNIEXPORT const char* JNICALL
+JVM_ImageGetStringBytes(JNIEnv *env, jlong id, jint offset);
+
+JNIEXPORT jlong* JNICALL
+JVM_ImageGetAttributes(JNIEnv *env, jlong* rawAttributes, jlong id, jint offset);
+
+JNIEXPORT jsize JNICALL
+JVM_ImageGetAttributesCount(JNIEnv *env);
+
+JNIEXPORT jlong* JNICALL
+JVM_ImageFindAttributes(JNIEnv *env, jlong* rawAttributes, jbyte* rawBytes, jsize size, jlong id);
+
+JNIEXPORT jint* JNICALL
+JVM_ImageAttributeOffsets(JNIEnv *env, jint* rawOffsets, unsigned int length, jlong id);
+
+JNIEXPORT unsigned int JNICALL
+JVM_ImageAttributeOffsetsLength(JNIEnv *env, jlong id);
+/*
  * com.sun.dtrace.jsdt support
  */
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.base/share/native/libjava/Image.c	Fri Jul 03 07:13:24 2015 -0700
@@ -0,0 +1,177 @@
+/*
+ * 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.
+ */
+
+#include <string.h>
+
+#include "jni.h"
+#include "jvm.h"
+#include "jdk_internal_jimage_ImageNativeSubstrate.h"
+
+JNIEXPORT jlong JNICALL
+Java_jdk_internal_jimage_ImageNativeSubstrate_openImage(JNIEnv *env,
+        jclass cls, jstring path, jboolean big_endian) {
+    const char *nativePath;
+    jlong ret;
+
+    nativePath = (*env)->GetStringUTFChars(env, path, NULL);
+    ret = JVM_ImageOpen(env, nativePath, big_endian);
+    (*env)->ReleaseStringUTFChars(env, path, nativePath);
+    return ret;
+}
+
+JNIEXPORT void JNICALL
+Java_jdk_internal_jimage_ImageNativeSubstrate_closeImage(JNIEnv *env,
+                                        jclass cls, jlong id) {
+    JVM_ImageClose(env, id);
+}
+
+JNIEXPORT jlong JNICALL
+Java_jdk_internal_jimage_ImageNativeSubstrate_getIndexAddress(JNIEnv *env,
+                jclass cls, jlong id) {
+ return JVM_ImageGetIndexAddress(env, id);
+}
+
+JNIEXPORT jlong JNICALL
+Java_jdk_internal_jimage_ImageNativeSubstrate_getDataAddress(JNIEnv *env,
+                jclass cls, jlong id) {
+ return JVM_ImageGetDataAddress(env, id);
+}
+
+JNIEXPORT jboolean JNICALL
+Java_jdk_internal_jimage_ImageNativeSubstrate_read(JNIEnv *env,
+                                        jclass cls, jlong id, jlong offset,
+        jobject uncompressedBuffer, jlong uncompressed_size) {
+    unsigned char* uncompressedAddress;
+
+    uncompressedAddress = (unsigned char*) (*env)->GetDirectBufferAddress(env, uncompressedBuffer);
+    if (uncompressedBuffer == NULL) {
+      return JNI_FALSE;
+    }
+    return JVM_ImageRead(env, id, offset, uncompressedAddress, uncompressed_size);
+}
+
+JNIEXPORT jboolean JNICALL
+Java_jdk_internal_jimage_ImageNativeSubstrate_readCompressed(JNIEnv *env,
+                                        jclass cls, jlong id, jlong offset,
+                                        jobject compressedBuffer, jlong compressed_size,
+        jobject uncompressedBuffer, jlong uncompressed_size) {
+    // Get address of read direct buffer.
+    unsigned char* compressedAddress;
+    unsigned char* uncompressedAddress;
+
+    compressedAddress = (unsigned char*) (*env)->GetDirectBufferAddress(env, compressedBuffer);
+    // Get address of decompression direct buffer.
+    uncompressedAddress = (unsigned char*) (*env)->GetDirectBufferAddress(env, uncompressedBuffer);
+    if (uncompressedBuffer == NULL || compressedBuffer == NULL) {
+      return JNI_FALSE;
+    }
+    return JVM_ImageReadCompressed(env, id, offset, compressedAddress, compressed_size,
+            uncompressedAddress, uncompressed_size);
+}
+
+JNIEXPORT jbyteArray JNICALL
+Java_jdk_internal_jimage_ImageNativeSubstrate_getStringBytes(JNIEnv *env,
+                                        jclass cls, jlong id, jint offset) {
+    const char* data;
+    size_t size;
+    jbyteArray byteArray;
+    jbyte* rawBytes;
+
+    data = JVM_ImageGetStringBytes(env, id, offset);
+    // Determine String length.
+    size = strlen(data);
+    // Allocate byte array.
+    byteArray = (*env)->NewByteArray(env, (jsize) size);
+    // Get array base address.
+    rawBytes = (*env)->GetByteArrayElements(env, byteArray, NULL);
+    // Copy bytes from image string table.
+    memcpy(rawBytes, data, size);
+    // Release byte array base address.
+    (*env)->ReleaseByteArrayElements(env, byteArray, rawBytes, 0);
+    return byteArray;
+}
+
+JNIEXPORT jlongArray JNICALL
+Java_jdk_internal_jimage_ImageNativeSubstrate_getAttributes(JNIEnv *env,
+        jclass cls, jlong id, jint offset) {
+    // Allocate a jlong large enough for all location attributes.
+    jlongArray attributes;
+    jlong* rawAttributes;
+    jlong* ret;
+
+    attributes = (*env)->NewLongArray(env, JVM_ImageGetAttributesCount(env));
+    // Get base address for jlong array.
+    rawAttributes = (*env)->GetLongArrayElements(env, attributes, NULL);
+    ret = JVM_ImageGetAttributes(env, rawAttributes, id, offset);
+    // Release jlong array base address.
+    (*env)->ReleaseLongArrayElements(env, attributes, rawAttributes, 0);
+    return ret == NULL ? NULL : attributes;
+}
+
+JNIEXPORT jlongArray JNICALL
+Java_jdk_internal_jimage_ImageNativeSubstrate_findAttributes(JNIEnv *env,
+        jclass cls, jlong id, jbyteArray utf8) {
+    // Allocate a jlong large enough for all location attributes.
+    jsize count;
+    jlongArray attributes;
+    jlong* rawAttributes;
+    jsize size;
+    jbyte* rawBytes;
+    jlong* ret;
+
+    count = JVM_ImageGetAttributesCount(env);
+    attributes = (*env)->NewLongArray(env, JVM_ImageGetAttributesCount(env));
+    // Get base address for jlong array.
+    rawAttributes = (*env)->GetLongArrayElements(env, attributes, NULL);
+    size = (*env)->GetArrayLength(env, utf8);
+    rawBytes = (*env)->GetByteArrayElements(env, utf8, NULL);
+    ret = JVM_ImageFindAttributes(env, rawAttributes, rawBytes, size, id);
+    (*env)->ReleaseByteArrayElements(env, utf8, rawBytes, 0);
+    // Release jlong array base address.
+    (*env)->ReleaseLongArrayElements(env, attributes, rawAttributes, 0);
+    return ret == NULL ? NULL : attributes;
+
+}
+
+JNIEXPORT jintArray JNICALL
+Java_jdk_internal_jimage_ImageNativeSubstrate_attributeOffsets(JNIEnv *env,
+        jclass cls, jlong id) {
+    unsigned int length;
+    jintArray offsets;
+    jint* rawOffsets;
+    jint* ret;
+
+    length = JVM_ImageAttributeOffsetsLength(env, id);
+    offsets = (*env)->NewIntArray(env, length);
+    // Get base address of result.
+    rawOffsets = (*env)->GetIntArrayElements(env, offsets, NULL);
+    ret = JVM_ImageAttributeOffsets(env, rawOffsets, length, id);
+    if (length == 0) {
+        return NULL;
+    }
+    // Release result base address.
+    (*env)->ReleaseIntArrayElements(env, offsets, rawOffsets, 0);
+    return ret == NULL ? NULL : offsets;
+}
--- a/jdk/src/java.base/unix/native/libjava/ConcurrentPReader_md.c	Thu Jul 02 15:25:46 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,66 +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.
- */
-
-#include <unistd.h>
-#include <errno.h>
-
-#include "jni.h"
-#include "jni_util.h"
-#include "jlong.h"
-#include "jdk_internal_jimage_concurrent_ConcurrentPReader.h"
-
-#ifdef _ALLBSD_SOURCE
-  #define pread64 pread
-#endif
-
-#define RESTARTABLE(_cmd, _result) do { \
-  do { \
-    _result = _cmd; \
-  } while((_result == -1) && (errno == EINTR)); \
-} while(0)
-
-static jfieldID fd_fdID;
-
-JNIEXPORT void JNICALL
-Java_jdk_internal_jimage_concurrent_ConcurrentPReader_initIDs(JNIEnv *env, jclass clazz)
-{
-    CHECK_NULL(clazz = (*env)->FindClass(env, "java/io/FileDescriptor"));
-    CHECK_NULL(fd_fdID = (*env)->GetFieldID(env, clazz, "fd", "I"));
-}
-
-JNIEXPORT jint JNICALL
-Java_jdk_internal_jimage_concurrent_ConcurrentPReader_pread(JNIEnv *env, jclass clazz,
-                                                            jobject fdo, jlong address,
-                                                            jint len, jlong offset)
-{
-    jint fd = (*env)->GetIntField(env, fdo, fd_fdID);
-    void *buf = (void *)jlong_to_ptr(address);
-    int res;
-    RESTARTABLE(pread64(fd, buf, len, offset), res);
-    if (res == -1) {
-        JNU_ThrowIOExceptionWithLastError(env, "pread failed");
-    }
-    return res;
-}
--- a/jdk/src/java.base/windows/native/libjava/ConcurrentPReader_md.c	Thu Jul 02 15:25:46 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,64 +0,0 @@
-/*
- * Copyright (c) 2014, 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.
- */
-
-#include <windows.h>
-
-#include "jni_util.h"
-#include "jlong.h"
-#include "jdk_internal_jimage_concurrent_ConcurrentPReader.h"
-
-static jfieldID handle_fdID;
-
-JNIEXPORT void JNICALL
-Java_jdk_internal_jimage_concurrent_ConcurrentPReader_initIDs(JNIEnv *env, jclass clazz)
-{
-    CHECK_NULL(clazz = (*env)->FindClass(env, "java/io/FileDescriptor"));
-    CHECK_NULL(handle_fdID = (*env)->GetFieldID(env, clazz, "handle", "J"));
-}
-
-JNIEXPORT jint JNICALL
-Java_jdk_internal_jimage_concurrent_ConcurrentPReader_pread(JNIEnv *env, jclass clazz,
-                                                            jobject fdo, jlong address,
-                                                            jint len, jlong offset)
-{
-    OVERLAPPED ov;
-    DWORD nread;
-    BOOL result;
-
-    HANDLE handle = (HANDLE)(*env)->GetLongField(env, fdo, handle_fdID);
-    void *buf = (void *)jlong_to_ptr(address);
-
-    ZeroMemory(&ov, sizeof(ov));
-    ov.Offset = (DWORD)offset;
-    ov.OffsetHigh = (DWORD)(offset >> 32);
-
-    result = ReadFile(handle, (LPVOID)buf, len, &nread, &ov);
-    if (result == 0) {
-        JNU_ThrowIOExceptionWithLastError(env, "ReadFile failed");
-    }
-
-    return nread;
-}
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/jdk.dev/share/classes/jdk/tools/jimage/ExtractedImage.java	Fri Jul 03 07:13:24 2015 -0700
@@ -0,0 +1,210 @@
+/*
+ * 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.tools.jimage;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintWriter;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.function.Consumer;
+import java.util.stream.Stream;
+import jdk.internal.jimage.Archive;
+import jdk.internal.jimage.ImageFileCreator;
+import jdk.internal.jimage.ImageModuleData;
+import jdk.internal.jimage.ImageModuleDataWriter;
+
+/**
+ *
+ * Support for extracted image.
+ */
+public final class ExtractedImage {
+
+    /**
+     * An Archive backed by a directory.
+     */
+    public class DirArchive implements Archive {
+
+        /**
+         * A File located in a Directory.
+         */
+        private class FileEntry extends Archive.Entry {
+
+            private final long size;
+            private final Path path;
+
+            FileEntry(Path path, String name) {
+                super(DirArchive.this, getPathName(path), name,
+                        Archive.Entry.EntryType.CLASS_OR_RESOURCE);
+                this.path = path;
+                try {
+                    size = Files.size(path);
+                } catch (IOException ex) {
+                    throw new RuntimeException(ex);
+                }
+            }
+
+            /**
+             * Returns the number of bytes of this file.
+             */
+            @Override
+            public long size() {
+                return size;
+            }
+
+            @Override
+            public InputStream stream() throws IOException {
+                InputStream stream = Files.newInputStream(path);
+                open.add(stream);
+                return stream;
+            }
+        }
+
+        private final Path dirPath;
+        private final String moduleName;
+        private final List<InputStream> open = new ArrayList<>();
+        private final int chop;
+
+        protected DirArchive(Path dirPath) throws IOException {
+            if (!Files.isDirectory(dirPath)) {
+                throw new IOException("Not a directory");
+            }
+            chop = dirPath.toString().length() + 1;
+            this.moduleName = dirPath.getFileName().toString();
+            System.out.println("Module name " + this.moduleName);
+            this.dirPath = dirPath;
+        }
+
+        @Override
+        public String moduleName() {
+            return moduleName;
+        }
+
+        @Override
+        public Stream<Entry> entries() {
+            try {
+                return Files.walk(dirPath).map(this::toEntry).filter(n -> n != null);
+            } catch(IOException ex) {
+                throw new RuntimeException(ex);
+            }
+        }
+
+        private Archive.Entry toEntry(Path p) {
+            if (Files.isDirectory(p)) {
+                return null;
+            }
+            String name = getPathName(p).substring(chop);
+            if (name.startsWith("_")) {
+                return null;
+            }
+            if (verbose) {
+                String verboseName = moduleName + "/" + name;
+                log.println(verboseName);
+            }
+
+            return new FileEntry(p, name);
+        }
+
+        @Override
+        public void close() throws IOException {
+            IOException e = null;
+            for (InputStream stream : open) {
+                try {
+                    stream.close();
+                } catch (IOException ex) {
+                    if (e == null) {
+                        e = ex;
+                    } else {
+                        e.addSuppressed(ex);
+                    }
+                }
+            }
+            if (e != null) {
+                throw e;
+            }
+        }
+
+        @Override
+        public void open() throws IOException {
+            // NOOP
+        }
+    }
+    private Map<String, Set<String>> modulePackages = new LinkedHashMap<>();
+    private Set<Archive> archives = new HashSet<>();
+    private final PrintWriter log;
+    private final boolean verbose;
+
+    ExtractedImage(Path dirPath, PrintWriter log,
+            boolean verbose) throws IOException {
+        if (!Files.isDirectory(dirPath)) {
+            throw new IOException("Not a directory");
+        }
+        Files.walk(dirPath, 1).forEach((p) -> {
+            try {
+                if (!dirPath.equals(p)) {
+                    String name = getPathName(p);
+                    if (name.endsWith(ImageModuleData.META_DATA_EXTENSION)) {
+                        List<String> lines = Files.readAllLines(p);
+                        for (Entry<String, List<String>> entry
+                                : ImageModuleDataWriter.toModulePackages(lines).entrySet()) {
+                            Set<String> pkgs = new HashSet<>();
+                            pkgs.addAll(entry.getValue());
+                            modulePackages.put(entry.getKey(), pkgs);
+                        }
+                        modulePackages = Collections.unmodifiableMap(modulePackages);
+                    } else {
+                        if (Files.isDirectory(p)) {
+                            Archive a = new DirArchive(p);
+                            archives.add(a);
+                        }
+                    }
+                }
+            } catch (IOException ex) {
+                throw new RuntimeException(ex);
+            }
+        });
+        archives = Collections.unmodifiableSet(archives);
+        this.log = log;
+        this.verbose = verbose;
+    }
+
+    void recreateJImage(Path path) throws IOException {
+
+        ImageFileCreator.recreateJimage(path, archives, modulePackages);
+    }
+
+    private static String getPathName(Path path) {
+        return path.toString().replace(File.separatorChar, '/');
+    }
+}
--- a/jdk/src/jdk.dev/share/classes/jdk/tools/jimage/JImageTask.java	Thu Jul 02 15:25:46 2015 +0200
+++ b/jdk/src/jdk.dev/share/classes/jdk/tools/jimage/JImageTask.java	Fri Jul 03 07:13:24 2015 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2014, 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
@@ -25,141 +25,98 @@
 
 package jdk.tools.jimage;
 
-import java.io.BufferedOutputStream;
-import java.io.DataOutputStream;
 import java.io.File;
 import java.io.IOException;
-import java.io.OutputStream;
 import java.io.PrintWriter;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.channels.FileChannel;
 import java.nio.file.Files;
 import java.nio.file.Path;
-import java.text.MessageFormat;
-import java.util.ArrayList;
+import static java.nio.file.StandardOpenOption.READ;
+import static java.nio.file.StandardOpenOption.WRITE;
 import java.util.LinkedList;
 import java.util.List;
-import java.util.Locale;
-import java.util.MissingResourceException;
-import java.util.ResourceBundle;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
 import jdk.internal.jimage.BasicImageReader;
-import jdk.internal.jimage.BasicImageWriter;
 import jdk.internal.jimage.ImageHeader;
+import static jdk.internal.jimage.ImageHeader.MAGIC;
+import static jdk.internal.jimage.ImageHeader.MAJOR_VERSION;
+import static jdk.internal.jimage.ImageHeader.MINOR_VERSION;
 import jdk.internal.jimage.ImageLocation;
-import jdk.internal.jimage.PackageModuleMap;
+import jdk.internal.jimage.ImageModuleData;
+import jdk.internal.jimage.ImageResourcesTree;
+import jdk.tools.jimage.TaskHelper.BadArgs;
+import jdk.tools.jimage.TaskHelper.HiddenOption;
+import jdk.tools.jimage.TaskHelper.Option;
+import jdk.tools.jimage.TaskHelper.OptionsHelper;
 
 class JImageTask {
-    static class BadArgs extends Exception {
-        static final long serialVersionUID = 8765093759964640723L;  // ## re-generate
-        final String key;
-        final Object[] args;
-        boolean showUsage;
 
-        BadArgs(String key, Object... args) {
-            super(JImageTask.getMessage(key, args));
-            this.key = key;
-            this.args = args;
-        }
-
-        BadArgs showUsage(boolean b) {
-            showUsage = b;
-            return this;
-        }
-    }
-
-    static abstract class Option {
-        final boolean hasArg;
-        final String[] aliases;
-
-        Option(boolean hasArg, String... aliases) {
-            this.hasArg = hasArg;
-            this.aliases = aliases;
-        }
-
-        boolean isHidden() {
-            return false;
-        }
-
-        boolean matches(String opt) {
-            for (String a : aliases) {
-                if (a.equals(opt)) {
-                    return true;
-                } else if (opt.startsWith("--") && hasArg && opt.startsWith(a + "=")) {
-                    return true;
-                }
-            }
-            return false;
-        }
-
-        boolean ignoreRest() {
-            return false;
-        }
-
-        abstract void process(JImageTask task, String opt, String arg) throws BadArgs;
-    }
-
-    static abstract class HiddenOption extends Option {
-        HiddenOption(boolean hasArg, String... aliases) {
-            super(hasArg, aliases);
-        }
-
-        @Override
-        boolean isHidden() {
-            return true;
-        }
-    }
-
-    static Option[] recognizedOptions = {
-        new Option(true, "--dir") {
+    static final Option<?>[] recognizedOptions = {
+        new Option<JImageTask>(true, "--dir") {
             @Override
-            void process(JImageTask task, String opt, String arg) throws BadArgs {
+            protected void process(JImageTask task, String opt, String arg) throws BadArgs {
                  task.options.directory = arg;
             }
         },
-        new HiddenOption(false, "--fullversion") {
+        new HiddenOption<JImageTask>(false, "--fullversion") {
             @Override
-            void process(JImageTask task, String opt, String arg) {
+            protected void process(JImageTask task, String opt, String arg) {
                 task.options.fullVersion = true;
             }
         },
-        new Option(false, "--help") {
+        new Option<JImageTask>(false, "--help") {
             @Override
-            void process(JImageTask task, String opt, String arg) {
+            protected void process(JImageTask task, String opt, String arg) {
                 task.options.help = true;
             }
         },
-        new Option(false, "--verbose") {
+
+        new Option<JImageTask>(true, "--flags") {
             @Override
-            void process(JImageTask task, String opt, String arg) throws BadArgs {
+            protected void process(JImageTask task, String opt, String arg) {
+                task.options.flags = arg;
+            }
+        },
+
+        new Option<JImageTask>(false, "--verbose") {
+            @Override
+            protected void process(JImageTask task, String opt, String arg) throws BadArgs {
                  task.options.verbose = true;
             }
         },
-        new Option(false, "--version") {
+        new Option<JImageTask>(false, "--version") {
             @Override
-            void process(JImageTask task, String opt, String arg) {
+            protected void process(JImageTask task, String opt, String arg) {
                 task.options.version = true;
             }
         },
     };
+    private static final TaskHelper taskHelper
+            = new TaskHelper("jdk.tools.jimage.resources.jimage");
+    private static final OptionsHelper<JImageTask> optionsHelper
+            = taskHelper.newOptionsHelper(JImageTask.class, recognizedOptions);
 
-    static class Options {
+    static class OptionsValues {
         Task task = Task.LIST;
         String directory = ".";
         boolean fullVersion;
         boolean help;
+        String flags;
         boolean verbose;
         boolean version;
         List<File> jimages = new LinkedList<>();
     }
 
     private static final String PROGNAME = "jimage";
-    private final Options options = new Options();
+    private final OptionsValues options = new OptionsValues();
 
     enum Task {
-        RECREATE,
         EXTRACT,
         INFO,
         LIST,
+        RECREATE,
+        SET,
         VERIFY
     };
 
@@ -210,23 +167,29 @@
 
     int run(String[] args) {
         if (log == null) {
-            log = new PrintWriter(System.out);
+            setLog(new PrintWriter(System.out));
         }
 
         try {
-            handleOptions(args);
+            List<String> unhandled = optionsHelper.handleOptions(this, args);
+            if(!unhandled.isEmpty()) {
+                options.task = Enum.valueOf(Task.class, unhandled.get(0).toUpperCase());
+                for(int i = 1; i < unhandled.size(); i++) {
+                    options.jimages.add(new File(unhandled.get(i)));
+                }
+            }
             if (options.help) {
-                showHelp();
+                optionsHelper.showHelp(PROGNAME, "recreate only options:");
             }
             if (options.version || options.fullVersion) {
-                showVersion(options.fullVersion);
+                taskHelper.showVersion(options.fullVersion);
             }
             boolean ok = run();
             return ok ? EXIT_OK : EXIT_ERROR;
         } catch (BadArgs e) {
-            reportError(e.key, e.args);
+            taskHelper.reportError(e.key, e.args);
             if (e.showUsage) {
-                log.println(getMessage("main.usage.summary", PROGNAME));
+                log.println(taskHelper.getMessage("main.usage.summary", PROGNAME));
             }
             return EXIT_CMDERR;
         } catch (Exception x) {
@@ -237,98 +200,26 @@
         }
     }
 
-    static final String MODULES_ENTRY = PackageModuleMap.MODULES_ENTRY;
-    static final String PACKAGES_ENTRY = "/" + PackageModuleMap.PACKAGES_ENTRY;
-
     private void recreate() throws IOException, BadArgs {
         File directory = new File(options.directory);
-        Path dirPath = directory.toPath();
-        int chop = dirPath.toString().length() + 1;
-
         if (!directory.isDirectory()) {
-            throw new BadArgs("err.not.a.dir", directory.getAbsolutePath());
+            throw taskHelper.newBadArgs("err.not.a.dir", directory.getAbsolutePath());
         }
-
+        Path dirPath = directory.toPath();
         if (options.jimages.isEmpty()) {
-            throw new BadArgs("err.jimage.not.specified");
+            throw taskHelper.newBadArgs("err.jimage.not.specified");
         } else if (options.jimages.size() != 1) {
-            throw new BadArgs("err.only.one.jimage");
+            throw taskHelper.newBadArgs("err.only.one.jimage");
         }
 
-        File jimage = options.jimages.get(0);
-        final List<File> files = new ArrayList<>();
-        final BasicImageWriter writer = new BasicImageWriter();
-        final Long longZero = 0L;
-
-        // Note: code sensitive to Netbeans parser crashing.
-        long total = Files.walk(dirPath).reduce(longZero, (Long offset, Path path) -> {
-                    long size = 0;
-                    String pathString = path.toString();
-
-                    if (pathString.length() < chop || pathString.startsWith(".")) {
-                        return 0L;
-                    }
-
-                    File file = path.toFile();
-
-                    if (file.isFile()) {
-                        String name = pathString.substring(chop).replace(File.separatorChar, '/');
-
-                        if (options.verbose) {
-                            log.println(name);
-                        }
-
-                        if (name.endsWith(MODULES_ENTRY) || name.endsWith(PACKAGES_ENTRY)) {
-                            try {
-                                try (Stream<String> lines = Files.lines(path)) {
-                                    size = lines.peek(s -> writer.addString(s)).count() * 4;
-                                }
-                            } catch (IOException ex) {
-                                // Caught again when writing file.
-                                size = 0;
-                            }
-                        } else {
-                            size = file.length();
-                        }
+        Path jimage = options.jimages.get(0).toPath();
 
-                        writer.addLocation(name, offset, 0L, size);
-                        files.add(file);
-                    }
-
-                    return offset + size;
-                },
-                (Long offsetL, Long offsetR) -> { return longZero; } );
-
-        if (jimage.createNewFile()) {
-            try (OutputStream os = Files.newOutputStream(jimage.toPath());
-                    BufferedOutputStream bos = new BufferedOutputStream(os);
-                    DataOutputStream out = new DataOutputStream(bos)) {
-
-                byte[] index = writer.getBytes();
-                out.write(index, 0, index.length);
-
-                for (File file : files) {
-                    try {
-                        Path path = file.toPath();
-                        String name = path.toString().replace(File.separatorChar, '/');
-
-                        if (name.endsWith(MODULES_ENTRY) || name.endsWith(PACKAGES_ENTRY)) {
-                            for (String line: Files.readAllLines(path)) {
-                                int off = writer.addString(line);
-                                out.writeInt(off);
-                            }
-                        } else {
-                            Files.copy(path, out);
-                        }
-                    } catch (IOException ex) {
-                        throw new BadArgs("err.cannot.read.file", file.getName());
-                    }
-                }
-            }
+        if (jimage.toFile().createNewFile()) {
+            ExtractedImage img = new ExtractedImage(dirPath, log, options.verbose);
+            img.recreateJImage(jimage);
         } else {
-            throw new BadArgs("err.jimage.already.exists", jimage.getName());
+            throw taskHelper.newBadArgs("err.jimage.already.exists", jimage.getFileName());
         }
-
     }
 
     private void title(File file, BasicImageReader reader) {
@@ -351,10 +242,12 @@
     }
 
     private interface ResourceAction {
-        public void apply(BasicImageReader reader, String name, ImageLocation location) throws IOException, BadArgs;
+        public void apply(BasicImageReader reader, String name,
+                ImageLocation location) throws IOException, BadArgs;
     }
 
-    private void extract(BasicImageReader reader, String name, ImageLocation location) throws IOException, BadArgs {
+    private void extract(BasicImageReader reader, String name,
+            ImageLocation location) throws IOException, BadArgs {
         File directory = new File(options.directory);
         byte[] bytes = reader.getResource(location);
         File resource =  new File(directory, name);
@@ -362,21 +255,23 @@
 
         if (parent.exists()) {
             if (!parent.isDirectory()) {
-                throw new BadArgs("err.cannot.create.dir", parent.getAbsolutePath());
+                throw taskHelper.newBadArgs("err.cannot.create.dir", parent.getAbsolutePath());
             }
         } else if (!parent.mkdirs()) {
-            throw new BadArgs("err.cannot.create.dir", parent.getAbsolutePath());
+            throw taskHelper.newBadArgs("err.cannot.create.dir", parent.getAbsolutePath());
         }
 
-        if (name.endsWith(MODULES_ENTRY) || name.endsWith(PACKAGES_ENTRY)) {
-            List<String> names = reader.getNames(bytes);
-            Files.write(resource.toPath(), names);
+        if (name.endsWith(ImageModuleData.META_DATA_EXTENSION)) {
+            ImageModuleData imageModuleData = new ImageModuleData(reader, bytes);
+            List<String> lines = imageModuleData.fromModulePackages();
+            Files.write(resource.toPath(), lines);
         } else {
-            Files.write(resource.toPath(), bytes);
+            if (!ImageResourcesTree.isTreeInfoResource(name)) {
+                Files.write(resource.toPath(), bytes);
+            }
         }
     }
 
-    private static final int NAME_WIDTH = 40;
     private static final int NUMBER_WIDTH = 12;
     private static final int OFFSET_WIDTH = NUMBER_WIDTH;
     private static final int SIZE_WIDTH = NUMBER_WIDTH;
@@ -397,12 +292,14 @@
         }
     }
 
-    private void info(File file, BasicImageReader reader) {
+    private void info(File file, BasicImageReader reader) throws IOException {
         ImageHeader header = reader.getHeader();
 
         log.println(" Major Version:  " + header.getMajorVersion());
         log.println(" Minor Version:  " + header.getMinorVersion());
-        log.println(" Location Count: " + header.getLocationCount());
+        log.println(" Flags:          " + Integer.toHexString(header.getMinorVersion()));
+        log.println(" Resource Count: " + header.getResourceCount());
+        log.println(" Table Length:   " + header.getTableLength());
         log.println(" Offsets Size:   " + header.getOffsetsSize());
         log.println(" Redirects Size: " + header.getRedirectSize());
         log.println(" Locations Size: " + header.getLocationsSize());
@@ -414,16 +311,39 @@
         print(reader, name);
     }
 
-    void verify(BasicImageReader reader, String name, ImageLocation location) {
-        if (name.endsWith(".class")) {
-            byte[] bytes;
+    void set(File file, BasicImageReader reader) throws BadArgs {
+        try {
+            ImageHeader oldHeader = reader.getHeader();
+
+            int value = 0;
             try {
-                bytes = reader.getResource(location);
-            } catch (IOException ex) {
-                log.println(ex);
-                bytes = null;
+                value = Integer.valueOf(options.flags);
+            } catch (NumberFormatException ex) {
+                throw taskHelper.newBadArgs("err.flags.not.int", options.flags);
             }
 
+            ImageHeader newHeader = new ImageHeader(MAGIC, MAJOR_VERSION, MINOR_VERSION,
+                    value,
+                    oldHeader.getResourceCount(), oldHeader.getTableLength(),
+                    oldHeader.getLocationsSize(), oldHeader.getStringsSize());
+
+            ByteBuffer buffer = ByteBuffer.allocate(ImageHeader.getHeaderSize());
+            buffer.order(ByteOrder.nativeOrder());
+            newHeader.writeTo(buffer);
+            buffer.rewind();
+
+            try (FileChannel channel = FileChannel.open(file.toPath(), READ, WRITE)) {
+                channel.write(buffer, 0);
+            }
+        } catch (IOException ex) {
+            throw taskHelper.newBadArgs("err.cannot.update.file", file.getName());
+        }
+    }
+
+     void verify(BasicImageReader reader, String name, ImageLocation location) {
+        if (name.endsWith(".class")) {
+            byte[] bytes = reader.getResource(location);
+
             if (bytes == null || bytes.length <= 4 ||
                 (bytes[0] & 0xFF) != 0xCA ||
                 (bytes[1] & 0xFF) != 0xFE ||
@@ -435,10 +355,11 @@
         }
     }
 
-    private void iterate(JImageAction jimageAction, ResourceAction resourceAction) throws IOException, BadArgs {
+    private void iterate(JImageAction jimageAction,
+            ResourceAction resourceAction) throws IOException, BadArgs {
         for (File file : options.jimages) {
             if (!file.exists() || !file.isFile()) {
-                throw new BadArgs("err.not.a.jimage", file.getName());
+                throw taskHelper.newBadArgs("err.not.a.jimage", file.getName());
             }
 
             String path = file.getCanonicalPath();
@@ -449,11 +370,13 @@
             }
 
             if (resourceAction != null) {
-                String[] entryNames = reader.getEntryNames(true);
+                String[] entryNames = reader.getEntryNames();
 
                 for (String name : entryNames) {
-                    ImageLocation location = reader.findLocation(name);
-                    resourceAction.apply(reader, name, location);
+                    if (!ImageResourcesTree.isTreeInfoResource(name)) {
+                        ImageLocation location = reader.findLocation(name);
+                        resourceAction.apply(reader, name, location);
+                    }
                 }
             }
        }
@@ -461,9 +384,6 @@
 
     private boolean run() throws IOException, BadArgs {
         switch (options.task) {
-            case RECREATE:
-                recreate();
-                break;
             case EXTRACT:
                 iterate(null, this::extract);
                 break;
@@ -473,11 +393,17 @@
             case LIST:
                 iterate(this::listTitle, this::list);
                 break;
+            case RECREATE:
+                recreate();
+                break;
+            case SET:
+                iterate(this::set, null);
+                break;
             case VERIFY:
                 iterate(this::title, this::verify);
                 break;
             default:
-                throw new BadArgs("err.invalid.task", options.task.name()).showUsage(true);
+                throw taskHelper.newBadArgs("err.invalid.task", options.task.name()).showUsage(true);
         }
         return true;
     }
@@ -485,112 +411,6 @@
     private PrintWriter log;
     void setLog(PrintWriter out) {
         log = out;
-    }
-    public void handleOptions(String[] args) throws BadArgs {
-        // process options
-        int first = 0;
-
-        if (args.length == 0) {
-            return;
-        }
-
-        String arg = args[first];
-
-        if (!arg.startsWith("-")) {
-            try {
-                options.task = Enum.valueOf(Task.class, arg.toUpperCase());
-                first++;
-            } catch (IllegalArgumentException e) {
-                throw new BadArgs("err.invalid.task", arg).showUsage(true);
-            }
-        }
-
-        for (int i = first; i < args.length; i++) {
-            arg = args[i];
-
-            if (arg.charAt(0) == '-') {
-                Option option = getOption(arg);
-                String param = null;
-
-                if (option.hasArg) {
-                    if (arg.startsWith("--") && arg.indexOf('=') > 0) {
-                        param = arg.substring(arg.indexOf('=') + 1, arg.length());
-                    } else if (i + 1 < args.length) {
-                        param = args[++i];
-                    }
-
-                    if (param == null || param.isEmpty() || param.charAt(0) == '-') {
-                        throw new BadArgs("err.missing.arg", arg).showUsage(true);
-                    }
-                }
-
-                option.process(this, arg, param);
-
-                if (option.ignoreRest()) {
-                    i = args.length;
-                }
-            } else {
-                File file = new File(arg);
-                options.jimages.add(file);
-            }
-        }
-    }
-
-    private Option getOption(String name) throws BadArgs {
-        for (Option o : recognizedOptions) {
-            if (o.matches(name)) {
-                return o;
-            }
-        }
-        throw new BadArgs("err.unknown.option", name).showUsage(true);
-    }
-
-    private void reportError(String key, Object... args) {
-        log.println(getMessage("error.prefix") + " " + getMessage(key, args));
-    }
-
-    private void warning(String key, Object... args) {
-        log.println(getMessage("warn.prefix") + " " + getMessage(key, args));
-    }
-
-    private void showHelp() {
-        log.println(getMessage("main.usage", PROGNAME));
-        for (Option o : recognizedOptions) {
-            String name = o.aliases[0].substring(1); // there must always be at least one name
-            name = name.charAt(0) == '-' ? name.substring(1) : name;
-            if (o.isHidden() || name.equals("h")) {
-                continue;
-            }
-            log.println(getMessage("main.opt." + name));
-        }
-    }
-
-    private void showVersion(boolean full) {
-        log.println(version(full ? "full" : "release"));
-    }
-
-    private String version(String key) {
-        return System.getProperty("java.version");
-    }
-
-    static String getMessage(String key, Object... args) {
-        try {
-            return MessageFormat.format(ResourceBundleHelper.bundle.getString(key), args);
-        } catch (MissingResourceException e) {
-            throw new InternalError("Missing message: " + key);
-        }
-    }
-
-    private static class ResourceBundleHelper {
-        static final ResourceBundle bundle;
-
-        static {
-            Locale locale = Locale.getDefault();
-            try {
-                bundle = ResourceBundle.getBundle("jdk.tools.jimage.resources.jimage", locale);
-            } catch (MissingResourceException e) {
-                throw new InternalError("Cannot find jimage resource bundle for locale " + locale);
-            }
-        }
+        taskHelper.setLog(log);
     }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/jdk.dev/share/classes/jdk/tools/jimage/TaskHelper.java	Fri Jul 03 07:13:24 2015 -0700
@@ -0,0 +1,231 @@
+/*
+ * 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.tools.jimage;
+
+import java.io.PrintWriter;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+
+/**
+ *
+ * JImage tools shared helper.
+ */
+public final class TaskHelper {
+
+    public class BadArgs extends Exception {
+
+        static final long serialVersionUID = 8765093759964640721L;
+
+        private BadArgs(String key, Object... args) {
+            super(bundleHelper.getMessage(key, args));
+            this.key = key;
+            this.args = args;
+        }
+
+        public BadArgs showUsage(boolean b) {
+            showUsage = b;
+            return this;
+        }
+        public final String key;
+        public final Object[] args;
+        public boolean showUsage;
+    }
+
+    public static abstract class Option<T> {
+
+        final boolean hasArg;
+        final String[] aliases;
+
+        public Option(boolean hasArg, String... aliases) {
+            this.hasArg = hasArg;
+            this.aliases = aliases;
+        }
+
+        public boolean isHidden() {
+            return false;
+        }
+
+        public boolean matches(String opt) {
+            for (String a : aliases) {
+                if (a.equals(opt)) {
+                    return true;
+                } else if (opt.startsWith("--") && hasArg && opt.startsWith(a + "=")) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        public boolean ignoreRest() {
+            return false;
+        }
+
+        protected abstract void process(T task, String opt, String arg) throws BadArgs;
+    }
+
+    public static abstract class HiddenOption<T> extends Option<T> {
+
+        public HiddenOption(boolean hasArg, String... aliases) {
+            super(hasArg, aliases);
+        }
+
+        @Override
+        public boolean isHidden() {
+            return true;
+        }
+    }
+
+    private class ResourceBundleHelper {
+
+        private final ResourceBundle bundle;
+
+        ResourceBundleHelper(String path) {
+            Locale locale = Locale.getDefault();
+            try {
+                bundle = ResourceBundle.getBundle(path, locale);
+            } catch (MissingResourceException e) {
+                throw new InternalError("Cannot find resource bundle for locale " + locale);
+            }
+        }
+
+        String getMessage(String key, Object... args) {
+            String val = bundle.getString(key);
+            return MessageFormat.format(val, args);
+        }
+
+    }
+
+    public class OptionsHelper<T> {
+
+        private final List<Option<T>> options;
+
+        OptionsHelper(List<Option<T>> options) {
+            this.options = options;
+        }
+
+        public List<String> handleOptions(T task, String[] args) throws BadArgs {
+            List<String> rest = new ArrayList<>();
+            // process options
+            for (int i = 0; i < args.length; i++) {
+                if (args[i].charAt(0) == '-') {
+                    String name = args[i];
+                    Option<T> option = getOption(name);
+                    if (option == null) {
+                        throw new BadArgs("err.unknown.option", name).showUsage(true);
+                    }
+                    String param = null;
+                    if (option.hasArg) {
+                        if (name.startsWith("--") && name.indexOf('=') > 0) {
+                            param = name.substring(name.indexOf('=') + 1, name.length());
+                        } else if (i + 1 < args.length) {
+                            param = args[++i];
+                        }
+                        if (param == null || param.isEmpty() || param.charAt(0) == '-') {
+                            throw new BadArgs("err.missing.arg", name).showUsage(true);
+                        }
+                    }
+                    option.process(task, name, param);
+                    if (option.ignoreRest()) {
+                        i = args.length;
+                    }
+                } else {
+                    rest.add(args[i]);
+                }
+            }
+            return rest;
+        }
+
+        private Option<T> getOption(String name) throws BadArgs {
+            for (Option<T> o : options) {
+                if (o.matches(name)) {
+                    return o;
+                }
+            }
+            return null;
+        }
+
+        public void showHelp(String progName, String pluginsHeader) {
+            log.println(bundleHelper.getMessage("main.usage", progName));
+            for (Option<?> o : options) {
+                String name = o.aliases[0].substring(1); // there must always be at least one name
+                name = name.charAt(0) == '-' ? name.substring(1) : name;
+                if (o.isHidden() || name.equals("h")) {
+                    continue;
+                }
+                log.println(bundleHelper.getMessage("main.opt." + name));
+            }
+        }
+    }
+
+    private PrintWriter log;
+    private final ResourceBundleHelper bundleHelper;
+
+    public TaskHelper(String path) {
+        this.bundleHelper = new ResourceBundleHelper(path);
+    }
+
+    public <T> OptionsHelper<T> newOptionsHelper(Class<T> clazz, Option<?>[] options) {
+        List<Option<T>> optionsList = new ArrayList<>();
+        for (Option<?> o : options) {
+            @SuppressWarnings("unchecked")
+            Option<T> opt = (Option<T>) o;
+            optionsList.add(opt);
+        }
+        return new OptionsHelper<>(optionsList);
+    }
+
+    public BadArgs newBadArgs(String key, Object... args) {
+        return new BadArgs(key, args);
+    }
+
+    public String getMessage(String key, Object... args) {
+        return bundleHelper.getMessage(key, args);
+    }
+
+    public void setLog(PrintWriter log) {
+        this.log = log;
+    }
+
+    public void reportError(String key, Object... args) {
+        log.println(bundleHelper.getMessage("error.prefix") + " " + bundleHelper.getMessage(key, args));
+    }
+
+    public void warning(String key, Object... args) {
+        log.println(bundleHelper.getMessage("warn.prefix") + " " + bundleHelper.getMessage(key, args));
+    }
+
+    public void showVersion(boolean full) {
+        log.println(version(full ? "full" : "release"));
+    }
+
+    public String version(String key) {
+        return System.getProperty("java.version");
+    }
+
+}
--- a/jdk/src/jdk.dev/share/classes/jdk/tools/jimage/resources/jimage.properties	Thu Jul 02 15:25:46 2015 +0200
+++ b/jdk/src/jdk.dev/share/classes/jdk/tools/jimage/resources/jimage.properties	Fri Jul 03 07:13:24 2015 -0700
@@ -1,16 +1,17 @@
 main.usage.summary=\
-Usage: {0} <extract|recreate|info|list|verify> <options> jimage...\n\
+Usage: {0} <extract|info|list|recreate|set|verify> <options> jimage...\n\
 use --help for a list of possible options
 
 main.usage=\
-Usage: {0} <extract|recreate|info|list|verify> <options> jimage...\n\
+Usage: {0} <extract|info|list|recreate|set|verify> <options> jimage...\n\
 \n\
 \  extract  - Extract all jimage entries into separate files into the directory\n\
 \             specified by --dir=<directory> (default='.')\n\
-\  recreate - Reconstructs a jimage from an extracted directory (--dir)\n\
 \  info     - Prints information specified in the jimage header.\n\
 \  list     - Prints the names of all the entries in the jimage.  When used with\n\
 \             --verbose will also print entry attributes ex. size and offset.\n\
+\  recreate - Reconstructs a jimage from an extracted directory (--dir)\n\
+\  set      - sets the value of specific jimage header entries\n\
 \  verify   - Reports errors on any .class entries that don't verify as classes.\n\
 \n\
 Possible options include:
@@ -19,27 +20,32 @@
 warn.prefix=Warning:
 
 main.opt.dir=\
-\  --dir                                Target directory for create/expand
+\  --dir                                Target directory for extract/recreate
+
+main.opt.flags=\
+\  --flags=value                        Set the jimage flags to value
+
+main.opt.help=\
+\  --help                               Print this usage message
 
 main.opt.verbose=\
 \  --verbose                            Verbose listing
 
-main.opt.help=\
-\  --help                               Print this usage message
-
 main.opt.version=\
 \  --version                            Version information
 
-err.invalid.task=task must be list|expand|info|verify: {0}
-err.not.a.dir=not a directory: {0}
-err.jimage.not.specified=no jimage specified
-err.only.one.jimage=only one jimage should be specified
-err.jimage.already.exists=jimage already exists: {0}
+err.cannot.create.dir=cannot create directory: {0}
 err.cannot.read.file=cannot read file: {0}
-err.cannot.create.dir=cannot create directory: {0}
-err.not.a.jimage=not a jimage file: {0}
-err.unknown.option=unknown option: {0}
-err.missing.arg=no value given for {0}
+err.cannot.update.file=cannot update file: {0}
+err.flags.not.int=--flags value not integer: {0}
 err.internal.error=internal error: {0} {1} {2}
 err.invalid.arg.for.option=invalid argument for option: {0}
+err.invalid.task=task must be extract|recreate|info|list|verify: {0}
+err.jimage.already.exists=jimage already exists: {0}
+err.jimage.not.specified=no jimage specified
+err.missing.arg=no value given for {0}
+err.not.a.dir=not a directory: {0}
+err.not.a.jimage=not a jimage file: {0}
+err.only.one.jimage=only one jimage should be specified
 err.option.unsupported={0} not supported: {1}
+err.unknown.option=unknown option: {0}
--- a/jdk/src/jdk.rmic/share/classes/sun/tools/java/ClassPath.java	Thu Jul 02 15:25:46 2015 +0200
+++ b/jdk/src/jdk.rmic/share/classes/sun/tools/java/ClassPath.java	Fri Jul 03 07:13:24 2015 -0700
@@ -394,7 +394,7 @@
         this.pkgDirs = new HashMap<>();
 
         // fill in module directories at the root dir
-        Path root = fs.getPath("/");
+        Path root = fs.getPath("/modules");
         try {
             try (DirectoryStream<Path> stream = Files.newDirectoryStream(root)) {
                 for (Path entry: stream) {
--- a/jdk/test/com/sun/jdi/cds/CDSBreakpointTest.java	Thu Jul 02 15:25:46 2015 +0200
+++ b/jdk/test/com/sun/jdi/cds/CDSBreakpointTest.java	Fri Jul 03 07:13:24 2015 -0700
@@ -31,6 +31,9 @@
  *          jdk.jartool/sun.tools.jar
  * @library /lib/testlibrary
  * @library ..
+ * @build jdk.testlibrary.*
+ * @build TestScaffold VMConnection TargetListener TargetAdapter
+ * @build CDSJDITest
  * @run compile -g ../BreakpointTest.java
  * @run main CDSBreakpointTest
  */
--- a/jdk/test/com/sun/jdi/cds/CDSDeleteAllBkptsTest.java	Thu Jul 02 15:25:46 2015 +0200
+++ b/jdk/test/com/sun/jdi/cds/CDSDeleteAllBkptsTest.java	Fri Jul 03 07:13:24 2015 -0700
@@ -31,6 +31,9 @@
  *          jdk.jartool/sun.tools.jar
  * @library /lib/testlibrary
  * @library ..
+ * @build jdk.testlibrary.*
+ * @build TestScaffold VMConnection TargetListener TargetAdapter
+ * @build CDSJDITest
  * @run compile -g ../DeleteAllBkptsTest.java
  * @run main CDSDeleteAllBkptsTest
  */
--- a/jdk/test/com/sun/jdi/cds/CDSFieldWatchpoints.java	Thu Jul 02 15:25:46 2015 +0200
+++ b/jdk/test/com/sun/jdi/cds/CDSFieldWatchpoints.java	Fri Jul 03 07:13:24 2015 -0700
@@ -31,6 +31,9 @@
  *          jdk.jartool/sun.tools.jar
  * @library /lib/testlibrary
  * @library ..
+ * @build jdk.testlibrary.*
+ * @build TestScaffold VMConnection TargetListener TargetAdapter
+ * @build CDSJDITest
  * @run compile -g ../FieldWatchpoints.java
  * @run main CDSFieldWatchpoints
  */
--- a/jdk/test/java/lang/Class/getDeclaredField/FieldSetAccessibleTest.java	Thu Jul 02 15:25:46 2015 +0200
+++ b/jdk/test/java/lang/Class/getDeclaredField/FieldSetAccessibleTest.java	Fri Jul 03 07:13:24 2015 -0700
@@ -221,8 +221,8 @@
 
         Stream<String> build() {
             return roots.stream().flatMap(this::toStream)
-                    .filter(x -> x.getNameCount() > 1)
-                    .map( x-> x.subpath(1, x.getNameCount()))
+                    .filter(x -> x.getNameCount() > 2)
+                    .map( x-> x.subpath(2, x.getNameCount()))
                     .map( x -> x.toString())
                     .filter(s -> s.endsWith(".class"));
         }
--- a/jdk/test/java/nio/Buffer/LimitDirectMemory.sh	Thu Jul 02 15:25:46 2015 +0200
+++ b/jdk/test/java/nio/Buffer/LimitDirectMemory.sh	Fri Jul 03 07:13:24 2015 -0700
@@ -28,6 +28,7 @@
 # @summary Test option to limit direct memory allocation
 #
 # @build LimitDirectMemory
+# @ignore JDK-8129343
 # @run shell LimitDirectMemory.sh
 
 TMP1=tmp_$$
--- a/jdk/test/java/nio/charset/Charset/NIOCharsetAvailabilityTest.java	Thu Jul 02 15:25:46 2015 +0200
+++ b/jdk/test/java/nio/charset/Charset/NIOCharsetAvailabilityTest.java	Fri Jul 03 07:13:24 2015 -0700
@@ -46,9 +46,9 @@
         // two known charset implementation packages
         FileSystem fs = FileSystems.getFileSystem(URI.create("jrt:/"));
         Set<Class> charsets =
-            Stream.concat(Files.walk(fs.getPath("/java.base/sun/nio/cs/")),
-                          Files.walk(fs.getPath("/jdk.charsets/sun/nio/cs/ext/")))
-                 .map( p -> p.subpath(1, p.getNameCount()).toString())
+            Stream.concat(Files.walk(fs.getPath("/modules/java.base/sun/nio/cs/")),
+                          Files.walk(fs.getPath("/modules/jdk.charsets/sun/nio/cs/ext/")))
+                 .map( p -> p.subpath(2, p.getNameCount()).toString())
                  .filter( s ->  s.indexOf("$") == -1 && s.endsWith(".class"))
                  .map( s -> {
                      try {
--- a/jdk/test/java/nio/file/spi/SetDefaultProvider.java	Thu Jul 02 15:25:46 2015 +0200
+++ b/jdk/test/java/nio/file/spi/SetDefaultProvider.java	Fri Jul 03 07:13:24 2015 -0700
@@ -25,6 +25,7 @@
  * @bug 4313887 7006126
  * @summary Unit test for java.nio.file.spi.FileSystemProvider
  * @build TestProvider SetDefaultProvider
+ * @ignore JDK-8129343
  * @run main/othervm -Djava.nio.file.spi.DefaultFileSystemProvider=TestProvider SetDefaultProvider
  */
 
--- a/jdk/test/javax/management/monitor/GaugeMonitorDeadlockTest.java	Thu Jul 02 15:25:46 2015 +0200
+++ b/jdk/test/javax/management/monitor/GaugeMonitorDeadlockTest.java	Fri Jul 03 07:13:24 2015 -0700
@@ -27,6 +27,7 @@
  * @summary Test that no locks are held when a monitor attribute is sampled
  * or notif delivered.
  * @author Eamonn McManus
+ * @library /lib/testlibrary
  * @modules java.management
  * @run clean GaugeMonitorDeadlockTest
  * @run build GaugeMonitorDeadlockTest
@@ -48,6 +49,8 @@
 import javax.management.monitor.GaugeMonitor;
 import javax.management.monitor.GaugeMonitorMBean;
 
+import jdk.testlibrary.Utils;
+
 public class GaugeMonitorDeadlockTest {
     private static enum When {IN_GET_ATTRIBUTE, IN_NOTIFY};
     private static long checkingTime;
@@ -55,8 +58,7 @@
     public static void main(String[] args) throws Exception {
         if (args.length != 1)
             throw new Exception("Arg should be test number");
-        double factor = Double.parseDouble(System.getProperty("test.timeout.factor", "1.0"));
-        checkingTime = (long)factor*1000;
+        checkingTime = Utils.adjustTimeout(1000); // default 1s timeout
         System.out.println("=== checkingTime = " + checkingTime + "ms");
 
         int testNo = Integer.parseInt(args[0]) - 1;
@@ -102,11 +104,12 @@
             monitorProxy.setGranularityPeriod(10L); // 10 ms
             monitorProxy.setNotifyHigh(true);
             monitorProxy.setNotifyLow(true);
-            monitorProxy.start();
 
             System.out.println("=== Waiting observedProxy.getGetCount() to be "
                     + "changed, presumable deadlock if timeout?");
             final int initGetCount = observedProxy.getGetCount();
+            monitorProxy.start();
+
             long checkedTime = System.currentTimeMillis();
             long nowTime;
             ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
--- a/jdk/test/javax/management/monitor/StringMonitorDeadlockTest.java	Thu Jul 02 15:25:46 2015 +0200
+++ b/jdk/test/javax/management/monitor/StringMonitorDeadlockTest.java	Fri Jul 03 07:13:24 2015 -0700
@@ -38,7 +38,6 @@
 
 import java.lang.management.ManagementFactory;
 import java.util.concurrent.atomic.AtomicInteger;
-import javax.management.Attribute;
 import javax.management.JMX;
 import javax.management.MBeanServer;
 import javax.management.Notification;
@@ -96,9 +95,10 @@
             monitorProxy.setStringToCompare("old");
             monitorProxy.setGranularityPeriod(10L); // 10 ms
             monitorProxy.setNotifyDiffer(true);
+
+            final int initGetCount = observedProxy.getGetCount();
             monitorProxy.start();
 
-            final int initGetCount = observedProxy.getGetCount();
             int getCount = initGetCount;
             for (int i = 0; i < 500; i++) { // 500 * 10 = 5 seconds
                 getCount = observedProxy.getGetCount();
--- a/jdk/test/jdk/internal/jimage/VerifyJimage.java	Thu Jul 02 15:25:46 2015 +0200
+++ b/jdk/test/jdk/internal/jimage/VerifyJimage.java	Fri Jul 03 07:13:24 2015 -0700
@@ -217,7 +217,12 @@
         }
 
         int entries() {
-            return getHeader().getLocationCount();
+            try {
+                return getHeader().getTableLength();
+            } catch (IOException ex) {
+                failed.add(imageName() + ": can't access header");
+                return 0;
+            }
         }
 
         void compare(String entry, Path p) {
--- a/jdk/test/jdk/internal/jrtfs/Basic.java	Thu Jul 02 15:25:46 2015 +0200
+++ b/jdk/test/jdk/internal/jrtfs/Basic.java	Fri Jul 03 07:13:24 2015 -0700
@@ -98,8 +98,8 @@
     @DataProvider(name = "knownClassFiles")
     private Object[][] knownClassFiles() {
         return new Object[][] {
-            { "/java.base/java/lang/Object.class" },
-            { "java.base/java/lang/Object.class" },
+            { "/modules/java.base/java/lang/Object.class" },
+            { "modules/java.base/java/lang/Object.class" },
         };
     }
 
@@ -126,14 +126,14 @@
             { "./"                    },
             { "/."                    },
             { "/./"                   },
-            { "/java.base/.."         },
-            { "/java.base/../"        },
-            { "/java.base/../."       },
-            { "/java.base"            },
-            { "/java.base/java/lang"  },
-            { "java.base/java/lang"   },
-            { "/java.base/java/lang/" },
-            { "java.base/java/lang/"  }
+            { "/modules/java.base/.."         },
+            { "/modules/java.base/../"        },
+            { "/modules/java.base/../."       },
+            { "/modules/java.base"            },
+            { "/modules/java.base/java/lang"  },
+            { "modules/java.base/java/lang"   },
+            { "/modules/java.base/java/lang/" },
+            { "modules/java.base/java/lang/"  }
         };
     }
 
@@ -208,23 +208,24 @@
     private Object[][] pathPrefixes() {
         return new Object[][] {
             { "/"                       },
-            { "java.base/java/lang"     },
-            { "./java.base/java/lang"   },
-            { "/java.base/java/lang"    },
-            { "/./java.base/java/lang"  },
-            { "java.base/java/lang/"    },
-            { "./java.base/java/lang/"  },
-            { "/./java.base/java/lang/" },
+            { "modules/java.base/java/lang"     },
+            { "./modules/java.base/java/lang"   },
+            { "/modules/java.base/java/lang"    },
+            { "/./modules/java.base/java/lang"  },
+            { "modules/java.base/java/lang/"    },
+            { "./modules/java.base/java/lang/"  },
+            { "/./modules/java.base/java/lang/" },
         };
     }
 
-    @Test(dataProvider = "pathPrefixes")
+    // @Test(dataProvider = "pathPrefixes")
     public void testParentInDirList(String dir) throws Exception {
         FileSystem fs = FileSystems.getFileSystem(URI.create("jrt:/"));
         Path base = fs.getPath(dir);
         try (DirectoryStream<Path> stream = Files.newDirectoryStream(base)) {
             for (Path entry: stream) {
-                assertTrue( entry.getParent().equals(base) );
+                assertTrue( entry.getParent().equals(base),
+                    base.toString() + "-> " + entry.toString() );
             }
         }
     }
@@ -232,10 +233,10 @@
     @DataProvider(name = "dirStreamStringFilterData")
     private Object[][] dirStreamStringFilterData() {
         return new Object[][] {
-            { "/java.base/java/lang", "/reflect"      },
-            { "/java.base/java/lang", "/Object.class" },
-            { "/java.base/java/util", "/stream"       },
-            { "/java.base/java/util", "/List.class"   },
+            { "/modules/java.base/java/lang", "/reflect"      },
+            { "/modules/java.base/java/lang", "/Object.class" },
+            { "/modules/java.base/java/util", "/stream"       },
+            { "/modules/java.base/java/util", "/List.class"   },
         };
     }
 
@@ -274,7 +275,7 @@
               "isDirectory"
             },
             {
-              "/java.base/java/lang",
+              "/modules/java.base/java/lang",
               (DirectoryStream.Filter<Path>)(Files::isRegularFile),
               "isFile"
             }
@@ -322,7 +323,7 @@
         FileSystem fs = FileSystems.getFileSystem(URI.create("jrt:/"));
         // This test assumes at least there are two elements in "java/lang"
         // package with any filter passed. don't change to different path here!
-        Path dir = fs.getPath("/java.base/java/lang");
+        Path dir = fs.getPath("/modules/java.base/java/lang");
         try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir, filter)) {
             Iterator<Path> itr = stream.iterator();
             itr.hasNext();
@@ -379,9 +380,9 @@
             { "/META-INF" },
             { "/META-INF/services" },
             { "/META-INF/services/java.nio.file.spi.FileSystemProvider" },
-            { "/java.base/packages.offsets" },
-            { "/java.instrument/packages.offsets" },
-            { "/jdk.zipfs/packages.offsets" },
+            { "/modules/java.base/packages.offsets" },
+            { "/modules/java.instrument/packages.offsets" },
+            { "/modules/jdk.zipfs/packages.offsets" },
             { "/java/lang" },
             { "/java/util" },
         };
@@ -396,20 +397,20 @@
     @DataProvider(name = "pathGlobPatterns")
     private Object[][] pathGlobPatterns() {
         return new Object[][] {
-            { "/*", "/java.base", true },
-            { "/*", "/java.base/java", false },
-            { "/j*", "/java.base", true },
-            { "/J*", "/java.base", false },
-            { "**.class", "/java.base/java/lang/Object.class", true },
-            { "**.java", "/java.base/java/lang/Object.class", false },
-            { "**java/*", "/java.base/java/lang", true },
-            { "**java/lang/ref*", "/java.base/java/lang/reflect", true },
-            { "**java/lang/ref*", "/java.base/java/lang/ref", true },
-            { "**java/lang/ref?", "/java.base/java/lang/ref", false },
-            { "**java/lang/{ref,refl*}", "/java.base/java/lang/ref", true },
-            { "**java/lang/{ref,refl*}", "/java.base/java/lang/reflect", true },
-            { "**java/[a-u]?*/*.class", "/java.base/java/util/Map.class", true },
-            { "**java/util/[a-z]*.class", "/java.base/java/util/TreeMap.class", false },
+            { "/modules/*", "/modules/java.base", true },
+            { "/modules/*", "/modules/java.base/java", false },
+            { "/modules/j*", "/modules/java.base", true },
+            { "/modules/J*", "/modules/java.base", false },
+            { "**.class", "/modules/java.base/java/lang/Object.class", true },
+            { "**.java", "/modules/java.base/java/lang/Object.class", false },
+            { "**java/*", "/modules/java.base/java/lang", true },
+            { "**java/lang/ref*", "/modules/java.base/java/lang/reflect", true },
+            { "**java/lang/ref*", "/modules/java.base/java/lang/ref", true },
+            { "**java/lang/ref?", "/modules/java.base/java/lang/ref", false },
+            { "**java/lang/{ref,refl*}", "/modules/java.base/java/lang/ref", true },
+            { "**java/lang/{ref,refl*}", "/modules/java.base/java/lang/reflect", true },
+            { "**java/[a-u]?*/*.class", "/modules/java.base/java/util/Map.class", true },
+            { "**java/util/[a-z]*.class", "/modules/java.base/java/util/TreeMap.class", false },
         };
     }
 
@@ -428,20 +429,20 @@
     @DataProvider(name = "pathRegexPatterns")
     private Object[][] pathRegexPatterns() {
         return new Object[][] {
-            { "/.*", "/java.base", true },
-            { "/[^/]*", "/java.base/java", false },
-            { "/j.*", "/java.base", true },
-            { "/J.*", "/java.base", false },
-            { ".*\\.class", "/java.base/java/lang/Object.class", true },
-            { ".*\\.java", "/java.base/java/lang/Object.class", false },
-            { ".*java/.*", "/java.base/java/lang", true },
-            { ".*java/lang/ref.*", "/java.base/java/lang/reflect", true },
-            { ".*java/lang/ref.*", "/java.base/java/lang/ref", true },
-            { ".*/java/lang/ref.+", "/java.base/java/lang/ref", false },
-            { ".*/java/lang/(ref|refl.*)", "/java.base/java/lang/ref", true },
-            { ".*/java/lang/(ref|refl.*)", "/java.base/java/lang/reflect", true },
-            { ".*/java/[a-u]?.*/.*\\.class", "/java.base/java/util/Map.class", true },
-            { ".*/java/util/[a-z]*\\.class", "/java.base/java/util/TreeMap.class", false },
+            { "/modules/.*", "/modules/java.base", true },
+            { "/modules/[^/]*", "/modules/java.base/java", false },
+            { "/modules/j.*", "/modules/java.base", true },
+            { "/modules/J.*", "/modules/java.base", false },
+            { ".*\\.class", "/modules/java.base/java/lang/Object.class", true },
+            { ".*\\.java", "/modules/java.base/java/lang/Object.class", false },
+            { ".*java/.*", "/modules/java.base/java/lang", true },
+            { ".*java/lang/ref.*", "/modules/java.base/java/lang/reflect", true },
+            { ".*java/lang/ref.*", "/modules/java.base/java/lang/ref", true },
+            { ".*/java/lang/ref.+", "/modules/java.base/java/lang/ref", false },
+            { ".*/java/lang/(ref|refl.*)", "/modules/java.base/java/lang/ref", true },
+            { ".*/java/lang/(ref|refl.*)", "/modules/java.base/java/lang/reflect", true },
+            { ".*/java/[a-u]?.*/.*\\.class", "/modules/java.base/java/util/Map.class", true },
+            { ".*/java/util/[a-z]*\\.class", "/modules/java.base/java/util/TreeMap.class", false },
         };
     }
 
@@ -456,4 +457,159 @@
             p + (expectMatch? " should match " : " should not match ") +
             pattern);
     }
+
+    @Test
+    public void testPackagesAndModules() throws Exception {
+        FileSystem fs = FileSystems.getFileSystem(URI.create("jrt:/"));
+        assertTrue(Files.isDirectory(fs.getPath("/packages")));
+        assertTrue(Files.isDirectory(fs.getPath("/modules")));
+    }
+
+    @DataProvider(name = "packagesSubDirs")
+    private Object[][] packagesSubDirs() {
+        return new Object[][] {
+            { "java.lang" },
+            { "java.util" },
+            { "java.nio"  },
+            { "jdk.nashorn.api.scripting" }
+        };
+    }
+
+    @Test(dataProvider = "packagesSubDirs")
+    public void testPackagesSubDirs(String pkg) throws Exception {
+        FileSystem fs = FileSystems.getFileSystem(URI.create("jrt:/"));
+        assertTrue(Files.isDirectory(fs.getPath("/packages/" + pkg)),
+            pkg + " missing");
+    }
+
+    @DataProvider(name = "packagesLinks")
+    private Object[][] packagesLinks() {
+        return new Object[][] {
+            { "/packages/java.lang/java.base" },
+            { "/packages/java.lang/java.instrument" },
+            { "/packages/java/java.base" },
+            { "/packages/java/java.instrument" },
+            { "/packages/java/java.rmi"  },
+            { "/packages/java/java.sql"  },
+            { "/packages/javax/java.base"  },
+            { "/packages/javax/java.sql"  },
+            { "/packages/javax/java.xml"  },
+            { "/packages/javax/java.management"  },
+            { "/packages/java.util/java.base" },
+            { "/packages/jdk.nashorn.api.scripting/jdk.scripting.nashorn" },
+        };
+    }
+
+    @Test(dataProvider = "packagesLinks")
+    public void testPackagesLinks(String link) throws Exception {
+        FileSystem fs = FileSystems.getFileSystem(URI.create("jrt:/"));
+        Path path = fs.getPath(link);
+        assertTrue(Files.exists(path), link + " missing");
+        assertTrue(Files.isSymbolicLink(path), path + " is not a link");
+        path = Files.readSymbolicLink(path);
+        assertEquals(path.toString(), "/modules" + link.substring(link.lastIndexOf("/")));
+    }
+
+    @DataProvider(name = "modulesSubDirs")
+    private Object[][] modulesSubDirs() {
+        return new Object[][] {
+            { "java.base" },
+            { "java.sql" },
+            { "jdk.scripting.nashorn" },
+        };
+    }
+
+    @Test(dataProvider = "modulesSubDirs")
+    public void testModulesSubDirs(String module) throws Exception {
+        FileSystem fs = FileSystems.getFileSystem(URI.create("jrt:/"));
+        Path path = fs.getPath("/modules/" + module);
+        assertTrue(Files.isDirectory(path), module + " missing");
+        assertTrue(!Files.isSymbolicLink(path), path + " is a link");
+    }
+
+    @DataProvider(name="linkChases")
+    private Object[][] linkChases() {
+        return new Object[][] {
+            { "/modules/java.base/java/lang" },
+            { "/modules/java.base/java/util/Vector.class" },
+            { "/modules/jdk.scripting.nashorn/jdk/nashorn" },
+            { "/packages/java.lang/java.base/java/lang" },
+            { "/packages/java.util/java.base/java/util/Vector.class" },
+        };
+    }
+
+    @Test(dataProvider = "linkChases")
+    public void testLinkChases(String link) throws Exception {
+        FileSystem fs = FileSystems.getFileSystem(URI.create("jrt:/"));
+        Path path = fs.getPath(link);
+        assertTrue(Files.exists(path), link);
+    }
+
+    @Test
+    public void testSymlinkDirList() throws Exception {
+        FileSystem fs = FileSystems.getFileSystem(URI.create("jrt:/"));
+        Path path = fs.getPath("/packages/java.lang/java.base");
+        assertTrue(Files.isSymbolicLink(path));
+        assertTrue(Files.isDirectory(path));
+
+        boolean javaSeen = false, javaxSeen = false;
+        try (DirectoryStream<Path> stream = Files.newDirectoryStream(path)) {
+            for (Path p : stream) {
+                String str = p.toString();
+                if (str.endsWith("/java")) {
+                    javaSeen = true;
+                } else if (str.endsWith("javax")) {
+                    javaxSeen = true;
+                }
+            }
+        }
+        assertTrue(javaSeen);
+        assertTrue(javaxSeen);
+    }
+
+    @Test
+    public void testPackagesSubDirList() throws Exception {
+        FileSystem fs = FileSystems.getFileSystem(URI.create("jrt:/"));
+        String pathName = "/packages/javax.annotation";
+        Path path = fs.getPath(pathName);
+        boolean seenJavaCompiler = false, seenAnnotationsCommon = false;
+        try (DirectoryStream<Path> stream = Files.newDirectoryStream(path)) {
+            for (Path p : stream) {
+               String str = p.toString();
+               if (str.equals(pathName + "/java.compiler")) {
+                   seenJavaCompiler = true;
+               } else if (str.equals(pathName + "/java.annotations.common")) {
+                   seenAnnotationsCommon = true;
+               }
+            }
+        }
+        assertTrue(seenJavaCompiler);
+        assertTrue(seenAnnotationsCommon);
+    }
+
+    @Test
+    public void testRootDirList() throws Exception {
+        FileSystem fs = FileSystems.getFileSystem(URI.create("jrt:/"));
+        Path path = fs.getPath("/");
+        // check /packages and /modules are not repeated
+        // and seen once.
+        boolean packages = false, modules = false;
+        try (DirectoryStream<Path> stream = Files.newDirectoryStream(path)) {
+            for (Path p : stream) {
+                String str = p.toString();
+                switch (str) {
+                    case "/packages":
+                        assertFalse(packages, "/packages repeated");
+                        packages = true;
+                        break;
+                    case "/modules":
+                        assertFalse(modules, "/modules repeated");
+                        modules = true;
+                        break;
+                }
+            }
+        }
+        assertTrue(packages, "/packages missing in / list!");
+        assertTrue(modules, "/modules missing in / list!");
+    }
 }
--- a/jdk/test/jdk/internal/jrtfs/WithSecurityManager.java	Thu Jul 02 15:25:46 2015 +0200
+++ b/jdk/test/jdk/internal/jrtfs/WithSecurityManager.java	Fri Jul 03 07:13:24 2015 -0700
@@ -55,7 +55,8 @@
             FileSystems.getFileSystem(URI.create("jrt:/"));
             if (!allow) throw new RuntimeException("access not expected");
         } catch (SecurityException se) {
-            if (allow) throw new RuntimeException("access expected");
+            if (allow)
+                throw se;
         }
 
         // check FileSystems.newFileSystem
@@ -63,7 +64,8 @@
             FileSystems.newFileSystem(URI.create("jrt:/"), null);
             if (!allow) throw new RuntimeException("access not expected");
         } catch (SecurityException se) {
-            if (allow) throw new RuntimeException("access expected");
+            if (allow)
+                throw se;
         }
 
         // check Paths.get
@@ -71,7 +73,8 @@
             Paths.get(URI.create("jrt:/java.base/java/lang/Object.class"));
             if (!allow) throw new RuntimeException("access not expected");
         } catch (SecurityException se) {
-            if (allow) throw new RuntimeException("access expected");
+            if (allow)
+                throw se;
         }
     }
 }