/* * 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.tools.jlink.internal; import java.io.BufferedOutputStream; 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.Objects; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; import jdk.tools.jlink.internal.Archive.Entry; import jdk.tools.jlink.internal.Archive.Entry.EntryType; import jdk.tools.jlink.internal.ResourcePoolManager.CompressedModuleData; import jdk.tools.jlink.plugin.PluginException; import jdk.tools.jlink.plugin.ResourcePool; import jdk.tools.jlink.plugin.ResourcePoolEntry; /** * 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 Map> entriesForModule = new HashMap<>(); private final ImagePluginStack plugins; private ImageFileCreator(ImagePluginStack plugins) { this.plugins = Objects.requireNonNull(plugins); } public static ExecutableImage create(Set archives, ImagePluginStack plugins) throws IOException { return ImageFileCreator.create(archives, ByteOrder.nativeOrder(), plugins); } public static ExecutableImage create(Set archives, ByteOrder byteOrder) throws IOException { return ImageFileCreator.create(archives, byteOrder, new ImagePluginStack()); } public static ExecutableImage create(Set archives, ByteOrder byteOrder, ImagePluginStack plugins) throws IOException { ImageFileCreator image = new ImageFileCreator(plugins); try { image.readAllEntries(archives); // write to modular image image.writeImage(archives, byteOrder); } finally { //Close all archives for (Archive a : archives) { a.close(); } } return plugins.getExecutableImage(); } private void readAllEntries(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); }); } public static void recreateJimage(Path jimageFile, Set archives, ImagePluginStack pluginSupport) throws IOException { try { Map> entriesForModule = archives.stream().collect(Collectors.toMap( Archive::moduleName, a -> { try (Stream entries = a.entries()) { return entries.collect(Collectors.toList()); } })); ByteOrder order = ByteOrder.nativeOrder(); BasicImageWriter writer = new BasicImageWriter(order); ResourcePoolManager pool = createPoolManager(archives, entriesForModule, order, writer); try (OutputStream fos = Files.newOutputStream(jimageFile); BufferedOutputStream bos = new BufferedOutputStream(fos); DataOutputStream out = new DataOutputStream(bos)) { generateJImage(pool, writer, pluginSupport, out); } } finally { //Close all archives for (Archive a : archives) { a.close(); } } } private void writeImage(Set archives, ByteOrder byteOrder) throws IOException { BasicImageWriter writer = new BasicImageWriter(byteOrder); ResourcePoolManager allContent = createPoolManager(archives, entriesForModule, byteOrder, writer); ResourcePool result = generateJImage(allContent, writer, plugins, plugins.getJImageFileOutputStream()); //Handle files. try { plugins.storeFiles(allContent.resourcePool(), result, writer); } catch (Exception ex) { if (JlinkTask.DEBUG) { ex.printStackTrace(); } throw new IOException(ex); } } private static ResourcePool generateJImage(ResourcePoolManager allContent, BasicImageWriter writer, ImagePluginStack pluginSupport, DataOutputStream out ) throws IOException { ResourcePool resultResources; try { resultResources = pluginSupport.visitResources(allContent); } catch (PluginException pe) { if (JlinkTask.DEBUG) { pe.printStackTrace(); } throw pe; } catch (Exception ex) { if (JlinkTask.DEBUG) { ex.printStackTrace(); } throw new IOException(ex); } Set duplicates = new HashSet<>(); long[] offset = new long[1]; 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 resultResources.entries().forEach(res -> { if (res.type().equals(ResourcePoolEntry.Type.CLASS_OR_RESOURCE)) { String path = res.path(); content.add(res); long uncompressedSize = res.contentLength(); long compressedSize = 0; if (res instanceof CompressedModuleData) { CompressedModuleData comp = (CompressedModuleData) res; compressedSize = res.contentLength(); uncompressedSize = comp.getUncompressedSize(); } long onFileSize = res.contentLength(); 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[0] += onFileSize; return; } duplicates.add(path); writer.addLocation(path, offset[0], compressedSize, uncompressedSize); paths.add(path); offset[0] += onFileSize; } }); ImageResourcesTree tree = new ImageResourcesTree(offset[0], writer, paths); // write header and indices byte[] bytes = writer.getBytes(); out.write(bytes, 0, bytes.length); // write module content content.stream().forEach((res) -> { res.write(out); }); tree.addContent(out); out.close(); return resultResources; } private static ResourcePoolManager createPoolManager(Set archives, Map> entriesForModule, ByteOrder byteOrder, BasicImageWriter writer) throws IOException { ResourcePoolManager resources = new ResourcePoolManager(byteOrder, new StringTable() { @Override public int addString(String str) { return writer.addString(str); } @Override public String getString(int id) { return writer.getString(id); } }); for (Archive archive : archives) { String mn = archive.moduleName(); for (Entry entry : entriesForModule.get(mn)) { resources.add(new ArchiveEntryResourcePoolEntry(mn, entry.getResourceName(), entry)); } } return resources; } /** * 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); } /** * Returns the path of the resource. */ public static String resourceName(String path) { Objects.requireNonNull(path); String s = path.substring(1); int index = s.indexOf("/"); return s.substring(index + 1); } public static String toPackage(String name) { return toPackage(name, false); } 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 ""; } } }