Path: blob/aarch64-shenandoah-jdk8u272-b10/langtools/src/share/classes/com/sun/tools/jdeps/JdepsTask.java
38899 views
/*1* Copyright (c) 2012, 2017, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/24package com.sun.tools.jdeps;2526import com.sun.tools.classfile.AccessFlags;27import com.sun.tools.classfile.ClassFile;28import com.sun.tools.classfile.ConstantPoolException;29import com.sun.tools.classfile.Dependencies;30import com.sun.tools.classfile.Dependencies.ClassFileError;31import com.sun.tools.classfile.Dependency;32import com.sun.tools.classfile.Dependency.Location;33import com.sun.tools.jdeps.PlatformClassPath.JDKArchive;34import static com.sun.tools.jdeps.Analyzer.Type.*;35import java.io.*;36import java.nio.file.DirectoryStream;37import java.nio.file.Files;38import java.nio.file.Path;39import java.nio.file.Paths;40import java.text.MessageFormat;41import java.util.*;42import java.util.regex.Pattern;4344/**45* Implementation for the jdeps tool for static class dependency analysis.46*/47class JdepsTask {48static class BadArgs extends Exception {49static final long serialVersionUID = 8765093759964640721L;50BadArgs(String key, Object... args) {51super(JdepsTask.getMessage(key, args));52this.key = key;53this.args = args;54}5556BadArgs showUsage(boolean b) {57showUsage = b;58return this;59}60final String key;61final Object[] args;62boolean showUsage;63}6465static abstract class Option {66Option(boolean hasArg, String... aliases) {67this.hasArg = hasArg;68this.aliases = aliases;69}7071boolean isHidden() {72return false;73}7475boolean matches(String opt) {76for (String a : aliases) {77if (a.equals(opt))78return true;79if (hasArg && opt.startsWith(a + "="))80return true;81}82return false;83}8485boolean ignoreRest() {86return false;87}8889abstract void process(JdepsTask task, String opt, String arg) throws BadArgs;90final boolean hasArg;91final String[] aliases;92}9394static abstract class HiddenOption extends Option {95HiddenOption(boolean hasArg, String... aliases) {96super(hasArg, aliases);97}9899boolean isHidden() {100return true;101}102}103104static Option[] recognizedOptions = {105new Option(false, "-h", "-?", "-help") {106void process(JdepsTask task, String opt, String arg) {107task.options.help = true;108}109},110new Option(true, "-dotoutput") {111void process(JdepsTask task, String opt, String arg) throws BadArgs {112Path p = Paths.get(arg);113if (Files.exists(p) && (!Files.isDirectory(p) || !Files.isWritable(p))) {114throw new BadArgs("err.invalid.path", arg);115}116task.options.dotOutputDir = arg;117}118},119new Option(false, "-s", "-summary") {120void process(JdepsTask task, String opt, String arg) {121task.options.showSummary = true;122task.options.verbose = SUMMARY;123}124},125new Option(false, "-v", "-verbose",126"-verbose:package",127"-verbose:class") {128void process(JdepsTask task, String opt, String arg) throws BadArgs {129switch (opt) {130case "-v":131case "-verbose":132task.options.verbose = VERBOSE;133task.options.filterSameArchive = false;134task.options.filterSamePackage = false;135break;136case "-verbose:package":137task.options.verbose = PACKAGE;138break;139case "-verbose:class":140task.options.verbose = CLASS;141break;142default:143throw new BadArgs("err.invalid.arg.for.option", opt);144}145}146},147new Option(true, "-cp", "-classpath") {148void process(JdepsTask task, String opt, String arg) {149task.options.classpath = arg;150}151},152new Option(true, "-p", "-package") {153void process(JdepsTask task, String opt, String arg) {154task.options.packageNames.add(arg);155}156},157new Option(true, "-e", "-regex") {158void process(JdepsTask task, String opt, String arg) {159task.options.regex = arg;160}161},162163new Option(true, "-f", "-filter") {164void process(JdepsTask task, String opt, String arg) {165task.options.filterRegex = arg;166}167},168new Option(false, "-filter:package",169"-filter:archive",170"-filter:none") {171void process(JdepsTask task, String opt, String arg) {172switch (opt) {173case "-filter:package":174task.options.filterSamePackage = true;175task.options.filterSameArchive = false;176break;177case "-filter:archive":178task.options.filterSameArchive = true;179task.options.filterSamePackage = false;180break;181case "-filter:none":182task.options.filterSameArchive = false;183task.options.filterSamePackage = false;184break;185}186}187},188new Option(true, "-include") {189void process(JdepsTask task, String opt, String arg) throws BadArgs {190task.options.includePattern = Pattern.compile(arg);191}192},193new Option(false, "-P", "-profile") {194void process(JdepsTask task, String opt, String arg) throws BadArgs {195task.options.showProfile = true;196if (Profile.getProfileCount() == 0) {197throw new BadArgs("err.option.unsupported", opt, getMessage("err.profiles.msg"));198}199}200},201new Option(false, "-apionly") {202void process(JdepsTask task, String opt, String arg) {203task.options.apiOnly = true;204}205},206new Option(false, "-R", "-recursive") {207void process(JdepsTask task, String opt, String arg) {208task.options.depth = 0;209// turn off filtering210task.options.filterSameArchive = false;211task.options.filterSamePackage = false;212}213},214new Option(false, "-jdkinternals") {215void process(JdepsTask task, String opt, String arg) {216task.options.findJDKInternals = true;217task.options.verbose = CLASS;218if (task.options.includePattern == null) {219task.options.includePattern = Pattern.compile(".*");220}221}222},223new Option(false, "-version") {224void process(JdepsTask task, String opt, String arg) {225task.options.version = true;226}227},228new HiddenOption(false, "-fullversion") {229void process(JdepsTask task, String opt, String arg) {230task.options.fullVersion = true;231}232},233new HiddenOption(false, "-showlabel") {234void process(JdepsTask task, String opt, String arg) {235task.options.showLabel = true;236}237},238new HiddenOption(false, "-q", "-quiet") {239void process(JdepsTask task, String opt, String arg) {240task.options.nowarning = true;241}242},243new HiddenOption(true, "-depth") {244void process(JdepsTask task, String opt, String arg) throws BadArgs {245try {246task.options.depth = Integer.parseInt(arg);247} catch (NumberFormatException e) {248throw new BadArgs("err.invalid.arg.for.option", opt);249}250}251},252};253254private static final String PROGNAME = "jdeps";255private final Options options = new Options();256private final List<String> classes = new ArrayList<>();257258private PrintWriter log;259void setLog(PrintWriter out) {260log = out;261}262263/**264* Result codes.265*/266static final int EXIT_OK = 0, // Completed with no errors.267EXIT_ERROR = 1, // Completed but reported errors.268EXIT_CMDERR = 2, // Bad command-line arguments269EXIT_SYSERR = 3, // System error or resource exhaustion.270EXIT_ABNORMAL = 4;// terminated abnormally271272int run(String[] args) {273if (log == null) {274log = new PrintWriter(System.out);275}276try {277handleOptions(args);278if (options.help) {279showHelp();280}281if (options.version || options.fullVersion) {282showVersion(options.fullVersion);283}284if (classes.isEmpty() && options.includePattern == null) {285if (options.help || options.version || options.fullVersion) {286return EXIT_OK;287} else {288showHelp();289return EXIT_CMDERR;290}291}292if (options.regex != null && options.packageNames.size() > 0) {293showHelp();294return EXIT_CMDERR;295}296if (options.findJDKInternals &&297(options.regex != null || options.packageNames.size() > 0 || options.showSummary)) {298showHelp();299return EXIT_CMDERR;300}301if (options.showSummary && options.verbose != SUMMARY) {302showHelp();303return EXIT_CMDERR;304}305boolean ok = run();306return ok ? EXIT_OK : EXIT_ERROR;307} catch (BadArgs e) {308reportError(e.key, e.args);309if (e.showUsage) {310log.println(getMessage("main.usage.summary", PROGNAME));311}312return EXIT_CMDERR;313} catch (IOException e) {314return EXIT_ABNORMAL;315} finally {316log.flush();317}318}319320private final List<Archive> sourceLocations = new ArrayList<>();321private boolean run() throws IOException {322// parse classfiles and find all dependencies323findDependencies();324325Analyzer analyzer = new Analyzer(options.verbose, new Analyzer.Filter() {326@Override327public boolean accepts(Location origin, Archive originArchive,328Location target, Archive targetArchive)329{330if (options.findJDKInternals) {331// accepts target that is JDK class but not exported332return isJDKArchive(targetArchive) &&333!((JDKArchive) targetArchive).isExported(target.getClassName());334} else if (options.filterSameArchive) {335// accepts origin and target that from different archive336return originArchive != targetArchive;337}338return true;339}340});341342// analyze the dependencies343analyzer.run(sourceLocations);344345// output result346if (options.dotOutputDir != null) {347Path dir = Paths.get(options.dotOutputDir);348Files.createDirectories(dir);349generateDotFiles(dir, analyzer);350} else {351printRawOutput(log, analyzer);352}353354if (options.findJDKInternals && !options.nowarning) {355showReplacements(analyzer);356}357return true;358}359360private void generateSummaryDotFile(Path dir, Analyzer analyzer) throws IOException {361// If verbose mode (-v or -verbose option),362// the summary.dot file shows package-level dependencies.363Analyzer.Type summaryType =364(options.verbose == PACKAGE || options.verbose == SUMMARY) ? SUMMARY : PACKAGE;365Path summary = dir.resolve("summary.dot");366try (PrintWriter sw = new PrintWriter(Files.newOutputStream(summary));367SummaryDotFile dotfile = new SummaryDotFile(sw, summaryType)) {368for (Archive archive : sourceLocations) {369if (!archive.isEmpty()) {370if (options.verbose == PACKAGE || options.verbose == SUMMARY) {371if (options.showLabel) {372// build labels listing package-level dependencies373analyzer.visitDependences(archive, dotfile.labelBuilder(), PACKAGE);374}375}376analyzer.visitDependences(archive, dotfile, summaryType);377}378}379}380}381382private void generateDotFiles(Path dir, Analyzer analyzer) throws IOException {383// output individual .dot file for each archive384if (options.verbose != SUMMARY) {385for (Archive archive : sourceLocations) {386if (analyzer.hasDependences(archive)) {387Path dotfile = dir.resolve(archive.getName() + ".dot");388try (PrintWriter pw = new PrintWriter(Files.newOutputStream(dotfile));389DotFileFormatter formatter = new DotFileFormatter(pw, archive)) {390analyzer.visitDependences(archive, formatter);391}392}393}394}395// generate summary dot file396generateSummaryDotFile(dir, analyzer);397}398399private void printRawOutput(PrintWriter writer, Analyzer analyzer) {400RawOutputFormatter depFormatter = new RawOutputFormatter(writer);401RawSummaryFormatter summaryFormatter = new RawSummaryFormatter(writer);402for (Archive archive : sourceLocations) {403if (!archive.isEmpty()) {404analyzer.visitDependences(archive, summaryFormatter, SUMMARY);405if (analyzer.hasDependences(archive) && options.verbose != SUMMARY) {406analyzer.visitDependences(archive, depFormatter);407}408}409}410}411412private boolean isValidClassName(String name) {413if (!Character.isJavaIdentifierStart(name.charAt(0))) {414return false;415}416for (int i=1; i < name.length(); i++) {417char c = name.charAt(i);418if (c != '.' && !Character.isJavaIdentifierPart(c)) {419return false;420}421}422return true;423}424425/*426* Dep Filter configured based on the input jdeps option427* 1. -p and -regex to match target dependencies428* 2. -filter:package to filter out same-package dependencies429*430* This filter is applied when jdeps parses the class files431* and filtered dependencies are not stored in the Analyzer.432*433* -filter:archive is applied later in the Analyzer as the434* containing archive of a target class may not be known until435* the entire archive436*/437class DependencyFilter implements Dependency.Filter {438final Dependency.Filter filter;439final Pattern filterPattern;440DependencyFilter() {441if (options.regex != null) {442this.filter = Dependencies.getRegexFilter(Pattern.compile(options.regex));443} else if (options.packageNames.size() > 0) {444this.filter = Dependencies.getPackageFilter(options.packageNames, false);445} else {446this.filter = null;447}448449this.filterPattern =450options.filterRegex != null ? Pattern.compile(options.filterRegex) : null;451}452@Override453public boolean accepts(Dependency d) {454if (d.getOrigin().equals(d.getTarget())) {455return false;456}457String pn = d.getTarget().getPackageName();458if (options.filterSamePackage && d.getOrigin().getPackageName().equals(pn)) {459return false;460}461462if (filterPattern != null && filterPattern.matcher(pn).matches()) {463return false;464}465return filter != null ? filter.accepts(d) : true;466}467}468469/**470* Tests if the given class matches the pattern given in the -include option471* or if it's a public class if -apionly option is specified472*/473private boolean matches(String classname, AccessFlags flags) {474if (options.apiOnly && !flags.is(AccessFlags.ACC_PUBLIC)) {475return false;476} else if (options.includePattern != null) {477return options.includePattern.matcher(classname.replace('/', '.')).matches();478} else {479return true;480}481}482483private void findDependencies() throws IOException {484Dependency.Finder finder =485options.apiOnly ? Dependencies.getAPIFinder(AccessFlags.ACC_PROTECTED)486: Dependencies.getClassDependencyFinder();487Dependency.Filter filter = new DependencyFilter();488489List<Archive> archives = new ArrayList<>();490Deque<String> roots = new LinkedList<>();491List<Path> paths = new ArrayList<>();492for (String s : classes) {493Path p = Paths.get(s);494if (Files.exists(p)) {495paths.add(p);496archives.add(Archive.getInstance(p));497} else {498if (isValidClassName(s)) {499roots.add(s);500} else {501warning("warn.invalid.arg", s);502}503}504}505sourceLocations.addAll(archives);506507List<Archive> classpaths = new ArrayList<>(); // for class file lookup508classpaths.addAll(getClassPathArchives(options.classpath, paths));509if (options.includePattern != null) {510archives.addAll(classpaths);511}512classpaths.addAll(PlatformClassPath.getArchives());513514// add all classpath archives to the source locations for reporting515sourceLocations.addAll(classpaths);516517// warn about Multi-Release jars518for (Archive a : sourceLocations) {519if (a.reader().isMultiReleaseJar()) {520warning("warn.mrjar.usejdk9", a.getPathName());521}522}523524// Work queue of names of classfiles to be searched.525// Entries will be unique, and for classes that do not yet have526// dependencies in the results map.527Deque<String> deque = new LinkedList<>();528Set<String> doneClasses = new HashSet<>();529530// get the immediate dependencies of the input files531for (Archive a : archives) {532for (ClassFile cf : a.reader().getClassFiles()) {533String classFileName;534try {535classFileName = cf.getName();536} catch (ConstantPoolException e) {537throw new ClassFileError(e);538}539540// tests if this class matches the -include or -apiOnly option if specified541if (!matches(classFileName, cf.access_flags)) {542continue;543}544545if (!doneClasses.contains(classFileName)) {546doneClasses.add(classFileName);547}548549for (Dependency d : finder.findDependencies(cf)) {550if (filter.accepts(d)) {551String cn = d.getTarget().getName();552if (!doneClasses.contains(cn) && !deque.contains(cn)) {553deque.add(cn);554}555a.addClass(d.getOrigin(), d.getTarget());556} else {557// ensure that the parsed class is added the archive558a.addClass(d.getOrigin());559}560}561for (String name : a.reader().skippedEntries()) {562warning("warn.skipped.entry", name, a.getPathName());563}564}565}566567// add Archive for looking up classes from the classpath568// for transitive dependency analysis569Deque<String> unresolved = roots;570int depth = options.depth > 0 ? options.depth : Integer.MAX_VALUE;571do {572String name;573while ((name = unresolved.poll()) != null) {574if (doneClasses.contains(name)) {575continue;576}577ClassFile cf = null;578for (Archive a : classpaths) {579cf = a.reader().getClassFile(name);580if (cf != null) {581String classFileName;582try {583classFileName = cf.getName();584} catch (ConstantPoolException e) {585throw new ClassFileError(e);586}587if (!doneClasses.contains(classFileName)) {588// if name is a fully-qualified class name specified589// from command-line, this class might already be parsed590doneClasses.add(classFileName);591// process @jdk.Exported for JDK classes592if (isJDKArchive(a)) {593((JDKArchive)a).processJdkExported(cf);594}595for (Dependency d : finder.findDependencies(cf)) {596if (depth == 0) {597// ignore the dependency598a.addClass(d.getOrigin());599break;600} else if (filter.accepts(d)) {601a.addClass(d.getOrigin(), d.getTarget());602String cn = d.getTarget().getName();603if (!doneClasses.contains(cn) && !deque.contains(cn)) {604deque.add(cn);605}606} else {607// ensure that the parsed class is added the archive608a.addClass(d.getOrigin());609}610}611}612break;613}614}615if (cf == null) {616doneClasses.add(name);617}618}619unresolved = deque;620deque = new LinkedList<>();621} while (!unresolved.isEmpty() && depth-- > 0);622}623624public void handleOptions(String[] args) throws BadArgs {625// process options626for (int i=0; i < args.length; i++) {627if (args[i].charAt(0) == '-') {628String name = args[i];629Option option = getOption(name);630String param = null;631if (option.hasArg) {632if (name.startsWith("-") && name.indexOf('=') > 0) {633param = name.substring(name.indexOf('=') + 1, name.length());634} else if (i + 1 < args.length) {635param = args[++i];636}637if (param == null || param.isEmpty() || param.charAt(0) == '-') {638throw new BadArgs("err.missing.arg", name).showUsage(true);639}640}641option.process(this, name, param);642if (option.ignoreRest()) {643i = args.length;644}645} else {646// process rest of the input arguments647for (; i < args.length; i++) {648String name = args[i];649if (name.charAt(0) == '-') {650throw new BadArgs("err.option.after.class", name).showUsage(true);651}652classes.add(name);653}654}655}656}657658private Option getOption(String name) throws BadArgs {659for (Option o : recognizedOptions) {660if (o.matches(name)) {661return o;662}663}664throw new BadArgs("err.unknown.option", name).showUsage(true);665}666667private void reportError(String key, Object... args) {668log.println(getMessage("error.prefix") + " " + getMessage(key, args));669}670671private void warning(String key, Object... args) {672log.println(getMessage("warn.prefix") + " " + getMessage(key, args));673}674675private void showHelp() {676log.println(getMessage("main.usage", PROGNAME));677for (Option o : recognizedOptions) {678String name = o.aliases[0].substring(1); // there must always be at least one name679name = name.charAt(0) == '-' ? name.substring(1) : name;680if (o.isHidden() || name.equals("h") || name.startsWith("filter:")) {681continue;682}683log.println(getMessage("main.opt." + name));684}685}686687private void showVersion(boolean full) {688log.println(version(full ? "full" : "release"));689}690691private String version(String key) {692// key=version: mm.nn.oo[-milestone]693// key=full: mm.mm.oo[-milestone]-build694if (ResourceBundleHelper.versionRB == null) {695return System.getProperty("java.version");696}697try {698return ResourceBundleHelper.versionRB.getString(key);699} catch (MissingResourceException e) {700return getMessage("version.unknown", System.getProperty("java.version"));701}702}703704static String getMessage(String key, Object... args) {705try {706return MessageFormat.format(ResourceBundleHelper.bundle.getString(key), args);707} catch (MissingResourceException e) {708throw new InternalError("Missing message: " + key);709}710}711712private static class Options {713boolean help;714boolean version;715boolean fullVersion;716boolean showProfile;717boolean showSummary;718boolean apiOnly;719boolean showLabel;720boolean findJDKInternals;721boolean nowarning;722// default is to show package-level dependencies723// and filter references from same package724Analyzer.Type verbose = PACKAGE;725boolean filterSamePackage = true;726boolean filterSameArchive = false;727String filterRegex;728String dotOutputDir;729String classpath = "";730int depth = 1;731Set<String> packageNames = new HashSet<>();732String regex; // apply to the dependences733Pattern includePattern; // apply to classes734}735private static class ResourceBundleHelper {736static final ResourceBundle versionRB;737static final ResourceBundle bundle;738static final ResourceBundle jdkinternals;739740static {741Locale locale = Locale.getDefault();742try {743bundle = ResourceBundle.getBundle("com.sun.tools.jdeps.resources.jdeps", locale);744} catch (MissingResourceException e) {745throw new InternalError("Cannot find jdeps resource bundle for locale " + locale);746}747try {748versionRB = ResourceBundle.getBundle("com.sun.tools.jdeps.resources.version");749} catch (MissingResourceException e) {750throw new InternalError("version.resource.missing");751}752try {753jdkinternals = ResourceBundle.getBundle("com.sun.tools.jdeps.resources.jdkinternals");754} catch (MissingResourceException e) {755throw new InternalError("Cannot find jdkinternals resource bundle");756}757}758}759760/*761* Returns the list of Archive specified in cpaths and not included762* initialArchives763*/764private List<Archive> getClassPathArchives(String cpaths, List<Path> initialArchives)765throws IOException766{767List<Archive> result = new ArrayList<>();768if (cpaths.isEmpty()) {769return result;770}771772List<Path> paths = new ArrayList<>();773for (String p : cpaths.split(File.pathSeparator)) {774if (p.length() > 0) {775// wildcard to parse all JAR files e.g. -classpath dir/*776int i = p.lastIndexOf(".*");777if (i > 0) {778Path dir = Paths.get(p.substring(0, i));779try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir, "*.jar")) {780for (Path entry : stream) {781paths.add(entry);782}783}784} else {785paths.add(Paths.get(p));786}787}788}789for (Path p : paths) {790if (Files.exists(p) && !hasSameFile(initialArchives, p)) {791result.add(Archive.getInstance(p));792}793}794return result;795}796797private boolean hasSameFile(List<Path> paths, Path p2) throws IOException {798for (Path p1 : paths) {799if (Files.isSameFile(p1, p2)) {800return true;801}802}803return false;804}805806class RawOutputFormatter implements Analyzer.Visitor {807private final PrintWriter writer;808private String pkg = "";809RawOutputFormatter(PrintWriter writer) {810this.writer = writer;811}812@Override813public void visitDependence(String origin, Archive originArchive,814String target, Archive targetArchive) {815String tag = toTag(target, targetArchive);816if (options.verbose == VERBOSE) {817writer.format(" %-50s -> %-50s %s%n", origin, target, tag);818} else {819if (!origin.equals(pkg)) {820pkg = origin;821writer.format(" %s (%s)%n", origin, originArchive.getName());822}823writer.format(" -> %-50s %s%n", target, tag);824}825}826}827828class RawSummaryFormatter implements Analyzer.Visitor {829private final PrintWriter writer;830RawSummaryFormatter(PrintWriter writer) {831this.writer = writer;832}833@Override834public void visitDependence(String origin, Archive originArchive,835String target, Archive targetArchive) {836writer.format("%s -> %s", originArchive.getName(), targetArchive.getPathName());837if (options.showProfile && JDKArchive.isProfileArchive(targetArchive)) {838writer.format(" (%s)", target);839}840writer.format("%n");841}842}843844class DotFileFormatter implements Analyzer.Visitor, AutoCloseable {845private final PrintWriter writer;846private final String name;847DotFileFormatter(PrintWriter writer, Archive archive) {848this.writer = writer;849this.name = archive.getName();850writer.format("digraph \"%s\" {%n", name);851writer.format(" // Path: %s%n", archive.getPathName());852}853854@Override855public void close() {856writer.println("}");857}858859@Override860public void visitDependence(String origin, Archive originArchive,861String target, Archive targetArchive) {862String tag = toTag(target, targetArchive);863writer.format(" %-50s -> \"%s\";%n",864String.format("\"%s\"", origin),865tag.isEmpty() ? target866: String.format("%s (%s)", target, tag));867}868}869870class SummaryDotFile implements Analyzer.Visitor, AutoCloseable {871private final PrintWriter writer;872private final Analyzer.Type type;873private final Map<Archive, Map<Archive,StringBuilder>> edges = new HashMap<>();874SummaryDotFile(PrintWriter writer, Analyzer.Type type) {875this.writer = writer;876this.type = type;877writer.format("digraph \"summary\" {%n");878}879880@Override881public void close() {882writer.println("}");883}884885@Override886public void visitDependence(String origin, Archive originArchive,887String target, Archive targetArchive) {888String targetName = type == PACKAGE ? target : targetArchive.getName();889if (type == PACKAGE) {890String tag = toTag(target, targetArchive, type);891if (!tag.isEmpty())892targetName += " (" + tag + ")";893} else if (options.showProfile && JDKArchive.isProfileArchive(targetArchive)) {894targetName += " (" + target + ")";895}896String label = getLabel(originArchive, targetArchive);897writer.format(" %-50s -> \"%s\"%s;%n",898String.format("\"%s\"", origin), targetName, label);899}900901String getLabel(Archive origin, Archive target) {902if (edges.isEmpty())903return "";904905StringBuilder label = edges.get(origin).get(target);906return label == null ? "" : String.format(" [label=\"%s\",fontsize=9]", label.toString());907}908909Analyzer.Visitor labelBuilder() {910// show the package-level dependencies as labels in the dot graph911return new Analyzer.Visitor() {912@Override913public void visitDependence(String origin, Archive originArchive,914String target, Archive targetArchive)915{916Map<Archive,StringBuilder> labels = edges.get(originArchive);917if (!edges.containsKey(originArchive)) {918edges.put(originArchive, labels = new HashMap<>());919}920StringBuilder sb = labels.get(targetArchive);921if (sb == null) {922labels.put(targetArchive, sb = new StringBuilder());923}924String tag = toTag(target, targetArchive, PACKAGE);925addLabel(sb, origin, target, tag);926}927928void addLabel(StringBuilder label, String origin, String target, String tag) {929label.append(origin).append(" -> ").append(target);930if (!tag.isEmpty()) {931label.append(" (" + tag + ")");932}933label.append("\\n");934}935};936}937}938939/**940* Test if the given archive is part of the JDK941*/942private boolean isJDKArchive(Archive archive) {943return JDKArchive.class.isInstance(archive);944}945946/**947* If the given archive is JDK archive, this method returns the profile name948* only if -profile option is specified; it accesses a private JDK API and949* the returned value will have "JDK internal API" prefix950*951* For non-JDK archives, this method returns the file name of the archive.952*/953private String toTag(String name, Archive source, Analyzer.Type type) {954if (!isJDKArchive(source)) {955return source.getName();956}957958JDKArchive jdk = (JDKArchive)source;959boolean isExported = false;960if (type == CLASS || type == VERBOSE) {961isExported = jdk.isExported(name);962} else {963isExported = jdk.isExportedPackage(name);964}965Profile p = getProfile(name, type);966if (isExported) {967// exported API968return options.showProfile && p != null ? p.profileName() : "";969} else {970return "JDK internal API (" + source.getName() + ")";971}972}973974private String toTag(String name, Archive source) {975return toTag(name, source, options.verbose);976}977978private Profile getProfile(String name, Analyzer.Type type) {979String pn = name;980if (type == CLASS || type == VERBOSE) {981int i = name.lastIndexOf('.');982pn = i > 0 ? name.substring(0, i) : "";983}984return Profile.getProfile(pn);985}986987/**988* Returns the recommended replacement API for the given classname;989* or return null if replacement API is not known.990*/991private String replacementFor(String cn) {992String name = cn;993String value = null;994while (value == null && name != null) {995try {996value = ResourceBundleHelper.jdkinternals.getString(name);997} catch (MissingResourceException e) {998// go up one subpackage level999int i = name.lastIndexOf('.');1000name = i > 0 ? name.substring(0, i) : null;1001}1002}1003return value;1004};10051006private void showReplacements(Analyzer analyzer) {1007Map<String,String> jdkinternals = new TreeMap<>();1008boolean useInternals = false;1009for (Archive source : sourceLocations) {1010useInternals = useInternals || analyzer.hasDependences(source);1011for (String cn : analyzer.dependences(source)) {1012String repl = replacementFor(cn);1013if (repl != null && !jdkinternals.containsKey(cn)) {1014jdkinternals.put(cn, repl);1015}1016}1017}1018if (useInternals) {1019log.println();1020warning("warn.replace.useJDKInternals", getMessage("jdeps.wiki.url"));1021}1022if (!jdkinternals.isEmpty()) {1023log.println();1024log.format("%-40s %s%n", "JDK Internal API", "Suggested Replacement");1025log.format("%-40s %s%n", "----------------", "---------------------");1026for (Map.Entry<String,String> e : jdkinternals.entrySet()) {1027log.format("%-40s %s%n", e.getKey(), e.getValue());1028}1029}10301031}1032}103310341035