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