| /* |
| * 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<String> configs = new ArrayList<String>(); |
| List<String> depconfigs = new ArrayList<String>(); |
| 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<Module> modules = new ArrayList<Module>(); |
| |
| static void buildModules(List<String> configs, |
| List<String> 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<String, Set<Module>> packages = new TreeMap<String, Set<Module>>(); |
| Set<String> splitPackages = new TreeSet<String>(); |
| |
| for (Module m : modules) { |
| if (m.group() == m) { |
| for (PackageInfo info : m.getPackageInfos()) { |
| Set<Module> value = packages.get(info.pkgName); |
| if (value == null) { |
| value = new TreeSet<Module>(); |
| 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 <options>"); |
| System.out.println("Options: "); |
| System.out.println("\t-jdkhome <JDK home> where all jars will be parsed"); |
| System.out.println("\t-cpath <classpath> where classes and jars will be parsed"); |
| System.out.println("\t Either -jdkhome or -cpath option can be used."); |
| System.out.println("\t-config <module config file>"); |
| System.out.println("\t This option can be repeated for multiple module config files"); |
| System.out.println("\t-output <output dir>"); |
| System.out.println("\t-nomerge specify not to merge modules"); |
| System.out.println("\t-showdynamic show dynamic dependencies in the reports"); |
| System.exit(-1); |
| } |
| } |