1 /* 2 * Copyright (c) 2015, 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.tools.jlink.internal; 26 27 import java.io.ByteArrayInputStream; 28 import java.io.DataOutputStream; 29 import java.io.IOException; 30 import java.lang.module.ModuleDescriptor; 31 import java.nio.ByteOrder; 32 import java.util.ArrayList; 33 import java.util.Collection; 34 import java.util.Collections; 35 import java.util.Comparator; 36 import java.util.HashMap; 37 import java.util.List; 38 import java.util.Map; 39 import java.util.Objects; 40 import java.util.Optional; 41 import java.util.Set; 42 import java.util.function.Function; 43 import java.util.stream.Collectors; 44 import java.util.stream.Stream; 45 46 import jdk.internal.jimage.decompressor.Decompressor; 47 import jdk.tools.jlink.plugin.Plugin; 48 import jdk.tools.jlink.plugin.ExecutableImage; 49 import jdk.tools.jlink.builder.ImageBuilder; 50 import jdk.tools.jlink.plugin.TransformerPlugin; 51 import jdk.tools.jlink.plugin.PluginException; 52 import jdk.tools.jlink.plugin.ModulePool; 53 import jdk.tools.jlink.plugin.LinkModule; 54 import jdk.tools.jlink.plugin.ModuleEntry; 55 import jdk.tools.jlink.plugin.PostProcessorPlugin; 56 57 /** 58 * Plugins Stack. Plugins entry point to apply transformations onto resources 59 * and files. 60 */ 61 public final class ImagePluginStack { 62 63 public interface ImageProvider { 64 65 ExecutableImage retrieve(ImagePluginStack stack) throws IOException; 66 } 67 68 public static final class OrderedResourcePool extends ModulePoolImpl { 69 70 private final List<ModuleEntry> orderedList = new ArrayList<>(); 71 72 public OrderedResourcePool(ByteOrder order, StringTable table) { 73 super(order, table); 74 } 75 76 /** 77 * Add a resource. 78 * 79 * @param resource The Resource to add. 80 */ 81 @Override 82 public void add(ModuleEntry resource) { 83 super.add(resource); 84 orderedList.add(resource); 85 } 86 87 List<ModuleEntry> getOrderedList() { 88 return Collections.unmodifiableList(orderedList); 89 } 90 } 91 92 private final static class CheckOrderResourcePool extends ModulePoolImpl { 93 94 private final List<ModuleEntry> orderedList; 95 private int currentIndex; 96 97 public CheckOrderResourcePool(ByteOrder order, List<ModuleEntry> orderedList, StringTable table) { 98 super(order, table); 99 this.orderedList = orderedList; 100 } 101 102 /** 103 * Add a resource. 104 * 105 * @param resource The Resource to add. 106 */ 107 @Override 108 public void add(ModuleEntry resource) { 109 ModuleEntry ordered = orderedList.get(currentIndex); 110 if (!resource.equals(ordered)) { 111 throw new PluginException("Resource " + resource.getPath() + " not in the right order"); 112 } 113 super.add(resource); 114 currentIndex += 1; 115 } 116 } 117 118 private static final class PreVisitStrings implements StringTable { 119 120 private int currentid = 0; 121 private final Map<String, Integer> stringsUsage = new HashMap<>(); 122 123 private final Map<String, Integer> stringsMap = new HashMap<>(); 124 private final Map<Integer, String> reverseMap = new HashMap<>(); 125 126 @Override 127 public int addString(String str) { 128 Objects.requireNonNull(str); 129 Integer count = stringsUsage.get(str); 130 if (count == null) { 131 count = 0; 132 } 133 count += 1; 134 stringsUsage.put(str, count); 135 Integer id = stringsMap.get(str); 136 if (id == null) { 137 id = currentid; 138 stringsMap.put(str, id); 139 currentid += 1; 140 reverseMap.put(id, str); 141 } 142 143 return id; 144 } 145 146 private List<String> getSortedStrings() { 147 Stream<java.util.Map.Entry<String, Integer>> stream 148 = stringsUsage.entrySet().stream(); 149 // Remove strings that have a single occurence 150 List<String> result = stream.sorted(Comparator.comparing(e -> e.getValue(), 151 Comparator.reverseOrder())).filter((e) -> { 152 return e.getValue() > 1; 153 }).map(java.util.Map.Entry::getKey). 154 collect(Collectors.toList()); 155 return result; 156 } 157 158 @Override 159 public String getString(int id) { 160 return reverseMap.get(id); 161 } 162 } 163 164 private final Plugin lastSorter; 165 private final List<TransformerPlugin> contentPlugins = new ArrayList<>(); 166 private final List<PostProcessorPlugin> postProcessingPlugins = new ArrayList<>(); 167 private final List<ResourcePrevisitor> resourcePrevisitors = new ArrayList<>(); 168 169 private final ImageBuilder imageBuilder; 170 171 public ImagePluginStack() { 172 this(null, Collections.emptyList(), null, 173 Collections.emptyList()); 174 } 175 176 public ImagePluginStack(ImageBuilder imageBuilder, 177 List<TransformerPlugin> contentPlugins, 178 Plugin lastSorter, 179 List<PostProcessorPlugin> postprocessingPlugins) { 180 Objects.requireNonNull(contentPlugins); 181 this.lastSorter = lastSorter; 182 for (TransformerPlugin p : contentPlugins) { 183 Objects.requireNonNull(p); 184 if (p instanceof ResourcePrevisitor) { 185 resourcePrevisitors.add((ResourcePrevisitor) p); 186 } 187 this.contentPlugins.add(p); 188 } 189 for (PostProcessorPlugin p : postprocessingPlugins) { 190 Objects.requireNonNull(p); 191 this.postProcessingPlugins.add(p); 192 } 193 this.imageBuilder = imageBuilder; 194 } 195 196 public void operate(ImageProvider provider) throws Exception { 197 ExecutableImage img = provider.retrieve(this); 198 List<String> arguments = new ArrayList<>(); 199 for (PostProcessorPlugin plugin : postProcessingPlugins) { 200 List<String> lst = plugin.process(img); 201 if (lst != null) { 202 arguments.addAll(lst); 203 } 204 } 205 img.storeLaunchArgs(arguments); 206 } 207 208 public DataOutputStream getJImageFileOutputStream() throws IOException { 209 return imageBuilder.getJImageOutputStream(); 210 } 211 212 public ImageBuilder getImageBuilder() { 213 return imageBuilder; 214 } 215 216 /** 217 * Resource Plugins stack entry point. All resources are going through all 218 * the plugins. 219 * 220 * @param resources The set of resources to visit 221 * @return The result of the visit. 222 * @throws IOException 223 */ 224 public ModulePoolImpl visitResources(ModulePoolImpl resources) 225 throws Exception { 226 Objects.requireNonNull(resources); 227 resources.setReadOnly(); 228 if (resources.isEmpty()) { 229 return new ModulePoolImpl(resources.getByteOrder(), 230 resources.getStringTable()); 231 } 232 PreVisitStrings previsit = new PreVisitStrings(); 233 for (ResourcePrevisitor p : resourcePrevisitors) { 234 p.previsit(resources, previsit); 235 } 236 237 // Store the strings resulting from the previsit. 238 List<String> sorted = previsit.getSortedStrings(); 239 for (String s : sorted) { 240 resources.getStringTable().addString(s); 241 } 242 243 ModulePoolImpl current = resources; 244 List<ModuleEntry> frozenOrder = null; 245 for (TransformerPlugin p : contentPlugins) { 246 current.setReadOnly(); 247 ModulePoolImpl output = null; 248 if (p == lastSorter) { 249 if (frozenOrder != null) { 250 throw new Exception("Order of resources is already frozen. Plugin " 251 + p.getName() + " is badly located"); 252 } 253 // Create a special Resource pool to compute the indexes. 254 output = new OrderedResourcePool(current.getByteOrder(), 255 resources.getStringTable()); 256 } else {// If we have an order, inject it 257 if (frozenOrder != null) { 258 output = new CheckOrderResourcePool(current.getByteOrder(), 259 frozenOrder, resources.getStringTable()); 260 } else { 261 output = new ModulePoolImpl(current.getByteOrder(), 262 resources.getStringTable()); 263 } 264 } 265 p.visit(current, output); 266 if (output.isEmpty()) { 267 throw new Exception("Invalid resource pool for plugin " + p); 268 } 269 if (output instanceof OrderedResourcePool) { 270 frozenOrder = ((OrderedResourcePool) output).getOrderedList(); 271 } 272 273 current = output; 274 } 275 current.setReadOnly(); 276 return current; 277 } 278 279 /** 280 * This pool wrap the original pool and automatically uncompress ModuleEntry 281 * if needed. 282 */ 283 private class LastPool implements ModulePool { 284 private class LastModule implements LinkModule { 285 286 final LinkModule module; 287 288 LastModule(LinkModule module) { 289 this.module = module; 290 } 291 292 @Override 293 public String getName() { 294 return module.getName(); 295 } 296 297 @Override 298 public Optional<ModuleEntry> findEntry(String path) { 299 Optional<ModuleEntry> d = module.findEntry(path); 300 return d.isPresent()? Optional.of(getUncompressed(d.get())) : Optional.empty(); 301 } 302 303 @Override 304 public ModuleDescriptor getDescriptor() { 305 return module.getDescriptor(); 306 } 307 308 @Override 309 public void add(ModuleEntry data) { 310 throw new PluginException("pool is readonly"); 311 } 312 313 @Override 314 public Set<String> getAllPackages() { 315 return module.getAllPackages(); 316 } 317 318 @Override 319 public String toString() { 320 return getName(); 321 } 322 323 @Override 324 public Stream<ModuleEntry> entries() { 325 List<ModuleEntry> lst = new ArrayList<>(); 326 module.entries().forEach(md -> { 327 lst.add(getUncompressed(md)); 328 }); 329 return lst.stream(); 330 } 331 332 @Override 333 public int getEntryCount() { 334 return module.getEntryCount(); 335 } 336 } 337 private final ModulePoolImpl pool; 338 Decompressor decompressor = new Decompressor(); 339 Collection<ModuleEntry> content; 340 341 LastPool(ModulePoolImpl pool) { 342 this.pool = pool; 343 } 344 345 @Override 346 public boolean isReadOnly() { 347 return true; 348 } 349 350 @Override 351 public void add(ModuleEntry resource) { 352 throw new PluginException("pool is readonly"); 353 } 354 355 @Override 356 public Optional<LinkModule> findModule(String name) { 357 Optional<LinkModule> module = pool.findModule(name); 358 return module.isPresent()? Optional.of(new LastModule(module.get())) : Optional.empty(); 359 } 360 361 /** 362 * The collection of modules contained in this pool. 363 * 364 * @return The collection of modules. 365 */ 366 @Override 367 public Stream<? extends LinkModule> modules() { 368 List<LinkModule> modules = new ArrayList<>(); 369 pool.modules().forEach(m -> { 370 modules.add(new LastModule(m)); 371 }); 372 return modules.stream(); 373 } 374 375 @Override 376 public int getModuleCount() { 377 return pool.getModuleCount(); 378 } 379 380 /** 381 * Get all resources contained in this pool instance. 382 * 383 * @return The stream of resources; 384 */ 385 @Override 386 public Stream<? extends ModuleEntry> entries() { 387 if (content == null) { 388 content = new ArrayList<>(); 389 pool.entries().forEach(md -> { 390 content.add(getUncompressed(md)); 391 }); 392 } 393 return content.stream(); 394 } 395 396 @Override 397 public int getEntryCount() { 398 return pool.getEntryCount(); 399 } 400 401 /** 402 * Get the resource for the passed path. 403 * 404 * @param path A resource path 405 * @return A Resource instance if the resource is found 406 */ 407 @Override 408 public Optional<ModuleEntry> findEntry(String path) { 409 Objects.requireNonNull(path); 410 Optional<ModuleEntry> res = pool.findEntry(path); 411 return res.isPresent()? Optional.of(getUncompressed(res.get())) : Optional.empty(); 412 } 413 414 @Override 415 public boolean contains(ModuleEntry res) { 416 return pool.contains(res); 417 } 418 419 @Override 420 public boolean isEmpty() { 421 return pool.isEmpty(); 422 } 423 424 @Override 425 public void transformAndCopy(Function<ModuleEntry, ModuleEntry> visitor, ModulePool output) { 426 pool.transformAndCopy(visitor, output); 427 } 428 429 @Override 430 public ByteOrder getByteOrder() { 431 return pool.getByteOrder(); 432 } 433 434 @Override 435 public Map<String, String> getReleaseProperties() { 436 return Collections.unmodifiableMap(pool.getReleaseProperties()); 437 } 438 439 private ModuleEntry getUncompressed(ModuleEntry res) { 440 if (res != null) { 441 if (res instanceof ModulePoolImpl.CompressedModuleData) { 442 try { 443 byte[] bytes = decompressor.decompressResource(getByteOrder(), 444 (int offset) -> pool.getStringTable().getString(offset), 445 res.getBytes()); 446 res = res.create(bytes); 447 } catch (IOException ex) { 448 throw new PluginException(ex); 449 } 450 } 451 } 452 return res; 453 } 454 } 455 456 /** 457 * Make the imageBuilder to store files. 458 * 459 * @param original 460 * @param transformed 461 * @param writer 462 * @throws java.lang.Exception 463 */ 464 public void storeFiles(ModulePoolImpl original, ModulePoolImpl transformed, 465 BasicImageWriter writer) 466 throws Exception { 467 Objects.requireNonNull(original); 468 Objects.requireNonNull(transformed); 469 Optional<LinkModule> javaBase = transformed.findModule("java.base"); 470 javaBase.ifPresent(mod -> { 471 try { 472 Map<String, String> release = transformed.getReleaseProperties(); 473 // fill release information available from transformed "java.base" module! 474 ModuleDescriptor desc = mod.getDescriptor(); 475 desc.osName().ifPresent(s -> release.put("OS_NAME", s)); 476 desc.osVersion().ifPresent(s -> release.put("OS_VERSION", s)); 477 desc.osArch().ifPresent(s -> release.put("OS_ARCH", s)); 478 } catch (Exception ignored) {} 479 }); 480 481 imageBuilder.storeFiles(new LastPool(transformed)); 482 } 483 484 public ExecutableImage getExecutableImage() throws IOException { 485 return imageBuilder.getExecutableImage(); 486 } 487 }