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
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 }
|
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.ModuleInfo.Dependence;
27 import java.io.BufferedReader;
28 import java.io.File;
29 import java.io.FileReader;
30 import java.io.IOException;
31 import java.util.ArrayDeque;
32 import java.util.Collection;
33 import java.util.Collections;
34 import java.util.Deque;
35 import java.util.HashSet;
36 import java.util.LinkedHashMap;
37 import java.util.Map;
38 import java.util.Properties;
39 import java.util.Set;
40 import java.util.TreeSet;
41
42
43 /**
44 * Module contains a list of classes and resources.
45 *
46 * @author Mandy Chung
47 */
48 public class Module implements Comparable<Module> {
49
50 private static final Map<String, Module> modules =
51 new LinkedHashMap<String, Module>();
52
53 public static Collection<Module> getAllModules() {
54 return Collections.unmodifiableCollection(modules.values());
55 }
56
57 public static void addModule(Module m) {
58 String name = m.name();
59 if (modules.containsKey(name)) {
60 throw new RuntimeException("module \"" + name + "\" already exists");
61 }
62 modules.put(name, m);
63 }
64
65 public static Module addModule(ModuleConfig config) {
66 String name = config.module;
67 if (modules.containsKey(name)) {
68 throw new RuntimeException("module \"" + name + "\" already exists");
69 }
70 Module m = new Module(config);
71 addModule(m);
72 return m;
73 }
74
75 public static Module findModule(String name) {
76 return modules.get(name);
77 }
78
79 private static String baseModuleName = "base";
80 static void setBaseModule(String name) {
81 if (name == null || name.isEmpty()) {
82 throw new RuntimeException("Null or empty base module");
83 }
84 baseModuleName = name;
85 }
86
87 private static Properties moduleProps = new Properties();
88 static String getModuleProperty(String key) {
89 return moduleProps.getProperty(key);
90 }
91
92 static void setModuleProperties(String file) throws IOException {
93 File f = new File(file);
94 BufferedReader reader = null;
95 try {
96 reader = new BufferedReader(new FileReader(f));
97 moduleProps.load(reader);
98 } finally {
99 if (reader != null) {
100 reader.close();
101 }
102 }
103 }
104 private final String name;
105 private final ModuleConfig config;
106 private final Set<Klass> classes;
107 private final Set<ResourceFile> resources;
108 private final Set<Reference> unresolved;
109 private final Set<Module> members;
110 // update during the analysis
111 private Module group;
112 private ModuleInfo minfo;
113 private boolean isBaseModule;
114
115 protected Module(ModuleConfig config) {
116 this.name = config.module;
117 this.isBaseModule = name.equals(baseModuleName);
118 this.classes = new TreeSet<Klass>();
119 this.resources = new TreeSet<ResourceFile>();
120 this.config = config;
121 this.unresolved = new HashSet<Reference>();
122 this.members = new TreeSet<Module>();
123 this.group = this; // initialize to itself
124 }
125
126 String name() {
127 return name;
128 }
129
130 ModuleConfig config() {
131 return config;
132 }
133
134 Module group() {
135 return group;
136 }
137
138 boolean isBase() {
139 return isBaseModule;
140 }
141
142 Set<Klass> classes() {
143 return Collections.unmodifiableSet(classes);
144 }
145
146 Set<ResourceFile> resources() {
147 return Collections.unmodifiableSet(resources);
148 }
149
150 Set<Module> members() {
151 return Collections.unmodifiableSet(members);
152 }
153
154 boolean contains(Klass k) {
155 return k != null && classes.contains(k);
156 }
157
158 boolean isEmpty() {
159 return classes.isEmpty()
160 && resources.isEmpty()
161 && mainClass() == null;
162 }
163
164 boolean allowEmpty() {
165 return moduleProps.getProperty(name + ".allow.empty") != null;
166 }
167
168 // returns itself.
169 public Module exporter(Module from) {
170 return this;
171 }
172
173 protected boolean isTopLevel() {
174 // module with no class is not included except the base module
175 return this.group == this
176 && (isBase() || !isEmpty() || !config.requires().isEmpty() || allowEmpty());
177 }
178
179 Klass mainClass() {
180 String cls = config.mainClass();
181 if (cls == null) {
182 return null;
183 }
184
185 Klass k = Klass.findKlass(cls);
186 return k;
187 }
188
189 @Override
190 public int compareTo(Module o) {
191 if (o == null) {
192 return -1;
193 }
194 return name.compareTo(o.name);
195 }
196
197 @Override
198 public String toString() {
199 return name;
200 }
201
202 void addKlass(Klass k) {
203 classes.add(k);
204 k.setModule(this);
205 }
206
207 void addResource(ResourceFile res) {
208 resources.add(res);
209 res.setModule(this);
210 }
211
212 void processRootsAndReferences() {
213 // start with the root set
214 Deque<Klass> pending = new ArrayDeque<Klass>();
215 for (Klass k : Klass.getAllClasses()) {
216 if (k.getModule() != null) {
217 continue;
218 }
219
220 String classname = k.getClassName();
221 if (config.matchesRoot(classname) && !config.isExcluded(classname)) {
222 addKlass(k);
223 pending.add(k);
224 }
225 }
226
227 // follow all references
228 Klass k;
229 while ((k = pending.poll()) != null) {
230 if (!classes.contains(k)) {
231 addKlass(k);
232 }
233
234 for (Klass other : k.getReferencedClasses()) {
235 Module otherModule = other.getModule();
236 if (otherModule != null && otherModule != this) {
237 // this module is dependent on otherModule
238 continue;
239 }
240
241 if (!classes.contains(other)) {
242 if (config.isExcluded(other.getClassName())) {
243 // reference to an excluded class
244 unresolved.add(new Reference(k, other));
245 } else {
246 pending.add(other);
247 }
248 }
249 }
250 }
251
252 // add other matching classes that don't require dependency analysis
253 for (Klass c : Klass.getAllClasses()) {
254 if (c.getModule() == null) {
255 String classname = c.getClassName();
256 if (config.matchesIncludes(classname) && !config.isExcluded(classname)) {
257 addKlass(c);
258 // dependencies
259 for (Klass other : c.getReferencedClasses()) {
260 Module otherModule = other.getModule();
261 if (otherModule == null) {
262 unresolved.add(new Reference(c, other));
263 }
264 }
265 }
266 }
267 }
268
269 // add other matching classes that don't require dependency analysis
270 for (ResourceFile res : ResourceFile.getAllResources()) {
271 if (res.getModule() == null) {
272 String name = res.getName();
273 if (config.matchesIncludes(name) && !config.isExcluded(name)) {
274 addResource(res);
275 }
276 }
277 }
278 }
279
280 boolean isModuleDependence(Klass k) {
281 Module m = k.getModule();
282 return m == null || (!classes.contains(k) && !m.isBase());
283 }
284
285 Module getModuleDependence(Klass k) {
286 if (isModuleDependence(k)) {
287 Module m = k.getModule();
288 if (group == this && m != null) {
289 // top-level module
290 return m.group;
291 } else {
292 return m;
293 }
294
295 }
296 return null;
297 }
298
299 <P> void visitMembers(Set<Module> visited, ModuleVisitor<P> visitor, P p) {
300 if (!visited.contains(this)) {
301 visited.add(this);
302 visitor.preVisit(this, p);
303 for (Module m : members) {
304 m.visitMembers(visited, visitor, p);
305 visitor.visited(this, m, p);
306 }
307 visitor.postVisit(this, p);
308 } else {
309 throw new RuntimeException("Cycle detected: module " + this.name);
310 }
311 }
312
313 void addMember(Module m) {
314 // merge class list
315 for (Klass k : m.classes) {
316 classes.add(k);
317 }
318
319 // merge resource list
320 for (ResourceFile res : m.resources) {
321 resources.add(res);
322 }
323 }
324
325 static void buildModuleMembers() {
326 // set up module member relationship
327 for (Module m : modules.values()) {
328 m.group = m; // initialize to itself
329 for (String name : m.config.members()) {
330 Module member = modules.get(name);
331 if (member == null) {
332 throw new RuntimeException("module \"" + name + "\" doesn't exist");
333 }
334 m.members.add(member);
335 }
336 }
337
338 // set up the top-level module
339 ModuleVisitor<Module> groupSetter = new ModuleVisitor<Module>() {
340
341 public void preVisit(Module m, Module p) {
342 m.group = p;
343 if (p.isBaseModule) {
344 // all members are also base
345 m.isBaseModule = true;
346 }
347 }
348
349 public void visited(Module m, Module child, Module p) {
350 // nop - breadth-first search
351 }
352
353 public void postVisit(Module m, Module p) {
354 // nop - breadth-first search
355 }
356 };
357
358 // propagate the top-level module to all its members
359 for (Module p : modules.values()) {
360 for (Module m : p.members) {
361 if (m.group == m) {
362 m.visitMembers(new TreeSet<Module>(), groupSetter, p);
363 }
364 }
365 }
366
367 ModuleVisitor<Module> mergeClassList = new ModuleVisitor<Module>() {
368
369 public void preVisit(Module m, Module p) {
370 // nop - depth-first search
371 }
372
373 public void visited(Module m, Module child, Module p) {
374 m.addMember(child);
375 }
376
377 public void postVisit(Module m, Module p) {
378 }
379 };
380
381 Set<Module> visited = new TreeSet<Module>();
382 Set<Module> groups = new TreeSet<Module>();
383 for (Module m : modules.values()) {
384 if (m.group() == m) {
385 groups.add(m);
386 if (m.members().size() > 0) {
387 // merge class list from all its members
388 m.visitMembers(visited, mergeClassList, m);
389 }
390 }
391 }
392 }
393
394
395 ModuleInfo getModuleInfo() {
396 return minfo;
397 }
398
399 void setModuleInfo(ModuleInfo mi) {
400 if (minfo != null)
401 throw new AssertionError("ModuleInfo already created for " + name);
402 minfo = mi;
403 }
404
405 public interface Visitor<R, P> {
406
407 R visitClass(Klass k, P p);
408
409 R visitResource(ResourceFile r, P p);
410 }
411
412 public <R, P> void visit(Visitor<R, P> visitor, P p) {
413 for (Klass c : classes) {
414 visitor.visitClass(c, p);
415 }
416 for (ResourceFile res : resources) {
417 visitor.visitResource(res, p);
418 }
419 }
420
421 static class Reference implements Comparable<Reference> {
422
423 final Klass referrer, referree;
424
425 Reference(Klass referrer, Klass referree) {
426 this.referrer = referrer;
427 this.referree = referree;
428 }
429
430 Klass referrer() {
431 return referrer;
432 }
433
434 Klass referree() {
435 return referree;
436 }
437
438 @Override
439 public int hashCode() {
440 return referrer.hashCode() ^ referree.hashCode();
441 }
442
443 @Override
446 return false;
447 }
448 if (this == obj) {
449 return true;
450 }
451
452 Reference r = (Reference) obj;
453 return (this.referrer.equals(r.referrer) && this.referree.equals(r.referree));
454 }
455
456 @Override
457 public int compareTo(Reference r) {
458 int ret = referrer.compareTo(r.referrer);
459 if (ret == 0) {
460 ret = referree.compareTo(r.referree);
461 }
462 return ret;
463 }
464 }
465
466 interface ModuleVisitor<P> {
467
468 public void preVisit(Module m, P param);
469
470 public void visited(Module m, Module child, P param);
471
472 public void postVisit(Module m, P param);
473 }
474 }
|