--- old/make/tools/classanalyzer/src/com/sun/classanalyzer/ClassAnalyzer.java Wed Oct 20 09:30:53 2010 +++ new/make/tools/classanalyzer/src/com/sun/classanalyzer/ClassAnalyzer.java Wed Oct 20 09:30:52 2010 @@ -23,25 +23,36 @@ package com.sun.classanalyzer; import com.sun.classanalyzer.AnnotatedDependency.*; -import com.sun.classanalyzer.Module.Dependency; -import com.sun.classanalyzer.Module.PackageInfo; -import com.sun.classanalyzer.Module.RequiresModule; -import java.io.BufferedReader; +import com.sun.classanalyzer.Module.*; +import com.sun.classanalyzer.ModuleInfo.*; import java.io.IOException; -import java.util.ArrayList; -import java.util.List; import java.io.File; -import java.io.FileReader; import java.io.PrintWriter; -import java.util.LinkedHashSet; -import java.util.Map; -import java.util.Properties; -import java.util.Set; -import java.util.TreeMap; -import java.util.TreeSet; +import java.util.*; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; /** + * Analyze the class dependencies of all classes in a given classpath + * and assign classes and resource files into modules as defined + * in the input configuration files. * + * The ClassAnalyzer tool will generate the following reports + * modules.list + * modules.summary + * modules.dot + * and for each module named , + * .classlist + * .resources + * .summary + * .dependencies + * + * If -moduleinfo option is specified, /module-info.java + * will be created under the given directory. + * + * The -update option can be specified to perform an incremental analysis + * rather than parsing all class files. + * * @author Mandy Chung */ public class ClassAnalyzer { @@ -48,15 +59,18 @@ public static void main(String[] args) throws Exception { String jdkhome = null; - String cpath = null; + String cparg = null; List configs = new ArrayList(); List depconfigs = new ArrayList(); - String output = "."; - String minfoPath = null; - String nonCorePkgs = null; - String properties = null; + String version = null; + String classlistDir = "."; + String minfoDir = null; + String nonCorePkgsFile = null; + ClassPaths cpaths = null; boolean mergeModules = true; + boolean apiOnly = false; boolean showDynamic = false; + boolean update = false; // process arguments int i = 0; @@ -63,154 +77,309 @@ while (i < args.length) { String arg = args[i++]; if (arg.equals("-jdkhome")) { + if (cparg != null) { + error("Both -jdkhome and -classpath are set"); + } jdkhome = getOption(args, i++); - } else if (arg.equals("-cpath")) { - cpath = getOption(args, i++); + cpaths = ClassPaths.newJDKClassPaths(jdkhome); + } else if (arg.equals("-classpath")) { + if (jdkhome != null) { + error("Both -jdkhome and -classpath are set"); + } + cparg = getOption(args, i++); + cpaths = ClassPaths.newInstance(cparg); } else if (arg.equals("-config")) { - configs.add(getOption(args, i++)); + configs.add(getOption(args, i++)); } else if (arg.equals("-depconfig")) { - depconfigs.add(getOption(args, i++)); + depconfigs.add(getOption(args, i++)); + } else if (arg.equals("-properties")) { + Module.setModuleProperties(getOption(args, i++)); } else if (arg.equals("-output")) { - output = getOption(args, i++); + classlistDir = getOption(args, i++); + } else if (arg.equals("-update")) { + update = true; } else if (arg.equals("-moduleinfo")) { - minfoPath = getOption(args, i++); - } else if (arg.equals("-base")) { - Module.setBaseModule(getOption(args, i++)); + minfoDir = getOption(args, i++); } else if (arg.equals("-version")) { - Module.setVersion(getOption(args, i++)); - } else if (arg.equals("-properties")) { - Module.setModuleProperties(getOption(args, i++)); - } else if (arg.equals("-noncorepkgs")) { - nonCorePkgs = getOption(args, i++); + version = getOption(args, i++); } else if (arg.equals("-nomerge")) { // analyze the fine-grained module dependencies mergeModules = false; + } else if (arg.equals("-api")) { + // analyze the fine-grained module dependencies + apiOnly = true; } else if (arg.equals("-showdynamic")) { showDynamic = true; + } else if (arg.equals("-noncorepkgs")) { + nonCorePkgsFile = getOption(args, i++); } else { - System.err.println("Invalid option: " + arg); - usage(); + error("Invalid option: " + arg); } } - if ((jdkhome == null && cpath == null) || (jdkhome != null && cpath != null)) { - usage(); + if (jdkhome == null && cparg == null) { + error("-jdkhome and -classpath not set"); } + if (configs.isEmpty()) { - usage(); + error("-config not set"); } + if (version == null) { + error("-version not set"); + } + + ModuleBuilder builder; if (jdkhome != null) { - ClassPath.setJDKHome(jdkhome); - } else if (cpath != null) { - ClassPath.setClassPath(cpath); + PlatformModuleBuilder pmb = + new PlatformModuleBuilder(configs, depconfigs, mergeModules, version); + if (nonCorePkgsFile != null) { + pmb.readNonCorePackagesFrom(nonCorePkgsFile); + } + builder = pmb; + } else { + builder = new ModuleBuilder(configs, depconfigs, mergeModules, version); } - - if (nonCorePkgs != null) { - Platform.addNonCorePkgs(nonCorePkgs); + + ClassAnalyzer analyzer = new ClassAnalyzer(cpaths, builder, classlistDir); + // parse class and resource files + analyzer.run(update, apiOnly); + + // print reports and module-info.java + analyzer.generateReports(classlistDir, showDynamic); + if (minfoDir != null) { + analyzer.printModuleInfos(minfoDir); } + } + private final ClassPaths cpaths; + private final ModuleBuilder builder; + private final File classlistDir; + private final File moduleList; + private final Set updatedModules; // updated modules - // create output directory if it doesn't exist - File dir = getDir(output); + ClassAnalyzer(ClassPaths cpaths, ModuleBuilder builder, String clistDir) { + this.cpaths = cpaths; + this.builder = builder; + this.classlistDir = new File(clistDir); + this.moduleList = new File(clistDir, "modules.list"); + this.updatedModules = new TreeSet(); + } - File moduleInfoSrc; - if (minfoPath == null) { - moduleInfoSrc = getDir(dir, "src"); + void run(boolean update, boolean apiOnly) throws IOException { + if (update) { + // incremental + if (!moduleList.exists()) { + // fall back to the default - analyze the entire jdk + update = false; + } + } + // parse class and resource files + processClassPaths(update, apiOnly); + + // build modules & packages + builder.run(); + + if (update) { + updatedModules.addAll(cpaths.getModules()); } else { - moduleInfoSrc = getDir(minfoPath); + updatedModules.addAll(builder.getModules()); } + } - buildModules(configs, depconfigs, mergeModules); + public void generateReports(String output, boolean showDynamic) + throws IOException { + File outputDir = new File(output); + if (!outputDir.exists()) + Files.mkdirs(outputDir); - // generate output files only for top-level modules - for (Module m : Module.getTopLevelModules()) { - String module = m.name(); - m.printClassListTo(resolve(dir, module, "classlist")); - m.printResourceListTo(resolve(dir, module, "resources")); - m.printSummaryTo(resolve(dir, module, "summary")); - m.printDependenciesTo(resolve(dir, module, "dependencies"), showDynamic); + if (updatedModules.size() > 0) { + printModulesList(); } - // Generate other summary reports + // only print classlist of the recompiled modules + for (Module m : updatedModules) { + // write classlist and resourcelist of a module + ClassListWriter writer = new ClassListWriter(outputDir, m); + writer.printClassList(); + writer.printResourceList(); + writer.printDependencies(); + + // write the summary and modules.list files + printModuleSummary(outputDir, m); + printModuleList(outputDir, m); + } + + printSummary(outputDir, showDynamic); + } + + void processClassPaths(boolean update, boolean apiOnly) throws IOException { + // TODO: always parseDeps? + boolean parseDeps = update == false; + ClassPaths.Filter filter = null; + + long timestamp = update ? moduleList.lastModified() : -1L; + if (timestamp > 0) { + // for incremental build, only update the modules with + // recompiled classes or resources files. + final long ts = timestamp; + filter = new ClassPaths.Filter() { + + @Override + public boolean accept(File f) { + return (f.isDirectory() + ? true + : f.lastModified() > ts); + } + + @Override + public boolean accept(JarFile jf, JarEntry e) throws IOException { + long lastModified = e.getTime(); + return lastModified <= 0 || lastModified > ts; + } + }; + + // load modules from the existing class list and resource list + builder.loadModulesFrom(classlistDir); + } + + // parse class and resource files + cpaths.parse(filter, parseDeps, apiOnly); + if (Trace.traceOn) { + cpaths.printStats(); + } + } + + public void printModuleInfos(String minfoDir) throws IOException { + for (Module m : updatedModules) { + ModuleInfo minfo = m.getModuleInfo(); + File mdir = new File(minfoDir, m.name()); + PrintWriter writer = new PrintWriter(Files.resolve(mdir, "module-info", "java")); + try { + writer.println(minfo.toString()); + } finally { + writer.close(); + } + } + } + + public void printSummary(File dir, boolean showDynamic) throws IOException { printModulesSummary(dir, showDynamic); printModulesDot(dir, showDynamic); printPackagesSummary(dir); - - // Generate module-info.java for all modules - printModuleInfos(moduleInfoSrc); - - // generate modules.list file that list the top-level modules - // and its sub-modules. The modules build reads this file - // to reconstruct the module content for each top-level module - printModulesList(dir); } private static String getOption(String[] args, int index) { if (index < args.length) { - return args[index]; + return args[index]; } else { - usage(); + usage(); } return null; } - static void buildModules(List configs, - List depconfigs, - boolean mergeModules) throws IOException { - List modules = new ArrayList(); - - // create modules based on the input config files - for (String file : configs) { - for (ModuleConfig mconfig : ModuleConfig.readConfigurationFile(file)) { - modules.add(Module.addModule(mconfig)); + private void printModuleSummary(File dir, Module m) throws IOException { + PrintWriter summary = + new PrintWriter(Files.resolve(dir, m.name(), "summary")); + try { + ModuleInfo mi = m.getModuleInfo(); + long total = 0L; + int count = 0; + summary.format("%10s\t%10s\t%s%n", "Bytes", "Classes", "Package name"); + for (PackageInfo info : mi.packages()) { + if (info.count > 0) { + summary.format("%10d\t%10d\t%s%n", + info.filesize, info.count, info.pkgName); + total += info.filesize; + count += info.count; + } } + summary.format("%nTotal: %d bytes (uncompressed) %d classes%n", + total, count); + } finally { + summary.close(); } + } - // parse class files - ClassPath.parseAllClassFiles(); - - // Add additional dependencies if specified - if (depconfigs != null && depconfigs.size() > 0) { - DependencyConfig.parse(depconfigs); + private void printModuleList(File dir, Module m) throws IOException { + String s = Module.getModuleProperty(m.name() + ".modules.list"); + if (s == null || Boolean.parseBoolean(s) == false) { + return; } - // process the roots and dependencies to get the classes for each module - for (Module m : modules) { - m.processRootsAndReferences(); - } + PrintWriter mlist = new PrintWriter(Files.resolve(dir, m.name(), "modules.list")); + try { + Set deps = m.getModuleInfo().dependences( + new Dependence.Filter() { - // update the dependencies for classes that were subsequently allocated - // to modules - for (Module m : modules) { - m.fixupDependencies(); + @Override + public boolean accept(Dependence d) { + return !d.isOptional(); + } + }); + for (Module dm : deps) { + mlist.format("%s\n", dm.name()); + } + } finally { + mlist.close(); } + } - if (mergeModules) { - Module.buildModuleMembers(); + private void printModuleGroup(Module group, PrintWriter writer) { + ModuleVisitor> visitor = new ModuleVisitor>() { + + public void preVisit(Module p, Set leafnodes) { + } + + public void visited(Module p, Module m, Set leafnodes) { + if (m.members().isEmpty()) { + leafnodes.add(m); + } + } + + public void postVisit(Module p, Set leafnodes) { + } + }; + + Set visited = new TreeSet(); + Set members = new TreeSet(); + group.visitMembers(visited, visitor, members); + + // prints leaf members that are the modules defined in + // the modules.config files + writer.format("%s ", group); + for (Module m : members) { + writer.format("%s ", m); } + writer.println(); + } - Platform.fixupPlatformModules(); + public void printModulesList() throws IOException { + // print module group / members relationship in + // the dependences order so that its dependences are listed first + PrintWriter writer = new PrintWriter(moduleList); + try { + for (Module m : builder.getModules()) { + printModuleGroup(m, writer); + } + } finally { + writer.close(); + } } - private static void printModulesSummary(File dir, boolean showDynamic) throws IOException { + public void printModulesSummary(File dir, boolean showDynamic) throws IOException { // print summary of dependencies PrintWriter writer = new PrintWriter(new File(dir, "modules.summary")); try { - for (Module m : Module.getTopLevelModules()) { - for (Dependency dep : m.dependences()) { - if (!showDynamic && dep.dynamic && !dep.optional) { - continue; - } - if (dep.module == null || !dep.module.isBase()) { - + for (Module m : builder.getModules()) { + ModuleInfo mi = m.getModuleInfo(); + for (Dependence dep : mi.requires()) { + if (!dep.getModule().isBase()) { String prefix = ""; - if (dep.optional) { + if (dep.isOptional()) { prefix = "[optional] "; - } else if (dep.dynamic) { - prefix = "[dynamic] "; } - Module other = dep != null ? dep.module : null; + Module other = dep.getModule(); writer.format("%s%s -> %s%n", prefix, m, other); } } @@ -220,25 +389,20 @@ } } - private static void printModulesDot(File dir, boolean showDynamic) throws IOException { + private void printModulesDot(File dir, boolean showDynamic) throws IOException { PrintWriter writer = new PrintWriter(new File(dir, "modules.dot")); try { writer.println("digraph jdk {"); - for (Module m : Module.getTopLevelModules()) { - for (Dependency dep : m.dependences()) { - if (!showDynamic && dep.dynamic && !dep.optional) { - continue; - } - if (dep.module == null || !dep.module.isBase()) { + for (Module m : builder.getModules()) { + ModuleInfo mi = m.getModuleInfo(); + for (Dependence dep : mi.requires()) { + if (!dep.getModule().isBase()) { String style = ""; String color = ""; String property = ""; - if (dep.optional) { + if (dep.isOptional()) { style = "style=dotted"; } - if (dep.dynamic) { - color = "color=red"; - } if (style.length() > 0 || color.length() > 0) { String comma = ""; if (style.length() > 0 && color.length() > 0) { @@ -246,7 +410,7 @@ } property = String.format(" [%s%s%s]", style, comma, color); } - Module other = dep != null ? dep.module : null; + Module other = dep.getModule(); writer.format(" \"%s\" -> \"%s\"%s;%n", m, other, property); } } @@ -257,105 +421,19 @@ } } - private static boolean isJdkTool(Module m) { - Set tools = Module.findModule(Platform.JDK_TOOLS).requires(); - for (RequiresModule t : tools) { - if (t.module() == m) { - return true; - } - } - return false; - } - - private static void printMembers(Module m, PrintWriter writer) { - for (Module member : m.members()) { - if (!member.isEmpty() || member.allowEmpty() || m.allowEmpty()) { - writer.format("%s ", member); - printMembers(member, writer); - } - } - if (m.members().isEmpty() && isJdkTool(m)) { - String name = m.name().substring(4, m.name().length()); - writer.format("%s ", name); - } - } - - private static void printModuleGroup(Module group, PrintWriter writer) { - writer.format("%s ", group); - printMembers(group, writer); - writer.println(); - } - - private static void printPlatformModulesList(File dir, Module m) throws IOException { - m.printDepModuleListTo(resolve(dir, m.name(), "modules.list")); - } - - private static void printModulesList(File dir) throws IOException { - // Generate ordered list of dependences - // for constructing the base/module images - printPlatformModulesList(dir, Platform.jdkModule()); - printPlatformModulesList(dir, Platform.jreModule()); - printPlatformModulesList(dir, Platform.jdkBaseModule()); - printPlatformModulesList(dir, Platform.jdkBaseToolModule()); - - // print module group / members relationship in - // the dependences order so that its dependences are listed first - PrintWriter writer = new PrintWriter(new File(dir, "modules.list")); - try { - Module jdk = Platform.jdkModule(); - Set allModules = new LinkedHashSet(jdk.orderedDependencies()); - // put the boot module first - allModules.add(Platform.bootModule()); - for (Module m : Module.getTopLevelModules()) { - if (!allModules.contains(m)) { - allModules.addAll(m.orderedDependencies()); - } - } - - for (Module m : allModules) { - printModuleGroup(m, writer); - } - - } finally { - writer.close(); - } - } - - private static void printModuleInfos(File dir) throws IOException { - for (Module m : Module.getTopLevelModules()) { - File mdir = getDir(dir, m.name()); - m.printModuleInfoTo(resolve(mdir, "module-info", "java")); - } - } - - private static void printPackagesSummary(File dir) throws IOException { + private void printPackagesSummary(File dir) throws IOException { // print package / module relationship PrintWriter writer = new PrintWriter(new File(dir, "modules.pkginfo")); try { - Map> packages = new TreeMap>(); - Set splitPackages = new TreeSet(); - - for (Module m : Module.getTopLevelModules()) { - for (PackageInfo info : m.getPackageInfos()) { - Set value = packages.get(info.pkgName); - if (value == null) { - value = new TreeSet(); - packages.put(info.pkgName, value); - } else { - // package in more than one module - splitPackages.add(info.pkgName); - } - value.add(m); - } - } - // packages that are splitted among multiple modules writer.println("Packages splitted across modules:-\n"); writer.format("%-60s %s\n", "Package", "Module"); - for (String pkgname : splitPackages) { + Map> splitPackages = builder.getSplitPackages(); + for (Map.Entry> e : splitPackages.entrySet()) { + String pkgname = e.getKey(); writer.format("%-60s", pkgname); - for (Module m : packages.get(pkgname)) { + for (Module m : e.getValue()) { writer.format(" %s", m); } writer.println(); @@ -362,7 +440,7 @@ } writer.println("\nPackage-private dependencies:-"); - for (String pkgname : splitPackages) { + for (String pkgname : splitPackages.keySet()) { for (Klass k : Klass.getAllClasses()) { if (k.getPackageName().equals(pkgname)) { Module m = k.getModule(); @@ -369,9 +447,9 @@ // check if this klass references a package-private // class that is in a different module for (Klass other : k.getReferencedClasses()) { - if (other.getModule() != m && - !other.isPublic() && - other.getPackageName().equals(pkgname)) { + if (other.getModule() != m + && !other.isPublic() + && other.getPackageName().equals(pkgname)) { String from = k.getClassName() + " (" + m + ")"; writer.format("%-60s -> %s (%s)\n", from, other, other.getModule()); } @@ -385,52 +463,29 @@ } - private static String resolve(File dir, String mname, String suffix) { - File f = new File(dir, mname + "." + suffix); - return f.toString(); - + private static void error(String msg) { + System.err.println("ERROR: " + msg); + System.out.println(usage()); + System.exit(-1); } - private static File getDir(File path, String subdir) { - File dir = new File(path, subdir); - if (!dir.isDirectory()) { - if (!dir.exists()) { - boolean created = dir.mkdir(); - if (!created) { - throw new RuntimeException("Unable to create `" + dir + "'"); - } - } - } - return dir; + private static String usage() { + StringBuilder sb = new StringBuilder(); + sb.append("Usage: ClassAnalyzer \n"); + sb.append("Options: \n"); + sb.append("\t-jdkhome where all jars will be parsed\n"); + sb.append("\t-classpath where classes and jars will be parsed\n"); + sb.append("\t Either -jdkhome or -classpath option can be used.\n"); + sb.append("\t-config \n"); + sb.append("\t This option can be repeated for multiple module config files\n"); + sb.append("\t-output \n"); + sb.append("\t-update update modules with newer files\n"); + sb.append("\t-moduleinfo \n"); + sb.append("\t-properties module's properties\n"); + sb.append("\t-noncorepkgs NON_CORE_PKGS.gmk\n"); + sb.append("\t-version \n"); + sb.append("\t-showdynamic show dynamic dependencies in the reports\n"); + sb.append("\t-nomerge specify not to merge modules\n"); + return sb.toString(); } - - private static File getDir(String path) { - File dir = new File(path); - if (!dir.isDirectory()) { - if (!dir.exists()) { - boolean created = dir.mkdir(); - if (!created) { - throw new RuntimeException("Unable to create `" + dir + "'"); - } - } - } - return dir; - } - - private static void usage() { - System.out.println("Usage: ClassAnalyzer "); - System.out.println("Options: "); - System.out.println("\t-jdkhome where all jars will be parsed"); - System.out.println("\t-cpath where classes and jars will be parsed"); - System.out.println("\t Either -jdkhome or -cpath option can be used."); - System.out.println("\t-config "); - System.out.println("\t This option can be repeated for multiple module config files"); - System.out.println("\t-output "); - System.out.println("\t-moduleinfo "); - System.out.println("\t-base "); - System.out.println("\t-version "); - System.out.println("\t-showdynamic show dynamic dependencies in the reports"); - System.out.println("\t-nomerge specify not to merge modules"); - System.exit(-1); - } }