1 /*
   2  * Copyright (c) 2009, 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  */
  24 package com.sun.classanalyzer;
  25 
  26 import com.sun.classanalyzer.AnnotatedDependency.OptionalDependency;
  27 import java.io.IOException;
  28 import java.io.PrintWriter;
  29 import java.util.ArrayDeque;
  30 import java.util.Collection;
  31 import java.util.Collections;
  32 import java.util.Deque;
  33 import java.util.HashSet;
  34 import java.util.LinkedHashMap;
  35 import java.util.Map;
  36 import java.util.Set;
  37 import java.util.TreeMap;
  38 import java.util.TreeSet;
  39 
  40 /**
  41  *
  42  * @author Mandy Chung
  43  */
  44 public class Module implements Comparable<Module> {
  45 
  46     private static Map<String, Module> modules = new LinkedHashMap<String, Module>();
  47 
  48     public static Module addModule(ModuleConfig config) {
  49         String name = config.module;
  50         if (modules.containsKey(name)) {
  51             throw new RuntimeException("module \"" + name + "\" already exists");
  52         }
  53 
  54         Module m = new Module(config);
  55         modules.put(name, m);
  56         return m;
  57     }
  58 
  59     public static Module findModule(String name) {
  60         return modules.get(name);
  61     }
  62 
  63     static Collection<Module> getAllModules() {
  64         return Collections.unmodifiableCollection(modules.values());
  65     }
  66     private final String name;
  67     private final ModuleConfig config;
  68     private final Set<Klass> classes;
  69     private final Set<ResourceFile> resources;
  70     private final Set<Reference> unresolved;
  71     private final Set<Dependency> dependents;
  72     private final Map<String, PackageInfo> packages;
  73     private final Set<Module> members;
  74     private Module group;
  75     private boolean isBaseModule;
  76 
  77     private Module(ModuleConfig config) {
  78         this.name = config.module;
  79         this.isBaseModule = config.isBase;
  80         this.classes = new TreeSet<Klass>();
  81         this.resources = new TreeSet<ResourceFile>();
  82         this.config = config;
  83         this.unresolved = new HashSet<Reference>();
  84         this.dependents = new TreeSet<Dependency>();
  85         this.packages = new TreeMap<String, PackageInfo>();
  86         this.members = new TreeSet<Module>();
  87         this.group = this; // initialize to itself
  88     }
  89 
  90     String name() {
  91         return name;
  92     }
  93 
  94     Module group() {
  95         return group;
  96     }
  97 
  98     boolean isBase() {
  99         return isBaseModule;
 100     }
 101 
 102     Set<Module> members() {
 103         return members;
 104     }
 105 
 106     boolean contains(Klass k) {
 107         return k != null && classes.contains(k);
 108     }
 109 
 110     boolean isEmpty() {
 111         return classes.isEmpty() && resources.isEmpty();
 112     }
 113 
 114     /**
 115      * Returns an Iterable of Dependency, only one for each dependent
 116      * module of the strongest dependency (i.e.
 117      * hard static > hard dynamic > optional static > optional dynamic
 118      */
 119     Iterable<Dependency> dependents() {
 120         Map<Module, Dependency> deps = new LinkedHashMap<Module, Dependency>();
 121         for (Dependency dep : dependents) {
 122             Dependency d = deps.get(dep.module);
 123             if (d == null || dep.compareTo(d) > 0) {
 124                 deps.put(dep.module, dep);
 125             }
 126         }
 127         return deps.values();
 128     }
 129 
 130     @Override
 131     public int compareTo(Module o) {
 132         if (o == null) {
 133             return -1;
 134         }
 135         return name.compareTo(o.name);
 136     }
 137 
 138     @Override
 139     public String toString() {
 140         return name;
 141     }
 142 
 143     void addKlass(Klass k) {
 144         classes.add(k);
 145         k.setModule(this);
 146 
 147         // update package statistics
 148         String pkg = k.getPackageName();
 149         PackageInfo pkginfo = packages.get(pkg);
 150         if (pkginfo == null) {
 151             pkginfo = new PackageInfo(pkg);
 152             packages.put(pkg, pkginfo);
 153         }
 154         if (k.exists()) {
 155             // only count the class that is parsed
 156             pkginfo.add(k.getFileSize());
 157         }
 158     }
 159 
 160     void addResource(ResourceFile res) {
 161         resources.add(res);
 162         res.setModule(this);
 163     }
 164 
 165     void processRootsAndReferences() {
 166         // start with the root set
 167         Deque<Klass> pending = new ArrayDeque<Klass>();
 168         for (Klass k : Klass.getAllClasses()) {
 169             if (k.getModule() != null) {
 170                 continue;
 171             }
 172             String classname = k.getClassName();
 173             if (config.matchesRoot(classname) && !config.isExcluded(classname)) {
 174                 addKlass(k);
 175                 pending.add(k);
 176             }
 177         }
 178 
 179         // follow all references
 180         Klass k;
 181         while ((k = pending.poll()) != null) {
 182             if (!classes.contains(k)) {
 183                 addKlass(k);
 184             }
 185             for (Klass other : k.getReferencedClasses()) {
 186                 Module otherModule = other.getModule();
 187                 if (otherModule != null && otherModule != this) {
 188                     // this module is dependent on otherModule
 189                     addDependency(k, other);
 190                     continue;
 191                 }
 192 
 193                 if (!classes.contains(other)) {
 194                     if (config.isExcluded(other.getClassName())) {
 195                         // reference to an excluded class
 196                         unresolved.add(new Reference(k, other));
 197                     } else {
 198                         pending.add(other);
 199                     }
 200                 }
 201             }
 202         }
 203 
 204         // add other matching classes that don't require dependency analysis
 205         for (Klass c : Klass.getAllClasses()) {
 206             if (c.getModule() == null) {
 207                 String classname = c.getClassName();
 208                 if (config.matchesIncludes(classname) && !config.isExcluded(classname)) {
 209                     addKlass(c);
 210                     // dependencies
 211                     for (Klass other : c.getReferencedClasses()) {
 212                         Module otherModule = other.getModule();
 213                         if (otherModule == null) {
 214                             unresolved.add(new Reference(c, other));
 215                         } else {
 216                             if (otherModule != this) {
 217                                 // this module is dependent on otherModule
 218                                 addDependency(c, other);
 219                             }
 220                         }
 221                     }
 222                 }
 223             }
 224         }
 225 
 226 
 227         // add other matching classes that don't require dependency analysis
 228         for (ResourceFile res : ResourceFile.getAllResources()) {
 229             if (res.getModule() == null) {
 230                 String name = res.getName();
 231                 if (config.matchesIncludes(name) && !config.isExcluded(name)) {
 232                     addResource(res);
 233                 }
 234             }
 235         }
 236     }
 237 
 238     void addDependency(Klass from, Klass to) {
 239         Dependency dep = new Dependency(from, to);
 240         dependents.add(dep);
 241     }
 242 
 243     void fixupDependencies() {
 244         // update dependencies for classes that were allocated to modules after
 245         // this module was processed.
 246         for (Reference ref : unresolved) {
 247             Module m = ref.referree().getModule();
 248             if (m == null || m != this) {
 249                 addDependency(ref.referrer, ref.referree);
 250             }
 251         }
 252 
 253         fixupAnnotatedDependencies();
 254     }
 255 
 256     private void fixupAnnotatedDependencies() {
 257         // add dependencies that this klass may depend on due to the AnnotatedDependency
 258         dependents.addAll(AnnotatedDependency.getDependencies(this));
 259     }
 260 
 261     boolean isModuleDependence(Klass k) {
 262         Module m = k.getModule();
 263         return m == null || (!classes.contains(k) && !m.isBase());
 264     }
 265 
 266     Module getModuleDependence(Klass k) {
 267         if (isModuleDependence(k)) {
 268             Module m = k.getModule();
 269             if (group == this && m != null) {
 270                 // top-level module
 271                 return m.group;
 272             } else {
 273                 return m;
 274             }
 275         }
 276         return null;
 277     }
 278 
 279     <P> void visit(Set<Module> visited, Visitor<P> visitor, P p) {
 280         if (!visited.contains(this)) {
 281             visited.add(this);
 282             visitor.preVisit(this, p);
 283             for (Module m : members) {
 284                 m.visit(visited, visitor, p);
 285                 visitor.postVisit(this, m, p);
 286             }
 287         } else {
 288             throw new RuntimeException("Cycle detected: module " + this.name);
 289         }
 290     }
 291 
 292     void addMember(Module m) {
 293         // merge class list
 294         for (Klass k : m.classes) {
 295             classes.add(k);
 296         }
 297 
 298         // merge resource list
 299         for (ResourceFile res : m.resources) {
 300             resources.add(res);
 301         }
 302 
 303         // merge the package statistics
 304         for (PackageInfo pinfo : m.getPackageInfos()) {
 305             String packageName = pinfo.pkgName;
 306             PackageInfo pkginfo = packages.get(packageName);
 307             if (pkginfo == null) {
 308                 pkginfo = new PackageInfo(packageName);
 309                 packages.put(packageName, pkginfo);
 310             }
 311             pkginfo.add(pinfo);
 312         }
 313     }
 314 
 315     static void buildModuleMembers() {
 316         // set up module member relationship
 317         for (Module m : modules.values()) {
 318             m.group = m; // initialize to itself
 319             for (String name : m.config.members()) {
 320                 Module member = modules.get(name);
 321                 if (member == null) {
 322                     throw new RuntimeException("module \"" + name + "\" doesn't exist");
 323                 }
 324                 m.members.add(member);
 325             }
 326         }
 327 
 328         // set up the top-level module
 329         Visitor<Module> groupSetter = new Visitor<Module>() {
 330 
 331             public void preVisit(Module m, Module p) {
 332                 m.group = p;
 333                 if (p.isBaseModule) {
 334                     // all members are also base
 335                     m.isBaseModule = true;
 336                 }
 337             }
 338 
 339             public void postVisit(Module m, Module child, Module p) {
 340                 // nop - breadth-first search
 341             }
 342         };
 343 
 344         // propagate the top-level module to all its members
 345         for (Module p : modules.values()) {
 346             for (Module m : p.members) {
 347                 if (m.group == m) {
 348                     m.visit(new TreeSet<Module>(), groupSetter, p);
 349                 }
 350             }
 351         }
 352 
 353         Visitor<Module> mergeClassList = new Visitor<Module>() {
 354 
 355             public void preVisit(Module m, Module p) {
 356                 // nop - depth-first search
 357             }
 358 
 359             public void postVisit(Module m, Module child, Module p) {
 360                 m.addMember(child);
 361             }
 362         };
 363 
 364         Set<Module> visited = new TreeSet<Module>();
 365         for (Module m : modules.values()) {
 366             if (m.group() == m) {
 367                 if (m.members().size() > 0) {
 368                     // merge class list from all its members
 369                     m.visit(visited, mergeClassList, m);
 370                 }
 371 
 372                 // clear the dependencies before fixup
 373                 m.dependents.clear();
 374 
 375                 // fixup dependencies
 376                 for (Klass k : m.classes) {
 377                     for (Klass other : k.getReferencedClasses()) {
 378                         if (m.isModuleDependence(other)) {
 379                             // this module is dependent on otherModule
 380                             m.addDependency(k, other);
 381                         }
 382                     }
 383                 }
 384 
 385                 // add dependencies that this klass may depend on due to the AnnotatedDependency
 386                 m.fixupAnnotatedDependencies();
 387             }
 388         }
 389     }
 390 
 391     class PackageInfo implements Comparable {
 392 
 393         final String pkgName;
 394         int count;
 395         long filesize;
 396 
 397         PackageInfo(String name) {
 398             this.pkgName = name;
 399             this.count = 0;
 400             this.filesize = 0;
 401         }
 402 
 403         void add(PackageInfo pkg) {
 404             this.count += pkg.count;
 405             this.filesize += pkg.filesize;
 406         }
 407 
 408         void add(long size) {
 409             count++;
 410             filesize += size;
 411 
 412         }
 413 
 414         @Override
 415         public int compareTo(Object o) {
 416             return pkgName.compareTo(((PackageInfo) o).pkgName);
 417         }
 418     }
 419 
 420     Set<PackageInfo> getPackageInfos() {
 421         return new TreeSet<PackageInfo>(packages.values());
 422     }
 423 
 424     void printSummaryTo(String output) throws IOException {
 425         PrintWriter writer = new PrintWriter(output);
 426         try {
 427             long total = 0L;
 428             int count = 0;
 429             writer.format("%10s\t%10s\t%s\n", "Bytes", "Classes", "Package name");
 430             for (String pkg : packages.keySet()) {
 431                 PackageInfo info = packages.get(pkg);
 432                 if (info.count > 0) {
 433                     writer.format("%10d\t%10d\t%s\n", info.filesize, info.count, pkg);
 434                     total += info.filesize;
 435                     count += info.count;
 436                 }
 437             }
 438 
 439             writer.format("\nTotal: %d bytes (uncompressed) %d classes\n", total, count);
 440         } finally {
 441             writer.close();
 442         }
 443 
 444     }
 445 
 446     void printClassListTo(String output) throws IOException {
 447         // no file created if the module doesn't have any class nor resource
 448         if (isEmpty()) {
 449             return;
 450         }
 451 
 452         PrintWriter writer = new PrintWriter(output);
 453         try {
 454             for (Klass c : classes) {
 455                 if (c.exists()) {
 456                     writer.format("%s\n", c.getClassFilePathname());
 457                 } else {
 458                     trace("%s in module %s missing\n", c, this);
 459                 }
 460             }
 461 
 462         } finally {
 463             writer.close();
 464         }
 465     }
 466 
 467     void printResourceListTo(String output) throws IOException {
 468         // no file created if the module doesn't have any resource file
 469         if (resources.isEmpty()) {
 470             return;
 471         }
 472 
 473         PrintWriter writer = new PrintWriter(output);
 474         try {
 475             for (ResourceFile res : resources) {
 476                 writer.format("%s\n", res.getPathname());
 477             }
 478         } finally {
 479             writer.close();
 480         }
 481     }
 482 
 483     void printDependenciesTo(String output, boolean showDynamic) throws IOException {
 484         // no file created if the module doesn't have any class
 485         if (isEmpty()) {
 486             return;
 487         }
 488 
 489         PrintWriter writer = new PrintWriter(output);
 490         try {
 491             // classes that this klass may depend on due to the AnnotatedDependency
 492             Map<Reference, Set<AnnotatedDependency>> annotatedDeps = AnnotatedDependency.getReferences(this);
 493 
 494             for (Klass klass : classes) {
 495                 Set<Klass> references = klass.getReferencedClasses();
 496                 for (Klass other : references) {
 497                     String classname = klass.getClassName();
 498                     boolean optional = OptionalDependency.isOptional(klass, other);
 499                     if (optional) {
 500                         classname = "[optional] " + classname;
 501                     }
 502 
 503                     Module m = getModuleDependence(other);
 504                     if (m != null || other.getModule() == null) {
 505                         writer.format("%-40s -> %s (%s)", classname, other, m);
 506                         Reference ref = new Reference(klass, other);
 507                         if (annotatedDeps.containsKey(ref)) {
 508                             for (AnnotatedDependency ad : annotatedDeps.get(ref)) {
 509                                 writer.format(" %s", ad.getTag());
 510                             }
 511                             // printed; so remove the dependency from the annotated deps list
 512                             annotatedDeps.remove(ref);
 513                         }
 514                         writer.format("\n");
 515                     }
 516                 }
 517             }
 518 
 519 
 520             // print remaining dependencies specified in AnnotatedDependency list
 521             if (annotatedDeps.size() > 0) {
 522                 for (Map.Entry<Reference, Set<AnnotatedDependency>> entry : annotatedDeps.entrySet()) {
 523                     Reference ref = entry.getKey();
 524                     Module m = getModuleDependence(ref.referree);
 525                     if (m != null || ref.referree.getModule() == null) {
 526                         String classname = ref.referrer.getClassName();
 527                         boolean optional = true;
 528                         boolean dynamic = true;
 529                         String tag = "";
 530                         for (AnnotatedDependency ad : entry.getValue()) {
 531                             if (optional && !ad.isOptional()) {
 532                                 optional = false;
 533                                 tag = ad.getTag();
 534                             }
 535                             if (!ad.isDynamic()) {
 536                                 dynamic = false;
 537                             }
 538                         }
 539                         if (!showDynamic && optional && dynamic) {
 540                             continue;
 541                         }
 542                         if (optional) {
 543                             if (dynamic) {
 544                                 classname = "[dynamic] " + classname;
 545                             } else {
 546                                 classname = "[optional] " + classname;
 547                             }
 548                         }
 549                         writer.format("%-40s -> %s (%s) %s%n", classname, ref.referree, m, tag);
 550                     }
 551                 }
 552             }
 553 
 554         } finally {
 555             writer.close();
 556         }
 557     }
 558 
 559     static class Dependency implements Comparable<Dependency> {
 560 
 561         final Module module;
 562         final boolean optional;
 563         final boolean dynamic;
 564 
 565         Dependency(Klass from, Klass to) {
 566             // static dependency
 567             this.module = to.getModule() != null ? to.getModule().group() : null;
 568             this.optional = OptionalDependency.isOptional(from, to);
 569             this.dynamic = false;
 570         }
 571 
 572         Dependency(Module m, boolean optional, boolean dynamic) {
 573             this.module = m != null ? m.group() : null;
 574             this.optional = optional;
 575             this.dynamic = dynamic;
 576         }
 577 
 578         @Override
 579         public boolean equals(Object obj) {
 580             if (!(obj instanceof Dependency)) {
 581                 return false;
 582             }
 583             if (this == obj) {
 584                 return true;
 585             }
 586 
 587             Dependency d = (Dependency) obj;
 588             if (this.module != d.module) {
 589                 return false;
 590             } else {
 591                 return this.optional == d.optional && this.dynamic == d.dynamic;
 592             }
 593         }
 594 
 595         @Override
 596         public int hashCode() {
 597             int hash = 3;
 598             hash = 19 * hash + (this.module != null ? this.module.hashCode() : 0);
 599             hash = 19 * hash + (this.optional ? 1 : 0);
 600             hash = 19 * hash + (this.dynamic ? 1 : 0);
 601             return hash;
 602         }
 603 
 604         @Override
 605         public int compareTo(Dependency d) {
 606             if (this.equals(d)) {
 607                 return 0;
 608             }
 609 
 610             // Hard static > hard dynamic > optional static > optional dynamic
 611             if (this.module == d.module) {
 612                 if (this.optional == d.optional) {
 613                     return this.dynamic ? -1 : 1;
 614                 } else {
 615                     return this.optional ? -1 : 1;
 616                 }
 617             } else if (this.module != null && d.module != null) {
 618                 return (this.module.compareTo(d.module));
 619             } else {
 620                 return (this.module == null) ? -1 : 1;
 621             }
 622         }
 623 
 624         @Override
 625         public String toString() {
 626             String s = module.name();
 627             if (dynamic && optional) {
 628                 s += " (dynamic)";
 629             } else if (optional) {
 630                 s += " (optional)";
 631             }
 632             return s;
 633         }
 634     }
 635 
 636     static class Reference implements Comparable<Reference> {
 637 
 638         private final Klass referrer, referree;
 639 
 640         Reference(Klass referrer, Klass referree) {
 641             this.referrer = referrer;
 642             this.referree = referree;
 643         }
 644 
 645         Klass referrer() {
 646             return referrer;
 647         }
 648 
 649         Klass referree() {
 650             return referree;
 651         }
 652 
 653         @Override
 654         public int hashCode() {
 655             return referrer.hashCode() ^ referree.hashCode();
 656         }
 657 
 658         @Override
 659         public boolean equals(Object obj) {
 660             if (!(obj instanceof Reference)) {
 661                 return false;
 662             }
 663             if (this == obj) {
 664                 return true;
 665             }
 666 
 667             Reference r = (Reference) obj;
 668             return (this.referrer.equals(r.referrer) &&
 669                     this.referree.equals(r.referree));
 670         }
 671 
 672         @Override
 673         public int compareTo(Reference r) {
 674             int ret = referrer.compareTo(r.referrer);
 675             if (ret == 0) {
 676                 ret = referree.compareTo(r.referree);
 677             }
 678             return ret;
 679         }
 680     }
 681 
 682     interface Visitor<P> {
 683 
 684         public void preVisit(Module m, P param);
 685 
 686         public void postVisit(Module m, Module child, P param);
 687     }
 688     private static boolean traceOn = System.getProperty("classanalyzer.debug") != null;
 689 
 690     private static void trace(String format, Object... params) {
 691         System.err.format(format, params);
 692     }
 693 }