1 /*
   2  * Copyright (c) 2009, 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  */
  24 package com.sun.classanalyzer;
  25 
  26 import com.sun.classanalyzer.AnnotatedDependency.OptionalDependency;
  27 import java.io.BufferedReader;
  28 import java.io.File;
  29 import java.io.FileReader;
  30 import java.io.IOException;
  31 import java.io.PrintWriter;
  32 import java.util.ArrayDeque;
  33 import java.util.Collection;
  34 import java.util.Collections;
  35 import java.util.Deque;
  36 import java.util.HashSet;
  37 import java.util.LinkedHashMap;
  38 import java.util.LinkedHashSet;
  39 import java.util.LinkedList;
  40 import java.util.List;
  41 import java.util.Map;
  42 import java.util.Properties;
  43 import java.util.Set;
  44 import java.util.TreeMap;
  45 import java.util.TreeSet;
  46 
  47 import static com.sun.classanalyzer.Platform.*;
  48 import static com.sun.classanalyzer.Trace.*;
  49 
  50 /**
  51  *
  52  * @author Mandy Chung
  53  */
  54 public class Module implements Comparable<Module> {
  55 
  56     private static final Map<String, Module> modules = new LinkedHashMap<String, Module>();
  57 
  58     /**
  59      * Returns the top-level modules that are defined in
  60      * the input module config files.
  61      *
  62      */
  63     static Collection<Module> getTopLevelModules() {
  64         Set<Module> result = new LinkedHashSet<Module>();
  65         // always put the boot module first and then the base
  66         if (Platform.bootModule() != null) {
  67             result.add(Platform.bootModule());
  68         }
  69         result.add(findModule(baseModuleName));
  70 
  71         for (Module m : modules.values()) {
  72             if (m.isTopLevel()) {
  73                 result.add(m);
  74             }
  75         }
  76         return Collections.unmodifiableCollection(result);
  77     }
  78 
  79     public static Module addModule(ModuleConfig config) {
  80         String name = config.module;
  81         if (modules.containsKey(name)) {
  82             throw new RuntimeException("module \"" + name + "\" already exists");
  83         }
  84         Module m;
  85         if (Platform.isBootModule(config.module)) {
  86             m = Platform.createBootModule(config);
  87         } else {
  88             m = new Module(config);
  89         }
  90         modules.put(name, m);
  91         return m;
  92     }
  93 
  94     public static Module findModule(String name) {
  95         return modules.get(name);
  96     }
  97     private static String baseModuleName = "base";
  98     private static String version = "7-ea";
  99 
 100     static void setBaseModule(String name) {
 101         baseModuleName = name;
 102     }
 103 
 104     static void setVersion(String ver) {
 105         version = ver;
 106     }
 107     private static Properties moduleProps = new Properties();
 108 
 109     static void setModuleProperties(String file) throws IOException {
 110         File f = new File(file);
 111         BufferedReader reader = null;
 112         try {
 113             reader = new BufferedReader(new FileReader(f));
 114             moduleProps.load(reader);
 115         } finally {
 116             if (reader != null) {
 117                 reader.close();
 118             }
 119         }
 120     }
 121     private final String name;
 122     private final ModuleConfig config;
 123     private final Set<Klass> classes;
 124     private final Set<ResourceFile> resources;
 125     private final Set<Reference> unresolved;
 126     private final Map<String, PackageInfo> packages;
 127     private final Set<Dependency> dependents;
 128     private final Set<Module> members;
 129     private final Set<RequiresModule> requires;
 130     // update during the analysis
 131     private Set<Module> permits;
 132     private Module group;
 133     private boolean isBaseModule;
 134     private int platformApiCount;
 135 
 136     protected Module(ModuleConfig config) {
 137         this.name = config.module;
 138         this.isBaseModule = name.equals(baseModuleName);
 139         this.classes = new TreeSet<Klass>();
 140         this.resources = new TreeSet<ResourceFile>();
 141         this.config = config;
 142         this.unresolved = new HashSet<Reference>();
 143         this.dependents = new TreeSet<Dependency>();
 144         this.packages = new TreeMap<String, PackageInfo>();
 145         this.members = new TreeSet<Module>();
 146         this.requires = new TreeSet<RequiresModule>(config.requires());
 147         this.group = this; // initialize to itself
 148         this.platformApiCount = 0;
 149     }
 150 
 151     String name() {
 152         return name;
 153     }
 154 
 155     Module group() {
 156         return group;
 157     }
 158 
 159     boolean isBase() {
 160         return isBaseModule;
 161     }
 162 
 163     // requires local for JRE modules that are strongly
 164     // connected with the boot module
 165     boolean isBootConnected() {
 166         for (RequiresModule rm : requires) {
 167             if (Platform.isBootModule(rm.modulename)) {
 168                 return true;
 169             }
 170         }
 171         return false;
 172     }
 173     private Module moduleForRequires;
 174 
 175     synchronized Module toRequiredModule() {
 176         if (moduleForRequires == null) {
 177             // create a module for external requires if needed
 178             moduleForRequires = Platform.toRequiresModule(this);
 179         }
 180         return moduleForRequires;
 181     }
 182 
 183     Set<Module> members() {
 184         return members;
 185     }
 186 
 187     boolean hasPlatformAPIs() {
 188         return platformApiCount > 0;
 189     }
 190 
 191     boolean contains(Klass k) {
 192         return k != null && classes.contains(k);
 193     }
 194 
 195     boolean isEmpty() {
 196         return classes.isEmpty() &&
 197                 resources.isEmpty() &&
 198                 mainClass() == null;
 199     }
 200 
 201     boolean allowEmpty() {
 202         return moduleProps.getProperty(name + ".allow.empty") != null;
 203     }
 204 
 205     Module alias() {
 206         String mn = moduleProps.getProperty(name + ".alias");
 207         Module m = this;
 208         if (mn != null) {
 209             m = findModule(mn);
 210             if (m == null) {
 211                 throw new RuntimeException(name + ".alias = " + mn + " not found");
 212             }
 213         }
 214         return m;
 215     }
 216 
 217     protected boolean isTopLevel() {
 218         // module with no class is not included except the base module
 219         return this.group == this &&
 220                 (isBase() || !isEmpty() || isAggregator() || allowEmpty());
 221     }
 222 
 223     boolean isAggregator() {
 224         // a module is an aggregator if it has no class and resource and no main class
 225         // but has a list of requires.
 226         if (isEmpty() && requires.size() > 0) {
 227             // return false if it requires only jdk.boot
 228             if (requires.size() == 1) {
 229                 for (RequiresModule rm : requires) {
 230                     if (Platform.isBootModule(rm.modulename)) {
 231                         return false;
 232                     }
 233                 }
 234             }
 235             return true;
 236         }
 237 
 238         return false;
 239     }
 240 
 241     // fixup permits and requires set after modules are merged
 242     void fixupModuleInfo() {
 243         Set<Module> newPermits = new TreeSet<Module>();
 244         for (Module m : permits()) {
 245             // in case multiple permits from the same group
 246             newPermits.add(m.group());
 247         }
 248         permits.clear();
 249         permits.addAll(newPermits);
 250 
 251         // fixup requires set
 252         Set<RequiresModule> newRequires = new TreeSet<RequiresModule>();
 253         for (RequiresModule rm : requires) {
 254             Module req = rm.module();
 255             if (req.isEmpty() && !req.isAggregator()) {
 256                 // remove from requires set if empty and not a module aggregator
 257                 continue;
 258             }
 259 
 260             newRequires.add(rm);
 261             if (req.requirePermits()) {
 262                 req.permits().add(this.group());
 263             }
 264         }
 265         requires.clear();
 266         requires.addAll(newRequires);
 267 
 268         // add this to the permits set of its dependences if needed
 269         for (Dependency d : dependences()) {
 270             if (d.dynamic && !d.optional) {
 271                 // ignore dynamic dependencies for now
 272                 continue;
 273             }
 274 
 275             // add permits for all local dependencies
 276             Module dm = d.module();
 277             if (dm.requirePermits()) {
 278                 dm.permits().add(this.group());
 279             }
 280         }
 281     }
 282 
 283     Klass mainClass() {
 284         String cls = config.mainClass();
 285         if (cls == null) {
 286             return null;
 287         }
 288 
 289         Klass k = Klass.findKlass(cls);
 290         return k;
 291     }
 292 
 293     synchronized Set<Module> permits() {
 294         if (permits == null) {
 295             this.permits = new TreeSet<Module>();
 296             // initialize the permits set
 297             for (String s : config.permits()) {
 298                 Module m = findModule(s);
 299                 if (m != null) {
 300                     permits.add(m.group());
 301                 } else {
 302                     throw new RuntimeException("module " + s +
 303                             " specified in the permits rule for " + name + " doesn't exist");
 304                 }
 305             }
 306         }
 307         return permits;
 308     }
 309 
 310     Set<RequiresModule> requires() {
 311         return requires;
 312     }
 313 
 314     Collection<Dependency> dependents() {
 315         Map<Module, Dependency> deps = new LinkedHashMap<Module, Dependency>();
 316         for (Dependency dep : dependents) {
 317             Dependency d = deps.get(dep.module());
 318             if (d == null || dep.compareTo(d) > 0) {
 319                 deps.put(dep.module(), dep);
 320             }
 321         }
 322         return deps.values();
 323     }
 324 
 325     boolean requires(Module m) {
 326         for (RequiresModule rm : requires()) {
 327             if (rm.module() == m)
 328                 return true;
 329         }
 330         return false;
 331     }
 332     /**
 333      * Returns a Collection of Dependency, only one for each dependent
 334      * module of the strongest dependency (i.e.
 335      * hard static > hard dynamic > optional static > optional dynamic
 336      */
 337     Collection<Dependency> dependences() {
 338         Set<Dependency> result = new TreeSet<Dependency>();
 339         for (Dependency d : dependents()) {
 340             Module dm = d.module();
 341             Module rm = dm;
 342             if (!dm.alias().requires(this)) {
 343                 // use alias as the dependence except this module
 344                 // is required by the alias that will result in
 345                 // a recursive dependence.
 346                 rm = dm.alias();
 347             }
 348             if (!isBootConnected()) {
 349                 // If it's a local module requiring jdk.boot, retain
 350                 // the original requires; otherwise, use its external
 351                 // module
 352                 rm = rm.toRequiredModule();
 353             }
 354 
 355             result.add(new Dependency(rm, d.optional, d.dynamic));
 356         }
 357         return result;
 358     }
 359 
 360     @Override
 361     public int compareTo(Module o) {
 362         if (o == null) {
 363             return -1;
 364         }
 365         return name.compareTo(o.name);
 366     }
 367 
 368     @Override
 369     public String toString() {
 370         return name;
 371     }
 372 
 373     void addKlass(Klass k) {
 374         classes.add(k);
 375         k.setModule(this);
 376         if (k.isPlatformAPI()) {
 377             platformApiCount++;
 378         }
 379 
 380         // update package statistics
 381         String pkg = k.getPackageName();
 382         PackageInfo pkginfo = packages.get(pkg);
 383         if (pkginfo == null) {
 384             pkginfo = new PackageInfo(pkg);
 385             packages.put(pkg, pkginfo);
 386         }
 387 
 388         if (k.exists()) {
 389             // only count the class that is parsed
 390             pkginfo.add(k.getFileSize());
 391         }
 392     }
 393 
 394     void addResource(ResourceFile res) {
 395         resources.add(res);
 396         res.setModule(this);
 397     }
 398 
 399     void processRootsAndReferences() {
 400         // start with the root set
 401         Deque<Klass> pending = new ArrayDeque<Klass>();
 402         for (Klass k : Klass.getAllClasses()) {
 403             if (k.getModule() != null) {
 404                 continue;
 405             }
 406 
 407             String classname = k.getClassName();
 408             if (config.matchesRoot(classname) && !config.isExcluded(classname)) {
 409                 addKlass(k);
 410                 pending.add(k);
 411             }
 412         }
 413 
 414         // follow all references
 415         Klass k;
 416         while ((k = pending.poll()) != null) {
 417             if (!classes.contains(k)) {
 418                 addKlass(k);
 419             }
 420 
 421             for (Klass other : k.getReferencedClasses()) {
 422                 Module otherModule = other.getModule();
 423                 if (otherModule != null && otherModule != this) {
 424                     // this module is dependent on otherModule
 425                     addDependency(k, other);
 426                     continue;
 427                 }
 428 
 429                 if (!classes.contains(other)) {
 430                     if (config.isExcluded(other.getClassName())) {
 431                         // reference to an excluded class
 432                         unresolved.add(new Reference(k, other));
 433                     } else {
 434                         pending.add(other);
 435                     }
 436                 }
 437             }
 438         }
 439 
 440         // add other matching classes that don't require dependency analysis
 441         for (Klass c : Klass.getAllClasses()) {
 442             if (c.getModule() == null) {
 443                 String classname = c.getClassName();
 444                 if (config.matchesIncludes(classname) && !config.isExcluded(classname)) {
 445                     addKlass(c);
 446                     // dependencies
 447                     for (Klass other : c.getReferencedClasses()) {
 448                         Module otherModule = other.getModule();
 449                         if (otherModule == null) {
 450                             unresolved.add(new Reference(c, other));
 451                         } else {
 452                             if (otherModule != this) {
 453                                 // this module is dependent on otherModule
 454                                 addDependency(c, other);
 455                             }
 456                         }
 457                     }
 458                 }
 459             }
 460         }
 461 
 462         // add other matching classes that don't require dependency analysis
 463         for (ResourceFile res : ResourceFile.getAllResources()) {
 464             if (res.getModule() == null) {
 465                 String name = res.getName();
 466                 if (config.matchesIncludes(name) && !config.isExcluded(name)) {
 467                     addResource(res);
 468                 }
 469             }
 470         }
 471     }
 472 
 473     void addDependency(Klass from, Klass to) {
 474         Dependency dep = new Dependency(from, to);
 475         dependents.add(dep);
 476     }
 477 
 478     void addRequiresModule(Module m) {
 479         addRequiresModule(m, false);
 480     }
 481 
 482     void addRequiresModule(Module m, boolean optional) {
 483         requires.add(new RequiresModule(m, optional));
 484         if (m.requirePermits()) {
 485             m.permits().add(this);
 486         }
 487     }
 488 
 489     boolean requirePermits() {
 490         return (name().startsWith("sun.") ||
 491                 permits().size() > 0);
 492     }
 493 
 494     void fixupDependencies() {
 495         // update dependencies for classes that were allocated to modules after
 496         // this module was processed.
 497         for (Reference ref : unresolved) {
 498             Module m = ref.referree().getModule();
 499             if (m == null || m != this) {
 500                 addDependency(ref.referrer, ref.referree);
 501             }
 502         }
 503 
 504         // add dependency due to the main class
 505         Klass k = mainClass();
 506         if (k != null) {
 507             dependents.add(new Dependency(k.getModule(), false, false));
 508         }
 509         fixupAnnotatedDependencies();
 510     }
 511 
 512     private void fixupAnnotatedDependencies() {
 513         // add dependencies that this klass may depend on due to the AnnotatedDependency
 514         dependents.addAll(AnnotatedDependency.getDependencies(this));
 515     }
 516 
 517     boolean isModuleDependence(Klass k) {
 518         Module m = k.getModule();
 519         return m == null || (!classes.contains(k) && !m.isBase());
 520     }
 521 
 522     Module getModuleDependence(Klass k) {
 523         if (isModuleDependence(k)) {
 524             Module m = k.getModule();
 525             if (group == this && m != null) {
 526                 // top-level module
 527                 return m.group;
 528             } else {
 529                 return m;
 530             }
 531 
 532         }
 533         return null;
 534     }
 535 
 536     <P> void visitMember(Set<Module> visited, Visitor<P> visitor, P p) {
 537         if (!visited.contains(this)) {
 538             visited.add(this);
 539             visitor.preVisit(this, p);
 540             for (Module m : members) {
 541                 m.visitMember(visited, visitor, p);
 542                 visitor.visited(this, m, p);
 543             }
 544             visitor.postVisit(this, p);
 545         } else {
 546             throw new RuntimeException("Cycle detected: module " + this.name);
 547         }
 548     }
 549 
 550     private Set<Module> getDepModules() {
 551         Set<Module> deps = new TreeSet<Module>();
 552         for (Dependency d : dependences()) {
 553             if (d.dynamic || d.optional) {
 554                 // ignore dynamic or optional dependencies for now
 555                 continue;
 556             }
 557             deps.add(d.module());
 558         }
 559         for (RequiresModule req : requires) {
 560             if (req.optional) {
 561                 // ignore optional dependencies for now
 562                 continue;
 563             }
 564             deps.add(req.module());
 565         }
 566         return deps;
 567     }
 568 
 569     <P> void visitDependence(Set<Module> visited, Visitor<P> visitor, P p) {
 570         if (!visited.contains(this)) {
 571             visited.add(this);
 572 
 573             visitor.preVisit(this, p);
 574             for (Module m : getDepModules()) {
 575                 m.visitDependence(visited, visitor, p);
 576                 visitor.visited(this, m, p);
 577             }
 578             visitor.postVisit(this, p);
 579         }
 580     }
 581 
 582     void addMember(Module m) {
 583         // merge class list
 584         for (Klass k : m.classes) {
 585             classes.add(k);
 586         }
 587 
 588         // merge resource list
 589         for (ResourceFile res : m.resources) {
 590             resources.add(res);
 591         }
 592 
 593         platformApiCount += m.platformApiCount;
 594 
 595         // merge the package statistics
 596         for (PackageInfo pinfo : m.getPackageInfos()) {
 597             String packageName = pinfo.pkgName;
 598             PackageInfo pkginfo = packages.get(packageName);
 599             if (pkginfo == null) {
 600                 pkginfo = new PackageInfo(packageName);
 601                 packages.put(packageName, pkginfo);
 602             }
 603 
 604             pkginfo.add(pinfo);
 605         }
 606 
 607         // merge all permits and requires set
 608         permits().addAll(m.permits());
 609         requires().addAll(m.requires());
 610     }
 611 
 612     static void buildModuleMembers() {
 613         // set up module member relationship
 614         for (Module m : modules.values()) {
 615             m.group = m; // initialize to itself
 616             for (String name : m.config.members()) {
 617                 Module member = modules.get(name);
 618                 if (member == null) {
 619                     throw new RuntimeException("module \"" + name + "\" doesn't exist");
 620                 }
 621                 m.members.add(member);
 622             }
 623         }
 624 
 625         // set up the top-level module
 626         Visitor<Module> groupSetter = new Visitor<Module>() {
 627 
 628             public void preVisit(Module m, Module p) {
 629                 m.group = p;
 630                 if (p.isBaseModule) {
 631                     // all members are also base
 632                     m.isBaseModule = true;
 633                 }
 634             }
 635 
 636             public void visited(Module m, Module child, Module p) {
 637                 // nop - breadth-first search
 638             }
 639 
 640             public void postVisit(Module m, Module p) {
 641                 // nop - breadth-first search
 642             }
 643         };
 644 
 645         // propagate the top-level module to all its members
 646         for (Module p : modules.values()) {
 647             for (Module m : p.members) {
 648                 if (m.group == m) {
 649                     m.visitMember(new TreeSet<Module>(), groupSetter, p);
 650                 }
 651             }
 652         }
 653 
 654         Visitor<Module> mergeClassList = new Visitor<Module>() {
 655 
 656             public void preVisit(Module m, Module p) {
 657                 // nop - depth-first search
 658             }
 659 
 660             public void visited(Module m, Module child, Module p) {
 661                 m.addMember(child);
 662             }
 663 
 664             public void postVisit(Module m, Module p) {
 665             }
 666         };
 667 
 668         Set<Module> visited = new TreeSet<Module>();
 669         Set<Module> groups = new TreeSet<Module>();
 670         for (Module m : modules.values()) {
 671             if (m.group() == m) {
 672                 groups.add(m);
 673                 if (m.members().size() > 0) {
 674                     // merge class list from all its members
 675                     m.visitMember(visited, mergeClassList, m);
 676                 }
 677 
 678                 // clear the dependencies before fixup
 679                 m.dependents.clear();
 680 
 681                 // fixup dependencies
 682                 for (Klass k : m.classes) {
 683                     for (Klass other : k.getReferencedClasses()) {
 684                         if (m.isModuleDependence(other)) {
 685                             // this module is dependent on otherModule
 686                             m.addDependency(k, other);
 687                         }
 688                     }
 689                 }
 690 
 691                 // add dependency due to the main class
 692                 Klass k = m.mainClass();
 693                 if (k != null && m.isModuleDependence(k)) {
 694                     m.dependents.add(new Dependency(k.getModule().group(), false, false));
 695                 }
 696 
 697                 // add dependencies that this klass may depend on due to the AnnotatedDependency
 698                 m.fixupAnnotatedDependencies();
 699             }
 700         }
 701     }
 702 
 703     Set<Module> orderedDependencies() {
 704         Visitor<Set<Module>> walker = new Visitor<Set<Module>>() {
 705 
 706             public void preVisit(Module m, Set<Module> result) {
 707                 // nop - depth-first search
 708             }
 709 
 710             public void visited(Module m, Module child, Set<Module> result) {
 711             }
 712 
 713             public void postVisit(Module m, Set<Module> result) {
 714                 result.add(m);
 715             }
 716         };
 717 
 718         Set<Module> visited = new TreeSet<Module>();
 719         Set<Module> result = new LinkedHashSet<Module>();
 720 
 721         visitDependence(visited, walker, result);
 722         return result;
 723     }
 724 
 725     class PackageInfo implements Comparable {
 726 
 727         final String pkgName;
 728         int count;
 729         long filesize;
 730 
 731         PackageInfo(String name) {
 732             this.pkgName = name;
 733             this.count = 0;
 734             this.filesize = 0;
 735         }
 736 
 737         void add(PackageInfo pkg) {
 738             this.count += pkg.count;
 739             this.filesize += pkg.filesize;
 740         }
 741 
 742         void add(long size) {
 743             count++;
 744             filesize += size;
 745 
 746         }
 747 
 748         @Override
 749         public int compareTo(Object o) {
 750             return pkgName.compareTo(((PackageInfo) o).pkgName);
 751         }
 752     }
 753 
 754     Set<PackageInfo> getPackageInfos() {
 755         return new TreeSet<PackageInfo>(packages.values());
 756     }
 757 
 758     void printSummaryTo(String output) throws IOException {
 759         PrintWriter writer = new PrintWriter(output);
 760         try {
 761             long total = 0L;
 762             int count = 0;
 763             int nonCoreAPIs = 0;
 764             writer.format("%10s\t%10s\t%s%n", "Bytes", "Classes", "Package name");
 765             for (String pkg : packages.keySet()) {
 766                 PackageInfo info = packages.get(pkg);
 767                 if (info.count > 0) {
 768                     if (Platform.isNonCoreAPI(pkg)) {
 769                         nonCoreAPIs += info.count;
 770                         writer.format("%10d\t%10d\t%s (*)%n",
 771                                 info.filesize, info.count, pkg);
 772                     } else {
 773                         writer.format("%10d\t%10d\t%s%n",
 774                                 info.filesize, info.count, pkg);
 775                     }
 776                     total += info.filesize;
 777                     count += info.count;
 778                 }
 779             }
 780 
 781 
 782             writer.format("%nTotal: %d bytes (uncompressed) %d classes%n",
 783                     total, count);
 784             writer.format("APIs: %d core %d non-core (*)%n",
 785                     platformApiCount, nonCoreAPIs);
 786         } finally {
 787             writer.close();
 788         }
 789 
 790     }
 791 
 792     void printClassListTo(String output) throws IOException {
 793         if (classes.isEmpty()) {
 794             return;
 795         }
 796 
 797         PrintWriter writer = new PrintWriter(output);
 798         try {
 799             for (Klass c : classes) {
 800                 if (c.exists()) {
 801                     writer.format("%s\n", c.getClassFilePathname());
 802                 } else {
 803                     trace("%s in module %s missing\n", c, this);
 804                 }
 805             }
 806 
 807         } finally {
 808             writer.close();
 809         }
 810 
 811     }
 812 
 813     void printResourceListTo(String output) throws IOException {
 814         // no file created if the module doesn't have any resource file
 815         if (resources.isEmpty()) {
 816             return;
 817         }
 818 
 819         PrintWriter writer = new PrintWriter(output);
 820         try {
 821             for (ResourceFile res : resources) {
 822                 writer.format("%s\n", res.getPathname());
 823             }
 824 
 825         } finally {
 826             writer.close();
 827         }
 828 
 829     }
 830 
 831     void printDependenciesTo(String output, boolean showDynamic) throws IOException {
 832         PrintWriter writer = new PrintWriter(output);
 833         try {
 834             // classes that this klass may depend on due to the AnnotatedDependency
 835             Map<Reference, Set<AnnotatedDependency>> annotatedDeps = AnnotatedDependency.getReferences(this);
 836 
 837             for (Klass klass : classes) {
 838                 Set<Klass> references = klass.getReferencedClasses();
 839                 for (Klass other : references) {
 840                     String classname = klass.getClassName();
 841                     boolean optional = OptionalDependency.isOptional(klass, other);
 842                     if (optional) {
 843                         classname = "[optional] " + classname;
 844                     }
 845 
 846                     Module m = getModuleDependence(other);
 847                     if (m != null || other.getModule() == null) {
 848                         writer.format("%-40s -> %s (%s)", classname, other, m);
 849                         Reference ref = new Reference(klass, other);
 850                         if (annotatedDeps.containsKey(ref)) {
 851                             for (AnnotatedDependency ad : annotatedDeps.get(ref)) {
 852                                 writer.format(" %s", ad.getTag());
 853                             }
 854                             // printed; so remove the dependency from the annotated deps list
 855                             annotatedDeps.remove(ref);
 856                         }
 857                         writer.format("\n");
 858                     }
 859                 }
 860             }
 861 
 862             // print remaining dependencies specified in AnnotatedDependency list
 863             if (annotatedDeps.size() > 0) {
 864                 for (Map.Entry<Reference, Set<AnnotatedDependency>> entry : annotatedDeps.entrySet()) {
 865                     Reference ref = entry.getKey();
 866                     Module m = getModuleDependence(ref.referree);
 867                     if (m != null || ref.referree.getModule() == null) {
 868                         String classname = ref.referrer.getClassName();
 869                         boolean optional = true;
 870                         boolean dynamic = true;
 871                         String tag = "";
 872                         for (AnnotatedDependency ad : entry.getValue()) {
 873                             if (optional && !ad.isOptional()) {
 874                                 optional = false;
 875                                 tag = ad.getTag();
 876                             }
 877 
 878                             if (!ad.isDynamic()) {
 879                                 dynamic = false;
 880                             }
 881                         }
 882                         if (!showDynamic && optional && dynamic) {
 883                             continue;
 884                         }
 885 
 886                         if (optional) {
 887                             classname = "[optional] " + classname;
 888                         } else if (dynamic) {
 889                             classname = "[dynamic] " + classname;
 890                         }
 891                         writer.format("%-40s -> %s (%s) %s%n", classname, ref.referree, m, tag);
 892                     }
 893                 }
 894             }
 895         } finally {
 896             writer.close();
 897         }
 898 
 899     }
 900 
 901     // print module dependency list
 902     void printDepModuleListTo(String output) throws IOException {
 903         PrintWriter writer = new PrintWriter(output);
 904         try {
 905             for (Module m : orderedDependencies()) {
 906                 writer.format("%s\n", m.name());
 907             }
 908             if (Platform.legacyModule() != null &&
 909                     (this == Platform.jdkBaseModule() ||
 910                     this == Platform.jdkModule() ||
 911                     this == Platform.jreModule())) {
 912                 // add legacy module in the modules.list
 913                 // so that it will install legacy module as well.
 914                 writer.format("%s\n", Platform.legacyModule());
 915             }
 916         } finally {
 917             writer.close();
 918         }
 919     }
 920 
 921     void printModuleInfoTo(String output) throws IOException {
 922         PrintWriter writer = new PrintWriter(output);
 923         try {
 924             writer.format("module %s @ %s {%n", name, version);
 925             String formatSep = "    requires";
 926             Map<String, RequiresModule> reqs = new TreeMap<String, RequiresModule>();
 927             for (RequiresModule rm : requires()) {
 928                 reqs.put(rm.module().name(), rm);
 929             }
 930 
 931             for (Dependency dep : dependences()) {
 932                 Module dm = dep.module();
 933                 if (!isBootConnected()) {
 934                     // If it's a local module requiring jdk.boot, retain
 935                     // the original requires
 936                     dm = dm.toRequiredModule();
 937                 }
 938 
 939                 if (dm == null) {
 940                     System.err.format("WARNING: module %s has a dependency on null module%n", name);
 941                 }
 942 
 943                 StringBuilder attributes = new StringBuilder();
 944                 RequiresModule rm = reqs.get(dm.name());
 945 
 946                 if (rm != null && rm.reexport) {
 947                     attributes.append(" public");
 948                 }
 949 
 950                 if (isBootConnected() || (rm != null && rm.local)) {
 951                     attributes.append(" local");
 952                 }
 953 
 954                 if (dep.optional || (rm != null && rm.optional)) {
 955                     attributes.append(" optional");
 956                 }
 957 
 958                 // FIXME: ignore dynamic dependencies
 959                 // Filter out optional dependencies for the boot module
 960                 // which are addded in the jdk.base module instead
 961                 if (!dep.dynamic || dep.optional) {
 962                     reqs.remove(dm.name());
 963                     writer.format("%s%s %s @ %s;%n",
 964                             formatSep,
 965                             attributes.toString(),
 966                             dep != null ? dm : "null", version);
 967                 }
 968 
 969             }
 970             // additional requires
 971             if (reqs.size() > 0) {
 972                 for (RequiresModule rm : reqs.values()) {
 973                     StringBuilder attributes = new StringBuilder();
 974                     if (rm.reexport) {
 975                         attributes.append(" public");
 976                     }
 977                     if (rm.optional) {
 978                         attributes.append(" optional");
 979                     }
 980                     if (isBootConnected() || rm.local) {
 981                         attributes.append(" local");
 982                     }
 983 
 984                     writer.format("%s%s %s @ %s;%n", formatSep, attributes.toString(), rm.module(), version);
 985                 }
 986             }
 987 
 988             // permits
 989             if (permits().size() > 0) {
 990                 formatSep = "    permits";
 991                 for (Module p : permits()) {
 992                     writer.format("%s %s", formatSep, p);
 993                     formatSep = ",";
 994                 }
 995                 writer.format(";%n");
 996             }
 997             if (mainClass() != null) {
 998                 writer.format("    class %s;%n", mainClass().getClassName());
 999             }
1000             writer.format("}%n");
1001         } finally {
1002             writer.close();
1003         }
1004     }
1005 
1006     static class Dependency implements Comparable<Dependency> {
1007 
1008         protected Module module;
1009         final boolean optional;
1010         final boolean dynamic;
1011 
1012         Dependency(Klass from, Klass to) {
1013             // static dependency
1014             this.module = to.getModule() != null ? to.getModule().group() : null;
1015             this.optional = OptionalDependency.isOptional(from, to);
1016             this.dynamic = false;
1017         }
1018 
1019         Dependency(Module m, boolean optional, boolean dynamic) {
1020             this.module = m != null ? m.group() : null;
1021             this.optional = optional;
1022             this.dynamic = dynamic;
1023         }
1024 
1025         Module module() {
1026             return module;
1027         }
1028 
1029         public boolean isLocal(Module from) {
1030             if (module().isBootConnected()) {
1031                 // local requires if the requesting module is the boot module
1032                 // or it's an aggregate platform module
1033                 return true;
1034             }
1035 
1036             for (PackageInfo pkg : from.getPackageInfos()) {
1037                 // local dependence if any package this module owns is splitted
1038                 // across its dependence
1039                 for (PackageInfo p : module().getPackageInfos()) {
1040                     if (pkg.pkgName.equals(p.pkgName)) {
1041                         return true;
1042                     }
1043                 }
1044             }
1045             return false;
1046         }
1047 
1048         @Override
1049         public boolean equals(Object obj) {
1050             if (!(obj instanceof Dependency)) {
1051                 return false;
1052             }
1053             if (this == obj) {
1054                 return true;
1055             }
1056 
1057             Dependency d = (Dependency) obj;
1058             if (this.module() != d.module()) {
1059                 return false;
1060             } else {
1061                 return this.optional == d.optional && this.dynamic == d.dynamic;
1062             }
1063         }
1064 
1065         @Override
1066         public int hashCode() {
1067             int hash = 3;
1068             hash = 19 * hash + (this.module() != null ? this.module().hashCode() : 0);
1069             hash = 19 * hash + (this.optional ? 1 : 0);
1070             hash = 19 * hash + (this.dynamic ? 1 : 0);
1071             return hash;
1072         }
1073 
1074         @Override
1075         public int compareTo(Dependency d) {
1076             if (this.equals(d)) {
1077                 return 0;
1078             }
1079 
1080             // Hard static > hard dynamic > optional static > optional dynamic
1081             if (this.module() == d.module()) {
1082                 if (this.optional == d.optional) {
1083                     return this.dynamic ? -1 : 1;
1084                 } else {
1085                     return this.optional ? -1 : 1;
1086                 }
1087             } else if (this.module() != null && d.module() != null) {
1088                 return (this.module().compareTo(d.module()));
1089             } else {
1090                 return (this.module() == null) ? -1 : 1;
1091             }
1092         }
1093 
1094         @Override
1095         public String toString() {
1096             String s = module().name();
1097             if (optional) {
1098                 s += " (optional)";
1099             } else if (dynamic) {
1100                 s += " (dynamic)";
1101             }
1102             return s;
1103         }
1104     }
1105 
1106     static class RequiresModule extends Dependency {
1107 
1108         final String modulename;
1109         final boolean reexport;
1110         final boolean local;
1111 
1112         public RequiresModule(String name, boolean optional, boolean reexport, boolean local) {
1113             super(null, optional, false /* dynamic */);
1114             this.modulename = name;
1115             this.reexport = reexport;
1116             this.local = local;
1117         }
1118 
1119         public RequiresModule(Module m, boolean optional) {
1120             super(m, optional, false);
1121             this.modulename = m.name();
1122             this.reexport = true;
1123             this.local = false;
1124         }
1125 
1126         // deferred initialization until it's called.
1127         // must call after all modules are merged.
1128         synchronized Module fixupModule() {
1129             if (module == null) {
1130                 Module m = findModule(modulename);
1131                 if (m == null) {
1132                     throw new RuntimeException("Required module \"" + modulename + "\" doesn't exist");
1133                 }
1134                 module = m.group();
1135             }
1136             return module;
1137         }
1138 
1139         @Override
1140         Module module() {
1141             return fixupModule();
1142         }
1143 
1144         @Override
1145         public int compareTo(Dependency d) {
1146             RequiresModule rm = (RequiresModule) d;
1147             if (this.equals(rm)) {
1148                 return 0;
1149             }
1150             return modulename.compareTo(rm.modulename);
1151         }
1152 
1153         @Override
1154         public boolean equals(Object obj) {
1155             if (!(obj instanceof RequiresModule)) {
1156                 return false;
1157             }
1158             if (this == obj) {
1159                 return true;
1160             }
1161 
1162             RequiresModule d = (RequiresModule) obj;
1163             return this.modulename.equals(d.modulename);
1164         }
1165 
1166         @Override
1167         public int hashCode() {
1168             int hash = 3;
1169             hash = 19 * hash + this.modulename.hashCode();
1170             return hash;
1171         }
1172 
1173         @Override
1174         public String toString() {
1175             String s = reexport ? "public " : "";
1176             if (optional) {
1177                 s += "optional ";
1178             }
1179             s += modulename;
1180             return s;
1181         }
1182     }
1183 
1184     static class Reference implements Comparable<Reference> {
1185 
1186         private final Klass referrer, referree;
1187 
1188         Reference(Klass referrer, Klass referree) {
1189             this.referrer = referrer;
1190             this.referree = referree;
1191         }
1192 
1193         Klass referrer() {
1194             return referrer;
1195         }
1196 
1197         Klass referree() {
1198             return referree;
1199         }
1200 
1201         @Override
1202         public int hashCode() {
1203             return referrer.hashCode() ^ referree.hashCode();
1204         }
1205 
1206         @Override
1207         public boolean equals(Object obj) {
1208             if (!(obj instanceof Reference)) {
1209                 return false;
1210             }
1211             if (this == obj) {
1212                 return true;
1213             }
1214 
1215             Reference r = (Reference) obj;
1216             return (this.referrer.equals(r.referrer) && this.referree.equals(r.referree));
1217         }
1218 
1219         @Override
1220         public int compareTo(Reference r) {
1221             int ret = referrer.compareTo(r.referrer);
1222             if (ret == 0) {
1223                 ret = referree.compareTo(r.referree);
1224             }
1225             return ret;
1226         }
1227     }
1228 
1229     interface Visitor<P> {
1230 
1231         public void preVisit(Module m, P param);
1232 
1233         public void visited(Module m, Module child, P param);
1234 
1235         public void postVisit(Module m, P param);
1236     }
1237 }