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 import jdk.tools.jlink.internal.Archive.Entry; 44 import jdk.tools.jlink.internal.Archive.Entry.EntryType; 45 import jdk.tools.jlink.internal.ResourcePoolManager.CompressedModuleData; 46 import jdk.tools.jlink.plugin.PluginException; 47 import jdk.tools.jlink.plugin.ResourcePool; 48 import jdk.tools.jlink.plugin.ResourcePoolEntry; 49 50 /** 51 * An image (native endian.) 52 * <pre>{@code 53 * { 54 * u4 magic; 55 * u2 major_version; 56 * u2 minor_version; 57 * u4 resource_count; 58 * u4 table_length; 59 * u4 location_attributes_size; 60 * u4 strings_size; 61 * u4 redirect[table_length]; 62 * u4 offsets[table_length]; 63 * u1 location_attributes[location_attributes_size]; 64 * u1 strings[strings_size]; 65 * u1 content[if !EOF]; 66 * } 67 * }</pre> 68 */ 69 public final class ImageFileCreator { 70 private final Map<String, List<Entry>> entriesForModule = new HashMap<>(); 71 private final ImagePluginStack plugins; 72 private ImageFileCreator(ImagePluginStack plugins) { 73 this.plugins = Objects.requireNonNull(plugins); 74 } 75 76 public static ExecutableImage create(Set<Archive> archives, 77 ImagePluginStack plugins) 78 throws IOException { 79 return ImageFileCreator.create(archives, ByteOrder.nativeOrder(), 80 plugins); 81 } 82 83 public static ExecutableImage create(Set<Archive> archives, 84 ByteOrder byteOrder) 85 throws IOException { 86 return ImageFileCreator.create(archives, byteOrder, 87 new ImagePluginStack()); 88 } 89 90 public static ExecutableImage create(Set<Archive> archives, 91 ByteOrder byteOrder, 92 ImagePluginStack plugins) 93 throws IOException 94 { 95 ImageFileCreator image = new ImageFileCreator(plugins); 96 try { 97 image.readAllEntries(archives); 98 // write to modular image 99 image.writeImage(archives, byteOrder); 100 } finally { 101 //Close all archives 102 for (Archive a : archives) { 103 a.close(); 104 } 105 } 106 107 return plugins.getExecutableImage(); 108 } 109 110 private void readAllEntries(Set<Archive> archives) { 111 archives.stream().forEach((archive) -> { 112 Map<Boolean, List<Entry>> es; 113 try (Stream<Entry> entries = archive.entries()) { 114 es = entries.collect(Collectors.partitioningBy(n -> n.type() 115 == EntryType.CLASS_OR_RESOURCE)); 116 } 117 String mn = archive.moduleName(); 118 List<Entry> all = new ArrayList<>(); 119 all.addAll(es.get(false)); 120 all.addAll(es.get(true)); 121 entriesForModule.put(mn, all); 122 }); 123 } 124 125 public static boolean isClassPackage(String path) { 126 return path.endsWith(".class") && !path.endsWith("module-info.class"); 127 } 128 129 public static void recreateJimage(Path jimageFile, 130 Set<Archive> archives, 131 ImagePluginStack pluginSupport) 132 throws IOException { 133 try { 134 Map<String, List<Entry>> entriesForModule 135 = archives.stream().collect(Collectors.toMap( 136 Archive::moduleName, 137 a -> { 138 try (Stream<Entry> entries = a.entries()) { 139 return entries.collect(Collectors.toList()); 140 } 141 })); 142 ByteOrder order = ByteOrder.nativeOrder(); 143 BasicImageWriter writer = new BasicImageWriter(order); 144 ResourcePoolManager pool = createPoolManager(archives, entriesForModule, order, writer); 145 try (OutputStream fos = Files.newOutputStream(jimageFile); 146 BufferedOutputStream bos = new BufferedOutputStream(fos); 147 DataOutputStream out = new DataOutputStream(bos)) { 148 generateJImage(pool, writer, pluginSupport, out); 149 } 150 } finally { 151 //Close all archives 152 for (Archive a : archives) { 153 a.close(); 154 } 155 } 156 } 157 158 private void writeImage(Set<Archive> archives, 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(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 for (Archive archive : archives) { 269 String mn = archive.moduleName(); 270 for (Entry entry : entriesForModule.get(mn)) { 271 String path; 272 if (entry.type() == EntryType.CLASS_OR_RESOURCE) { 273 // Removal of "classes/" radical. 274 path = entry.name(); 275 if (path.endsWith("module-info.class")) { 276 path = "/" + path; 277 } else { 278 path = "/" + mn + "/" + path; 279 } 280 } else { 281 // Entry.path() contains the kind of file native, conf, bin, ... 282 // Keep it to avoid naming conflict (eg: native/jvm.cfg and config/jvm.cfg 283 path = "/" + mn + "/" + entry.path(); 284 } 285 286 resources.add(new ArchiveEntryResourcePoolEntry(mn, path, entry)); 287 } 288 } 289 return resources; 290 } 291 292 /** 293 * Helper method that splits a Resource path onto 3 items: module, parent 294 * and resource name. 295 * 296 * @param path 297 * @return An array containing module, parent and name. 298 */ 299 public static String[] splitPath(String path) { 300 Objects.requireNonNull(path); 301 String noRoot = path.substring(1); 302 int pkgStart = noRoot.indexOf("/"); 303 String module = noRoot.substring(0, pkgStart); 304 List<String> result = new ArrayList<>(); 305 result.add(module); 306 String pkg = noRoot.substring(pkgStart + 1); 307 String resName; 308 int pkgEnd = pkg.lastIndexOf("/"); 309 if (pkgEnd == -1) { // No package. 310 resName = pkg; 311 } else { 312 resName = pkg.substring(pkgEnd + 1); 313 } 314 315 pkg = toPackage(pkg, false); 316 result.add(pkg); 317 result.add(resName); 318 319 String[] array = new String[result.size()]; 320 return result.toArray(array); 321 } 322 323 private static String toPackage(String name, boolean log) { 324 int index = name.lastIndexOf('/'); 325 if (index > 0) { 326 return name.substring(0, index).replace('/', '.'); 327 } else { 328 // ## unnamed package 329 if (log) { 330 System.err.format("Warning: %s in unnamed package%n", name); 331 } 332 return ""; 333 } 334 } 335 }