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 }