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 }