2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
4 * Copyright (C) 1997-2016 Oracle and/or its affiliates. All rights reserved.
6 * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
7 * Other names may be trademarks of their respective owners.
9 * The contents of this file are subject to the terms of either the GNU
10 * General Public License Version 2 only ("GPL") or the Common
11 * Development and Distribution License("CDDL") (collectively, the
12 * "License"). You may not use this file except in compliance with the
13 * License. You can obtain a copy of the License at
14 * http://www.netbeans.org/cddl-gplv2.html
15 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
16 * specific language governing permissions and limitations under the
17 * License. When distributing the software, include this License Header
18 * Notice in each file and include the License file at
19 * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this
20 * particular file as subject to the "Classpath" exception as provided
21 * by Oracle in the GPL Version 2 section of the License file that
22 * accompanied this code. If applicable, add the following below the
23 * License Header, with the fields enclosed by brackets [] replaced by
24 * your own identifying information:
25 * "Portions Copyrighted [year] [name of copyright owner]"
29 * The Original Software is NetBeans. The Initial Developer of the Original
30 * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
31 * Microsystems, Inc. All Rights Reserved.
33 * If you wish your version of this file to be governed by only the CDDL
34 * or only the GPL Version 2, indicate your decision by adding
35 * "[Contributor] elects to include this software in this distribution
36 * under the [CDDL or GPL Version 2] license." If you do not indicate a
37 * single choice of license, a recipient has the option to distribute
38 * your version of this file under either the CDDL, the GPL Version 2 or
39 * to extend the choice of license to its licensees as provided above.
40 * However, if you add GPL Version 2 code and therefore, elected the GPL
41 * Version 2 license, then the option applies only if the new code is
42 * made subject to such option by the copyright holder.
44 package org.netbeans.lib.callgraph;
46 import org.netbeans.lib.callgraph.Arguments.InvalidArgumentsException;
47 import static org.netbeans.lib.callgraph.CallgraphControl.CMD_CLASSGRAPH;
48 import static org.netbeans.lib.callgraph.CallgraphControl.CMD_DISABLE_EIGHT_BIT_STRINGS;
49 import static org.netbeans.lib.callgraph.CallgraphControl.CMD_EXCLUDE;
50 import static org.netbeans.lib.callgraph.CallgraphControl.CMD_MAVEN;
51 import static org.netbeans.lib.callgraph.CallgraphControl.CMD_METHODGRAPH;
52 import static org.netbeans.lib.callgraph.CallgraphControl.CMD_NOSELF;
53 import static org.netbeans.lib.callgraph.CallgraphControl.CMD_OMIT_ABSTRACT;
54 import static org.netbeans.lib.callgraph.CallgraphControl.CMD_PACKAGEGRAPH;
55 import static org.netbeans.lib.callgraph.CallgraphControl.CMD_QUIET;
56 import static org.netbeans.lib.callgraph.CallgraphControl.CMD_REVERSE;
57 import static org.netbeans.lib.callgraph.CallgraphControl.CMD_SIMPLE;
58 import org.netbeans.lib.callgraph.io.JavaFilesIterator;
59 import org.netbeans.lib.callgraph.javac.JavacRunner;
60 import org.netbeans.lib.callgraph.javac.SourceElement;
61 import org.netbeans.lib.callgraph.javac.SourcesInfo;
62 import org.netbeans.lib.callgraph.util.MergeIterator;
64 import java.io.IOException;
65 import java.io.PrintStream;
66 import java.util.ArrayList;
67 import java.util.Collections;
68 import java.util.HashSet;
69 import java.util.Iterator;
70 import java.util.LinkedList;
71 import java.util.List;
73 import java.util.concurrent.atomic.AtomicReference;
74 import java.util.function.Consumer;
77 * Scans a source folder and runs javac against any Java sources present, and
78 * builds graphs of what calls what within those. The library classpath does not
79 * need to be set, and the folders do not need to be package roots - we are
80 * instructing javac to treat errors as non-fatal. The available sources will be
83 * @author Tim Boudreau
85 public final class Callgraph {
87 private boolean noself;
88 private boolean maven;
89 private boolean quiet;
90 private boolean simple;
91 private final Set<String> excludes = new HashSet<>();
92 private final Set<File> folders = new HashSet<>();
93 private File classgraphFile;
94 private File methodgraphFile;
95 private File packagegraphFile;
96 private Listener listener;
97 private boolean omitAbstract;
98 private boolean useJavaStrings;
99 private boolean reverse;
101 private Callgraph() {
105 * Configure a Callgraph to build.
107 * @return A call graph to configure
109 public static Callgraph configure() {
110 return new Callgraph();
113 public static void main(String[] args) throws IOException {
114 CallgraphControl arguments = null;
116 arguments = new Arguments(args);
117 } catch (InvalidArgumentsException ex) {
118 // this will be a help message describing usage and the invalid
120 System.err.println(ex.getMessage());
123 assert arguments != null;
124 invoke(arguments, arguments.isVerbose() ? new LoggingListener() : null);
128 * Run javac and produce output.
130 * @param arguments The parsed arguments
131 * @return The list of all methods found, sorted by qname
132 * @throws IOException If i/o fails
134 static List<SourceElement> invoke(CallgraphControl arguments, Listener listener) throws IOException {
135 SourcesInfo info = new SourcesInfo(arguments.isDisableEightBitStrings(), arguments.isAggressive());
136 // Build an iterable of all Java sources (without collecting them all ahead of time)
137 List<Iterable<File>> iterables = new LinkedList<>();
138 for (File folder : arguments) {
139 iterables.add(JavaFilesIterator.iterable(folder));
141 // The thing that will run javac
142 JavacRunner runner = new JavacRunner(info, MergeIterator.toIterable(iterables), listener);
143 AtomicReference<File> lastFile = new AtomicReference<>();
144 Consumer<File> monitor = lastFile::set;
146 Set<SourceElement> allElements = runner.go(monitor, lastFile);
148 List<SourceElement> all = new ArrayList<>(allElements);
149 // Sort, so textual output is more human-friendly
150 Collections.sort(all);
151 // Now write files and print output
152 if (!all.isEmpty()) {
153 PrintStream outStream = createPrintStreamIfNotNull(arguments.methodGraphFile());
154 PrintStream packageStream = createPrintStreamIfNotNull(arguments.packageGraphFile());
155 PrintStream classStream = createPrintStreamIfNotNull(arguments.classGraphFile());
156 // duplicate avoidance
157 Set<CharSequence> emittedPackageLines = new HashSet<>();
158 Set<CharSequence> emittedClassLines = new HashSet<>();
159 List<Object> clazz = new ArrayList<>(5);
160 CharSequence lastClass = null;
162 List<Object> pkg = new ArrayList<>(5);
163 CharSequence lastPackage = null;
166 // Iterate every method
168 for (SourceElement sce : all) {
169 if (arguments.isExcluded(sce.qname().toString())) { // Ignore matches
172 List<SourceElement> outbounds = new ArrayList<>(arguments.isReverse() ? sce.getInboundReferences() : sce.getOutboundReferences());
173 Collections.sort(outbounds); // also sort connections
174 // Iterate the current method's connections
175 List<Object> mth = new ArrayList<>(5);
176 CharSequence currClazz = arguments.isShortNames() ? sce.typeName() : info.strings.concat(sce.packageName(), info.strings.DOT, sce.typeName());
177 if (!currClazz.equals(lastClass)) {
178 if (classStream != null) {
179 writeLine(clazz, info, emittedClassLines, classStream);
182 lastClass = currClazz;
184 if (clazz.isEmpty()) {
185 clazz.add(currClazz);
186 if (arguments.isExtendedProperties()) {
187 clazz.add(sce.isAbstract());
190 CharSequence currPkg = sce.packageName();
194 if (!currPkg.equals(lastPackage)) {
195 if (packageStream != null) {
196 writeLine(pkg, info, emittedPackageLines, packageStream);
198 lastPackage = currPkg;
201 for (SourceElement outbound : outbounds) {
202 if (arguments.isExcluded(outbound.qname().toString())) { // Ignore matches
205 // If we are ignoring abstract methods, do that - has no effect on classes
206 if (arguments.isOmitAbstract() && (outbound.isAbstract() | sce.isAbstract())) {
209 // If the argument is set, ignore cases where it's a class' method
210 // referencing another method on that class
211 if (!arguments.isSelfReferences() && sce.typeName().equals(outbound.typeName())) {
214 if (outStream != null || !arguments.isQuiet()) {
215 if (arguments.isShortNames()) {
216 mth.add(outbound.shortName());
218 mth.add(outbound.qname());
220 if (arguments.isExtendedProperties()) {
221 mth.add(outbound.isAbstract());
224 // Build the package graph output if necessary
225 if (packageStream != null) {
226 if (!outbound.packageName().equals(currPkg) || arguments.isSelfReferences()) {
227 pkg.add(outbound.packageName());
230 // Build the class graph output if necessary
231 if (classStream != null) {
232 CharSequence type1 = sce.typeName();
233 CharSequence type2 = outbound.typeName();
234 if (!arguments.isShortNames()) {
235 // type1 = info.strings.concat(sce.packageName(), info.strings.DOT, type1);
236 type2 = info.strings.concat(outbound.packageName(), info.strings.DOT, type2);
238 if (!type1.equals(type2) && !clazz.contains(type2)) {
240 if (arguments.isExtendedProperties()) {
241 clazz.add(outbound.isAbstract());
246 if (!arguments.isQuiet() || outStream != null) {
247 if (!mth.isEmpty()) {
248 CharSequence nm = arguments.isShortNames() ? sce.shortName() : sce.qname();
249 List<Object> l = mth;
250 if (arguments.isExtendedProperties()) {
251 mth.add(0, sce.isAbstract());
254 CharSequence line = info.strings.concatQuoted(l);
255 if (!arguments.isQuiet()) {
256 System.out.println(line);
258 if (outStream != null) {
259 outStream.println(line);
264 if (classStream != null && !clazz.isEmpty()) {
265 writeLine(clazz, info, emittedClassLines, classStream);
267 if (packageStream != null && !pkg.isEmpty()) {
268 writeLine(pkg, info, emittedPackageLines, packageStream);
271 for (PrintStream ps : new PrintStream[]{outStream, packageStream, classStream}) {
280 private static void writeLine(List<Object> clazz, SourcesInfo info, Set<CharSequence> emittedClassLines, PrintStream classStream) {
281 if (!clazz.isEmpty()) {
282 CharSequence cs = info.strings.concatQuoted(clazz);
283 if (!emittedClassLines.contains(cs)) {
284 classStream.println(cs);
285 emittedClassLines.add(cs);
290 private static PrintStream createPrintStreamIfNotNull(File outputFile) throws IOException {
291 PrintStream outStream = null;
292 if (outputFile != null) {
293 if (!outputFile.exists()) {
294 if (!outputFile.createNewFile()) {
295 throw new IllegalStateException("Could not create " + outputFile);
298 outStream = new PrintStream(outputFile);
303 public List<String> toCommandLineArguments() {
304 List<String> args = new ArrayList<>();
305 addBoolean(CMD_NOSELF, noself, args);
306 addBoolean(CMD_MAVEN, maven, args);
307 addBoolean(CMD_QUIET, quiet, args);
308 addBoolean(CMD_SIMPLE, simple, args);
309 addBoolean(CMD_REVERSE, reverse, args);
310 addBoolean(CMD_DISABLE_EIGHT_BIT_STRINGS, useJavaStrings, args);
311 addBoolean(CMD_OMIT_ABSTRACT, omitAbstract, args);
312 if (!excludes.isEmpty()) {
313 StringBuilder concat = new StringBuilder();
314 for (Iterator<String> it = excludes.iterator(); it.hasNext();) {
315 String next = it.next();
321 args.add("--" + CMD_EXCLUDE);
322 args.add(concat.toString());
324 addFile(CMD_CLASSGRAPH, classgraphFile, args);
325 addFile(CMD_METHODGRAPH, methodgraphFile, args);
326 addFile(CMD_PACKAGEGRAPH, packagegraphFile, args);
327 for (File fld : folders) {
328 args.add(fld.getAbsolutePath());
333 CallgraphControl build() throws IOException {
334 List<String> args = toCommandLineArguments();
335 // We use regular string processing so we get the argument validation
336 String[] argList = args.toArray(new String[args.size()]);
337 Arguments arguments = new Arguments(false, argList);
342 * Run this callgraph, writing output and returning the set of all methods
343 * found, sorted in qname order. Call SourceElement.getInboundReferences()
344 * and SourceElement.getOutboundReferences() to explore the graph.
346 * @return Sorted set of source elements
347 * @throws IOException If i/o fails
349 public List<SourceElement> run() throws IOException {
350 return Callgraph.invoke(build(), listener);
353 private void addFile(String command, File file, List<String> args) {
355 args.add("--" + command);
356 args.add(file.getAbsolutePath());
360 private void addBoolean(String command, boolean val, List<String> args) {
362 args.add("--" + command);
366 public Callgraph setListener(Listener listener) {
367 this.listener = listener;
371 public Callgraph packageGraphOutput(File file) {
372 packagegraphFile = file;
376 public Callgraph methodGraphOutput(File file) {
377 methodgraphFile = file;
381 public Callgraph classGraphOutput(File file) {
382 classgraphFile = file;
386 public Callgraph addSourceParent(File folder) {
391 public Callgraph excludePrefix(String prefix) {
392 excludes.add(prefix);
396 public Callgraph useSimpleClassNames() {
401 public Callgraph quiet() {
406 public Callgraph ignoreSelfReferences() {
411 public Callgraph scanFoldersForMavenProjects() {
416 public Callgraph reverse() {
421 public Callgraph useJavaStrings() {
422 useJavaStrings = true;
426 public Callgraph omitAbstract() {
431 private static final class LoggingListener implements Listener {
434 public void onStart() {
439 public void onFinish() {
440 System.out.println("Done.");
444 public void onStartActivity(String activity, int steps) {
446 System.out.println(activity + " (" + steps + " steps)");
451 public void onStep(String step) {
452 System.out.println("\t" + step);