blob: 0fdc5b145fa3044219bbdb0899667d3ba4c3a2bf [file] [log] [blame]
/*
* Copyright 2009 Sun Microsystems, Inc. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
* CA 95054 USA or visit www.sun.com if you need additional information or
* have any questions.
*
*/
package com.sun.classanalyzer;
import com.sun.classanalyzer.AnnotatedDependency.OptionalDependency;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayDeque;
import java.util.Collection;
import java.util.Collections;
import java.util.Deque;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
/**
*
* @author Mandy Chung
*/
public class Module implements Comparable<Module> {
private static Map<String, Module> modules = new LinkedHashMap<String, Module>();
public static Module addModule(ModuleConfig config) {
String name = config.module;
if (modules.containsKey(name)) {
throw new RuntimeException("module \"" + name + "\" already exists");
}
Module m = new Module(config);
modules.put(name, m);
return m;
}
public static Module findModule(String name) {
return modules.get(name);
}
static Collection<Module> getAllModules() {
return Collections.unmodifiableCollection(modules.values());
}
private final String name;
private final ModuleConfig config;
private final Set<Klass> classes;
private final Set<ResourceFile> resources;
private final Set<Reference> unresolved;
private final Set<Dependency> dependents;
private final Map<String, PackageInfo> packages;
private final Set<Module> members;
private Module group;
private boolean isBaseModule;
private Module(ModuleConfig config) {
this.name = config.module;
this.isBaseModule = config.isBase;
this.classes = new TreeSet<Klass>();
this.resources = new TreeSet<ResourceFile>();
this.config = config;
this.unresolved = new HashSet<Reference>();
this.dependents = new TreeSet<Dependency>();
this.packages = new TreeMap<String, PackageInfo>();
this.members = new TreeSet<Module>();
this.group = this; // initialize to itself
}
String name() {
return name;
}
Module group() {
return group;
}
boolean isBase() {
return isBaseModule;
}
Set<Module> members() {
return members;
}
boolean contains(Klass k) {
return k != null && classes.contains(k);
}
boolean isEmpty() {
return classes.isEmpty() && resources.isEmpty();
}
/**
* Returns an Iterable of Dependency, only one for each dependent
* module of the strongest dependency (i.e.
* hard static > hard dynamic > optional static > optional dynamic
*/
Iterable<Dependency> dependents() {
Map<Module, Dependency> deps = new LinkedHashMap<Module, Dependency>();
for (Dependency dep : dependents) {
Dependency d = deps.get(dep.module);
if (d == null || dep.compareTo(d) > 0) {
deps.put(dep.module, dep);
}
}
return deps.values();
}
@Override
public int compareTo(Module o) {
if (o == null) {
return -1;
}
return name.compareTo(o.name);
}
@Override
public String toString() {
return name;
}
void addKlass(Klass k) {
classes.add(k);
k.setModule(this);
// update package statistics
String pkg = k.getPackageName();
PackageInfo pkginfo = packages.get(pkg);
if (pkginfo == null) {
pkginfo = new PackageInfo(pkg);
packages.put(pkg, pkginfo);
}
if (k.exists()) {
// only count the class that is parsed
pkginfo.add(k.getFileSize());
}
}
void addResource(ResourceFile res) {
resources.add(res);
res.setModule(this);
}
void processRootsAndReferences() {
// start with the root set
Deque<Klass> pending = new ArrayDeque<Klass>();
for (Klass k : Klass.getAllClasses()) {
if (k.getModule() != null) {
continue;
}
String classname = k.getClassName();
if (config.matchesRoot(classname) && !config.isExcluded(classname)) {
addKlass(k);
pending.add(k);
}
}
// follow all references
Klass k;
while ((k = pending.poll()) != null) {
if (!classes.contains(k)) {
addKlass(k);
}
for (Klass other : k.getReferencedClasses()) {
Module otherModule = other.getModule();
if (otherModule != null && otherModule != this) {
// this module is dependent on otherModule
addDependency(k, other);
continue;
}
if (!classes.contains(other)) {
if (config.isExcluded(other.getClassName())) {
// reference to an excluded class
unresolved.add(new Reference(k, other));
} else {
pending.add(other);
}
}
}
}
// add other matching classes that don't require dependency analysis
for (Klass c : Klass.getAllClasses()) {
if (c.getModule() == null) {
String classname = c.getClassName();
if (config.matchesIncludes(classname) && !config.isExcluded(classname)) {
addKlass(c);
// dependencies
for (Klass other : c.getReferencedClasses()) {
Module otherModule = other.getModule();
if (otherModule == null) {
unresolved.add(new Reference(c, other));
} else {
if (otherModule != this) {
// this module is dependent on otherModule
addDependency(c, other);
}
}
}
}
}
}
// add other matching classes that don't require dependency analysis
for (ResourceFile res : ResourceFile.getAllResources()) {
if (res.getModule() == null) {
String name = res.getName();
if (config.matchesIncludes(name) && !config.isExcluded(name)) {
addResource(res);
}
}
}
}
void addDependency(Klass from, Klass to) {
Dependency dep = new Dependency(from, to);
dependents.add(dep);
}
void fixupDependencies() {
// update dependencies for classes that were allocated to modules after
// this module was processed.
for (Reference ref : unresolved) {
Module m = ref.referree().getModule();
if (m == null || m != this) {
addDependency(ref.referrer, ref.referree);
}
}
fixupAnnotatedDependencies();
}
private void fixupAnnotatedDependencies() {
// add dependencies that this klass may depend on due to the AnnotatedDependency
dependents.addAll(AnnotatedDependency.getDependencies(this));
}
boolean isModuleDependence(Klass k) {
Module m = k.getModule();
return m == null || (!classes.contains(k) && !m.isBase());
}
Module getModuleDependence(Klass k) {
if (isModuleDependence(k)) {
Module m = k.getModule();
if (group == this && m != null) {
// top-level module
return m.group;
} else {
return m;
}
}
return null;
}
<P> void visit(Set<Module> visited, Visitor<P> visitor, P p) {
if (!visited.contains(this)) {
visited.add(this);
visitor.preVisit(this, p);
for (Module m : members) {
m.visit(visited, visitor, p);
visitor.postVisit(this, m, p);
}
} else {
throw new RuntimeException("Cycle detected: module " + this.name);
}
}
void addMember(Module m) {
// merge class list
for (Klass k : m.classes) {
classes.add(k);
}
// merge resource list
for (ResourceFile res : m.resources) {
resources.add(res);
}
// merge the package statistics
for (PackageInfo pinfo : m.getPackageInfos()) {
String packageName = pinfo.pkgName;
PackageInfo pkginfo = packages.get(packageName);
if (pkginfo == null) {
pkginfo = new PackageInfo(packageName);
packages.put(packageName, pkginfo);
}
pkginfo.add(pinfo);
}
}
static void buildModuleMembers() {
// set up module member relationship
for (Module m : modules.values()) {
m.group = m; // initialize to itself
for (String name : m.config.members()) {
Module member = modules.get(name);
if (member == null) {
throw new RuntimeException("module \"" + name + "\" doesn't exist");
}
m.members.add(member);
}
}
// set up the top-level module
Visitor<Module> groupSetter = new Visitor<Module>() {
public void preVisit(Module m, Module p) {
m.group = p;
if (p.isBaseModule) {
// all members are also base
m.isBaseModule = true;
}
}
public void postVisit(Module m, Module child, Module p) {
// nop - breadth-first search
}
};
// propagate the top-level module to all its members
for (Module p : modules.values()) {
for (Module m : p.members) {
if (m.group == m) {
m.visit(new TreeSet<Module>(), groupSetter, p);
}
}
}
Visitor<Module> mergeClassList = new Visitor<Module>() {
public void preVisit(Module m, Module p) {
// nop - depth-first search
}
public void postVisit(Module m, Module child, Module p) {
m.addMember(child);
}
};
Set<Module> visited = new TreeSet<Module>();
for (Module m : modules.values()) {
if (m.group() == m) {
if (m.members().size() > 0) {
// merge class list from all its members
m.visit(visited, mergeClassList, m);
}
// clear the dependencies before fixup
m.dependents.clear();
// fixup dependencies
for (Klass k : m.classes) {
for (Klass other : k.getReferencedClasses()) {
if (m.isModuleDependence(other)) {
// this module is dependent on otherModule
m.addDependency(k, other);
}
}
}
// add dependencies that this klass may depend on due to the AnnotatedDependency
m.fixupAnnotatedDependencies();
}
}
}
class PackageInfo implements Comparable {
final String pkgName;
int count;
long filesize;
PackageInfo(String name) {
this.pkgName = name;
this.count = 0;
this.filesize = 0;
}
void add(PackageInfo pkg) {
this.count += pkg.count;
this.filesize += pkg.filesize;
}
void add(long size) {
count++;
filesize += size;
}
@Override
public int compareTo(Object o) {
return pkgName.compareTo(((PackageInfo) o).pkgName);
}
}
Set<PackageInfo> getPackageInfos() {
return new TreeSet<PackageInfo>(packages.values());
}
void printSummaryTo(String output) throws IOException {
PrintWriter writer = new PrintWriter(output);
try {
long total = 0L;
int count = 0;
writer.format("%10s\t%10s\t%s\n", "Bytes", "Classes", "Package name");
for (String pkg : packages.keySet()) {
PackageInfo info = packages.get(pkg);
if (info.count > 0) {
writer.format("%10d\t%10d\t%s\n", info.filesize, info.count, pkg);
total += info.filesize;
count += info.count;
}
}
writer.format("\nTotal: %d bytes (uncompressed) %d classes\n", total, count);
} finally {
writer.close();
}
}
void printClassListTo(String output) throws IOException {
// no file created if the module doesn't have any class nor resource
if (isEmpty()) {
return;
}
PrintWriter writer = new PrintWriter(output);
try {
for (Klass c : classes) {
if (c.exists()) {
writer.format("%s\n", c.getClassFilePathname());
} else {
trace("%s in module %s missing\n", c, this);
}
}
} finally {
writer.close();
}
}
void printResourceListTo(String output) throws IOException {
// no file created if the module doesn't have any resource file
if (resources.isEmpty()) {
return;
}
PrintWriter writer = new PrintWriter(output);
try {
for (ResourceFile res : resources) {
writer.format("%s\n", res.getPathname());
}
} finally {
writer.close();
}
}
void printDependenciesTo(String output, boolean showDynamic) throws IOException {
// no file created if the module doesn't have any class
if (isEmpty()) {
return;
}
PrintWriter writer = new PrintWriter(output);
try {
// classes that this klass may depend on due to the AnnotatedDependency
Map<Reference, Set<AnnotatedDependency>> annotatedDeps = AnnotatedDependency.getReferences(this);
for (Klass klass : classes) {
Set<Klass> references = klass.getReferencedClasses();
for (Klass other : references) {
String classname = klass.getClassName();
boolean optional = OptionalDependency.isOptional(klass, other);
if (optional) {
classname = "[optional] " + classname;
}
Module m = getModuleDependence(other);
if (m != null || other.getModule() == null) {
writer.format("%-40s -> %s (%s)", classname, other, m);
Reference ref = new Reference(klass, other);
if (annotatedDeps.containsKey(ref)) {
for (AnnotatedDependency ad : annotatedDeps.get(ref)) {
writer.format(" %s", ad.getTag());
}
// printed; so remove the dependency from the annotated deps list
annotatedDeps.remove(ref);
}
writer.format("\n");
}
}
}
// print remaining dependencies specified in AnnotatedDependency list
if (annotatedDeps.size() > 0) {
for (Map.Entry<Reference, Set<AnnotatedDependency>> entry : annotatedDeps.entrySet()) {
Reference ref = entry.getKey();
Module m = getModuleDependence(ref.referree);
if (m != null || ref.referree.getModule() == null) {
String classname = ref.referrer.getClassName();
boolean optional = true;
boolean dynamic = true;
String tag = "";
for (AnnotatedDependency ad : entry.getValue()) {
if (optional && !ad.isOptional()) {
optional = false;
tag = ad.getTag();
}
if (!ad.isDynamic()) {
dynamic = false;
}
}
if (!showDynamic && optional && dynamic) {
continue;
}
if (optional) {
if (dynamic) {
classname = "[dynamic] " + classname;
} else {
classname = "[optional] " + classname;
}
}
writer.format("%-40s -> %s (%s) %s%n", classname, ref.referree, m, tag);
}
}
}
} finally {
writer.close();
}
}
static class Dependency implements Comparable<Dependency> {
final Module module;
final boolean optional;
final boolean dynamic;
Dependency(Klass from, Klass to) {
// static dependency
this.module = to.getModule() != null ? to.getModule().group() : null;
this.optional = OptionalDependency.isOptional(from, to);
this.dynamic = false;
}
Dependency(Module m, boolean optional, boolean dynamic) {
this.module = m != null ? m.group() : null;
this.optional = optional;
this.dynamic = dynamic;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Dependency)) {
return false;
}
if (this == obj) {
return true;
}
Dependency d = (Dependency) obj;
if (this.module != d.module) {
return false;
} else {
return this.optional == d.optional && this.dynamic == d.dynamic;
}
}
@Override
public int hashCode() {
int hash = 3;
hash = 19 * hash + (this.module != null ? this.module.hashCode() : 0);
hash = 19 * hash + (this.optional ? 1 : 0);
hash = 19 * hash + (this.dynamic ? 1 : 0);
return hash;
}
@Override
public int compareTo(Dependency d) {
if (this.equals(d)) {
return 0;
}
// Hard static > hard dynamic > optional static > optional dynamic
if (this.module == d.module) {
if (this.optional == d.optional) {
return this.dynamic ? -1 : 1;
} else {
return this.optional ? -1 : 1;
}
} else if (this.module != null && d.module != null) {
return (this.module.compareTo(d.module));
} else {
return (this.module == null) ? -1 : 1;
}
}
@Override
public String toString() {
String s = module.name();
if (dynamic && optional) {
s += " (dynamic)";
} else if (optional) {
s += " (optional)";
}
return s;
}
}
static class Reference implements Comparable<Reference> {
private final Klass referrer, referree;
Reference(Klass referrer, Klass referree) {
this.referrer = referrer;
this.referree = referree;
}
Klass referrer() {
return referrer;
}
Klass referree() {
return referree;
}
@Override
public int hashCode() {
return referrer.hashCode() ^ referree.hashCode();
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Reference)) {
return false;
}
if (this == obj) {
return true;
}
Reference r = (Reference) obj;
return (this.referrer.equals(r.referrer) &&
this.referree.equals(r.referree));
}
@Override
public int compareTo(Reference r) {
int ret = referrer.compareTo(r.referrer);
if (ret == 0) {
ret = referree.compareTo(r.referree);
}
return ret;
}
}
interface Visitor<P> {
public void preVisit(Module m, P param);
public void postVisit(Module m, Module child, P param);
}
private static boolean traceOn = System.getProperty("classanalyzer.debug") != null;
private static void trace(String format, Object... params) {
System.err.format(format, params);
}
}