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.Collections; 36 import java.util.HashMap; 37 import java.util.HashSet; 38 import java.util.List; 39 import java.util.Map; 40 import java.util.Objects; 41 import java.util.Set; 42 import java.util.stream.Collectors; 43 import java.util.stream.Stream; 44 45 import jdk.tools.jlink.internal.Archive.Entry; 46 import jdk.tools.jlink.internal.Archive.Entry.EntryType; 47 import jdk.tools.jlink.internal.ResourcePoolManager.CompressedModuleData; 48 import jdk.tools.jlink.plugin.PluginException; 49 import jdk.tools.jlink.plugin.ResourcePool; 50 import jdk.tools.jlink.plugin.ResourcePoolEntry; 51 52 /** 53 * An image (native endian.) 54 * <pre>{@code 55 * { 56 * u4 magic; 57 * u2 major_version; 58 * u2 minor_version; 59 * u4 resource_count; 60 * u4 table_length; 61 * u4 location_attributes_size; 62 * u4 strings_size; 63 * u4 redirect[table_length]; 64 * u4 offsets[table_length]; 65 * u1 location_attributes[location_attributes_size]; 66 * u1 strings[strings_size]; 67 * u1 content[if !EOF]; 68 * } 69 * }</pre> 70 */ 71 public final class ImageFileCreator { 72 private final Map<String, List<Entry>> entriesForModule = new HashMap<>(); 73 private final ImagePluginStack plugins; 74 private ImageFileCreator(ImagePluginStack plugins) { 75 this.plugins = Objects.requireNonNull(plugins); 76 } 77 78 public static ExecutableImage create(Set<Archive> archives, 79 ImagePluginStack plugins) 80 throws IOException { 81 return ImageFileCreator.create(archives, Collections.emptySet(), ByteOrder.nativeOrder(), 82 plugins); 83 } 84 85 public static ExecutableImage create(Set<Archive> archives, 86 ByteOrder byteOrder) 87 throws IOException { 88 return ImageFileCreator.create(archives, Collections.emptySet(), byteOrder, 89 new ImagePluginStack()); 90 } 91 92 public static ExecutableImage create(Set<Archive> archives, 93 Set<String> rootModules, 94 ByteOrder byteOrder, 95 ImagePluginStack plugins) 96 throws IOException 97 { 98 ImageFileCreator image = new ImageFileCreator(plugins); 99 try { 100 image.readAllEntries(archives); 101 // write to modular image 102 image.writeImage(archives, rootModules, byteOrder); 103 } finally { 104 //Close all archives 105 for (Archive a : archives) { 106 a.close(); 107 } 108 } 109 110 return plugins.getExecutableImage(); 111 } 112 113 private void readAllEntries(Set<Archive> archives) { 114 archives.stream().forEach((archive) -> { 115 Map<Boolean, List<Entry>> es; 116 try (Stream<Entry> entries = archive.entries()) { 117 es = entries.collect(Collectors.partitioningBy(n -> n.type() 118 == EntryType.CLASS_OR_RESOURCE)); 119 } 120 String mn = archive.moduleName(); 121 List<Entry> all = new ArrayList<>(); 122 all.addAll(es.get(false)); 123 all.addAll(es.get(true)); 124 entriesForModule.put(mn, all); 125 }); 126 } 127 128 public static void recreateJimage(Path jimageFile, 129 Set<Archive> archives, 130 ImagePluginStack pluginSupport) 131 throws IOException { 132 try { 133 Map<String, List<Entry>> entriesForModule 134 = archives.stream().collect(Collectors.toMap( 135 Archive::moduleName, 136 a -> { 137 try (Stream<Entry> entries = a.entries()) { 138 return entries.collect(Collectors.toList()); 139 } 140 })); 141 ByteOrder order = ByteOrder.nativeOrder(); 142 BasicImageWriter writer = new BasicImageWriter(order); 143 ResourcePoolManager pool = createPoolManager(archives, entriesForModule, order, writer); 144 try (OutputStream fos = Files.newOutputStream(jimageFile); 145 BufferedOutputStream bos = new BufferedOutputStream(fos); 146 DataOutputStream out = new DataOutputStream(bos)) { 147 generateJImage(pool, writer, pluginSupport, out); 148 } 149 } finally { 150 //Close all archives 151 for (Archive a : archives) { 152 a.close(); 153 } 154 } 155 } 156 157 private void writeImage(Set<Archive> archives, 158 Set<String> rootModules, 159 ByteOrder byteOrder) 160 throws IOException { 161 BasicImageWriter writer = new BasicImageWriter(byteOrder); 162 ResourcePoolManager allContent = createPoolManager(archives, 163 entriesForModule, byteOrder, writer); 164 ResourcePool result = generateJImage(allContent, 165 writer, plugins, plugins.getJImageFileOutputStream()); 166 167 //Handle files. 168 try { 169 plugins.storeFiles(rootModules, allContent.resourcePool(), result, writer); 170 } catch (Exception ex) { 171 if (JlinkTask.DEBUG) { 172 ex.printStackTrace(); 173 } 174 throw new IOException(ex); 175 } 176 } 177 178 private static ResourcePool generateJImage(ResourcePoolManager allContent, 179 BasicImageWriter writer, 180 ImagePluginStack pluginSupport, 181 DataOutputStream out 182 ) throws IOException { 183 ResourcePool resultResources; 184 try { 185 resultResources = pluginSupport.visitResources(allContent); 186 } catch (PluginException pe) { 187 if (JlinkTask.DEBUG) { 188 pe.printStackTrace(); 189 } 190 throw pe; 191 } catch (Exception ex) { 192 if (JlinkTask.DEBUG) { 193 ex.printStackTrace(); 194 } 195 throw new IOException(ex); 196 } 197 Set<String> duplicates = new HashSet<>(); 198 long[] offset = new long[1]; 199 200 List<ResourcePoolEntry> content = new ArrayList<>(); 201 List<String> paths = new ArrayList<>(); 202 // the order of traversing the resources and the order of 203 // the module content being written must be the same 204 resultResources.entries().forEach(res -> { 205 if (res.type().equals(ResourcePoolEntry.Type.CLASS_OR_RESOURCE)) { 206 String path = res.path(); 207 content.add(res); 208 long uncompressedSize = res.contentLength(); 209 long compressedSize = 0; 210 if (res instanceof CompressedModuleData) { 211 CompressedModuleData comp 212 = (CompressedModuleData) res; 213 compressedSize = res.contentLength(); 214 uncompressedSize = comp.getUncompressedSize(); 215 } 216 long onFileSize = res.contentLength(); 217 218 if (duplicates.contains(path)) { 219 System.err.format("duplicate resource \"%s\", skipping%n", 220 path); 221 // TODO Need to hang bytes on resource and write 222 // from resource not zip. 223 // Skipping resource throws off writing from zip. 224 offset[0] += onFileSize; 225 return; 226 } 227 duplicates.add(path); 228 writer.addLocation(path, offset[0], compressedSize, uncompressedSize); 229 paths.add(path); 230 offset[0] += onFileSize; 231 } 232 }); 233 234 ImageResourcesTree tree = new ImageResourcesTree(offset[0], writer, paths); 235 236 // write header and indices 237 byte[] bytes = writer.getBytes(); 238 out.write(bytes, 0, bytes.length); 239 240 // write module content 241 content.stream().forEach((res) -> { 242 res.write(out); 243 }); 244 245 tree.addContent(out); 246 247 out.close(); 248 249 return resultResources; 250 } 251 252 private static ResourcePoolManager createPoolManager(Set<Archive> archives, 253 Map<String, List<Entry>> entriesForModule, 254 ByteOrder byteOrder, 255 BasicImageWriter writer) throws IOException { 256 ResourcePoolManager resources = new ResourcePoolManager(byteOrder, new StringTable() { 257 258 @Override 259 public int addString(String str) { 260 return writer.addString(str); 261 } 262 263 @Override 264 public String getString(int id) { 265 return writer.getString(id); 266 } 267 }); 268 269 for (Archive archive : archives) { 270 String mn = archive.moduleName(); 271 entriesForModule.get(mn).stream() 272 .map(e -> new ArchiveEntryResourcePoolEntry(mn, 273 e.getResourcePoolEntryName(), e)) 274 .forEach(resources::add); 275 } 276 return resources; 277 } 278 279 /** 280 * Helper method that splits a Resource path onto 3 items: module, parent 281 * and resource name. 282 * 283 * @param path 284 * @return An array containing module, parent and name. 285 */ 286 public static String[] splitPath(String path) { 287 Objects.requireNonNull(path); 288 String noRoot = path.substring(1); 289 int pkgStart = noRoot.indexOf("/"); 290 String module = noRoot.substring(0, pkgStart); 291 List<String> result = new ArrayList<>(); 292 result.add(module); 293 String pkg = noRoot.substring(pkgStart + 1); 294 String resName; 295 int pkgEnd = pkg.lastIndexOf("/"); 296 if (pkgEnd == -1) { // No package. 297 resName = pkg; 298 } else { 299 resName = pkg.substring(pkgEnd + 1); 300 } 301 302 pkg = toPackage(pkg, false); 303 result.add(pkg); 304 result.add(resName); 305 306 String[] array = new String[result.size()]; 307 return result.toArray(array); 308 } 309 310 /** 311 * Returns the path of the resource. 312 */ 313 public static String resourceName(String path) { 314 Objects.requireNonNull(path); 315 String s = path.substring(1); 316 int index = s.indexOf("/"); 317 return s.substring(index + 1); 318 } 319 320 public static String toPackage(String name) { 321 return toPackage(name, false); 322 } 323 324 private static String toPackage(String name, boolean log) { 325 int index = name.lastIndexOf('/'); 326 if (index > 0) { 327 return name.substring(0, index).replace('/', '.'); 328 } else { 329 // ## unnamed package 330 if (log) { 331 System.err.format("Warning: %s in unnamed package%n", name); 332 } 333 return ""; 334 } 335 } 336 }