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.internal.jimage; 26 27 import java.io.BufferedOutputStream; 28 import java.io.ByteArrayOutputStream; 29 import java.io.DataOutputStream; 30 import java.io.IOException; 31 import java.io.InputStream; 32 import java.io.OutputStream; 33 import java.nio.ByteBuffer; 34 import java.nio.ByteOrder; 35 import java.nio.file.Files; 36 import java.nio.file.Path; 37 import java.util.ArrayList; 38 import java.util.HashMap; 39 import java.util.HashSet; 40 import java.util.List; 41 import java.util.Map; 42 import java.util.Objects; 43 import java.util.Set; 44 import java.util.function.Consumer; 45 import java.util.function.Function; 46 import java.util.stream.Collectors; 47 import java.util.stream.Stream; 48 import jdk.internal.jimage.Archive.Entry; 49 import jdk.internal.jimage.Archive.Entry.EntryType; 50 import static jdk.internal.jimage.BasicImageWriter.BOOT_NAME; 51 import static jdk.internal.jimage.BasicImageWriter.IMAGE_EXT; 52 53 /** 54 * An image (native endian.) 55 * <pre>{@code 56 * { 57 * u4 magic; 58 * u2 major_version; 59 * u2 minor_version; 60 * u4 resource_count; 61 * u4 table_length; 62 * u4 location_attributes_size; 63 * u4 strings_size; 64 * u4 redirect[table_length]; 65 * u4 offsets[table_length]; 66 * u1 location_attributes[location_attributes_size]; 67 * u1 strings[strings_size]; 68 * u1 content[if !EOF]; 69 * } 70 * }</pre> 71 */ 72 public final class ImageFileCreator { 73 private final Path root; 74 private final Path mdir; 75 private final Map<String, List<Entry>> entriesForModule = new HashMap<>(); 76 private ImageFileCreator(Path path) { 77 this.root = path; 78 this.mdir = root.resolve(path.getFileSystem().getPath("lib", "modules")); 79 } 80 81 public static ImageFileCreator create(Path output, 82 Set<Archive> archives) 83 throws IOException { 84 return create(output, BOOT_NAME, archives, ByteOrder.nativeOrder()); 85 } 86 87 public static ImageFileCreator create(Path output, 88 Set<Archive> archives, 89 ByteOrder byteOrder) 90 throws IOException { 91 return create(output, BOOT_NAME, archives, byteOrder); 92 } 93 94 public static ImageFileCreator create(Path output, 95 String fileName, 96 Set<Archive> archives, 97 ByteOrder byteOrder) 98 throws IOException 99 { 100 ImageFileCreator image = new ImageFileCreator(output); 101 // get all entries 102 Map<String, Set<String>> modulePackagesMap = new HashMap<>(); 103 image.readAllEntries(modulePackagesMap, archives); 104 // write to modular image 105 image.writeImage(fileName, modulePackagesMap, archives, byteOrder); 106 return image; 107 } 108 109 private void readAllEntries(Map<String, Set<String>> modulePackagesMap, 110 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 // Extract package names 123 Set<String> pkgs = es.get(true).stream().map(Entry::name) 124 .filter(n -> isClassPackage(n)) 125 .map(ImageFileCreator::toPackage) 126 .collect(Collectors.toSet()); 127 modulePackagesMap.put(mn, pkgs); 128 }); 129 } 130 131 public static boolean isClassPackage(String path) { 132 return path.endsWith(".class"); 133 } 134 135 public static boolean isResourcePackage(String path) { 136 path = path.substring(1); 137 path = path.substring(path.indexOf("/")+1); 138 return !path.startsWith("META-INF/"); 139 } 140 141 public static void recreateJimage(Path jimageFile, 142 Set<Archive> archives, 143 Map<String, Set<String>> modulePackages) 144 throws IOException { 145 Map<String, List<Entry>> entriesForModule 146 = archives.stream().collect(Collectors.toMap( 147 Archive::moduleName, 148 a -> { 149 try(Stream<Entry> entries = a.entries()) { 150 return entries.collect(Collectors.toList()); 151 } 152 })); 153 Map<String, Archive> nameToArchive 154 = archives.stream() 155 .collect(Collectors.toMap(Archive::moduleName, Function.identity())); 156 ByteOrder order = ByteOrder.nativeOrder(); 157 ResourcePoolImpl resources = createResources(modulePackages, nameToArchive, 158 (Entry t) -> { 159 throw new UnsupportedOperationException("Not supported, no external file " 160 + "in a jimage file"); 161 }, entriesForModule, order); 162 String fileName = jimageFile.getRoot().toString(); 163 generateJImage(jimageFile, fileName, resources, order); 164 } 165 166 private void writeImage(String fileName, 167 Map<String, Set<String>> modulePackagesMap, 168 Set<Archive> archives, 169 ByteOrder byteOrder) 170 throws IOException { 171 Files.createDirectories(mdir); 172 ExternalFilesWriter filesWriter = new ExternalFilesWriter(root); 173 // name to Archive file 174 Map<String, Archive> nameToArchive 175 = archives.stream() 176 .collect(Collectors.toMap(Archive::moduleName, Function.identity())); 177 ResourcePoolImpl resources = createResources(modulePackagesMap, 178 nameToArchive, filesWriter, 179 entriesForModule, byteOrder); 180 generateJImage(mdir.resolve(fileName + IMAGE_EXT), fileName, resources, 181 byteOrder); 182 } 183 184 private static void generateJImage(Path img, 185 String fileName, 186 ResourcePoolImpl resources, 187 ByteOrder byteOrder 188 ) throws IOException { 189 BasicImageWriter writer = new BasicImageWriter(byteOrder); 190 191 Map<String, Set<String>> modulePackagesMap = resources.getModulePackages(); 192 193 try (OutputStream fos = Files.newOutputStream(img); 194 BufferedOutputStream bos = new BufferedOutputStream(fos); 195 DataOutputStream out = new DataOutputStream(bos)) { 196 Set<String> duplicates = new HashSet<>(); 197 ImageModuleDataWriter moduleData = 198 ImageModuleDataWriter.buildModuleData(writer, modulePackagesMap); 199 moduleData.addLocation(fileName, writer); 200 long offset = moduleData.size(); 201 202 List<ResourcePool.Resource> content = new ArrayList<>(); 203 List<String> paths = new ArrayList<>(); 204 // the order of traversing the resources and the order of 205 // the module content being written must be the same 206 for (ResourcePool.Resource res : resources.getResources()) { 207 String path = res.getPath(); 208 int index = path.indexOf("/META-INF/"); 209 if (index != -1) { 210 path = path.substring(index + 1); 211 } 212 213 content.add(res); 214 long uncompressedSize = res.getLength(); 215 long compressedSize = 0; 216 if (res instanceof ResourcePool.CompressedResource) { 217 ResourcePool.CompressedResource comp = 218 (ResourcePool.CompressedResource) res; 219 compressedSize = res.getLength(); 220 uncompressedSize = comp.getUncompressedSize(); 221 } 222 long onFileSize = res.getLength(); 223 224 if (duplicates.contains(path)) { 225 System.err.format("duplicate resource \"%s\", skipping%n", 226 path); 227 // TODO Need to hang bytes on resource and write 228 // from resource not zip. 229 // Skipping resource throws off writing from zip. 230 offset += onFileSize; 231 continue; 232 } 233 duplicates.add(path); 234 writer.addLocation(path, offset, compressedSize, uncompressedSize); 235 paths.add(path); 236 offset += onFileSize; 237 } 238 239 ImageResourcesTree tree = new ImageResourcesTree(offset, writer, paths); 240 241 // write header and indices 242 byte[] bytes = writer.getBytes(); 243 out.write(bytes, 0, bytes.length); 244 245 // write module meta data 246 moduleData.writeTo(out); 247 248 // write module content 249 for(ResourcePool.Resource res : content) { 250 byte[] buf = res.getByteArray(); 251 out.write(buf, 0, buf.length); 252 } 253 254 tree.addContent(out); 255 } 256 } 257 258 private static ResourcePoolImpl createResources(Map<String, Set<String>> modulePackagesMap, 259 Map<String, Archive> nameToArchive, 260 Consumer<Entry> externalFileHandler, 261 Map<String, List<Entry>> entriesForModule, 262 ByteOrder byteOrder) throws IOException { 263 ResourcePoolImpl resources = new ResourcePoolImpl(byteOrder); 264 Set<String> mods = modulePackagesMap.keySet(); 265 for (String mn : mods) { 266 for (Entry entry : entriesForModule.get(mn)) { 267 String path = entry.name(); 268 if (entry.type() == EntryType.CLASS_OR_RESOURCE) { 269 if (!entry.path().endsWith(BOOT_NAME)) { 270 try (InputStream stream = entry.stream()) { 271 byte[] bytes = readAllBytes(stream); 272 path = "/" + mn + "/" + path; 273 try { 274 resources.addResource(new ResourcePool.Resource(path, 275 ByteBuffer.wrap(bytes))); 276 } catch (Exception ex) { 277 throw new IOException(ex); 278 } 279 } 280 } 281 } else { 282 externalFileHandler.accept(entry); 283 } 284 } 285 // Done with this archive, close it. 286 Archive archive = nameToArchive.get(mn); 287 archive.close(); 288 } 289 return resources; 290 } 291 292 private static final int BUF_SIZE = 8192; 293 294 private static byte[] readAllBytes(InputStream is) throws IOException { 295 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 296 byte[] buf = new byte[BUF_SIZE]; 297 while (true) { 298 int n = is.read(buf); 299 if (n < 0) { 300 break; 301 } 302 baos.write(buf, 0, n); 303 } 304 return baos.toByteArray(); 305 } 306 307 /** 308 * Helper method that splits a Resource path onto 3 items: module, parent 309 * and resource name. 310 * 311 * @param path 312 * @return An array containing module, parent and name. 313 */ 314 public static String[] splitPath(String path) { 315 Objects.requireNonNull(path); 316 String noRoot = path.substring(1); 317 int pkgStart = noRoot.indexOf("/"); 318 String module = noRoot.substring(0, pkgStart); 319 List<String> result = new ArrayList<>(); 320 result.add(module); 321 String pkg = noRoot.substring(pkgStart + 1); 322 String resName; 323 int pkgEnd = pkg.lastIndexOf("/"); 324 if (pkgEnd == -1) { // No package. 325 resName = pkg; 326 } else { 327 resName = pkg.substring(pkgEnd + 1); 328 } 329 330 pkg = toPackage(pkg, false); 331 result.add(pkg); 332 result.add(resName); 333 334 String[] array = new String[result.size()]; 335 return result.toArray(array); 336 } 337 338 private static String toPackage(String name) { 339 String pkg = toPackage(name, true); 340 return pkg; 341 } 342 343 private static String toPackage(String name, boolean log) { 344 int index = name.lastIndexOf('/'); 345 if (index > 0) { 346 return name.substring(0, index).replace('/', '.'); 347 } else { 348 // ## unnamed package 349 if (log) { 350 System.err.format("Warning: %s in unnamed package%n", name); 351 } 352 return ""; 353 } 354 } 355 }