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.plugins.asm;
  26 
  27 import java.lang.module.ModuleDescriptor;
  28 import java.lang.module.ModuleDescriptor.Exports;
  29 import java.lang.module.ModuleDescriptor.Requires;
  30 import static java.lang.module.ModuleDescriptor.Requires.Modifier.PUBLIC;
  31 import java.nio.ByteBuffer;
  32 import java.util.ArrayList;
  33 import java.util.Collection;
  34 import java.util.HashMap;
  35 import java.util.HashSet;
  36 import java.util.LinkedHashMap;
  37 import java.util.List;
  38 import java.util.Map;
  39 import java.util.Map.Entry;
  40 import java.util.Objects;
  41 import java.util.Set;
  42 import jdk.internal.org.objectweb.asm.ClassReader;
  43 import jdk.internal.org.objectweb.asm.ClassWriter;
  44 import jdk.tools.jlink.internal.ModulePoolImpl;
  45 import jdk.tools.jlink.internal.plugins.asm.AsmPool.Sorter;
  46 import jdk.tools.jlink.plugin.ModuleEntry;
  47 import jdk.tools.jlink.plugin.PluginException;
  48 import jdk.tools.jlink.plugin.ModulePool;
  49 
  50 /**
  51  * A container for pools of ClassReader and other resource files. A pool of all
  52  * the resources or a pool for a given module can be retrieved
  53  */
  54 public final class AsmPools {
  55 
  56     /**
  57      * Sort the order in which the modules will be stored in the jimage file.
  58      */
  59     public interface ModuleSorter {
  60 
  61         /**
  62          * Sort the list of modules.
  63          *
  64          * @param modules The list of module names. The module will be stored in
  65          * the jimage following this order.
  66          * @return A list of module names that expresses the order in which the
  67          * modules are stored in the jimage.
  68          */
  69         public List<String> sort(List<String> modules);
  70     }
  71 
  72     private class AsmGlobalPoolImpl implements AsmGlobalPool {
  73 
  74         private Sorter sorter = null;
  75 
  76         private class GlobalWritableClassPool implements WritableClassPool {
  77 
  78             @Override
  79             public void addClass(ClassWriter writer) {
  80                 visitFirstNonFailingPool((AsmModulePool pool) -> {
  81                     pool.getTransformedClasses().addClass(writer);
  82                 });
  83             }
  84 
  85             @Override
  86             public void forgetClass(String className) {
  87                 visitFirstNonFailingPool((AsmModulePool pool) -> {
  88                     pool.getTransformedClasses().forgetClass(className);
  89                 });
  90             }
  91 
  92             @Override
  93             public ClassReader getClassReader(String binaryName) {
  94                 return visitPools((AsmModulePool pool) -> {
  95                     return pool.getTransformedClasses().getClassReader(binaryName);
  96                 });
  97             }
  98 
  99             @Override
 100             public Collection<ModuleEntry> getClasses() {
 101                 List<ModuleEntry> all = new ArrayList<>();
 102                 visitAllPools((AsmModulePool pool) -> {
 103                     for (ModuleEntry rf : pool.getTransformedClasses().getClasses()) {
 104                         all.add(rf);
 105                     }
 106                 });
 107                 return all;
 108             }
 109 
 110             @Override
 111             public ClassReader getClassReader(ModuleEntry res) {
 112                 return visitPools((AsmModulePool pool) -> {
 113                     return pool.getTransformedClasses().getClassReader(res);
 114                 });
 115             }
 116 
 117         }
 118 
 119         private class GlobalWritableResourcePool implements WritableResourcePool {
 120 
 121             @Override
 122             public void addResourceFile(ResourceFile resFile) {
 123                 visitFirstNonFailingPool((AsmModulePool pool) -> {
 124                     pool.getTransformedResourceFiles().addResourceFile(resFile);
 125                 });
 126             }
 127 
 128             @Override
 129             public void forgetResourceFile(String resourceName) {
 130                 visitFirstNonFailingPool((AsmModulePool pool) -> {
 131                     pool.getTransformedResourceFiles().forgetResourceFile(resourceName);
 132                 });
 133             }
 134 
 135             @Override
 136             public ResourceFile getResourceFile(String name) {
 137                 return visitPools((AsmModulePool pool) -> {
 138                     return pool.getTransformedResourceFiles().getResourceFile(name);
 139                 });
 140             }
 141 
 142             @Override
 143             public Collection<ModuleEntry> getResourceFiles() {
 144                 List<ModuleEntry> all = new ArrayList<>();
 145                 visitAllPools((AsmModulePool pool) -> {
 146                     for (ModuleEntry rf : pool.getTransformedResourceFiles().getResourceFiles()) {
 147                         all.add(rf);
 148                     }
 149                 });
 150                 return all;
 151             }
 152 
 153             @Override
 154             public ResourceFile getResourceFile(ModuleEntry res) {
 155                 return visitPools((AsmModulePool pool) -> {
 156                     return pool.getTransformedResourceFiles().getResourceFile(res);
 157                 });
 158             }
 159 
 160         }
 161 
 162         @Override
 163         public AsmPool.WritableClassPool getTransformedClasses() {
 164             return new GlobalWritableClassPool();
 165         }
 166 
 167         @Override
 168         public AsmPool.WritableResourcePool getTransformedResourceFiles() {
 169             return new GlobalWritableResourcePool();
 170         }
 171 
 172         @Override
 173         public void setSorter(AsmPool.Sorter sorter) {
 174             this.sorter = sorter;
 175         }
 176 
 177         @Override
 178         public Collection<ModuleEntry> getClasses() {
 179             List<ModuleEntry> all = new ArrayList<>();
 180             visitAllPools((AsmModulePool pool) -> {
 181                 for (ModuleEntry rf : pool.getClasses()) {
 182                     all.add(rf);
 183                 }
 184             });
 185             return all;
 186         }
 187 
 188         @Override
 189         public Collection<ModuleEntry> getResourceFiles() {
 190             List<ModuleEntry> all = new ArrayList<>();
 191             visitAllPools((AsmModulePool pool) -> {
 192                 for (ModuleEntry rf : pool.getResourceFiles()) {
 193                     all.add(rf);
 194                 }
 195             });
 196             return all;
 197         }
 198 
 199         @Override
 200         public AsmPool.ResourceFile getResourceFile(String binaryName) {
 201             return visitPools((AsmModulePool pool) -> {
 202                 return pool.getResourceFile(binaryName);
 203             });
 204         }
 205 
 206         @Override
 207         public ClassReader getClassReader(String binaryName) {
 208             return visitPoolsEx((AsmModulePool pool) -> {
 209                 return pool.getClassReader(binaryName);
 210             });
 211         }
 212 
 213         @Override
 214         public ResourceFile getResourceFile(ModuleEntry res) {
 215             return visitPools((AsmModulePool pool) -> {
 216                 return pool.getResourceFile(res);
 217             });
 218         }
 219 
 220         @Override
 221         public ClassReader getClassReader(ModuleEntry res) {
 222             return visitPoolsEx((AsmModulePool pool) -> {
 223                 return pool.getClassReader(res);
 224             });
 225         }
 226 
 227         @Override
 228         public void visitClassReaders(AsmPool.ClassReaderVisitor visitor) {
 229             visitAllPoolsEx((AsmModulePool pool) -> {
 230                 pool.visitClassReaders(visitor);
 231             });
 232         }
 233 
 234         @Override
 235         public void visitResourceFiles(AsmPool.ResourceFileVisitor visitor) {
 236             visitAllPoolsEx((AsmModulePool pool) -> {
 237                 pool.visitResourceFiles(visitor);
 238             });
 239         }
 240 
 241         @Override
 242         public void fillOutputResources(ModulePool outputResources) {
 243             AsmPools.this.fillOutputResources(outputResources);
 244         }
 245 
 246         @Override
 247         public void addPackageModuleMapping(String pkg, String module) {
 248             AsmModulePool p = pools.get(module);
 249             if (p == null) {
 250                 throw new PluginException("Unknown module " + module);
 251             }
 252             p.addPackage(pkg);
 253         }
 254 
 255         @Override
 256         public Set<String> getAccessiblePackages(String module) {
 257             AsmModulePool p = pools.get(module);
 258             if (p == null) {
 259                 return null;
 260             }
 261             ModuleDescriptor desc = p.getDescriptor();
 262             Set<String> packages = new HashSet<>();
 263             packages.addAll(p.getAllPackages());
 264 
 265             // Retrieve direct dependencies and indirect ones (public)
 266             Set<String> modules = new HashSet<>();
 267             for (Requires req : desc.requires()) {
 268                 modules.add(req.name());
 269                 addAllRequirePublicModules(req.name(), modules);
 270             }
 271             // Add exported packages of readable modules
 272             for (String readable : modules) {
 273                 AsmModulePool mp = pools.get(readable);
 274                 if (mp != null) {
 275                     for (Exports e : mp.getDescriptor().exports()) {
 276                         // exported to all or to the targeted module
 277                         if (e.targets().isEmpty() || e.targets().contains(module)) {
 278                             packages.add(e.source().replaceAll("\\.", "/"));
 279                         }
 280                     }
 281 
 282                 }
 283             }
 284             return packages;
 285         }
 286 
 287         private void addAllRequirePublicModules(String module, Set<String> modules) {
 288             AsmModulePool p = pools.get(module);
 289             if (p != null) {
 290                 for (Requires req : p.getDescriptor().requires()) {
 291                     if (req.modifiers().contains(PUBLIC)) {
 292                         modules.add(req.name());
 293                         addAllRequirePublicModules(req.name(), modules);
 294                     }
 295                 }
 296             }
 297         }
 298 
 299     }
 300 
 301     private interface VoidPoolVisitor {
 302 
 303         void visit(AsmModulePool pool);
 304     }
 305 
 306     private interface VoidPoolVisitorEx {
 307 
 308         void visit(AsmModulePool pool);
 309     }
 310 
 311     private interface RetPoolVisitor<P> {
 312 
 313         P visit(AsmModulePool pool);
 314     }
 315 
 316     private final Map<String, AsmModulePool> pools = new LinkedHashMap<>();
 317     private final AsmModulePool[] poolsArray;
 318     private final AsmGlobalPoolImpl global;
 319 
 320     private ModuleSorter moduleSorter;
 321 
 322     /**
 323      * A new Asm pools.
 324      *
 325      * @param inputResources The raw resources to build the pool from.
 326      */
 327     public AsmPools(ModulePool inputResources) {
 328         Objects.requireNonNull(inputResources);
 329         Map<String, ModulePool> resPools = new LinkedHashMap<>();
 330         Map<String, ModuleDescriptor> descriptors = new HashMap<>();
 331         inputResources.entries().forEach(res -> {
 332             ModulePool p = resPools.get(res.getModule());
 333             if (p == null) {
 334                 p = new ModulePoolImpl(inputResources.getByteOrder(),
 335                         ((ModulePoolImpl)inputResources).getStringTable());
 336                 resPools.put(res.getModule(), p);
 337             }
 338             if (res.getPath().endsWith("module-info.class")) {
 339                 ByteBuffer bb = ByteBuffer.wrap(res.getBytes());
 340                 ModuleDescriptor descriptor = ModuleDescriptor.read(bb);
 341                 descriptors.put(res.getModule(), descriptor);
 342             }
 343             p.add(res);
 344         });
 345         poolsArray = new AsmModulePool[resPools.size()];
 346         int i = 0;
 347 
 348         for (Entry<String, ModulePool> entry : resPools.entrySet()) {
 349             ModuleDescriptor descriptor = descriptors.get(entry.getKey());
 350             if (descriptor == null) {
 351                 throw new PluginException("module-info.class not found for " + entry.getKey() + " module");
 352             }
 353             AsmModulePool p = new AsmPoolImpl(entry.getValue(),
 354                     entry.getKey(), this, descriptor);
 355             pools.put(entry.getKey(), p);
 356             poolsArray[i] = p;
 357             i += 1;
 358         }
 359         global = new AsmGlobalPoolImpl();
 360     }
 361 
 362     /**
 363      * The pool containing all classes and other resources.
 364      *
 365      * @return The global pool
 366      */
 367     public AsmGlobalPool getGlobalPool() {
 368         return global;
 369     }
 370 
 371     /**
 372      * A pool for a given module
 373      *
 374      * @param name The module name
 375      * @return The pool that contains content of the passed module or null if
 376      * the module doesn't exist.
 377      */
 378     public AsmModulePool getModulePool(String name) {
 379         Objects.requireNonNull(name);
 380         return pools.get(name);
 381     }
 382 
 383     /**
 384      * The array of module pools.
 385      * @return The module pool array.
 386      */
 387     public AsmModulePool[] getModulePools() {
 388         return poolsArray.clone();
 389     }
 390 
 391     /**
 392      * Set a module sorter. Sorter is used when computing the output resources.
 393      *
 394      * @param moduleSorter The module sorter
 395      */
 396     public void setModuleSorter(ModuleSorter moduleSorter) {
 397         Objects.requireNonNull(moduleSorter);
 398         this.moduleSorter = moduleSorter;
 399     }
 400 
 401     /**
 402      * Returns the pool of all the resources (transformed and unmodified). The
 403      * input resources are replaced by the transformed ones. If a sorter has
 404      * been set, it is used to sort in modules.
 405      *
 406      * @param outputResources The pool used to fill the jimage.
 407      */
 408     public void fillOutputResources(ModulePool outputResources) {
 409         // First sort modules
 410         List<String> modules = new ArrayList<>();
 411         for (String k : pools.keySet()) {
 412             modules.add(k);
 413         }
 414         if (moduleSorter != null) {
 415             modules = moduleSorter.sort(modules);
 416         }
 417         ModulePool output = new ModulePoolImpl(outputResources.getByteOrder(),
 418                 ((ModulePoolImpl)outputResources).getStringTable());
 419         for (String mn : modules) {
 420             AsmPool pool = pools.get(mn);
 421             pool.fillOutputResources(output);
 422         }
 423         sort(outputResources, output, global.sorter);
 424     }
 425 
 426     static void sort(ModulePool outputResources,
 427             ModulePool transientOutput, Sorter sorter) {
 428         if (sorter != null) {
 429             List<String> order = sorter.sort(transientOutput);
 430             for (String s : order) {
 431                 outputResources.add(transientOutput.findEntry(s).get());
 432             }
 433         } else {
 434             transientOutput.entries().forEach(res-> {
 435                 outputResources.add(res);
 436             });
 437         }
 438     }
 439 
 440     private void visitFirstNonFailingPool(VoidPoolVisitorEx pv) {
 441         boolean found = false;
 442         for (Entry<String, AsmModulePool> entry : pools.entrySet()) {
 443             try {
 444                 pv.visit(entry.getValue());
 445                 found = true;
 446                 break;
 447             } catch (Exception ex) {
 448                 // XXX OK, try  another one.
 449             }
 450         }
 451         if (!found) {
 452             throw new PluginException("No module found");
 453         }
 454     }
 455 
 456     private void visitAllPools(VoidPoolVisitor pv) {
 457         for (Entry<String, AsmModulePool> entry : pools.entrySet()) {
 458             pv.visit(entry.getValue());
 459         }
 460     }
 461 
 462     private void visitAllPoolsEx(VoidPoolVisitorEx pv) {
 463         for (Entry<String, AsmModulePool> entry : pools.entrySet()) {
 464             pv.visit(entry.getValue());
 465         }
 466     }
 467 
 468     private <P> P visitPoolsEx(RetPoolVisitor<P> pv) {
 469         P p = null;
 470         for (Entry<String, AsmModulePool> entry : pools.entrySet()) {
 471             try {
 472                 p = pv.visit(entry.getValue());
 473                 if (p != null) {
 474                     break;
 475                 }
 476             } catch (Exception ex) {
 477                 // XXX OK, try  another one.
 478             }
 479         }
 480         return p;
 481     }
 482 
 483     private <P> P visitPools(RetPoolVisitor<P> pv) {
 484         P p = null;
 485         for (Entry<String, AsmModulePool> entry : pools.entrySet()) {
 486             try {
 487                 p = pv.visit(entry.getValue());
 488                 if (p != null) {
 489                     break;
 490                 }
 491             } catch (Exception ex) {
 492                 // XXX OK, try  another one.
 493             }
 494         }
 495         return p;
 496     }
 497 }