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 } |
|