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