--- /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 "";
+ }
+ }
+}