--- old/make/Tools.gmk 2015-06-23 14:28:47.000000000 +0200 +++ new/make/Tools.gmk 2015-06-23 14:28:47.000000000 +0200 @@ -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 --- old/make/mapfiles/libjava/mapfile-vers 2015-06-23 14:28:48.000000000 +0200 +++ new/make/mapfiles/libjava/mapfile-vers 2015-06-23 14:28:48.000000000 +0200 @@ -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 --- old/make/src/classes/build/tools/module/ImageBuilder.java 2015-06-23 14:28:49.000000000 +0200 +++ new/make/src/classes/build/tools/module/ImageBuilder.java 2015-06-23 14:28:48.000000000 +0200 @@ -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); - } - 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); + 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 "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); + }, "--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 "Byte order of the target runtime; {little,big}"; } - } + }, "--endian") }; private final Options options = new Options(); @@ -370,28 +363,35 @@ final Set bootModules; final Set extModules; final Set appModules; - final ImageModules imf; ImageFileHelper(Collection 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 archives = modules.stream() - .map(this::toModuleArchive) - .collect(Collectors.toSet()); - ImageFile.create(output, archives, imf, options.endian); + Set bootArchives = bootModules.stream() + .map(this::toModuleArchive) + .collect(Collectors.toSet()); + Set extArchives = extModules.stream() + .map(this::toModuleArchive) + .collect(Collectors.toSet()); + Set 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) { --- old/make/src/classes/build/tools/module/ModuleArchive.java 2015-06-23 14:28:49.000000000 +0200 +++ new/make/src/classes/build/tools/module/ModuleArchive.java 2015-06-23 14:28:49.000000000 +0200 @@ -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 opened = new ArrayList<>(); + public ModuleArchive(String moduleName, Path classes, Path cmds, Path libs, Path configs) { this.moduleName = moduleName; @@ -61,181 +65,118 @@ } @Override - public void visitResources(Consumer 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); + } + } + } + if (e != null) { + throw e; } - } - - private enum Section { - CLASSES, - CMDS, - LIBS, - CONFIGS } @Override - public void visitEntries(Consumer 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) - .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) + public Stream entries() { + List 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 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 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 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 stream = Files.walk(configs)) { + entries.addAll(stream .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 static class FileEntry implements Entry { - private final String name; - private final InputStream is; + private class FileEntry extends Entry { private final boolean isDirectory; - private final Section section; - FileEntry(String name, InputStream is, - boolean isDirectory, Section section) { - this.name = name; - this.is = is; + 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.section = section; - } - public String getName() { - return name; - } - public Section getSection() { - return section; - } - public InputStream getInputStream() { - return is; + this.size = size; } + public boolean isDirectory() { return isDirectory; } - } - - private Entry toEntry(Path entryPath, Path basePath, Section section) { - try { - return new FileEntry(basePath.relativize(entryPath).toString().replace('\\', '/'), - Files.newInputStream(entryPath), false, - section); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - @Override - public Consumer defaultImageWriter(Path path, OutputStream out) { - return new DefaultEntryWriter(path, out); - } - - private static class DefaultEntryWriter implements Consumer { - 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); + public long size() { + return size; } - 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); + @Override + public InputStream stream() throws IOException { + InputStream stream = Files.newInputStream(entryPath); + opened.add(stream); + return stream; } + } - 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"; - } + private Entry toEntry(Path entryPath, Path basePath, EntryType section) { + try { + String path = basePath.relativize(entryPath).toString().replace('\\', '/'); + return new FileEntry(entryPath, path, section, + false, Files.size(entryPath)); + } catch (IOException e) { + throw new UncheckedIOException(e); } } } --- old/src/java.base/share/classes/jdk/internal/jimage/Archive.java 2015-06-23 14:28:50.000000000 +0200 +++ new/src/java.base/share/classes/jdk/internal/jimage/Archive.java 2015-06-23 14:28:50.000000000 +0200 @@ -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 { + /** - * The module name. + * Entry is contained in an Archive */ - String moduleName(); + 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; + } /** - * Visits all classes and resources. + * The module name. */ - void visitResources(Consumer consumer); + String moduleName(); /** - * Visits all entries in the Archive. + * Stream of Entry. + * The stream of entries needs to be closed after use since + * (eg: it might cover lazy I/O based resources). + * So callers will need to use a try-with-resources block. */ - void visitEntries(Consumer consumer) ; + Stream 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 defaultImageWriter(Path path, OutputStream out); + void close() throws IOException; } --- old/src/java.base/share/classes/jdk/internal/jimage/BasicImageReader.java 2015-06-23 14:28:51.000000000 +0200 +++ new/src/java.base/share/classes/jdk/internal/jimage/BasicImageReader.java 2015-06-23 14:28:51.000000000 +0200 @@ -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() throws IOException { + return ImageHeader.readFrom( + getIndexIntBuffer(0, ImageHeader.getHeaderSize())); } - public ImageHeader getHeader() { - return header; + 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; + return substrate.findLocation(name, strings); + } - if (redirect < 0) { - // If no collision. - index = -redirect - 1; - } else { - // If collision, recompute hash code. - index = name.hashCode(redirect) % count; - } + public String[] getEntryNames() { + return IntStream.of(substrate.attributeOffsets()) + .filter(o -> o != 0) + .mapToObj(o -> ImageLocation.readFrom(this, o).getFullNameString()) + .sorted() + .toArray(String[]::new); + } - int offset = getOffset(index); + protected ImageLocation[] getAllLocations(boolean sorted) { + return IntStream.of(substrate.attributeOffsets()) + .filter(o -> o != 0) + .mapToObj(o -> ImageLocation.readFrom(this, o)) + .sorted(Comparator.comparing(ImageLocation::getFullNameString)) + .toArray(ImageLocation[]::new); + } - if (offset == 0) { - return null; - } + private IntBuffer getIndexIntBuffer(long offset, long size) + throws IOException { + ByteBuffer buffer = substrate.getIndexBuffer(offset, size); + buffer.order(byteOrder); - ImageLocation location = getLocation(offset); + return buffer.asIntBuffer(); + } - return location.verify(name) ? location : null; + ImageLocation getLocation(int offset) { + return ImageLocation.readFrom(this, offset); } - public String[] getEntryNames() { - return getEntryNames(true); + public long[] getAttributes(int offset) { + return substrate.getAttributes(offset); } - public String[] getEntryNames(boolean sorted) { - int count = header.getLocationCount(); - List list = new ArrayList<>(); + public String getString(int offset) { + return getUTF8String(offset).toString(); + } - for (int i = 0; i < count; i++) { - int offset = getOffset(i); + public UTF8String getUTF8String(int offset) { + return new UTF8String(substrate.getStringBytes(offset)); + } - if (offset != 0) { - ImageLocation location = ImageLocation.readFrom(locationsBuffer, offset, strings); - list.add(location.getFullnameString()); - } - } + private byte[] getBufferBytes(ByteBuffer buffer, long size) { + assert size < Integer.MAX_VALUE; + byte[] bytes = new byte[(int)size]; + buffer.get(bytes); - String[] array = list.toArray(new String[0]); + return bytes; + } - if (sorted) { - Arrays.sort(array); - } + private byte[] getBufferBytes(long offset, long size) { + ByteBuffer buffer = substrate.getDataBuffer(offset, size); - return array; + return getBufferBytes(buffer, size); } - protected ImageLocation[] getAllLocations(boolean sorted) { - int count = header.getLocationCount(); - List list = new ArrayList<>(); - - for (int i = 0; i < count; i++) { - int offset = getOffset(i); + 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 (offset != 0) { - ImageLocation location = ImageLocation.readFrom(locationsBuffer, offset, strings); - list.add(location); - } + if (substrate.supportsDataBuffer() && compressedSize == 0) { + return getBufferBytes(offset, uncompressedSize); } - ImageLocation[] array = list.toArray(new ImageLocation[0]); + ByteBuffer uncompressedBuffer = ImageBufferCache.getBuffer(uncompressedSize); + boolean isRead; - if (sorted) { - Arrays.sort(array, (ImageLocation loc1, ImageLocation loc2) -> - loc1.getFullnameString().compareTo(loc2.getFullnameString())); + 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); } - return array; - } + byte[] bytes = isRead ? getBufferBytes(uncompressedBuffer, + uncompressedSize) : null; - private IntBuffer getIntBuffer(long offset, long size) throws IOException { - MappedByteBuffer buffer = preader.channel().map(FileChannel.MapMode.READ_ONLY, offset, size); - buffer.order(byteOrder); + ImageBufferCache.releaseBuffer(uncompressedBuffer); - return buffer.asIntBuffer(); + return bytes; } - 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; - } + public byte[] getResource(String name) { + ImageLocation location = findLocation(name); - private int getRedirect(int index) { - return redirectBuffer.get(index); + return location != null ? getResource(location) : null; } - private int getOffset(int index) { - return offsetsBuffer.get(index); - } + 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; - private ImageLocation getLocation(int offset) { - return ImageLocation.readFrom(locationsBuffer, offset, strings); - } + if (substrate.supportsDataBuffer() && compressedSize == 0) { + return substrate.getDataBuffer(offset, uncompressedSize); + } - public String getString(int offset) { - return strings.get(offset).toString(); - } + ByteBuffer uncompressedBuffer = ImageBufferCache.getBuffer(uncompressedSize); + boolean isRead; - public byte[] getResource(ImageLocation loc) throws IOException { - long compressedSize = loc.getCompressedSize(); - assert compressedSize < Integer.MAX_VALUE; + 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 (compressedSize == 0) { - return preader.read((int)loc.getUncompressedSize(), - indexSize + loc.getContentOffset()); + if (isRead) { + return uncompressedBuffer; } else { - byte[] buf = preader.read((int)compressedSize, - indexSize + loc.getContentOffset()); - return ImageFile.Compressor.decompress(buf); + ImageBufferCache.releaseBuffer(uncompressedBuffer); + + return null; } } - public byte[] getResource(String name) throws IOException { + public ByteBuffer getResourceBuffer(String name) { ImageLocation location = findLocation(name); - return location != null ? getResource(location) : null; + return location != null ? getResourceBuffer(location) : null; } - public List getNames(String name) throws IOException { - return getNames(getResource(name)); - } + public InputStream getResourceStream(ImageLocation loc) { + byte[] bytes = getResource(loc); - public List getNames(byte[] bytes) { - IntBuffer buffer = ByteBuffer.wrap(bytes).asIntBuffer(); - List names = new ArrayList<>(); + return new ByteArrayInputStream(bytes); + } - while (buffer.hasRemaining()) { - int offset = buffer.get(); - names.add(getString(offset)); - } + public InputStream getResourceStream(String name) { + ImageLocation location = findLocation(name); - return names; + return location != null ? getResourceStream(location) : null; } } --- old/src/java.base/share/classes/jdk/internal/jimage/BasicImageWriter.java 2015-06-23 14:28:52.000000000 +0200 +++ new/src/java.base/share/classes/jdk/internal/jimage/BasicImageWriter.java 2015-06-23 14:28:52.000000000 +0200 @@ -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 input; + private ImageLocationWriter[] locations; + private List input; private ImageStream headerStream; private ImageStream redirectStream; private ImageStream locationOffsetStream; private ImageStream locationStream; private ImageStream allIndexStream; - static class ImageBucket implements Comparable { - final List list; - - ImageBucket() { - this.list = new ArrayList<>(); - } - - void add(ImageLocation location) { - list.add(location); - } - - int getSize() { - return list.size(); - } - - List 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); - input.add(location); - count++; + public String getString(int offset) { + UTF8String utf8 = strings.get(offset); + return utf8 != null? utf8.toString() : null; } - 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; - - 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; - } + 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); + length++; } - 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); - }); - - ImageBucket[] sorted = Arrays.asList(buckets).stream() - .filter((bucket) -> (bucket != null)) - .sorted() - .toArray(ImageBucket[]::new); - - return sorted; + ImageLocationWriter[] getLocations() { + return locations; } - private boolean packCollidedEntries(ImageBucket bucket, int count) { - List 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; - }); - - undo.clear(); - base++; - - if (base == 0) { - base = 1; - } + int getLocationsCount() { + return input.size(); + } - if (++retry > RETRY_LIMIT) { - return false; - } + private void generatePerfectHash() { + PerfectHashBuilder builder = + new PerfectHashBuilder<>( + new PerfectHashBuilder.Entry().getClass(), + new PerfectHashBuilder.Bucket().getClass()); - continue redo; - } + input.forEach((location) -> { + builder.put(location.getFullName(), location); + }); - locations[index] = location; - undo.add(index); - } + builder.generate(); - redirect[bucket.hashCode() % count] = base; + length = builder.getCount(); + redirect = builder.getRedirect(); + PerfectHashBuilder.Entry[] order = builder.getOrder(); + locations = new ImageLocationWriter[length]; - 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]; } } --- old/src/java.base/share/classes/jdk/internal/jimage/ImageHeader.java 2015-06-23 14:28:53.000000000 +0200 +++ new/src/java.base/share/classes/jdk/internal/jimage/ImageHeader.java 2015-06-23 14:28:53.000000000 +0200 @@ -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() { --- old/src/java.base/share/classes/jdk/internal/jimage/ImageLocation.java 2015-06-23 14:28:53.000000000 +0200 +++ new/src/java.base/share/classes/jdk/internal/jimage/ImageLocation.java 2015-06-23 14:28:53.000000000 +0200 @@ -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); - } - - 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("; "); - } + static ImageLocation readFrom(BasicImageReader reader, int offset) { + long[] attributes = reader.getAttributes(offset); + ImageStringsReader strings = reader.getStrings(); - return sb.toString(); + return new ImageLocation(attributes, strings); } } --- old/src/java.base/share/classes/jdk/internal/jimage/ImageReader.java 2015-06-23 14:28:54.000000000 +0200 +++ new/src/java.base/share/classes/jdk/internal/jimage/ImageReader.java 2015-06-23 14:28:54.000000000 +0200 @@ -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 packageMap; + private final ImageModuleData moduleData; // directory management implementation private final Map 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,56 +146,63 @@ this.fileAttrs = fileAttrs; } - public final void setIsRootDir() { - flags |= ROOT_DIR; + /** + * A node is completed when all its direct children have been built. + * + * @return + */ + public boolean isCompleted() { + return completed; } - public final boolean isRootDir() { - return (flags & ROOT_DIR) != 0; + public void setCompleted(boolean completed) { + this.completed = completed; } - public final void setIsModuleDir() { - flags |= MODULE_DIR; + public final void setIsRootDir() { + flags |= ROOT_DIR; } - public final boolean isModuleDir() { - return (flags & MODULE_DIR) != 0; + public final boolean isRootDir() { + return (flags & ROOT_DIR) != 0; } - public final void setIsMetaInfDir() { - flags |= METAINF_DIR; + public final void setIsPackagesDir() { + flags |= PACKAGES_DIR; } - public final boolean isMetaInfDir() { - return (flags & METAINF_DIR) != 0; + public final boolean isPackagesDir() { + return (flags & PACKAGES_DIR) != 0; } - public final void setIsTopLevelPackageDir() { - flags |= TOPLEVEL_PKG_DIR; + public final void setIsModulesDir() { + flags |= MODULES_DIR; } - public final boolean isTopLevelPackageDir() { - return (flags & TOPLEVEL_PKG_DIR) != 0; + public final boolean isModulesDir() { + return (flags & MODULES_DIR) != 0; } - public final void setIsHidden() { - flags |= HIDDEN; + public final UTF8String getName() { + return name; } - public final boolean isHidden() { - return (flags & HIDDEN) != 0; + public final BasicFileAttributes getFileAttributes() { + return fileAttrs; } - public final boolean isVisible() { - return !isHidden(); + // resolve this Node (if this is a soft link, get underlying Node) + public final Node resolveLink() { + return resolveLink(false); } - public final UTF8String getName() { - return name; + public Node resolveLink(boolean recursive) { + return this; } - public final BasicFileAttributes getFileAttributes() { - return fileAttrs; + // is this a soft link Node? + public boolean isLink() { + return false; } public boolean isDirectory() { @@ -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 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 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); - } - - Node metaInf = nodes.get(META_INF); - if (metaInf instanceof Directory) { - metaInf.setIsMetaInfDir(); - ((Directory)metaInf).walk(Node::setIsHidden); - } - fillPackageModuleInfo(); + // /packages dir + packagesDir = newDirectory(rootDir, PACKAGES_STRING); + packagesDir.setIsPackagesDir(); + + // /modules dir + modulesDir = newDirectory(rootDir, MODULES_STRING); + modulesDir.setIsModulesDir(); + 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 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 dirs(UTF8String parent) { + List splits = new ArrayList<>(); + + for (int i = 1; i < parent.length(); i++) { + if (parent.byteAt(i) == '/') { + splits.add(parent.substring(0, i)); + } + } + + splits.add(parent); + + return splits; + } + private Directory makeDirectories(UTF8String parent) { - assert !parent.isEmpty() : "non empty parent expected"; + Directory last = rootDir; + List dirs = dirs(parent); - 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); - } - Directory last = top; - while ((idx = parent.indexOf('/', idx + 1)) != -1) { - name = ROOT.concat(parent.substring(0, idx)); - Directory nextDir = (Directory) nodes.get(name); + 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()); --- old/src/java.base/share/classes/jdk/internal/jimage/ImageStream.java 2015-06-23 14:28:55.000000000 +0200 +++ new/src/java.base/share/classes/jdk/internal/jimage/ImageStream.java 2015-06-23 14:28:55.000000000 +0200 @@ -72,7 +72,7 @@ return this; } - private void ensure(int needs) { + void ensure(int needs) { assert 0 <= needs : "Negative needs"; if (needs > buffer.remaining()) { --- old/src/java.base/share/classes/jdk/internal/jimage/ImageStrings.java 2015-06-23 14:28:55.000000000 +0200 +++ new/src/java.base/share/classes/jdk/internal/jimage/ImageStrings.java 2015-06-23 14:28:55.000000000 +0200 @@ -25,83 +25,8 @@ package jdk.internal.jimage; -import java.nio.ByteBuffer; -import java.util.HashMap; +interface ImageStrings { + public UTF8String get(int offset); -class ImageStrings { - private static final int NOT_FOUND = -1; - static final int EMPTY_OFFSET = 0; - - private final HashMap 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); - - 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); } --- old/src/java.base/share/classes/jdk/internal/jimage/UTF8String.java 2015-06-23 14:28:56.000000000 +0200 +++ new/src/java.base/share/classes/jdk/internal/jimage/UTF8String.java 2015-06-23 14:28:56.000000000 +0200 @@ -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); - } } --- old/src/java.base/share/classes/jdk/internal/jrtfs/JrtDirectoryStream.java 2015-06-23 14:28:57.000000000 +0200 +++ new/src/java.base/share/classes/jdk/internal/jrtfs/JrtDirectoryStream.java 2015-06-23 14:28:57.000000000 +0200 @@ -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 --- old/src/java.base/share/classes/jdk/internal/jrtfs/JrtFileAttributeView.java 2015-06-23 14:28:58.000000000 +0200 +++ new/src/java.base/share/classes/jdk/internal/jrtfs/JrtFileAttributeView.java 2015-06-23 14:28:58.000000000 +0200 @@ -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 get(JrtPath path, Class type) { + static V get(JrtPath path, Class 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 --- old/src/java.base/share/classes/jdk/internal/jrtfs/JrtFileAttributes.java 2015-06-23 14:28:59.000000000 +0200 +++ new/src/java.base/share/classes/jdk/internal/jrtfs/JrtFileAttributes.java 2015-06-23 14:28:58.000000000 +0200 @@ -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 /////////// --- old/src/java.base/share/classes/jdk/internal/jrtfs/JrtFileSystem.java 2015-06-23 14:28:59.000000000 +0200 +++ new/src/java.base/share/classes/jdk/internal/jrtfs/JrtFileSystem.java 2015-06-23 14:28:59.000000000 +0200 @@ -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(); - extImage.close(); - appImage.close(); bootImage = null; + extImage.close(); extImage = null; + appImage.close(); 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) { - image = extImage; node = extImage.findNode(path); + image = extImage; } if (node == null) { - image = appImage; node = appImage.findNode(path); + image = appImage; + } + 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); + } + } } - if (node == null || node.isHidden()) { - throw new NoSuchFileException(getString(path)); + + 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 new NodeAndImage(node, image); + 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> packagesTreeChildren = new ConcurrentHashMap<>(); + /** * returns the list of child paths of the given directory "path" * @@ -369,49 +442,73 @@ Iterator 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 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 nodesToIterator(Path path, String childPrefix, List childNodes) { - List 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 f = childPrefix == null + ? child -> toJrtPath(child.getNameString()) + : child -> toJrtPath(childPrefix + child.getNameString().substring(1)); + return childNodes.stream().map(f).collect(toList()).iterator(); } - private List rootChildren; - private static void addRootDirContent(List dest, List 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 children) { + for (Node child : children) { + if (!(child.isModulesDir() || child.isPackagesDir())) { + rootChildren.add(child); } } } + private List 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 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 modulesDirIterator(byte[] path, String childPrefix) throws IOException { + initModulesChildren(path); + return nodesToIterator(new JrtPath(this, path), childPrefix, modulesChildren); + } + + private List 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 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(); --- old/src/java.base/share/classes/jdk/internal/jrtfs/JrtFileSystemProvider.java 2015-06-23 14:29:00.000000000 +0200 +++ new/src/java.base/share/classes/jdk/internal/jrtfs/JrtFileSystemProvider.java 2015-06-23 14:29:00.000000000 +0200 @@ -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 getFileAttributeView(Path path, Class 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; } --- old/src/java.base/share/classes/jdk/internal/jrtfs/JrtPath.java 2015-06-23 14:29:01.000000000 +0200 +++ new/src/java.base/share/classes/jdk/internal/jrtfs/JrtPath.java 2015-06-23 14:29:01.000000000 +0200 @@ -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 options, --- old/src/java.base/share/classes/jdk/internal/jrtfs/SystemImages.java 2015-06-23 14:29:01.000000000 +0200 +++ new/src/java.base/share/classes/jdk/internal/jrtfs/SystemImages.java 2015-06-23 14:29:01.000000000 +0200 @@ -42,6 +42,7 @@ static final Path bootImagePath; static final Path extImagePath; static final Path appImagePath; + static { PrivilegedAction pa = SystemImages::findHome; RUNTIME_HOME = AccessController.doPrivileged(pa); --- old/src/java.base/share/native/include/jvm.h 2015-06-23 14:29:02.000000000 +0200 +++ new/src/java.base/share/native/include/jvm.h 2015-06-23 14:29:02.000000000 +0200 @@ -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 */ --- old/src/jdk.dev/share/classes/jdk/tools/jimage/JImageTask.java 2015-06-23 14:29:03.000000000 +0200 +++ new/src/jdk.dev/share/classes/jdk/tools/jimage/JImageTask.java 2015-06-23 14:29:03.000000000 +0200 @@ -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(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(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(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(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(false, "--verbose") { + @Override + protected void process(JImageTask task, String opt, String arg) throws BadArgs { task.options.verbose = true; } }, - new Option(false, "--version") { + new Option(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 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 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 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 files = new ArrayList<>(); - final BasicImageWriter writer = new BasicImageWriter(); - final Long longZero = 0L; + Path jimage = options.jimages.get(0).toPath(); - // 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 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(); - } - - 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 names = reader.getNames(bytes); - Files.write(resource.toPath(), names); + if (name.endsWith(ImageModuleData.META_DATA_EXTENSION)) { + ImageModuleData imageModuleData = new ImageModuleData(reader, bytes); + List 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); } } --- old/src/jdk.dev/share/classes/jdk/tools/jimage/resources/jimage.properties 2015-06-23 14:29:04.000000000 +0200 +++ new/src/jdk.dev/share/classes/jdk/tools/jimage/resources/jimage.properties 2015-06-23 14:29:04.000000000 +0200 @@ -1,16 +1,17 @@ main.usage.summary=\ -Usage: {0} jimage...\n\ +Usage: {0} jimage...\n\ use --help for a list of possible options main.usage=\ -Usage: {0} jimage...\n\ +Usage: {0} jimage...\n\ \n\ \ extract - Extract all jimage entries into separate files into the directory\n\ \ specified by --dir= (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.verbose=\ -\ --verbose Verbose listing +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.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.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.read.file=cannot read file: {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} --- old/src/jdk.rmic/share/classes/sun/tools/java/ClassPath.java 2015-06-23 14:29:04.000000000 +0200 +++ new/src/jdk.rmic/share/classes/sun/tools/java/ClassPath.java 2015-06-23 14:29:04.000000000 +0200 @@ -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 stream = Files.newDirectoryStream(root)) { for (Path entry: stream) { --- old/test/java/lang/Class/getDeclaredField/FieldSetAccessibleTest.java 2015-06-23 14:29:05.000000000 +0200 +++ new/test/java/lang/Class/getDeclaredField/FieldSetAccessibleTest.java 2015-06-23 14:29:05.000000000 +0200 @@ -221,8 +221,8 @@ Stream 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")); } --- old/test/java/nio/Buffer/LimitDirectMemory.sh 2015-06-23 14:29:06.000000000 +0200 +++ new/test/java/nio/Buffer/LimitDirectMemory.sh 2015-06-23 14:29:06.000000000 +0200 @@ -28,6 +28,7 @@ # @summary Test option to limit direct memory allocation # # @build LimitDirectMemory +# @ignore JDK-8129343 # @run shell LimitDirectMemory.sh TMP1=tmp_$$ --- old/test/java/nio/charset/Charset/NIOCharsetAvailabilityTest.java 2015-06-23 14:29:07.000000000 +0200 +++ new/test/java/nio/charset/Charset/NIOCharsetAvailabilityTest.java 2015-06-23 14:29:06.000000000 +0200 @@ -46,9 +46,9 @@ // two known charset implementation packages FileSystem fs = FileSystems.getFileSystem(URI.create("jrt:/")); Set 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 { --- old/test/java/nio/file/spi/SetDefaultProvider.java 2015-06-23 14:29:07.000000000 +0200 +++ new/test/java/nio/file/spi/SetDefaultProvider.java 2015-06-23 14:29:07.000000000 +0200 @@ -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 */ --- old/test/jdk/internal/jimage/VerifyJimage.java 2015-06-23 14:29:08.000000000 +0200 +++ new/test/jdk/internal/jimage/VerifyJimage.java 2015-06-23 14:29:08.000000000 +0200 @@ -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) { --- old/test/jdk/internal/jrtfs/Basic.java 2015-06-23 14:29:09.000000000 +0200 +++ new/test/jdk/internal/jrtfs/Basic.java 2015-06-23 14:29:09.000000000 +0200 @@ -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 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)(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 stream = Files.newDirectoryStream(dir, filter)) { Iterator 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 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 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 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!"); + } } --- old/test/jdk/internal/jrtfs/WithSecurityManager.java 2015-06-23 14:29:09.000000000 +0200 +++ new/test/jdk/internal/jrtfs/WithSecurityManager.java 2015-06-23 14:29:09.000000000 +0200 @@ -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; } } } --- /dev/null 2015-06-23 14:29:10.000000000 +0200 +++ new/src/java.base/share/classes/jdk/internal/jimage/ExternalFilesWriter.java 2015-06-23 14:29:10.000000000 +0200 @@ -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 { + 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 2015-06-23 14:29:11.000000000 +0200 +++ new/src/java.base/share/classes/jdk/internal/jimage/ImageBufferCache.java 2015-06-23 14:29:11.000000000 +0200 @@ -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> + 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 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 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(); + } +} --- /dev/null 2015-06-23 14:29:12.000000000 +0200 +++ new/src/java.base/share/classes/jdk/internal/jimage/ImageFileCreator.java 2015-06-23 14:29:12.000000000 +0200 @@ -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.) + *
{@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];
+ * }
+ * }
+ */ +public final class ImageFileCreator { + private final Path root; + private final Path mdir; + private final Map> 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 archives) + throws IOException { + return create(output, BOOT_NAME, archives, ByteOrder.nativeOrder()); + } + + public static ImageFileCreator create(Path output, + Set archives, + ByteOrder byteOrder) + throws IOException { + return create(output, BOOT_NAME, archives, byteOrder); + } + + public static ImageFileCreator create(Path output, + String fileName, + Set archives, + ByteOrder byteOrder) + throws IOException + { + ImageFileCreator image = new ImageFileCreator(output); + // get all entries + Map> modulePackagesMap = new HashMap<>(); + image.readAllEntries(modulePackagesMap, archives); + // write to modular image + image.writeImage(fileName, modulePackagesMap, archives, byteOrder); + return image; + } + + private void readAllEntries(Map> modulePackagesMap, + Set archives) { + archives.stream().forEach((archive) -> { + Map> es; + try(Stream entries = archive.entries()) { + es = entries.collect(Collectors.partitioningBy(n -> n.type() + == EntryType.CLASS_OR_RESOURCE)); + } + String mn = archive.moduleName(); + List all = new ArrayList<>(); + all.addAll(es.get(false)); + all.addAll(es.get(true)); + entriesForModule.put(mn, all); + // Extract package names + Set 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 archives, + Map> modulePackages) + throws IOException { + Map> entriesForModule + = archives.stream().collect(Collectors.toMap( + Archive::moduleName, + a -> { + try(Stream entries = a.entries()) { + return entries.collect(Collectors.toList()); + } + })); + Map 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> modulePackagesMap, + Set archives, + ByteOrder byteOrder) + throws IOException { + Files.createDirectories(mdir); + ExternalFilesWriter filesWriter = new ExternalFilesWriter(root); + // name to Archive file + Map 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> modulePackagesMap = resources.getModulePackages(); + + try (OutputStream fos = Files.newOutputStream(img); + BufferedOutputStream bos = new BufferedOutputStream(fos); + DataOutputStream out = new DataOutputStream(bos)) { + Set duplicates = new HashSet<>(); + ImageModuleDataWriter moduleData = + ImageModuleDataWriter.buildModuleData(writer, modulePackagesMap); + moduleData.addLocation(fileName, writer); + long offset = moduleData.size(); + + List content = new ArrayList<>(); + List 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> modulePackagesMap, + Map nameToArchive, + Consumer externalFileHandler, + Map> entriesForModule, + ByteOrder byteOrder) throws IOException { + ResourcePoolImpl resources = new ResourcePoolImpl(byteOrder); + Set 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 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 ""; + } + } +} --- /dev/null 2015-06-23 14:29:13.000000000 +0200 +++ new/src/java.base/share/classes/jdk/internal/jimage/ImageJavaSubstrate.java 2015-06-23 14:29:12.000000000 +0200 @@ -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; + } +} --- /dev/null 2015-06-23 14:29:13.000000000 +0200 +++ new/src/java.base/share/classes/jdk/internal/jimage/ImageLocationBase.java 2015-06-23 14:29:13.000000000 +0200 @@ -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 2015-06-23 14:29:14.000000000 +0200 +++ new/src/java.base/share/classes/jdk/internal/jimage/ImageLocationWriter.java 2015-06-23 14:29:14.000000000 +0200 @@ -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 2015-06-23 14:29:15.000000000 +0200 +++ new/src/java.base/share/classes/jdk/internal/jimage/ImageModuleData.java 2015-06-23 14:29:14.000000000 +0200 @@ -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 fromModulePackages() { + List 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 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 packages = new ArrayList<>(count); + + for (int i = 0; i < count; i++) { + packages.add(stringAt(base + i)); + } + + return packages; + } + + return null; + } + + public List allPackageNames() { + List 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 allModuleNames() { + Set 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 packageModuleMap() { + Map 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 2015-06-23 14:29:15.000000000 +0200 +++ new/src/java.base/share/classes/jdk/internal/jimage/ImageModuleDataWriter.java 2015-06-23 14:29:15.000000000 +0200 @@ -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> modulePackages) { + PerfectHashBuilder packageToModule = new PerfectHashBuilder<>( + new PerfectHashBuilder.Entry().getClass(), + new PerfectHashBuilder.Bucket().getClass()); + PerfectHashBuilder> moduleToPackages = new PerfectHashBuilder<>( + new PerfectHashBuilder.Entry>().getClass(), + new PerfectHashBuilder.Bucket>().getClass()); + + modulePackages.entrySet().stream().forEach((entry) -> { + String moduleName = entry.getKey(); + List 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> modulePackagesMap) { + Set modules = modulePackagesMap.keySet(); + + Map> modulePackages = new LinkedHashMap<>(); + modules.stream().sorted().forEach((moduleName) -> { + List 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> toModulePackages(List lines) { + Map> modulePackages = new LinkedHashMap<>(); + + for (String line : lines) { + String[] parts = line.split(ImageModuleData.SEPARATOR); + String moduleName = parts[0]; + List 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 packageToModule, + PerfectHashBuilder> moduleToPackages) { + ImageStream stream = new ImageStream(writer.getByteOrder()); + + int[] ptmRedirect = packageToModule.getRedirect(); + int[] mtpRedirect = moduleToPackages.getRedirect(); + PerfectHashBuilder.Entry[] ptmOrder = packageToModule.getOrder(); + PerfectHashBuilder.Entry>[] mtpOrder = moduleToPackages.getOrder(); + + stream.putInt(ptmRedirect.length); + stream.putInt(mtpRedirect.length); + + for (int value : ptmRedirect) { + stream.putInt(value); + } + + for (PerfectHashBuilder.Entry 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> 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> entry : mtpOrder) { + if (entry != null) { + List 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; + } +} --- /dev/null 2015-06-23 14:29:16.000000000 +0200 +++ new/src/java.base/share/classes/jdk/internal/jimage/ImageNativeSubstrate.java 2015-06-23 14:29:16.000000000 +0200 @@ -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); + } +} --- /dev/null 2015-06-23 14:29:17.000000000 +0200 +++ new/src/java.base/share/classes/jdk/internal/jimage/ImageReaderFactory.java 2015-06-23 14:29:16.000000000 +0200 @@ -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 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 2015-06-23 14:29:17.000000000 +0200 +++ new/src/java.base/share/classes/jdk/internal/jimage/ImageResourcesTree.java 2015-06-23 14:29:17.000000000 +0200 @@ -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 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 directAccess = new HashMap<>(); + private final List paths; + private final Node root; + private Node modules; + private Node packages; + + private Tree(List paths) { + this.paths = paths; + root = new Node("", null); + buildTree(); + } + + private void buildTree() { + modules = new Node(MODULES, root); + directAccess.put(modules.getPath(), modules); + + Map> moduleToPackage = new TreeMap<>(); + Map> 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 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 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> 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> 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 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 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 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 computeContent() { + // Map used to associate Tree item with locations offset. + Map outLocations = new HashMap<>(); + for (ImageLocationWriter wr : writer.getLocations()) { + outLocations.put(wr.getFullNameString(), wr); + } + // Attach location to node + for (Map.Entry 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 outLocations) { + int[] ret = new int[current.children.size()]; + int i = 0; + for (java.util.Map.Entry 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 paths; + private final LocationsAdder adder; + + public ImageResourcesTree(long offset, BasicImageWriter writer, List 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 content = adder.computeContent(); + for (byte[] c : content) { + out.write(c, 0, c.length); + } + } +} --- /dev/null 2015-06-23 14:29:18.000000000 +0200 +++ new/src/java.base/share/classes/jdk/internal/jimage/ImageStringsReader.java 2015-06-23 14:29:18.000000000 +0200 @@ -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 2015-06-23 14:29:19.000000000 +0200 +++ new/src/java.base/share/classes/jdk/internal/jimage/ImageStringsWriter.java 2015-06-23 14:29:18.000000000 +0200 @@ -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 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 2015-06-23 14:29:19.000000000 +0200 +++ new/src/java.base/share/classes/jdk/internal/jimage/ImageSubstrate.java 2015-06-23 14:29:19.000000000 +0200 @@ -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(); +} --- /dev/null 2015-06-23 14:29:20.000000000 +0200 +++ new/src/java.base/share/classes/jdk/internal/jimage/PerfectHashBuilder.java 2015-06-23 14:29:20.000000000 +0200 @@ -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 { + private final static int RETRY_LIMIT = 1000; + + private Class entryComponent; + private Class bucketComponent; + + private final Map> map = new LinkedHashMap<>(); + private int[] redirect; + private Entry[] order; + private int count = 0; + + @SuppressWarnings("EqualsAndHashcode") + public static class Entry { + 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 implements Comparable> { + final List> list = new ArrayList<>(); + + void add(Entry entry) { + list.add(entry); + } + + int getSize() { + return list.size(); + } + + List> getList() { + return list; + } + + Entry 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 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[] getOrder() { + return order; + } + + public Entry put(String key, E value) { + return put(new UTF8String(key), value); + } + + public Entry put(UTF8String key, E value) { + return put(new Entry<>(key, value)); + } + + public Entry put(Entry entry) { + Entry 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[])Array.newInstance(entryComponent, count); + + Bucket[] sorted = createBuckets(); + int free = 0; + + for (Bucket 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[] createBuckets() { + Bucket[] buckets = (Bucket[])Array.newInstance(bucketComponent, count); + + map.values().stream().forEach((entry) -> { + int index = entry.hashCode() % count; + Bucket bucket = buckets[index]; + + if (bucket == null) { + buckets[index] = bucket = new Bucket<>(); + } + + bucket.add(entry); + }); + + Bucket[] sorted = Arrays.asList(buckets).stream() + .filter((bucket) -> (bucket != null)) + .sorted() + .toArray((length) -> { + return (Bucket[])Array.newInstance(bucketComponent, length); + }); + + return sorted; + } + + private boolean collidedEntries(Bucket bucket, int count) { + List undo = new ArrayList<>(); + int seed = UTF8String.HASH_MULTIPLIER + 1; + int retry = 0; + + redo: + while (true) { + for (Entry 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; + } + } --- /dev/null 2015-06-23 14:29:21.000000000 +0200 +++ new/src/java.base/share/classes/jdk/internal/jimage/ResourcePool.java 2015-06-23 14:29:20.000000000 +0200 @@ -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 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> 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 2015-06-23 14:29:21.000000000 +0200 +++ new/src/java.base/share/classes/jdk/internal/jimage/ResourcePoolImpl.java 2015-06-23 14:29:21.000000000 +0200 @@ -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 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 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> getModulePackages() { + Map> 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> moduleToPackage) { + for (Resource res : resources.values()) { + Set 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 2015-06-23 14:29:22.000000000 +0200 +++ new/src/java.base/share/classes/jdk/internal/jimage/StringTable.java 2015-06-23 14:29:22.000000000 +0200 @@ -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); +} --- /dev/null 2015-06-23 14:29:23.000000000 +0200 +++ new/src/java.base/share/classes/jdk/internal/jimage/decompressor/CompressedResourceHeader.java 2015-06-23 14:29:23.000000000 +0200 @@ -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 2015-06-23 14:29:24.000000000 +0200 +++ new/src/java.base/share/classes/jdk/internal/jimage/decompressor/Decompressor.java 2015-06-23 14:29:23.000000000 +0200 @@ -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 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 2015-06-23 14:29:24.000000000 +0200 +++ new/src/java.base/share/classes/jdk/internal/jimage/decompressor/ResourceDecompressor.java 2015-06-23 14:29:24.000000000 +0200 @@ -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 2015-06-23 14:29:25.000000000 +0200 +++ new/src/java.base/share/classes/jdk/internal/jimage/decompressor/ResourceDecompressorFactory.java 2015-06-23 14:29:25.000000000 +0200 @@ -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 2015-06-23 14:29:26.000000000 +0200 +++ new/src/java.base/share/classes/jdk/internal/jimage/decompressor/ResourceDecompressorRepository.java 2015-06-23 14:29:25.000000000 +0200 @@ -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 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 2015-06-23 14:29:26.000000000 +0200 +++ new/src/java.base/share/classes/jdk/internal/jimage/decompressor/ZipDecompressor.java 2015-06-23 14:29:26.000000000 +0200 @@ -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 2015-06-23 14:29:27.000000000 +0200 +++ new/src/java.base/share/classes/jdk/internal/jimage/decompressor/ZipDecompressorFactory.java 2015-06-23 14:29:27.000000000 +0200 @@ -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(); + } +} --- /dev/null 2015-06-23 14:29:28.000000000 +0200 +++ new/src/java.base/share/native/libjava/Image.c 2015-06-23 14:29:27.000000000 +0200 @@ -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 + +#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; +} --- /dev/null 2015-06-23 14:29:28.000000000 +0200 +++ new/src/jdk.dev/share/classes/jdk/tools/jimage/ExtractedImage.java 2015-06-23 14:29:28.000000000 +0200 @@ -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 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 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> modulePackages = new LinkedHashMap<>(); + private Set 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 lines = Files.readAllLines(p); + for (Entry> entry + : ImageModuleDataWriter.toModulePackages(lines).entrySet()) { + Set 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, '/'); + } +} --- /dev/null 2015-06-23 14:29:29.000000000 +0200 +++ new/src/jdk.dev/share/classes/jdk/tools/jimage/TaskHelper.java 2015-06-23 14:29:29.000000000 +0200 @@ -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 { + + 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 extends Option { + + 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 { + + private final List> options; + + OptionsHelper(List> options) { + this.options = options; + } + + public List handleOptions(T task, String[] args) throws BadArgs { + List rest = new ArrayList<>(); + // process options + for (int i = 0; i < args.length; i++) { + if (args[i].charAt(0) == '-') { + String name = args[i]; + Option 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 getOption(String name) throws BadArgs { + for (Option 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 OptionsHelper newOptionsHelper(Class clazz, Option[] options) { + List> optionsList = new ArrayList<>(); + for (Option o : options) { + @SuppressWarnings("unchecked") + Option opt = (Option) 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"); + } + +} --- old/src/java.base/share/classes/jdk/internal/jimage/ImageFile.java 2015-06-23 14:29:29.000000000 +0200 +++ /dev/null 2015-06-23 14:29:29.000000000 +0200 @@ -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.) - *
{@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];
- * }
- * }
- */ -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> 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 archives, - ImageModules modules) - throws IOException - { - return ImageFile.create(output, archives, modules, ByteOrder.nativeOrder()); - } - - public static ImageFile create(Path output, - Set 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 archives, - ByteOrder byteOrder) - throws IOException - { - // name to Archive file - Map nameToArchive = - archives.stream() - .collect(Collectors.toMap(Archive::moduleName, Function.identity())); - - Files.createDirectories(mdir); - for (Loader l : Loader.values()) { - Set 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 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 archives) - throws IOException - { - for (Archive archive : archives) { - List res = new ArrayList<>(); - archive.visitResources(x-> res.add(x)); - - String mn = archive.moduleName(); - resourcesForModule.put(mn, res); - - Set 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 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; - } - } -} --- old/src/java.base/share/classes/jdk/internal/jimage/ImageModules.java 2015-06-23 14:29:30.000000000 +0200 +++ /dev/null 2015-06-23 14:29:30.000000000 +0200 @@ -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 loaders = new LinkedHashMap<>(); - protected final Map> localPkgs = new HashMap<>(); - - protected ImageModules() {} - - public ImageModules(Set bootModules, - Set extModules, - Set appModules) throws IOException { - mapModulesToLoader(Loader.BOOT_LOADER, bootModules); - mapModulesToLoader(Loader.EXT_LOADER, extModules); - mapModulesToLoader(Loader.APP_LOADER, appModules); - } - - public Map> 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 pkgs) { - localPkgs.put(mn, pkgs); - } - - /* - * Returns the name of modules mapped to a given class loader in the image - */ - public Set getModules(Loader type) { - if (loaders.containsKey(type)) { - return loaders.get(type).modules(); - } else { - return Collections.emptySet(); - } - } - - private void mapModulesToLoader(Loader loader, Set modules) { - if (modules.isEmpty()) - return; - - // put java.base first - Set 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 modules; - LoaderModuleData(Loader loader, Set modules) { - this.loader = loader; - this.modules = Collections.unmodifiableSet(modules); - } - - Set 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 moduleOffsets = new LinkedHashMap<>(); - final Map> packageOffsets = new HashMap<>(); - final int size; - public ModuleIndex(Set 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 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; - } - } -} --- old/src/java.base/share/classes/jdk/internal/jimage/PReader.java 2015-06-23 14:29:30.000000000 +0200 +++ /dev/null 2015-06-23 14:29:30.000000000 +0200 @@ -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; - } - } -} --- old/src/java.base/share/classes/jdk/internal/jimage/PackageModuleMap.java 2015-06-23 14:29:31.000000000 +0200 +++ /dev/null 2015-06-23 14:29:31.000000000 +0200 @@ -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 readFrom(ImageReader reader) throws IOException { - Map result = new HashMap<>(); - List moduleNames = reader.getNames(MODULES_ENTRY); - - for (String moduleName : moduleNames) { - List packageNames = reader.getNames(moduleName + "/" + PACKAGES_ENTRY); - - for (String packageName : packageNames) { - result.put(packageName, moduleName); - } - } - return result; - } -} --- old/src/java.base/share/classes/jdk/internal/jimage/Resource.java 2015-06-23 14:29:31.000000000 +0200 +++ /dev/null 2015-06-23 14:29:31.000000000 +0200 @@ -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); - } -} --- old/src/java.base/share/classes/jdk/internal/jimage/concurrent/ConcurrentPReader.java 2015-06-23 14:29:31.000000000 +0200 +++ /dev/null 2015-06-23 14:29:31.000000000 +0200 @@ -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 CACHED_BUFFER = - new ThreadLocal() { - @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(); - } -} --- old/src/java.base/unix/native/libjava/ConcurrentPReader_md.c 2015-06-23 14:29:32.000000000 +0200 +++ /dev/null 2015-06-23 14:29:32.000000000 +0200 @@ -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 -#include - -#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; -} --- old/src/java.base/windows/native/libjava/ConcurrentPReader_md.c 2015-06-23 14:29:32.000000000 +0200 +++ /dev/null 2015-06-23 14:29:32.000000000 +0200 @@ -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 - -#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; -} -