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.OutputStream; 32 import java.nio.ByteOrder; 33 import java.nio.file.Files; 34 import java.nio.file.Path; 35 import java.util.ArrayList; 36 import java.util.HashMap; 37 import java.util.HashSet; 38 import java.util.List; 39 import java.util.Map; 40 import java.util.Set; 41 import java.util.function.Consumer; 42 import java.util.function.Function; 43 import java.util.stream.Collectors; 44 import java.util.zip.DataFormatException; 45 import java.util.zip.Deflater; 46 import java.util.zip.Inflater; 47 import jdk.internal.jimage.ImageModules.Loader; 48 import jdk.internal.jimage.ImageModules.ModuleIndex; 49 50 /** 51 * An image (native endian.) 52 * <pre>{@code 53 * { 54 * u4 magic; 55 * u2 major_version; 56 * u2 minor_version; 57 * u4 location_count; 58 * u4 location_attributes_size; 59 * u4 strings_size; 60 * u4 redirect[location_count]; 61 * u4 offsets[location_count]; 62 * u1 location_attributes[location_attributes_size]; 63 * u1 strings[strings_size]; 64 * u1 content[if !EOF]; 65 * } 66 * }</pre> 67 */ 68 public final class ImageFile { 69 private static final String JAVA_BASE = "java.base"; 70 private static final String IMAGE_EXT = ".jimage"; 71 private static final String JAR_EXT = ".jar"; 72 private final Path root; 73 private final Path mdir; 74 private final Map<String, List<Resource>> resourcesForModule = new HashMap<>(); 75 76 private ImageFile(Path path) { 77 this.root = path; 78 this.mdir = root.resolve(path.getFileSystem().getPath("lib", "modules")); 79 } 80 81 public static ImageFile open(Path path) throws IOException { 82 ImageFile lib = new ImageFile(path); 83 return lib.open(); 84 } 85 86 private ImageFile open() throws IOException { 87 Path path = mdir.resolve("bootmodules" + IMAGE_EXT); 88 89 ImageReader reader = new ImageReader(path.toString()); 90 ImageHeader header = reader.getHeader(); 91 92 if (header.getMagic() != ImageHeader.MAGIC) { 93 if (header.getMagic() == ImageHeader.BADMAGIC) { 94 throw new IOException(path + ": Image may be not be native endian"); 95 } else { 96 throw new IOException(path + ": Invalid magic number"); 97 } 98 } 99 100 if (header.getMajorVersion() > ImageHeader.MAJOR_VERSION || 101 (header.getMajorVersion() == ImageHeader.MAJOR_VERSION && 102 header.getMinorVersion() > ImageHeader.MINOR_VERSION)) { 103 throw new IOException("invalid version number"); 104 } 105 106 return this; 107 } 108 109 public static ImageFile create(Path output, 110 Set<Archive> archives, 111 ImageModules modules) 112 throws IOException 113 { 114 return ImageFile.create(output, archives, modules, ByteOrder.nativeOrder()); 115 } 116 117 public static ImageFile create(Path output, 118 Set<Archive> archives, 119 ImageModules modules, 120 ByteOrder byteOrder) 121 throws IOException 122 { 123 ImageFile lib = new ImageFile(output); 124 // get all resources 125 lib.readModuleEntries(modules, archives); 126 // write to modular image 127 lib.writeImage(modules, archives, byteOrder); 128 return lib; 129 } 130 131 private void writeImage(ImageModules modules, 132 Set<Archive> archives, 133 ByteOrder byteOrder) 134 throws IOException 135 { 136 // name to Archive file 137 Map<String, Archive> nameToArchive = 138 archives.stream() 139 .collect(Collectors.toMap(Archive::moduleName, Function.identity())); 140 141 Files.createDirectories(mdir); 142 for (Loader l : Loader.values()) { 143 Set<String> mods = modules.getModules(l); 144 145 try (OutputStream fos = Files.newOutputStream(mdir.resolve(l.getName() + IMAGE_EXT)); 146 BufferedOutputStream bos = new BufferedOutputStream(fos); 147 DataOutputStream out = new DataOutputStream(bos)) { 148 // store index in addition of the class loader map for boot loader 149 BasicImageWriter writer = new BasicImageWriter(byteOrder); 150 Set<String> duplicates = new HashSet<>(); 151 152 // build package map for modules and add as resources 153 ModuleIndex mindex = modules.buildModuleIndex(l, writer); 154 long offset = mindex.size(); 155 156 // the order of traversing the resources and the order of 157 // the module content being written must be the same 158 for (String mn : mods) { 159 for (Resource res : resourcesForModule.get(mn)) { 160 String path = res.name(); 161 long uncompressedSize = res.size(); 162 long compressedSize = res.csize(); 163 long onFileSize = compressedSize != 0 ? compressedSize : uncompressedSize; 164 165 if (duplicates.contains(path)) { 166 System.err.format("duplicate resource \"%s\", skipping%n", path); 167 // TODO Need to hang bytes on resource and write from resource not zip. 168 // Skipping resource throws off writing from zip. 169 offset += onFileSize; 170 continue; 171 } 172 duplicates.add(path); 173 writer.addLocation(path, offset, compressedSize, uncompressedSize); 174 offset += onFileSize; 175 } 176 } 177 178 // write header and indices 179 byte[] bytes = writer.getBytes(); 180 out.write(bytes, 0, bytes.length); 181 182 // write module table and packages 183 mindex.writeTo(out); 184 185 // write module content 186 for (String mn : mods) { 187 writeModule(nameToArchive.get(mn), out); 188 } 189 } 190 } 191 } 192 193 private void readModuleEntries(ImageModules modules, 194 Set<Archive> archives) 195 throws IOException 196 { 197 for (Archive archive : archives) { 198 List<Resource> res = new ArrayList<>(); 199 archive.visitResources(x-> res.add(x)); 200 201 String mn = archive.moduleName(); 202 resourcesForModule.put(mn, res); 203 204 Set<String> pkgs = res.stream().map(Resource::name) 205 .filter(n -> n.endsWith(".class")) 206 .map(this::toPackage) 207 .distinct() 208 .collect(Collectors.toSet()); 209 modules.setPackages(mn, pkgs); 210 } 211 } 212 213 private String toPackage(String name) { 214 int index = name.lastIndexOf('/'); 215 if (index > 0) { 216 return name.substring(0, index).replace('/', '.'); 217 } else { 218 // ## unnamed package 219 System.err.format("Warning: %s in unnamed package%n", name); 220 return ""; 221 } 222 } 223 224 private void writeModule(Archive archive, 225 OutputStream out) 226 throws IOException 227 { 228 Consumer<Archive.Entry> consumer = archive.defaultImageWriter(root, out); 229 archive.visitEntries(consumer); 230 } 231 232 233 static class Compressor { 234 public static byte[] compress(byte[] bytesIn) { 235 Deflater deflater = new Deflater(); 236 deflater.setInput(bytesIn); 237 ByteArrayOutputStream stream = new ByteArrayOutputStream(bytesIn.length); 238 byte[] buffer = new byte[1024]; 239 240 deflater.finish(); 241 while (!deflater.finished()) { 242 int count = deflater.deflate(buffer); 243 stream.write(buffer, 0, count); 244 } 245 246 try { 247 stream.close(); 248 } catch (IOException ex) { 249 return bytesIn; 250 } 251 252 byte[] bytesOut = stream.toByteArray(); 253 deflater.end(); 254 255 return bytesOut; 256 } 257 258 public static byte[] decompress(byte[] bytesIn) { 259 Inflater inflater = new Inflater(); 260 inflater.setInput(bytesIn); 261 ByteArrayOutputStream stream = new ByteArrayOutputStream(bytesIn.length); 262 byte[] buffer = new byte[1024]; 263 264 while (!inflater.finished()) { 265 int count; 266 267 try { 268 count = inflater.inflate(buffer); 269 } catch (DataFormatException ex) { 270 return null; 271 } 272 273 stream.write(buffer, 0, count); 274 } 275 276 try { 277 stream.close(); 278 } catch (IOException ex) { 279 return null; 280 } 281 282 byte[] bytesOut = stream.toByteArray(); 283 inflater.end(); 284 285 return bytesOut; 286 } 287 } 288 }