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