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