1 /*
   2  * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 package jdk.tools.jlink.internal;
  26 
  27 import java.io.BufferedOutputStream;
  28 import java.io.DataOutputStream;
  29 import java.io.IOException;
  30 import java.io.OutputStream;
  31 import java.nio.ByteOrder;
  32 import java.nio.file.Files;
  33 import java.nio.file.Path;
  34 import java.util.ArrayList;

  35 import java.util.HashMap;
  36 import java.util.HashSet;
  37 import java.util.List;
  38 import java.util.Map;
  39 import java.util.Objects;
  40 import java.util.Set;
  41 import java.util.stream.Collectors;
  42 import java.util.stream.Stream;
  43 
  44 import jdk.tools.jlink.internal.Archive.Entry;
  45 import jdk.tools.jlink.internal.Archive.Entry.EntryType;
  46 import jdk.tools.jlink.internal.ResourcePoolManager.CompressedModuleData;
  47 import jdk.tools.jlink.plugin.PluginException;
  48 import jdk.tools.jlink.plugin.ResourcePool;
  49 import jdk.tools.jlink.plugin.ResourcePoolEntry;
  50 
  51 /**
  52  * An image (native endian.)
  53  * <pre>{@code
  54  * {
  55  *   u4 magic;
  56  *   u2 major_version;
  57  *   u2 minor_version;
  58  *   u4 resource_count;
  59  *   u4 table_length;
  60  *   u4 location_attributes_size;
  61  *   u4 strings_size;
  62  *   u4 redirect[table_length];
  63  *   u4 offsets[table_length];
  64  *   u1 location_attributes[location_attributes_size];
  65  *   u1 strings[strings_size];
  66  *   u1 content[if !EOF];
  67  * }
  68  * }</pre>
  69  */
  70 public final class ImageFileCreator {
  71     private final Map<String, List<Entry>> entriesForModule = new HashMap<>();
  72     private final ImagePluginStack plugins;
  73     private ImageFileCreator(ImagePluginStack plugins) {
  74         this.plugins = Objects.requireNonNull(plugins);
  75     }
  76 
  77     public static ExecutableImage create(Set<Archive> archives,
  78             ImagePluginStack plugins)
  79             throws IOException {
  80         return ImageFileCreator.create(archives, ByteOrder.nativeOrder(),
  81                 plugins);
  82     }
  83 
  84     public static ExecutableImage create(Set<Archive> archives,
  85             ByteOrder byteOrder)
  86             throws IOException {
  87         return ImageFileCreator.create(archives, byteOrder,
  88                 new ImagePluginStack());
  89     }
  90 
  91     public static ExecutableImage create(Set<Archive> archives,

  92             ByteOrder byteOrder,
  93             ImagePluginStack plugins)
  94             throws IOException
  95     {
  96         ImageFileCreator image = new ImageFileCreator(plugins);
  97         try {
  98             image.readAllEntries(archives);
  99             // write to modular image
 100             image.writeImage(archives, byteOrder);
 101         } finally {
 102             //Close all archives
 103             for (Archive a : archives) {
 104                 a.close();
 105             }
 106         }
 107 
 108         return plugins.getExecutableImage();
 109     }
 110 
 111     private void readAllEntries(Set<Archive> archives) {
 112         archives.stream().forEach((archive) -> {
 113             Map<Boolean, List<Entry>> es;
 114             try (Stream<Entry> entries = archive.entries()) {
 115                 es = entries.collect(Collectors.partitioningBy(n -> n.type()
 116                         == EntryType.CLASS_OR_RESOURCE));
 117             }
 118             String mn = archive.moduleName();
 119             List<Entry> all = new ArrayList<>();
 120             all.addAll(es.get(false));
 121             all.addAll(es.get(true));
 122             entriesForModule.put(mn, all);
 123         });
 124     }
 125 
 126     public static void recreateJimage(Path jimageFile,
 127             Set<Archive> archives,
 128             ImagePluginStack pluginSupport)
 129             throws IOException {
 130         try {
 131             Map<String, List<Entry>> entriesForModule
 132                     = archives.stream().collect(Collectors.toMap(
 133                                     Archive::moduleName,
 134                                     a -> {
 135                                         try (Stream<Entry> entries = a.entries()) {
 136                                             return entries.collect(Collectors.toList());
 137                                         }
 138                                     }));
 139             ByteOrder order = ByteOrder.nativeOrder();
 140             BasicImageWriter writer = new BasicImageWriter(order);
 141             ResourcePoolManager pool = createPoolManager(archives, entriesForModule, order, writer);
 142             try (OutputStream fos = Files.newOutputStream(jimageFile);
 143                     BufferedOutputStream bos = new BufferedOutputStream(fos);
 144                     DataOutputStream out = new DataOutputStream(bos)) {
 145                 generateJImage(pool, writer, pluginSupport, out);
 146             }
 147         } finally {
 148             //Close all archives
 149             for (Archive a : archives) {
 150                 a.close();
 151             }
 152         }
 153     }
 154 
 155     private void writeImage(Set<Archive> archives,

 156             ByteOrder byteOrder)
 157             throws IOException {
 158         BasicImageWriter writer = new BasicImageWriter(byteOrder);
 159         ResourcePoolManager allContent = createPoolManager(archives,
 160                 entriesForModule, byteOrder, writer);
 161         ResourcePool result = generateJImage(allContent,
 162              writer, plugins, plugins.getJImageFileOutputStream());
 163 
 164         //Handle files.
 165         try {
 166             plugins.storeFiles(allContent.resourcePool(), result, writer);
 167         } catch (Exception ex) {
 168             if (JlinkTask.DEBUG) {
 169                 ex.printStackTrace();
 170             }
 171             throw new IOException(ex);
 172         }
 173     }
 174 
 175     private static ResourcePool generateJImage(ResourcePoolManager allContent,
 176             BasicImageWriter writer,
 177             ImagePluginStack pluginSupport,
 178             DataOutputStream out
 179     ) throws IOException {
 180         ResourcePool resultResources;
 181         try {
 182             resultResources = pluginSupport.visitResources(allContent);
 183         } catch (PluginException pe) {
 184             if (JlinkTask.DEBUG) {
 185                 pe.printStackTrace();
 186             }
 187             throw pe;
 188         } catch (Exception ex) {
 189             if (JlinkTask.DEBUG) {
 190                 ex.printStackTrace();
 191             }
 192             throw new IOException(ex);
 193         }
 194         Set<String> duplicates = new HashSet<>();
 195         long[] offset = new long[1];
 196 
 197         List<ResourcePoolEntry> content = new ArrayList<>();
 198         List<String> paths = new ArrayList<>();
 199                  // the order of traversing the resources and the order of
 200         // the module content being written must be the same
 201         resultResources.entries().forEach(res -> {
 202             if (res.type().equals(ResourcePoolEntry.Type.CLASS_OR_RESOURCE)) {
 203                 String path = res.path();
 204                 content.add(res);
 205                 long uncompressedSize = res.contentLength();
 206                 long compressedSize = 0;
 207                 if (res instanceof CompressedModuleData) {
 208                     CompressedModuleData comp
 209                             = (CompressedModuleData) res;
 210                     compressedSize = res.contentLength();
 211                     uncompressedSize = comp.getUncompressedSize();
 212                 }
 213                 long onFileSize = res.contentLength();
 214 
 215                 if (duplicates.contains(path)) {
 216                     System.err.format("duplicate resource \"%s\", skipping%n",
 217                             path);
 218                     // TODO Need to hang bytes on resource and write
 219                     // from resource not zip.
 220                     // Skipping resource throws off writing from zip.
 221                     offset[0] += onFileSize;
 222                     return;
 223                 }
 224                 duplicates.add(path);
 225                 writer.addLocation(path, offset[0], compressedSize, uncompressedSize);
 226                 paths.add(path);
 227                 offset[0] += onFileSize;
 228             }
 229         });
 230 
 231         ImageResourcesTree tree = new ImageResourcesTree(offset[0], writer, paths);
 232 
 233         // write header and indices
 234         byte[] bytes = writer.getBytes();
 235         out.write(bytes, 0, bytes.length);
 236 
 237         // write module content
 238         content.stream().forEach((res) -> {
 239             res.write(out);
 240         });
 241 
 242         tree.addContent(out);
 243 
 244         out.close();
 245 
 246         return resultResources;
 247     }
 248 
 249     private static ResourcePoolManager createPoolManager(Set<Archive> archives,
 250             Map<String, List<Entry>> entriesForModule,
 251             ByteOrder byteOrder,
 252             BasicImageWriter writer) throws IOException {
 253         ResourcePoolManager resources = new ResourcePoolManager(byteOrder, new StringTable() {
 254 
 255             @Override
 256             public int addString(String str) {
 257                 return writer.addString(str);
 258             }
 259 
 260             @Override
 261             public String getString(int id) {
 262                 return writer.getString(id);
 263             }
 264         });
 265 
 266         for (Archive archive : archives) {
 267             String mn = archive.moduleName();
 268             entriesForModule.get(mn).stream()
 269                 .map(e -> new ArchiveEntryResourcePoolEntry(mn,
 270                                     e.getResourcePoolEntryName(), e))
 271                 .forEach(resources::add);
 272         }
 273         return resources;
 274     }
 275 
 276     /**
 277      * Helper method that splits a Resource path onto 3 items: module, parent
 278      * and resource name.
 279      *
 280      * @param path
 281      * @return An array containing module, parent and name.
 282      */
 283     public static String[] splitPath(String path) {
 284         Objects.requireNonNull(path);
 285         String noRoot = path.substring(1);
 286         int pkgStart = noRoot.indexOf("/");
 287         String module = noRoot.substring(0, pkgStart);
 288         List<String> result = new ArrayList<>();
 289         result.add(module);
 290         String pkg = noRoot.substring(pkgStart + 1);
 291         String resName;
 292         int pkgEnd = pkg.lastIndexOf("/");
 293         if (pkgEnd == -1) { // No package.
 294             resName = pkg;
 295         } else {
 296             resName = pkg.substring(pkgEnd + 1);
 297         }
 298 
 299         pkg = toPackage(pkg, false);
 300         result.add(pkg);
 301         result.add(resName);
 302 
 303         String[] array = new String[result.size()];
 304         return result.toArray(array);
 305     }
 306 
 307     /**
 308      * Returns the path of the resource.
 309      */
 310     public static String resourceName(String path) {
 311         Objects.requireNonNull(path);
 312         String s = path.substring(1);
 313         int index = s.indexOf("/");
 314         return s.substring(index + 1);
 315     }
 316 
 317     public static String toPackage(String name) {
 318         return toPackage(name, false);
 319     }
 320 
 321     private static String toPackage(String name, boolean log) {
 322         int index = name.lastIndexOf('/');
 323         if (index > 0) {
 324             return name.substring(0, index).replace('/', '.');
 325         } else {
 326             // ## unnamed package
 327             if (log) {
 328                 System.err.format("Warning: %s in unnamed package%n", name);
 329             }
 330             return "";
 331         }
 332     }
 333 }
--- EOF ---