2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
4 * Copyright (C) 1997-2015 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;
47 import java.io.IOException;
48 import java.util.Collections;
49 import java.util.HashSet;
50 import java.util.Iterator;
51 import java.util.LinkedList;
52 import java.util.List;
56 * Parses and validates command-line arguments.
58 * @author Tim Boudreau
60 final class Arguments implements CallgraphControl {
62 private Set<File> folders = new HashSet<>();
63 private static final Command[] commands = new Command[]{
64 new NoSelfReferencesCommand(),
65 new ShortNamesCommand(),
66 new ExtendedPropertiesCommand(),
73 new PackageGraphFileCommand(),
74 new ClassGraphFileCommand(),
75 new OmitAbstractCommand(),
76 new DisableEightBitStringsCommand(),
77 new OmitAbstractCommand(),
80 new AggressiveCommand(),
83 private boolean noSelfReferences = false;
84 private boolean shortNames = false;
85 private boolean maven = false;
86 private boolean gradle = false;
87 private boolean ant = false;
88 private boolean xprop = false;
89 private boolean aggressive = false;
91 private Set<String> exclude = new HashSet<>();
92 private Set<String> ignore = new HashSet<>();
93 private boolean quiet;
94 private File classGraphFile;
95 private File packageGraphFile;
96 private boolean verbose;
97 private boolean omitAbstract;
98 private boolean disableEightBitStrings;
99 private boolean reverse;
101 Arguments(String... args) throws IOException {
105 Arguments(boolean abortIfWillNotOutput, String... args) throws IOException {
106 List<String> unknownArguments = new LinkedList<>();
107 List<String> errors = new LinkedList<>();
108 for (int i = 0; i < args.length;) {
110 for (Command c : commands) {
112 @SuppressWarnings("LeakingThisInConstructor")
113 int increment = c.parse(i, args, this);
118 } catch (IllegalArgumentException ex) {
119 errors.add(c.name + ": " + ex.getMessage());
123 unknownArguments.add(args[i]);
127 Set<String> folders = new HashSet<>();
128 for (String unknown : unknownArguments) {
129 if (unknown.startsWith("-")) {
131 errors.add("Unknown argument '" + unknown + "'");
133 folders.add(unknown);
136 if (folders.isEmpty()) {
137 errors.add("No folders of Java sources specified");
139 for (String folderName : folders) {
140 File folder = new File(folderName);
141 if (!folder.exists()) {
142 errors.add("Folder does not exist: " + folderName);
143 } else if (!folder.isDirectory()) {
144 errors.add("Not a folder: " + folderName);
146 this.folders.add(folder.getCanonicalFile());
150 if ((maven && gradle) || (ant && gradle) || (ant && maven)) {
151 errors.add("--maven, --ant and --gradle are mutually exclusive");
153 findMavenSubfolders(errors);
155 findGradleSubfolders(errors);
157 findAntSubfolders(errors);
159 Set<File> toIgnore = new HashSet<>();
160 for (String ig : ignore) {
161 File ff = new File(ig);
162 if (ff.exists() && ff.isDirectory()) {
163 ff = ff.getCanonicalFile();
164 for (File f : this.folders()) {
165 File f1 = f.getCanonicalFile();
169 if (f1.getPath().startsWith(ff.getPath())) {
175 for (String u : unknownArguments) {
176 if (!u.startsWith("-")) {
177 File f = new File(u);
179 f = f.getCanonicalFile();
180 File maybeIgnore = new File(f, ig);
181 if (maybeIgnore.exists() && maybeIgnore.isDirectory()) {
182 maybeIgnore = maybeIgnore.getCanonicalFile();
183 for (File fld : this.folders()) {
184 if (fld.equals(maybeIgnore)) {
186 } else if (fld.getAbsolutePath().startsWith(maybeIgnore.getAbsolutePath())) {
196 if (verbose && !toIgnore.isEmpty()) {
197 System.err.println("Ignoring the following projects:");
198 for (File ti : toIgnore) {
199 System.err.println(" - " + ti.getAbsolutePath());
202 this.folders.removeAll(toIgnore);
203 if (verbose && !this.folders.isEmpty()) {
204 System.err.println("Will scan the following source roots:");
205 for (File f : folders()) {
206 System.err.println(" " + f.getAbsolutePath());
210 if (packageGraphFile != null) {
211 File parent = packageGraphFile.getParentFile();
212 if (!parent.exists() || !parent.isDirectory()) {
213 errors.add("Parent folder for package graph output file does not exist: " + parent);
216 if (classGraphFile != null) {
217 File parent = classGraphFile.getParentFile();
218 if (!parent.exists() || !parent.isDirectory()) {
219 errors.add("Parent folder for class graph output file does not exist: " + parent);
222 if (outfile != null) {
223 File parent = outfile.getParentFile();
224 if (!parent.exists() || !parent.isDirectory()) {
225 errors.add("Parent folder for output file does not exist: " + parent);
227 } else if (abortIfWillNotOutput && quiet && packageGraphFile == null && classGraphFile == null) {
228 errors.add("-q or --quiet specified, but no output file specified - would not produce any output at all");
230 // XXX check if any folders are children of each other?
231 if (!errors.isEmpty()) {
232 throw new InvalidArgumentsException(help(errors), errors);
236 private void findMavenSubfolders(List<String> errors) {
237 Set<File> flds = new HashSet<>(this.folders);
238 this.folders.clear();
239 for (File f : flds) {
240 recurseSubfoldersLookingForMavenProjects(f);
242 if (this.folders.isEmpty()) {
243 errors.add("Did not find any maven projects (looked for pom.xml and src/main/java in all subfolders of folder list)");
247 private void recurseSubfoldersLookingForMavenProjects(File file) {
248 if (file.isDirectory()) {
250 File sources = srcMainJavaFolder(file);
251 if (sources != null) {
252 this.folders.add(sources);
255 for (File child : file.listFiles()) {
256 recurseSubfoldersLookingForMavenProjects(child);
261 private boolean hasPom(File fld) {
262 File pom = new File(fld, "pom.xml");
263 return pom.exists() && pom.isFile() && pom.canRead();
266 void findAntSubfolders(List<String> errors) {
267 Set<File> flds = new HashSet<>(this.folders);
268 this.folders.clear();
269 for (File f : flds) {
270 recurseSubfoldersLookingForAntProjects(f);
272 if (this.folders.isEmpty()) {
273 errors.add("Did not find any ant projects (looked for build.xml and src/ in all subfolders of folder list)");
277 private void recurseSubfoldersLookingForAntProjects(File file) {
278 if (file.isDirectory()) {
279 if (hasBuildXml(file)) {
280 File sources = new File(file, "src");
281 if (sources.exists() && sources.isDirectory()) {
282 this.folders.add(sources);
285 for (File child : file.listFiles()) {
286 recurseSubfoldersLookingForAntProjects(child);
291 private boolean hasBuildXml(File fld) {
292 File pom = new File(fld, "build.xml");
293 return pom.exists() && pom.isFile() && pom.canRead();
296 void findGradleSubfolders(List<String> errors) {
297 Set<File> flds = new HashSet<>(this.folders);
298 this.folders.clear();
299 for (File f : flds) {
300 recurseSubfoldersLookingForGradleProjects(f);
302 if (this.folders.isEmpty()) {
303 errors.add("Did not find any gradle projects (looked for *.gradle and src/main/java in all subfolders of folder list)");
307 private void recurseSubfoldersLookingForGradleProjects(File file) {
308 if (file.isDirectory()) {
309 if (hasGradleFile(file)) {
310 File sources = srcMainJavaFolder(file);
311 if (sources != null) {
312 this.folders.add(sources);
315 for (File child : file.listFiles()) {
316 recurseSubfoldersLookingForGradleProjects(child);
321 boolean hasGradleFile(File fld) {
322 return fld.listFiles((File pathname) -> {
323 return pathname.getName().endsWith(".gradle") && pathname.isFile() && pathname.canRead();
327 public boolean isGradle() {
331 private File srcMainJavaFolder(File projectFolder) {
332 File src = new File(projectFolder, "src");
333 File main = new File(src, "main");
334 File java = new File(main, "java");
335 if (java.exists() && java.isDirectory()) {
341 public boolean isVerbose() {
345 public boolean isDisableEightBitStrings() {
346 return disableEightBitStrings;
349 public boolean isReverse() {
354 public File methodGraphFile() {
359 public Set<File> folders() {
360 return Collections.unmodifiableSet(folders);
364 public boolean isSelfReferences() {
365 return noSelfReferences == false;
369 public boolean isShortNames() {
374 public boolean isMaven() {
379 public boolean isAnt() {
384 public boolean isExtendedProperties() {
388 public boolean isAggressive() {
393 public File classGraphFile() {
394 return classGraphFile;
398 public File packageGraphFile() {
399 return packageGraphFile;
403 public Iterator<File> iterator() {
404 return folders().iterator();
408 public Set<String> excludePrefixes() {
409 return Collections.unmodifiableSet(exclude);
412 public boolean isOmitAbstract() {
416 public boolean isExcluded(String qname) {
417 for (String ex : exclude) {
418 if (qname.startsWith(ex)) {
426 public boolean isQuiet() {
430 private String help(List<String> errors) {
431 StringBuilder sb = new StringBuilder("Callgraph prints a graph of things that call each other in a tree of Java sources,\n"
432 + "and can output graphs of what methods / classes / packages (or all of the above) call each other\nwithin that"
434 + "\n\nUsage:\njava -jar callgraph.jar ");
435 for (Command c : commands) {
439 sb.append("--").append(c.name).append(" | -").append(c.shortcut);
440 if (c.takesArgument) {
441 sb.append(" ").append(c.name);
448 sb.append("dir1 [dir2 dir3 ...]");
450 for (Command c : commands) {
452 sb.append("--").append(c.name).append(" / -").append(c.shortcut).append(" :\t").append(c.help());
454 if (!errors.isEmpty()) {
455 sb.append("\nErrors:\n");
456 for (String err : errors) {
457 sb.append('\t').append(err).append('\n');
460 return sb.toString();
463 private static abstract class Command {
465 protected final String name;
466 protected final String shortcut;
467 protected final boolean optional;
468 private final boolean takesArgument;
470 Command(String name, String shortcut, boolean optional, boolean takesArgument) {
472 this.shortcut = shortcut;
473 this.optional = optional;
474 this.takesArgument = takesArgument;
477 public int parse(int position, String[] args, Arguments toSet) {
478 boolean match = ("-" + shortcut).equals(args[position])
479 || ("--" + name).equals(args[position]);
483 return doParse(position, args, toSet);
486 protected abstract int doParse(int i, String[] args, Arguments toSet);
488 protected abstract String help();
490 public String toString() {
495 private static final class NoSelfReferencesCommand extends Command {
497 NoSelfReferencesCommand() {
498 super(CMD_NOSELF, "n", true, false);
502 protected int doParse(int i, String[] args, Arguments toSet) {
503 toSet.noSelfReferences = true;
508 protected String help() {
509 return "Hide intra-class calls (i.e. if Foo.bar() calls Foo.baz(), don't include it)";
513 private static final class DisableEightBitStringsCommand extends Command {
515 DisableEightBitStringsCommand() {
516 super(CMD_DISABLE_EIGHT_BIT_STRINGS, "u", true, false);
520 protected int doParse(int i, String[] args, Arguments toSet) {
521 toSet.noSelfReferences = true;
526 protected String help() {
527 return "Disable string memory optimizations - runs faster and supports unicode class names, but may run out of memory";
531 private static final class ReverseCommand extends Command {
534 super(CMD_REVERSE, "r", true, false);
538 protected int doParse(int i, String[] args, Arguments toSet) {
539 toSet.reverse = true;
544 protected String help() {
545 return "Reverse the edges of graph nodes";
549 private static final class AggressiveCommand extends Command {
551 AggressiveCommand() {
552 super(CMD_AGGRESSIVE_MEMORY, "z", true, false);
556 protected int doParse(int i, String[] args, Arguments toSet) {
557 toSet.aggressive = true;
562 protected String help() {
563 return "Aggressively optimize the 8-bit string intern table "
564 + "for large graphs, sacrificing performace for space";
568 private static final class ShortNamesCommand extends Command {
570 ShortNamesCommand() {
571 super(CMD_SIMPLE, "s", true, false);
575 protected int doParse(int i, String[] args, Arguments toSet) {
576 toSet.shortNames = true;
581 protected String help() {
582 return "Use simple class names without the package (may confuse results if two classes have the same name)";
586 private static final class ExtendedPropertiesCommand extends Command {
588 ExtendedPropertiesCommand() {
589 super(CMD_EXTENDED_PROPERTIES, "x", true, false);
593 protected int doParse(int i, String[] args, Arguments toSet) {
599 protected String help() {
600 return "Find all maven projects that are children of the passed folders, and scan their src/main/java subfolders";
604 private static final class AntCommand extends Command {
607 super(CMD_ANT, "a", true, false);
611 protected int doParse(int i, String[] args, Arguments toSet) {
617 protected String help() {
618 return "Find all ant projects that are children of the passed folders, and scan their src/ subfolders";
622 private static final class MavenCommand extends Command {
625 super(CMD_MAVEN, "m", true, false);
629 protected int doParse(int i, String[] args, Arguments toSet) {
635 protected String help() {
636 return "Find all maven projects that are children of the passed folders, and scan their src/main/java subfolders";
640 private static final class GradleCommand extends Command {
643 super(CMD_GRADLE, "g", true, false);
647 protected int doParse(int i, String[] args, Arguments toSet) {
653 protected String help() {
654 return "Find all gradle projects that are children of the passed folders, and scan their src/main/java subfolders";
658 private static final class OmitAbstractCommand extends Command {
660 OmitAbstractCommand() {
661 super(CMD_OMIT_ABSTRACT, "a", true, false);
665 protected int doParse(int i, String[] args, Arguments toSet) {
666 toSet.omitAbstract = true;
671 protected String help() {
672 return "Don't emit calls to abstract methods";
676 private static final class VerboseCommand extends Command {
679 super(CMD_VERBOSE, "v", true, false);
683 protected int doParse(int i, String[] args, Arguments toSet) {
684 toSet.verbose = true;
689 protected String help() {
690 return "Print output about what is being processed";
694 private static final class QuietCommand extends Command {
697 super(CMD_QUIET, "q", true, false);
701 protected int doParse(int i, String[] args, Arguments toSet) {
707 protected String help() {
708 return "Supress writing the graph to the standard output";
712 private static final class OutfileCommand extends Command {
715 super(CMD_METHODGRAPH, "o", true, true);
719 protected int doParse(int index, String[] args, Arguments toSet) {
720 if (args.length == index + 1) {
721 throw new IllegalArgumentException("--outfile or -o present but no output file specified");
723 toSet.outfile = new File(args[index + 1]);
728 protected String help() {
729 return "Set the output file for the method call graph";
733 private static final class ClassGraphFileCommand extends Command {
735 ClassGraphFileCommand() {
736 super(CMD_CLASSGRAPH, "c", true, true);
740 protected int doParse(int index, String[] args, Arguments toSet) {
741 if (args.length == index + 1) {
742 throw new IllegalArgumentException("--outfile or -o present but no output file specified");
744 toSet.classGraphFile = new File(args[index + 1]);
749 protected String help() {
750 return "Set the output file for the class call graph";
754 private static final class PackageGraphFileCommand extends Command {
756 PackageGraphFileCommand() {
757 super(CMD_PACKAGEGRAPH, "p", true, true);
761 protected int doParse(int index, String[] args, Arguments toSet) {
762 if (args.length == index + 1) {
763 throw new IllegalArgumentException("--outfile or -o present but no output file specified");
765 toSet.packageGraphFile = new File(args[index + 1]);
770 protected String help() {
771 return "Set the output file for the package call graph";
775 private static final class ExcludeCommand extends Command {
778 super("exclude", "e", true, true);
782 protected int doParse(int i, String[] args, Arguments toSet) {
783 if (args.length == i + 1) {
784 throw new IllegalArgumentException("--exclude or -e present but no exclusion list present");
786 for (String s : args[i + 1].split(",")) {
787 toSet.exclude.add(s);
793 protected String help() {
794 return "Exclude any relationships where the fully qualified class name starts with any pattern in this comma-delimited list of strings, e.g. -e foo.bar,foo.baz";
798 private static final class IgnoreCommand extends Command {
801 super(CMD_IGNORE, "i", true, true);
805 protected int doParse(int i, String[] args, Arguments toSet) {
806 if (args.length == i + 1) {
807 throw new IllegalArgumentException("--exclude or -e present but no exclusion list present");
809 for (String s : args[i + 1].split(",")) {
816 protected String help() {
817 return "Comma delimited list of folders or subfolders to ignore, absolute or relative to the base directory.";
821 static final class InvalidArgumentsException extends IllegalArgumentException {
823 private final List<String> errors;
825 InvalidArgumentsException(String msg, List<String> errors) {
827 this.errors = errors;
830 public List<String> errors() {
834 public boolean errorContains(String test) { //for tests
835 for (String err : errors) {
836 if (err.contains(test)) {