1 /*
   2  * Copyright (c) 2010, 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.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 package com.sun.classanalyzer;
  24 
  25 import com.sun.classanalyzer.AnnotatedDependency.OptionalDependency;
  26 import com.sun.classanalyzer.Module.ModuleVisitor;
  27 import com.sun.classanalyzer.ModuleInfo.Dependence;
  28 import com.sun.classanalyzer.ModuleInfo.PackageInfo;
  29 import static com.sun.classanalyzer.ModuleInfo.Dependence.Modifier.*;
  30 import java.io.File;
  31 import java.io.IOException;
  32 import java.util.*;
  33 
  34 /**
  35  * Module builder that creates modules as defined in the given
  36  * module configuration files.  The run() method assigns
  37  * all classes and resources according to the module definitions.
  38  * Additional dependency information can be specified e.g.
  39  * Class.forName, JNI_FindClass, and service providers.
  40  *
  41  * @see DependencyConfig
  42  * @author mchung
  43  */
  44 public class ModuleBuilder {
  45 
  46     private final List<String> depConfigs = new ArrayList<String>();
  47     private final Map<Module, ModuleInfo> moduleinfos =
  48             new LinkedHashMap<Module, ModuleInfo>();
  49     private final String version;
  50     private final boolean mergeModules;
  51 
  52     public ModuleBuilder(List<String> configs, String version)
  53             throws IOException {
  54         this(configs, null, true, version);
  55     }
  56 
  57     public ModuleBuilder(List<String> configs,
  58             List<String> depconfigs,
  59             boolean merge,
  60             String version)
  61             throws IOException {
  62         if (configs != null) {
  63             // create modules based on the input config files
  64             for (String file : configs) {
  65                 for (ModuleConfig mconfig : ModuleConfig.readConfigurationFile(file)) {
  66                     newModule(mconfig);
  67                 }
  68             }
  69         }
  70         if (depconfigs != null) {
  71             this.depConfigs.addAll(depconfigs);
  72         }
  73         this.mergeModules = merge;
  74         this.version = version;
  75     }
  76 
  77     /**
  78      * Returns a module of a given name with no main entry point.
  79      */
  80     public Module newModule(String name) throws IOException {
  81         return newModule(new ModuleConfig(name, null));
  82     }
  83 
  84     /**
  85      * Returns a module of a given ModuleConfig.
  86      */
  87     public Module newModule(ModuleConfig mconfig) {
  88         return Module.addModule(mconfig);
  89     }
  90 
  91     /**
  92      * Loads modules from the .classlist and .resources files in
  93      * the given classListDir.
  94      *
  95      */
  96     public Set<Module> loadModulesFrom(File classlistDir) throws IOException {
  97         ClassListReader reader = new ClassListReader(this);
  98         return reader.loadModulesFrom(classlistDir);
  99     }
 100 
 101     /**
 102      * This method assigns the classes and resource files
 103      * to modules and generates the package information and
 104      * the module information.
 105      *
 106      * This method can be overridden in a subclass implementation.
 107      */
 108     public void run() throws IOException {
 109         // assign classes and resource files to the modules and
 110         // group fine-grained modules per configuration files
 111         buildModules();
 112 
 113         // generate package information
 114         buildPackageInfos();
 115 
 116         // analyze cross-module dependencies and generate ModuleInfo
 117         buildModuleInfos();
 118     }
 119 
 120     /**
 121      * Returns the resulting top-level, non-empty modules.
 122      */
 123     public Set<Module> getModules() {
 124         return moduleinfos.keySet();
 125     }
 126 
 127     /**
 128      * Builds modules from the existing list of classes and resource
 129      * files according to the module configuration files.
 130      *
 131      */
 132     protected void buildModules() throws IOException {
 133         // Add additional dependencies after classes are added to the modules
 134         DependencyConfig.parse(depConfigs);
 135 
 136         // process the roots and dependencies to get the classes for each module
 137         Collection<Module> modules = Module.getAllModules();
 138         for (Module m : modules) {
 139             m.processRootsAndReferences();
 140         }
 141 
 142         if (mergeModules) {
 143             // group fine-grained modules
 144             Module.buildModuleMembers();
 145         }
 146     }
 147 
 148     /**
 149      * Build ModuleInfo for the top level modules.
 150      */
 151     protected void buildModuleInfos() {
 152         // backedges (i.e. reverse dependences)
 153         Map<Module, Set<Module>> backedges = new TreeMap<Module, Set<Module>>();
 154 
 155         // analyze the module's dependences and create ModuleInfo
 156         for (Module m : Module.getAllModules()) {
 157             if (m.isTopLevel()) {
 158                 ModuleInfo mi = buildModuleInfo(m);
 159                 m.setModuleInfo(mi);
 160                 moduleinfos.put(m, mi);
 161                 // keep track of the backedges
 162                 for (Dependence d : mi.requires()) {
 163                     // only add the top level modules
 164                     Module dep = d.getModule();
 165                     Set<Module> set = backedges.get(dep);
 166                     if (set == null) {
 167                         set = new TreeSet<Module>();
 168                         backedges.put(dep, set);
 169                     }
 170                     set.add(m);
 171                 }
 172             }
 173         }
 174 
 175         // fixup permits after all ModuleInfo are created in two passes:
 176         // 1. permits the requesting module if it requires local dependence
 177         // 2. if permits set is non-empty, permits
 178         //    all of its requesting modules
 179         for (ModuleInfo mi : moduleinfos.values()) {
 180             for (Dependence d : mi.requires()) {
 181                 if (d.isLocal()) {
 182                     Module dm = d.getModule();
 183                     moduleinfos.get(dm).addPermit(mi.getModule());
 184                 }
 185             }
 186         }
 187 
 188         for (Map.Entry<Module, Set<Module>> e : backedges.entrySet()) {
 189             Module dm = e.getKey();
 190             ModuleInfo dmi = moduleinfos.get(dm);
 191             if (dmi == null) {
 192                 throw new RuntimeException(dm + " null moduleinfo");
 193             }
 194             if (dmi.permits().size() > 0) {
 195                 for (Module m : e.getValue()) {
 196                     dmi.addPermit(m);
 197                 }
 198             }
 199         }
 200     }
 201 
 202     // module to packages
 203     private final Map<Module, Set<PackageInfo>> packagesForModule =
 204             new TreeMap<Module, Set<PackageInfo>>();
 205     // package name to PackageInfo set
 206     private final Map<String, Set<PackageInfo>> packages =
 207             new TreeMap<String, Set<PackageInfo>>();
 208     // module with split packages
 209     private final Map<Module, Set<PackageInfo>> modulesWithSplitPackage =
 210             new TreeMap<Module, Set<PackageInfo>>();
 211 
 212     /**
 213      * Builds PackageInfo for each top level module.
 214      */
 215     protected void buildPackageInfos() {
 216         for (Module m : Module.getAllModules()) {
 217             if (m.isTopLevel()) {
 218                 Set<PackageInfo> pkgs = getPackageInfos(m);
 219                 packagesForModule.put(m, pkgs);
 220             }
 221         }
 222 
 223         for (Map.Entry<Module, Set<PackageInfo>> e : packagesForModule.entrySet()) {
 224             Module m = e.getKey();
 225             for (PackageInfo p : e.getValue()) {
 226                 Set<PackageInfo> set = packages.get(p.pkgName);
 227                 if (set == null) {
 228                     set = new TreeSet<PackageInfo>();
 229                     packages.put(p.pkgName, set);
 230                 }
 231                 set.add(p);
 232             }
 233         }
 234 
 235         for (Map.Entry<String, Set<PackageInfo>> e : packages.entrySet()) {
 236             String pkg = e.getKey();
 237             if (e.getValue().size() > 1) {
 238                 for (PackageInfo pi : e.getValue()) {
 239                     Set<PackageInfo> set = modulesWithSplitPackage.get(pi.module);
 240                     if (set == null) {
 241                         set = new TreeSet<PackageInfo>();
 242                         modulesWithSplitPackage.put(pi.module, set);
 243                     }
 244                     set.add(pi);
 245                 }
 246             }
 247         }
 248     }
 249 
 250     public Map<String, Set<Module>> getSplitPackages() {
 251         Map<String, Set<Module>> result = new LinkedHashMap<String, Set<Module>>();
 252         for (Map.Entry<String, Set<PackageInfo>> e : packages.entrySet()) {
 253             String pkg = e.getKey();
 254             if (e.getValue().size() > 1) {
 255                 for (PackageInfo pi : e.getValue()) {
 256                     Set<Module> set = result.get(pkg);
 257                     if (set == null) {
 258                         set = new TreeSet<Module>();
 259                         result.put(pkg, set);
 260                     }
 261                     set.add(pi.module);
 262                 }
 263             }
 264         }
 265         return result;
 266     }
 267 
 268     private Set<PackageInfo> getPackageInfos(final Module m) {
 269         Map<String, PackageInfo> packages = new TreeMap<String, PackageInfo>();
 270         Module.Visitor<Void, Map<String, PackageInfo>> visitor =
 271                 new Module.Visitor<Void, Map<String, PackageInfo>>() {
 272 
 273                     @Override
 274                     public Void visitClass(Klass k, Map<String, PackageInfo> packages) {
 275                         // update package statistics
 276                         String pkg = k.getPackageName();
 277                         PackageInfo pkginfo = packages.get(pkg);
 278                         if (pkginfo == null) {
 279                             pkginfo = new PackageInfo(m, pkg);
 280                             packages.put(pkg, pkginfo);
 281                         }
 282 
 283                         if (k.exists()) {
 284                             // only count the class that is parsed
 285                             pkginfo.add(k.getFileSize());
 286                         }
 287                         return null;
 288                     }
 289 
 290                     @Override
 291                     public Void visitResource(ResourceFile r, Map<String, PackageInfo> packages) {
 292                         // nop
 293                         return null;
 294                     }
 295                 };
 296 
 297         m.visit(visitor, packages);
 298         return new TreeSet<PackageInfo>(packages.values());
 299     }
 300 
 301     private ModuleInfo buildModuleInfo(Module m) {
 302         Map<Module, Dependence> requires = new LinkedHashMap<Module, Dependence>();
 303         Set<Module> permits = new TreeSet<Module>();
 304 
 305         // add static dependences
 306         for (Klass from : m.classes()) {
 307             for (Klass to : from.getReferencedClasses()) {
 308                 if (m.isModuleDependence(to)) {
 309                     // is this dependence overridden as optional?
 310                     boolean optional = OptionalDependency.isOptional(from, to);
 311                     addDependence(requires, new Dependence(from, to, optional));
 312                 }
 313             }
 314         }
 315 
 316         // add dependency due to the main class
 317         Klass k = m.mainClass();
 318         if (k != null && m.isModuleDependence(k)) {
 319             addDependence(requires, new Dependence(k.getModule()));
 320         }
 321 
 322         // add requires and permits specified in the config files
 323         processModuleConfigs(m, requires, permits);
 324 
 325         // add dependencies due to the AnnotatedDependency
 326         for (Dependence d : AnnotatedDependency.getDependencies(m)) {
 327             addDependence(requires, d);
 328         }
 329 
 330         // Add LOCAL to the dependence and permits will be added
 331         // in the separate phase
 332         Set<PackageInfo> splitPkgs = modulesWithSplitPackage.get(m);
 333         if (splitPkgs != null) {
 334             for (PackageInfo sp : splitPkgs) {
 335                 Set<PackageInfo> pis = packages.get(sp.pkgName);
 336                 for (PackageInfo pi : pis) {
 337                     // is the package splitted with its dependence?
 338                     if (requires.containsKey(pi.module)) {
 339                         // If so, the dependence has to be LOCAL
 340                         requires.get(pi.module).addModifier(LOCAL);
 341                     }
 342                 }
 343             }
 344         }
 345 
 346         // use the module's exporter in the dependence
 347         Set<Dependence> depset = new TreeSet<Dependence>();
 348         for (Dependence d : requires.values()) {
 349             Dependence dep = d;
 350             if (!d.isLocal()) {
 351                 Module exp = d.getModule().exporter(m);
 352                 if (exp == null) {
 353                     throw new RuntimeException(d.getModule() + " null exporter");
 354                 }
 355                 if (d.getModule() != exp && exp != m) {
 356                     dep = new Dependence(exp, d.modifiers());
 357                 }
 358             }
 359             // ## not to include optional dependences in jdk.boot
 360             // ## should move this to jdk.base
 361             if (m instanceof PlatformModuleBuilder.BootModule && d.isOptional()) {
 362                 continue;
 363             }
 364 
 365             depset.add(dep);
 366         }
 367         ModuleInfo mi = new ModuleInfo(m, version, packagesForModule.get(m), depset, permits);
 368         return mi;
 369     }
 370 
 371     private void addDependence(Map<Module, Dependence> requires, Dependence d) {
 372         Module dm = d.getModule();
 373         Dependence dep = requires.get(dm);
 374         if (dep == null || dep.equals(d)) {
 375             requires.put(dm, d);
 376         } else {
 377             if (dep.getModule() != d.getModule()) {
 378                 throw new RuntimeException("Unexpected dependence " + dep + " != " + d);
 379             }
 380 
 381             // update the modifiers
 382             dep.update(d);
 383             requires.put(dm, dep);
 384         }
 385     }
 386 
 387     private void processModuleConfigs(final Module module,
 388             final Map<Module, Dependence> requires,
 389             final Set<Module> permits) {
 390         ModuleVisitor<Void> v = new ModuleVisitor<Void>() {
 391 
 392             public void preVisit(Module p, Void dummy) {
 393             }
 394 
 395             public void visited(Module p, Module m, Void dummy) {
 396                 for (Dependence d : m.config().requires()) {
 397                     addDependence(requires, d);
 398                 }
 399                 for (String name : m.config().permits()) {
 400                     Module pm = Module.findModule(name);
 401                     if (pm != null) {
 402                         permits.add(pm.group());
 403                     } else {
 404                         throw new RuntimeException("module " + name
 405                                 + " specified in the permits rule for " + m.name()
 406                                 + " doesn't exist");
 407                     }
 408                 }
 409             }
 410 
 411             public void postVisit(Module p, Void dummy) {
 412             }
 413         };
 414 
 415         Set<Module> visited = new TreeSet<Module>();
 416         // first add requires and permits for the module
 417         v.visited(module, module, null);
 418         // then visit their members
 419         module.visitMembers(visited, v, null);
 420     }
 421 }