/* * Copyright (c) 2009, Oracle and/or its affiliates. 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package com.sun.classanalyzer; import com.sun.classanalyzer.AnnotatedDependency.*; import com.sun.classanalyzer.Module.Dependency; import com.sun.classanalyzer.Module.PackageInfo; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.io.File; import java.io.PrintWriter; import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.TreeSet; /** * * @author Mandy Chung */ public class ClassAnalyzer { public static void main(String[] args) throws Exception { String jdkhome = null; String cpath = null; List configs = new ArrayList(); List depconfigs = new ArrayList(); String output = "."; boolean mergeModules = true; boolean showDynamic = false; // process arguments int i = 0; while (i < args.length) { String arg = args[i++]; if (arg.equals("-jdkhome")) { if (i < args.length) { jdkhome = args[i++]; } else { usage(); } } else if (arg.equals("-cpath")) { if (i < args.length) { cpath = args[i++]; } else { usage(); } } else if (arg.equals("-config")) { if (i < args.length) { configs.add(args[i++]); } else { usage(); } } else if (arg.equals("-depconfig")) { if (i < args.length) { depconfigs.add(args[i++]); } else { usage(); } } else if (arg.equals("-output")) { if (i < args.length) { output = args[i++]; } else { usage(); } } else if (arg.equals("-base")) { ModuleConfig.setBaseModule(args[i++]); } else if (arg.equals("-nomerge")) { // analyze the fine-grained module dependencies mergeModules = false; } else if (arg.equals("-showdynamic")) { showDynamic = true; } else { System.err.println("Invalid option: " + arg); usage(); } } if ((jdkhome == null && cpath == null) || (jdkhome != null && cpath != null)) { usage(); } if (configs.isEmpty()) { usage(); } if (jdkhome != null) { ClassPath.setJDKHome(jdkhome); } else if (cpath != null) { ClassPath.setClassPath(cpath); } // create output directory if it doesn't exist File dir = new File(output); if (!dir.isDirectory()) { if (!dir.exists()) { boolean created = dir.mkdir(); if (!created) { throw new RuntimeException("Unable to create `" + dir + "'"); } } } buildModules(configs, depconfigs, mergeModules); // generate output files for (Module m : modules) { // only generate reports for top-level modules if (m.group() == m) { m.printClassListTo(resolve(dir, m.name(), "classlist")); m.printResourceListTo(resolve(dir, m.name(), "resources")); m.printSummaryTo(resolve(dir, m.name(), "summary")); m.printDependenciesTo(resolve(dir, m.name(), "dependencies"), showDynamic); } } // Generate other summary reports printModulesSummary(dir, showDynamic); printModulesDot(dir, showDynamic); printModulesList(dir); printPackagesSummary(dir); } private static List modules = new ArrayList(); static void buildModules(List configs, List depconfigs, boolean mergeModules) throws IOException { // create modules based on the input config files for (String file : configs) { for (ModuleConfig mconfig : ModuleConfig.readConfigurationFile(file)) { modules.add(Module.addModule(mconfig)); } } // parse class files ClassPath.parseAllClassFiles(); // Add additional dependencies if specified if (depconfigs != null && depconfigs.size() > 0) { DependencyConfig.parse(depconfigs); } // process the roots and dependencies to get the classes for each module for (Module m : modules) { m.processRootsAndReferences(); } // update the dependencies for classes that were subsequently allocated // to modules for (Module m : modules) { m.fixupDependencies(); } if (mergeModules) { Module.buildModuleMembers(); } } private static 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 : modules) { // only show top-level module dependencies if (m.group() == m) { for (Dependency dep : m.dependents()) { if (!showDynamic && dep.dynamic && dep.optional) { continue; } if (dep.module == null || !dep.module.isBase()) { String prefix = ""; if (dep.optional) { if (dep.dynamic) { prefix = "[dynamic] "; } else { prefix = "[optional] "; } } Module other = dep != null ? dep.module : null; writer.format("%s%s -> %s%n", prefix, m, other); } } } } } finally { writer.close(); } } private static 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 : modules) { if (m.group() == m) { for (Dependency dep : m.dependents()) { if (!showDynamic && dep.dynamic && dep.optional) { continue; } if (dep.module == null || !dep.module.isBase()) { String style = ""; String color = ""; String property = ""; if (dep.optional) { 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) { comma = ", "; } property = String.format(" [%s%s%s]", style, comma, color); } Module other = dep != null ? dep.module : null; writer.format(" \"%s\" -> \"%s\"%s;%n", m, other, property); } } } } writer.println("}"); } finally { writer.close(); } } private static void printMembers(Module m, PrintWriter writer) { for (Module member : m.members()) { if (!member.isEmpty()) { writer.format("%s ", member); printMembers(member, writer); } } } private static void printModulesList(File dir) throws IOException { // print module group / members relationship PrintWriter writer = new PrintWriter(new File(dir, "modules.list")); try { for (Module m : modules) { if (m.group() == m && !m.isEmpty()) { writer.format("%s ", m); printMembers(m, writer); writer.println(); } } } finally { writer.close(); } } private static 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 : modules) { if (m.group() == m) { 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) { writer.format("%-60s", pkgname); for (Module m : packages.get(pkgname)) { writer.format(" %s", m); } writer.println(); } writer.println("\nPackage-private dependencies:-"); for (String pkgname : splitPackages) { for (Klass k : Klass.getAllClasses()) { if (k.getPackageName().equals(pkgname)) { Module m = k.getModule(); // 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)) { String from = k.getClassName() + " (" + m + ")"; writer.format("%-60s -> %s (%s)\n", from, other, other.getModule()); } } } } } } finally { writer.close(); } } private static String resolve(File dir, String mname, String suffix) { File f = new File(dir, mname + "." + suffix); return f.toString(); } 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-nomerge specify not to merge modules"); System.out.println("\t-showdynamic show dynamic dependencies in the reports"); System.exit(-1); } }