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 package com.sun.classanalyzer;
  24 
  25 import com.sun.classanalyzer.AnnotatedDependency.*;
  26 import com.sun.classanalyzer.Module.Dependency;
  27 import com.sun.classanalyzer.Module.PackageInfo;
  28 import com.sun.classanalyzer.Module.RequiresModule;
  29 import java.io.BufferedReader;
  30 import java.io.IOException;
  31 import java.util.ArrayList;
  32 import java.util.List;
  33 import java.io.File;
  34 import java.io.FileReader;
  35 import java.io.PrintWriter;
  36 import java.util.LinkedHashSet;
  37 import java.util.Map;
  38 import java.util.Properties;
  39 import java.util.Set;
  40 import java.util.TreeMap;
  41 import java.util.TreeSet;
  42 
  43 /**
  44  *
  45  * @author Mandy Chung
  46  */
  47 public class ClassAnalyzer {
  48 
  49     public static void main(String[] args) throws Exception {
  50         String jdkhome = null;
  51         String cpath = null;
  52         List<String> configs = new ArrayList<String>();
  53         List<String> depconfigs = new ArrayList<String>();
  54         String output = ".";
  55         String minfoPath = null;
  56         String nonCorePkgs = null;
  57         String properties = null;
  58         boolean mergeModules = true;
  59         boolean showDynamic = false;
  60 
  61         // process arguments
  62         int i = 0;
  63         while (i < args.length) {
  64             String arg = args[i++];
  65             if (arg.equals("-jdkhome")) {
  66                 jdkhome = getOption(args, i++);
  67             } else if (arg.equals("-cpath")) {
  68                     cpath = getOption(args, i++);
  69             } else if (arg.equals("-config")) {
  70                     configs.add(getOption(args, i++));
  71             } else if (arg.equals("-depconfig")) {
  72                         depconfigs.add(getOption(args, i++));
  73             } else if (arg.equals("-output")) {
  74                 output = getOption(args, i++);
  75             } else if (arg.equals("-moduleinfo")) {
  76                     minfoPath = getOption(args, i++);
  77             } else if (arg.equals("-base")) {
  78                 Module.setBaseModule(getOption(args, i++));
  79             } else if (arg.equals("-version")) {
  80                 Module.setVersion(getOption(args, i++));
  81             } else if (arg.equals("-properties")) {
  82                 Module.setModuleProperties(getOption(args, i++));
  83             } else if (arg.equals("-noncorepkgs")) {
  84                 nonCorePkgs = getOption(args, i++);
  85             } else if (arg.equals("-nomerge")) {
  86                 // analyze the fine-grained module dependencies
  87                 mergeModules = false;
  88             } else if (arg.equals("-showdynamic")) {
  89                 showDynamic = true;
  90             } else {
  91                 System.err.println("Invalid option: " + arg);
  92                 usage();
  93             }
  94         }
  95 
  96         if ((jdkhome == null && cpath == null) || (jdkhome != null && cpath != null)) {
  97             usage();
  98         }
  99         if (configs.isEmpty()) {
 100             usage();
 101         }
 102 
 103         if (jdkhome != null) {
 104             ClassPath.setJDKHome(jdkhome);
 105         } else if (cpath != null) {
 106             ClassPath.setClassPath(cpath);
 107         }
 108         
 109         if (nonCorePkgs != null) {
 110             Platform.addNonCorePkgs(nonCorePkgs);
 111         }
 112 
 113         // create output directory if it doesn't exist
 114         File dir = getDir(output);
 115 
 116         File moduleInfoSrc;
 117         if (minfoPath == null) {
 118             moduleInfoSrc = getDir(dir, "src");
 119         } else {
 120             moduleInfoSrc = getDir(minfoPath);
 121         }
 122 
 123         buildModules(configs, depconfigs, mergeModules);
 124 
 125         // generate output files only for top-level modules
 126         for (Module m : Module.getTopLevelModules()) {
 127             String module = m.name();
 128             m.printClassListTo(resolve(dir, module, "classlist"));
 129             m.printResourceListTo(resolve(dir, module, "resources"));
 130             m.printSummaryTo(resolve(dir, module, "summary"));
 131             m.printDependenciesTo(resolve(dir, module, "dependencies"), showDynamic);
 132         }
 133 
 134         // Generate other summary reports
 135         printModulesSummary(dir, showDynamic);
 136         printModulesDot(dir, showDynamic);
 137         printPackagesSummary(dir);
 138 
 139         // Generate module-info.java for all modules
 140         printModuleInfos(moduleInfoSrc);
 141 
 142         // generate modules.list file that list the top-level modules
 143         // and its sub-modules.  The modules build reads this file
 144         // to reconstruct the module content for each top-level module
 145         printModulesList(dir);
 146     }
 147 
 148     private static String getOption(String[] args, int index) {
 149         if (index < args.length) {
 150            return args[index];
 151         } else {
 152            usage();
 153         }
 154         return null;
 155     }
 156 
 157     static void buildModules(List<String> configs,
 158             List<String> depconfigs,
 159             boolean mergeModules) throws IOException {
 160         List<Module> modules = new ArrayList<Module>();
 161 
 162         // create modules based on the input config files
 163         for (String file : configs) {
 164             for (ModuleConfig mconfig : ModuleConfig.readConfigurationFile(file)) {
 165                 modules.add(Module.addModule(mconfig));
 166             }
 167         }
 168 
 169         // parse class files
 170         ClassPath.parseAllClassFiles();
 171 
 172         // Add additional dependencies if specified
 173         if (depconfigs != null && depconfigs.size() > 0) {
 174             DependencyConfig.parse(depconfigs);
 175         }
 176 
 177         // process the roots and dependencies to get the classes for each module
 178         for (Module m : modules) {
 179             m.processRootsAndReferences();
 180         }
 181 
 182         // update the dependencies for classes that were subsequently allocated
 183         // to modules
 184         for (Module m : modules) {
 185             m.fixupDependencies();
 186         }
 187 
 188         if (mergeModules) {
 189             Module.buildModuleMembers();
 190         }
 191 
 192         Platform.fixupPlatformModules();
 193     }
 194 
 195     private static void printModulesSummary(File dir, boolean showDynamic) throws IOException {
 196         // print summary of dependencies
 197         PrintWriter writer = new PrintWriter(new File(dir, "modules.summary"));
 198         try {
 199             for (Module m : Module.getTopLevelModules()) {
 200                 for (Dependency dep : m.dependences()) {
 201                     if (!showDynamic && dep.dynamic && !dep.optional) {
 202                         continue;
 203                     }
 204                     if (dep.module == null || !dep.module.isBase()) {
 205 
 206                         String prefix = "";
 207                         if (dep.optional) {
 208                             prefix = "[optional] ";
 209                         } else if (dep.dynamic) {
 210                             prefix = "[dynamic] ";
 211                         }
 212 
 213                         Module other = dep != null ? dep.module : null;
 214                         writer.format("%s%s -> %s%n", prefix, m, other);
 215                     }
 216                 }
 217             }
 218         } finally {
 219             writer.close();
 220         }
 221     }
 222 
 223     private static void printModulesDot(File dir, boolean showDynamic) throws IOException {
 224         PrintWriter writer = new PrintWriter(new File(dir, "modules.dot"));
 225         try {
 226             writer.println("digraph jdk {");
 227             for (Module m : Module.getTopLevelModules()) {
 228                 for (Dependency dep : m.dependences()) {
 229                     if (!showDynamic && dep.dynamic && !dep.optional) {
 230                         continue;
 231                     }
 232                     if (dep.module == null || !dep.module.isBase()) {
 233                         String style = "";
 234                         String color = "";
 235                         String property = "";
 236                         if (dep.optional) {
 237                             style = "style=dotted";
 238                         }
 239                         if (dep.dynamic) {
 240                             color = "color=red";
 241                         }
 242                         if (style.length() > 0 || color.length() > 0) {
 243                             String comma = "";
 244                             if (style.length() > 0 && color.length() > 0) {
 245                                 comma = ", ";
 246                             }
 247                             property = String.format(" [%s%s%s]", style, comma, color);
 248                         }
 249                         Module other = dep != null ? dep.module : null;
 250                         writer.format("    \"%s\" -> \"%s\"%s;%n", m, other, property);
 251                     }
 252                 }
 253             }
 254             writer.println("}");
 255         } finally {
 256             writer.close();
 257         }
 258     }
 259 
 260     private static boolean isJdkTool(Module m) {
 261         Set<RequiresModule> tools = Module.findModule(Platform.JDK_TOOLS).requires();
 262         for (RequiresModule t : tools) {
 263             if (t.module() == m) {
 264                 return true;
 265             }
 266         }
 267         return false;
 268     }
 269 
 270     private static void printMembers(Module m, PrintWriter writer) {
 271         for (Module member : m.members()) {
 272             if (!member.isEmpty() || member.allowEmpty() || m.allowEmpty()) {
 273                 writer.format("%s ", member);
 274                 printMembers(member, writer);
 275             }
 276         }
 277         if (m.members().isEmpty() && isJdkTool(m)) {
 278             String name = m.name().substring(4, m.name().length());
 279             writer.format("%s ", name);
 280         } 
 281     }
 282 
 283     private static void printModuleGroup(Module group, PrintWriter writer) {
 284         writer.format("%s ", group);
 285         printMembers(group, writer);
 286         writer.println();
 287     }
 288 
 289     private static void printPlatformModulesList(File dir, Module m) throws IOException {
 290         m.printDepModuleListTo(resolve(dir, m.name(), "modules.list"));
 291     }
 292 
 293     private static void printModulesList(File dir) throws IOException {
 294         // Generate ordered list of dependences
 295         // for constructing the base/module images
 296         printPlatformModulesList(dir, Platform.jdkModule());
 297         printPlatformModulesList(dir, Platform.jreModule());
 298         printPlatformModulesList(dir, Platform.jdkBaseModule());
 299         printPlatformModulesList(dir, Platform.jdkBaseToolModule());
 300 
 301         // print module group / members relationship in
 302         // the dependences order so that its dependences are listed first
 303         PrintWriter writer = new PrintWriter(new File(dir, "modules.list"));
 304         try {
 305             Module jdk = Platform.jdkModule();
 306             Set<Module> allModules = new LinkedHashSet<Module>(jdk.orderedDependencies());
 307             // put the boot module first
 308             allModules.add(Platform.bootModule());
 309             for (Module m : Module.getTopLevelModules()) {
 310                 if (!allModules.contains(m)) {
 311                     allModules.addAll(m.orderedDependencies());
 312                 }
 313             }
 314 
 315             for (Module m : allModules) {
 316                 printModuleGroup(m, writer);
 317             }
 318 
 319         } finally {
 320             writer.close();
 321         }
 322     }
 323 
 324     private static void printModuleInfos(File dir) throws IOException {
 325         for (Module m : Module.getTopLevelModules()) {
 326             File mdir = getDir(dir, m.name());
 327             m.printModuleInfoTo(resolve(mdir, "module-info", "java"));
 328         }
 329     }
 330 
 331     private static void printPackagesSummary(File dir) throws IOException {
 332         // print package / module relationship
 333         PrintWriter writer = new PrintWriter(new File(dir, "modules.pkginfo"));
 334         try {
 335             Map<String, Set<Module>> packages = new TreeMap<String, Set<Module>>();
 336             Set<String> splitPackages = new TreeSet<String>();
 337 
 338             for (Module m : Module.getTopLevelModules()) {
 339                 for (PackageInfo info : m.getPackageInfos()) {
 340                     Set<Module> value = packages.get(info.pkgName);
 341                     if (value == null) {
 342                         value = new TreeSet<Module>();
 343                         packages.put(info.pkgName, value);
 344                     } else {
 345                         // package in more than one module
 346                         splitPackages.add(info.pkgName);
 347                     }
 348                     value.add(m);
 349                 }
 350             }
 351 
 352             // packages that are splitted among multiple modules
 353             writer.println("Packages splitted across modules:-\n");
 354             writer.format("%-60s  %s\n", "Package", "Module");
 355 
 356             for (String pkgname : splitPackages) {
 357                 writer.format("%-60s", pkgname);
 358                 for (Module m : packages.get(pkgname)) {
 359                     writer.format("  %s", m);
 360                 }
 361                 writer.println();
 362             }
 363 
 364             writer.println("\nPackage-private dependencies:-");
 365             for (String pkgname : splitPackages) {
 366                 for (Klass k : Klass.getAllClasses()) {
 367                     if (k.getPackageName().equals(pkgname)) {
 368                         Module m = k.getModule();
 369                         // check if this klass references a package-private
 370                         // class that is in a different module
 371                         for (Klass other : k.getReferencedClasses()) {
 372                             if (other.getModule() != m &&
 373                                     !other.isPublic() &&
 374                                     other.getPackageName().equals(pkgname)) {
 375                                 String from = k.getClassName() + " (" + m + ")";
 376                                 writer.format("%-60s -> %s (%s)\n", from, other, other.getModule());
 377                             }
 378                         }
 379                     }
 380                 }
 381             }
 382         } finally {
 383             writer.close();
 384         }
 385 
 386     }
 387 
 388     private static String resolve(File dir, String mname, String suffix) {
 389         File f = new File(dir, mname + "." + suffix);
 390         return f.toString();
 391 
 392     }
 393 
 394     private static File getDir(File path, String subdir) {
 395         File dir = new File(path, subdir);
 396         if (!dir.isDirectory()) {
 397             if (!dir.exists()) {
 398                 boolean created = dir.mkdir();
 399                 if (!created) {
 400                     throw new RuntimeException("Unable to create `" + dir + "'");
 401                 }
 402             }
 403         }
 404         return dir;
 405     }
 406 
 407     private static File getDir(String path) {
 408         File dir = new File(path);
 409         if (!dir.isDirectory()) {
 410             if (!dir.exists()) {
 411                 boolean created = dir.mkdir();
 412                 if (!created) {
 413                     throw new RuntimeException("Unable to create `" + dir + "'");
 414                 }
 415             }
 416         }
 417         return dir;
 418     }
 419 
 420     private static void usage() {
 421         System.out.println("Usage: ClassAnalyzer <options>");
 422         System.out.println("Options: ");
 423         System.out.println("\t-jdkhome     <JDK home> where all jars will be parsed");
 424         System.out.println("\t-cpath       <classpath> where classes and jars will be parsed");
 425         System.out.println("\t             Either -jdkhome or -cpath option can be used.");
 426         System.out.println("\t-config      <module config file>");
 427         System.out.println("\t             This option can be repeated for multiple module config files");
 428         System.out.println("\t-output      <output dir>");
 429         System.out.println("\t-moduleinfo  <output dir of module-info.java>");
 430         System.out.println("\t-base        <base module>");
 431         System.out.println("\t-version     <module's version>");
 432         System.out.println("\t-showdynamic show dynamic dependencies in the reports");
 433         System.out.println("\t-nomerge     specify not to merge modules");
 434         System.exit(-1);
 435     }
 436 }