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 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 = Objects.requireNonNull(imageBuilder); 185 this.lastSorter = lastSorter; 186 this.plugins.addAll(Objects.requireNonNull(plugins)); 187 plugins.stream().forEach((p) -> { 188 Objects.requireNonNull(p); 189 if (p instanceof ResourcePrevisitor) { 190 resourcePrevisitors.add((ResourcePrevisitor) p); 191 } 192 }); 193 } 194 195 public void operate(ImageProvider provider) throws Exception { 196 ExecutableImage img = provider.retrieve(this); 197 List<String> arguments = new ArrayList<>(); 198 plugins.stream() 199 .filter(PostProcessor.class::isInstance) 200 .map((plugin) -> ((PostProcessor)plugin).process(img)) 201 .filter((lst) -> (lst != null)) 202 .forEach((lst) -> { 203 arguments.addAll(lst); 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 ResourcePool visitResources(ResourcePoolManager resources) 225 throws Exception { 226 Objects.requireNonNull(resources); 227 if (resources.isEmpty()) { 228 return new ResourcePoolManager(resources.byteOrder(), 229 resources.getStringTable()).resourcePool(); 230 } 231 PreVisitStrings previsit = new PreVisitStrings(); 232 resourcePrevisitors.stream().forEach((p) -> { 233 p.previsit(resources.resourcePool(), previsit); 234 }); 235 236 // Store the strings resulting from the previsit. 237 List<String> sorted = previsit.getSortedStrings(); 238 sorted.stream().forEach((s) -> { 239 resources.getStringTable().addString(s); 240 }); 241 242 ResourcePool resPool = resources.resourcePool(); 243 List<ResourcePoolEntry> frozenOrder = null; 244 for (Plugin p : plugins) { 245 ResourcePoolManager resMgr = null; 246 if (p == lastSorter) { 247 if (frozenOrder != null) { 248 throw new Exception("Order of resources is already frozen. Plugin " 249 + p.getName() + " is badly located"); 250 } 251 // Create a special Resource pool to compute the indexes. 252 resMgr = new OrderedResourcePoolManager(resPool.byteOrder(), 253 resources.getStringTable()); 254 } else {// If we have an order, inject it 255 if (frozenOrder != null) { 256 resMgr = new CheckOrderResourcePoolManager(resPool.byteOrder(), 257 frozenOrder, resources.getStringTable()); 258 } else { 259 resMgr = new ResourcePoolManager(resPool.byteOrder(), 260 resources.getStringTable()); 261 } 262 } 263 resPool = p.transform(resPool, resMgr.resourcePoolBuilder()); 264 if (resPool.isEmpty()) { 265 throw new Exception("Invalid resource pool for plugin " + p); 266 } 267 if (resPool instanceof OrderedResourcePoolManager.OrderedResourcePool) { 268 frozenOrder = ((OrderedResourcePoolManager.OrderedResourcePool)resPool).getOrderedList(); 269 } 270 } 271 return resPool; 272 } 273 274 /** 275 * This pool wrap the original pool and automatically uncompress ResourcePoolEntry 276 * if needed. 277 */ 278 private class LastPoolManager extends ResourcePoolManager { 279 private class LastModule implements ResourcePoolModule { 280 281 final ResourcePoolModule module; 282 // lazily initialized 283 ModuleDescriptor descriptor; 284 285 LastModule(ResourcePoolModule module) { 286 this.module = module; 287 } 288 289 @Override 290 public String name() { 291 return module.name(); 292 } 293 294 @Override 295 public Optional<ResourcePoolEntry> findEntry(String path) { 296 Optional<ResourcePoolEntry> d = module.findEntry(path); 297 return d.isPresent()? Optional.of(getUncompressed(d.get())) : Optional.empty(); 298 } 299 300 @Override 301 public ModuleDescriptor descriptor() { 302 if (descriptor == null) { 303 descriptor = ResourcePoolManager.readModuleDescriptor(this); 304 } 305 return descriptor; 306 } 307 308 @Override 309 public Set<String> packages() { 310 return module.packages(); 311 } 312 313 @Override 314 public String toString() { 315 return name(); 316 } 317 318 @Override 319 public Stream<ResourcePoolEntry> entries() { 320 List<ResourcePoolEntry> lst = new ArrayList<>(); 321 module.entries().forEach(md -> { 322 lst.add(getUncompressed(md)); 323 }); 324 return lst.stream(); 325 } 326 327 @Override 328 public int entryCount() { 329 return module.entryCount(); 330 } 331 } 332 333 private final ResourcePool pool; 334 Decompressor decompressor = new Decompressor(); 335 Collection<ResourcePoolEntry> content; 336 337 LastPoolManager(ResourcePool pool) { 338 this.pool = pool; 339 } 340 341 @Override 342 public void add(ResourcePoolEntry resource) { 343 throw new PluginException("pool is readonly"); 344 } 345 346 @Override 347 public Optional<ResourcePoolModule> findModule(String name) { 348 Optional<ResourcePoolModule> module = pool.moduleView().findModule(name); 349 return module.isPresent()? Optional.of(new LastModule(module.get())) : Optional.empty(); 350 } 351 352 /** 353 * The collection of modules contained in this pool. 354 * 355 * @return The collection of modules. 356 */ 357 @Override 358 public Stream<ResourcePoolModule> modules() { 359 List<ResourcePoolModule> modules = new ArrayList<>(); 360 pool.moduleView().modules().forEach(m -> { 361 modules.add(new LastModule(m)); 362 }); 363 return modules.stream(); 364 } 365 366 @Override 367 public int moduleCount() { 368 return pool.moduleView().moduleCount(); 369 } 370 371 /** 372 * Get all resources contained in this pool instance. 373 * 374 * @return The stream of resources; 375 */ 376 @Override 377 public Stream<ResourcePoolEntry> entries() { 378 if (content == null) { 379 content = new ArrayList<>(); 380 pool.entries().forEach(md -> { 381 content.add(getUncompressed(md)); 382 }); 383 } 384 return content.stream(); 385 } 386 387 @Override 388 public int entryCount() { 389 return pool.entryCount(); 390 } 391 392 /** 393 * Get the resource for the passed path. 394 * 395 * @param path A resource path 396 * @return A Resource instance if the resource is found 397 */ 398 @Override 399 public Optional<ResourcePoolEntry> findEntry(String path) { 400 Objects.requireNonNull(path); 401 Optional<ResourcePoolEntry> res = pool.findEntry(path); 402 return res.isPresent()? Optional.of(getUncompressed(res.get())) : Optional.empty(); 403 } 404 405 @Override 406 public Optional<ResourcePoolEntry> findEntryInContext(String path, ResourcePoolEntry context) { 407 Objects.requireNonNull(path); 408 Objects.requireNonNull(context); 409 Optional<ResourcePoolEntry> res = pool.findEntryInContext(path, context); 410 return res.map(this::getUncompressed); 411 } 412 413 @Override 414 public boolean contains(ResourcePoolEntry res) { 415 return pool.contains(res); 416 } 417 418 @Override 419 public boolean isEmpty() { 420 return pool.isEmpty(); 421 } 422 423 @Override 424 public ByteOrder byteOrder() { 425 return pool.byteOrder(); 426 } 427 428 private ResourcePoolEntry getUncompressed(ResourcePoolEntry res) { 429 if (res != null) { 430 if (res instanceof ResourcePoolManager.CompressedModuleData) { 431 try { 432 byte[] bytes = decompressor.decompressResource(byteOrder(), 433 (int offset) -> ((ResourcePoolImpl)pool).getStringTable().getString(offset), 434 res.contentBytes()); 435 res = res.copyWithContent(bytes); 436 } catch (IOException ex) { 437 if (JlinkTask.DEBUG) { 438 ex.printStackTrace(); 439 } 440 throw new PluginException(ex); 441 } 442 } 443 } 444 return res; 445 } 446 } 447 448 /** 449 * Make the imageBuilder to store files. 450 * 451 * @param original 452 * @param transformed 453 * @param writer 454 * @throws java.lang.Exception 455 */ 456 public void storeFiles(ResourcePool original, ResourcePool transformed, 457 BasicImageWriter writer) 458 throws Exception { 459 Objects.requireNonNull(original); 460 Objects.requireNonNull(transformed); 461 imageBuilder.storeFiles(new LastPoolManager(transformed).resourcePool()); 462 } 463 464 public ExecutableImage getExecutableImage() throws IOException { 465 return imageBuilder.getExecutableImage(); 466 } 467 }