ImageFileCreator.java revision 14906:5cfbcb4e6009
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 */ 25package jdk.tools.jlink.internal; 26 27import java.io.BufferedOutputStream; 28import java.io.DataOutputStream; 29import java.io.IOException; 30import java.io.OutputStream; 31import java.nio.ByteOrder; 32import java.nio.file.Files; 33import java.nio.file.Path; 34import java.util.ArrayList; 35import java.util.HashMap; 36import java.util.HashSet; 37import java.util.List; 38import java.util.Map; 39import java.util.Objects; 40import java.util.Set; 41import java.util.stream.Collectors; 42import java.util.stream.Stream; 43import jdk.tools.jlink.internal.Archive.Entry; 44import jdk.tools.jlink.internal.Archive.Entry.EntryType; 45import jdk.tools.jlink.internal.ModulePoolImpl.CompressedModuleData; 46import jdk.tools.jlink.plugin.ExecutableImage; 47import jdk.tools.jlink.plugin.PluginException; 48import jdk.tools.jlink.plugin.ModuleEntry; 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 */ 69public 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 ModulePoolImpl pool = createPools(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 ModulePoolImpl allContent = createPools(archives, 163 entriesForModule, byteOrder, writer); 164 ModulePoolImpl result = generateJImage(allContent, 165 writer, plugins, plugins.getJImageFileOutputStream()); 166 167 //Handle files. 168 try { 169 plugins.storeFiles(allContent, result, writer); 170 } catch (Exception ex) { 171 throw new IOException(ex); 172 } 173 } 174 175 private static ModulePoolImpl generateJImage(ModulePoolImpl allContent, 176 BasicImageWriter writer, 177 ImagePluginStack pluginSupport, 178 DataOutputStream out 179 ) throws IOException { 180 ModulePoolImpl resultResources; 181 try { 182 resultResources = pluginSupport.visitResources(allContent); 183 } catch (PluginException pe) { 184 throw pe; 185 } catch (Exception ex) { 186 throw new IOException(ex); 187 } 188 Set<String> duplicates = new HashSet<>(); 189 long[] offset = new long[1]; 190 191 List<ModuleEntry> content = new ArrayList<>(); 192 List<String> paths = new ArrayList<>(); 193 // the order of traversing the resources and the order of 194 // the module content being written must be the same 195 resultResources.entries().forEach(res -> { 196 if (res.getType().equals(ModuleEntry.Type.CLASS_OR_RESOURCE)) { 197 String path = res.getPath(); 198 content.add(res); 199 long uncompressedSize = res.getLength(); 200 long compressedSize = 0; 201 if (res instanceof CompressedModuleData) { 202 CompressedModuleData comp 203 = (CompressedModuleData) res; 204 compressedSize = res.getLength(); 205 uncompressedSize = comp.getUncompressedSize(); 206 } 207 long onFileSize = res.getLength(); 208 209 if (duplicates.contains(path)) { 210 System.err.format("duplicate resource \"%s\", skipping%n", 211 path); 212 // TODO Need to hang bytes on resource and write 213 // from resource not zip. 214 // Skipping resource throws off writing from zip. 215 offset[0] += onFileSize; 216 return; 217 } 218 duplicates.add(path); 219 writer.addLocation(path, offset[0], compressedSize, uncompressedSize); 220 paths.add(path); 221 offset[0] += onFileSize; 222 } 223 }); 224 225 ImageResourcesTree tree = new ImageResourcesTree(offset[0], writer, paths); 226 227 // write header and indices 228 byte[] bytes = writer.getBytes(); 229 out.write(bytes, 0, bytes.length); 230 231 // write module content 232 content.stream().forEach((res) -> { 233 res.write(out); 234 }); 235 236 tree.addContent(out); 237 238 out.close(); 239 240 return resultResources; 241 } 242 243 private static ModulePoolImpl createPools(Set<Archive> archives, 244 Map<String, List<Entry>> entriesForModule, 245 ByteOrder byteOrder, 246 BasicImageWriter writer) throws IOException { 247 ModulePoolImpl resources = new ModulePoolImpl(byteOrder, new StringTable() { 248 249 @Override 250 public int addString(String str) { 251 return writer.addString(str); 252 } 253 254 @Override 255 public String getString(int id) { 256 return writer.getString(id); 257 } 258 }); 259 for (Archive archive : archives) { 260 String mn = archive.moduleName(); 261 for (Entry entry : entriesForModule.get(mn)) { 262 String path; 263 if (entry.type() == EntryType.CLASS_OR_RESOURCE) { 264 // Removal of "classes/" radical. 265 path = entry.name(); 266 if (path.endsWith("module-info.class")) { 267 path = "/" + path; 268 } else { 269 path = "/" + mn + "/" + path; 270 } 271 } else { 272 // Entry.path() contains the kind of file native, conf, bin, ... 273 // Keep it to avoid naming conflict (eg: native/jvm.cfg and config/jvm.cfg 274 path = "/" + mn + "/" + entry.path(); 275 } 276 277 resources.add(new ArchiveEntryModuleEntry(mn, path, entry)); 278 } 279 } 280 return resources; 281 } 282 283 /** 284 * Helper method that splits a Resource path onto 3 items: module, parent 285 * and resource name. 286 * 287 * @param path 288 * @return An array containing module, parent and name. 289 */ 290 public static String[] splitPath(String path) { 291 Objects.requireNonNull(path); 292 String noRoot = path.substring(1); 293 int pkgStart = noRoot.indexOf("/"); 294 String module = noRoot.substring(0, pkgStart); 295 List<String> result = new ArrayList<>(); 296 result.add(module); 297 String pkg = noRoot.substring(pkgStart + 1); 298 String resName; 299 int pkgEnd = pkg.lastIndexOf("/"); 300 if (pkgEnd == -1) { // No package. 301 resName = pkg; 302 } else { 303 resName = pkg.substring(pkgEnd + 1); 304 } 305 306 pkg = toPackage(pkg, false); 307 result.add(pkg); 308 result.add(resName); 309 310 String[] array = new String[result.size()]; 311 return result.toArray(array); 312 } 313 314 private static String toPackage(String name, boolean log) { 315 int index = name.lastIndexOf('/'); 316 if (index > 0) { 317 return name.substring(0, index).replace('/', '.'); 318 } else { 319 // ## unnamed package 320 if (log) { 321 System.err.format("Warning: %s in unnamed package%n", name); 322 } 323 return ""; 324 } 325 } 326} 327