Misc improvements
authorTim Boudreau <tboudreau@netbeans.org>
Wed, 12 Apr 2017 05:37:43 -0400
changeset 18400c87c223efe6a
parent 18374 04a79821e760
child 18401 74b304e4e503
Misc improvements
callgraph/src/main/java/org/netbeans/lib/callgraph/Arguments.java
callgraph/src/main/java/org/netbeans/lib/callgraph/Callgraph.java
callgraph/src/main/java/org/netbeans/lib/callgraph/CallgraphControl.java
callgraph/src/main/java/org/netbeans/lib/callgraph/Listener.java
callgraph/src/main/java/org/netbeans/lib/callgraph/io/JavaFilesIterator.java
callgraph/src/main/java/org/netbeans/lib/callgraph/javac/ElementFinder.java
callgraph/src/main/java/org/netbeans/lib/callgraph/javac/JavacRunner.java
callgraph/src/main/java/org/netbeans/lib/callgraph/javac/SourceElement.java
callgraph/src/main/java/org/netbeans/lib/callgraph/javac/SourcesInfo.java
callgraph/src/main/java/org/netbeans/lib/callgraph/util/ComparableCharSequence.java
callgraph/src/main/java/org/netbeans/lib/callgraph/util/EightBitStrings.java
callgraph/src/test/java/org/netbeans/lib/callgraph/util/SmallStringTest.java
     1.1 --- a/callgraph/src/main/java/org/netbeans/lib/callgraph/Arguments.java	Sat Sep 03 02:41:36 2016 -0400
     1.2 +++ b/callgraph/src/main/java/org/netbeans/lib/callgraph/Arguments.java	Wed Apr 12 05:37:43 2017 -0400
     1.3 @@ -45,12 +45,17 @@
     1.4  
     1.5  import java.io.File;
     1.6  import java.io.IOException;
     1.7 +import java.util.Arrays;
     1.8  import java.util.Collections;
     1.9  import java.util.HashSet;
    1.10  import java.util.Iterator;
    1.11  import java.util.LinkedList;
    1.12  import java.util.List;
    1.13  import java.util.Set;
    1.14 +import java.util.concurrent.ConcurrentSkipListSet;
    1.15 +import java.util.regex.Matcher;
    1.16 +import java.util.regex.Pattern;
    1.17 +import org.netbeans.lib.callgraph.util.ComparableCharSequence;
    1.18  
    1.19  /**
    1.20   * Parses and validates command-line arguments.
    1.21 @@ -59,8 +64,8 @@
    1.22   */
    1.23  final class Arguments implements CallgraphControl {
    1.24  
    1.25 -    private Set<File> folders = new HashSet<>();
    1.26 -    private static final Command[] commands = new Command[]{
    1.27 +    private Set<File> folders = new ConcurrentSkipListSet<>();
    1.28 +    private static final Command[] COMMANDS = new Command[]{
    1.29          new NoSelfReferencesCommand(),
    1.30          new ShortNamesCommand(),
    1.31          new ExtendedPropertiesCommand(),
    1.32 @@ -74,12 +79,32 @@
    1.33          new ClassGraphFileCommand(),
    1.34          new OmitAbstractCommand(),
    1.35          new DisableEightBitStringsCommand(),
    1.36 -        new OmitAbstractCommand(),
    1.37          new QuietCommand(),
    1.38          new ReverseCommand(),
    1.39          new AggressiveCommand(),
    1.40 -        new VerboseCommand()
    1.41 +        new VerboseCommand(),
    1.42 +        new IgnoreShallowPackagesCommand(),
    1.43 +        new IgnoreAnonymousClassesCommand()
    1.44      };
    1.45 +
    1.46 +    static {
    1.47 +        if (new HashSet<>(Arrays.asList(COMMANDS)).size() != COMMANDS.length) {
    1.48 +            throw new AssertionError("Command list contains duplicates");
    1.49 +        }
    1.50 +        //sanity check
    1.51 +        for (int i = 0; i < COMMANDS.length; i++) {
    1.52 +            Command ca = COMMANDS[i];
    1.53 +            for (int j = i + 1; j < COMMANDS.length; j++) {
    1.54 +                Command cb = COMMANDS[j];
    1.55 +                if (ca.shortcut.equals(cb.shortcut)) {
    1.56 +                    throw new AssertionError("Conflicting shortcut '" + ca.shortcut
    1.57 +                            + "' for " + ca.getClass().getSimpleName() + " and "
    1.58 +                            + cb.getClass().getSimpleName());
    1.59 +                }
    1.60 +            }
    1.61 +        }
    1.62 +    }
    1.63 +    private boolean ignoreShallowPackages = false;
    1.64      private boolean noSelfReferences = false;
    1.65      private boolean shortNames = false;
    1.66      private boolean maven = false;
    1.67 @@ -97,6 +122,8 @@
    1.68      private boolean omitAbstract;
    1.69      private boolean disableEightBitStrings;
    1.70      private boolean reverse;
    1.71 +    final Set<File> ignoreFolders = new HashSet<>();
    1.72 +    private boolean ignoreAnonymousClasses;
    1.73  
    1.74      Arguments(String... args) throws IOException {
    1.75          this(true, args);
    1.76 @@ -107,7 +134,7 @@
    1.77          List<String> errors = new LinkedList<>();
    1.78          for (int i = 0; i < args.length;) {
    1.79              int oldPos = i;
    1.80 -            for (Command c : commands) {
    1.81 +            for (Command c : COMMANDS) {
    1.82                  try {
    1.83                      @SuppressWarnings("LeakingThisInConstructor")
    1.84                      int increment = c.parse(i, args, this);
    1.85 @@ -147,6 +174,9 @@
    1.86                  }
    1.87              }
    1.88          }
    1.89 +        if (verbose && (maven || gradle || ant)) {
    1.90 +            System.err.println("Scanning for projects in " + this.folders());
    1.91 +        }
    1.92          if ((maven && gradle) || (ant && gradle) || (ant && maven)) {
    1.93              errors.add("--maven, --ant and --gradle are mutually exclusive");
    1.94          } else if (maven) {
    1.95 @@ -160,7 +190,9 @@
    1.96          for (String ig : ignore) {
    1.97              File ff = new File(ig);
    1.98              if (ff.exists() && ff.isDirectory()) {
    1.99 +                ignoreFolders.add(ff);
   1.100                  ff = ff.getCanonicalFile();
   1.101 +                ignoreFolders.add(ff);
   1.102                  for (File f : this.folders()) {
   1.103                      File f1 = f.getCanonicalFile();
   1.104                      if (f1.equals(ff)) {
   1.105 @@ -193,46 +225,100 @@
   1.106                  }
   1.107              }
   1.108          }
   1.109 -        if (verbose && !toIgnore.isEmpty()) {
   1.110 -            System.err.println("Ignoring the following projects:");
   1.111 -            for (File ti : toIgnore) {
   1.112 -                System.err.println(" - " + ti.getAbsolutePath());
   1.113 +        if (verbose) {
   1.114 +            if (toIgnore.isEmpty()) {
   1.115 +                System.err.println("Not ignoring any folders");
   1.116 +            } else {
   1.117 +                System.err.println("Ignoring " + toIgnore.size() + " folders due to -i");
   1.118              }
   1.119          }
   1.120 +//        if (verbose && !toIgnore.isEmpty()) {
   1.121 +//            System.err.println("Ignoring the following projects:");
   1.122 +//            for (File ti : toIgnore) {
   1.123 +//                System.err.println(" - " + ti.getAbsolutePath());
   1.124 +//            }
   1.125 +//        }
   1.126          this.folders.removeAll(toIgnore);
   1.127          if (verbose && !this.folders.isEmpty()) {
   1.128              System.err.println("Will scan the following source roots:");
   1.129 -            for (File f : folders()) {
   1.130 -                System.err.println("  " + f.getAbsolutePath());
   1.131 +            StringBuilder sb = new StringBuilder();
   1.132 +
   1.133 +            for (Iterator<File> it = folders().iterator(); it.hasNext();) {
   1.134 +                File f = it.next();
   1.135 +                sb.append(f.getAbsolutePath());
   1.136 +                if (it.hasNext()) {
   1.137 +                    sb.append(", ");
   1.138 +                }
   1.139 +            }
   1.140 +            System.err.println(sb);
   1.141 +            if (maven || gradle || ant) {
   1.142 +                System.err.println("Found " + folders().size() + " source roots.");
   1.143              }
   1.144          }
   1.145  
   1.146          if (packageGraphFile != null) {
   1.147              File parent = packageGraphFile.getParentFile();
   1.148 -            if (!parent.exists() || !parent.isDirectory()) {
   1.149 +            if (parent == null || !parent.exists() || !parent.isDirectory()) {
   1.150                  errors.add("Parent folder for package graph output file does not exist: " + parent);
   1.151              }
   1.152          }
   1.153          if (classGraphFile != null) {
   1.154              File parent = classGraphFile.getParentFile();
   1.155 -            if (!parent.exists() || !parent.isDirectory()) {
   1.156 +            if (parent == null || !parent.exists() || !parent.isDirectory()) {
   1.157                  errors.add("Parent folder for class graph output file does not exist: " + parent);
   1.158              }
   1.159          }
   1.160          if (outfile != null) {
   1.161              File parent = outfile.getParentFile();
   1.162 -            if (!parent.exists() || !parent.isDirectory()) {
   1.163 +            if (parent == null || !parent.exists() || !parent.isDirectory()) {
   1.164                  errors.add("Parent folder for output file does not exist: " + parent);
   1.165              }
   1.166          } else if (abortIfWillNotOutput && quiet && packageGraphFile == null && classGraphFile == null) {
   1.167              errors.add("-q or --quiet specified, but no output file specified - would not produce any output at all");
   1.168          }
   1.169 -        // XXX check if any folders are children of each other?
   1.170 +        // Ensure no folders are nested inside each other
   1.171 +        Set<File> all = new HashSet<>(this.folders);
   1.172 +        int children = 0;
   1.173 +        for (File f1 : all) {
   1.174 +            for (File f2 : all) {
   1.175 +                if (f1 == f2) {
   1.176 +                    continue;
   1.177 +                }
   1.178 +                if (f2.getAbsolutePath().startsWith(f1.getAbsolutePath())) {
   1.179 +                    children++;
   1.180 +                    this.folders.remove(f2);
   1.181 +                }
   1.182 +            }
   1.183 +        }
   1.184 +        if (verbose) {
   1.185 +            System.err.println("Pruned " + children + " due to being children of other folders");
   1.186 +        }
   1.187          if (!errors.isEmpty()) {
   1.188              throw new InvalidArgumentsException(help(errors), errors);
   1.189          }
   1.190      }
   1.191  
   1.192 +    @Override
   1.193 +    public boolean accept(File f) {
   1.194 +        if (ignoreFolders.contains(f)) {
   1.195 +            return false;
   1.196 +        }
   1.197 +        for (File ig : ignoreFolders) {
   1.198 +            if (f.getAbsolutePath().startsWith(ig.getAbsolutePath())) {
   1.199 +                return false;
   1.200 +            }
   1.201 +        }
   1.202 +        return true;
   1.203 +    }
   1.204 +
   1.205 +    public boolean isIgnoreAbstract() {
   1.206 +        return omitAbstract;
   1.207 +    }
   1.208 +
   1.209 +    public boolean isIgnoreAnonymous() {
   1.210 +        return ignoreAnonymousClasses;
   1.211 +    }
   1.212 +
   1.213      private void findMavenSubfolders(List<String> errors) {
   1.214          Set<File> flds = new HashSet<>(this.folders);
   1.215          this.folders.clear();
   1.216 @@ -282,8 +368,15 @@
   1.217                      this.folders.add(sources);
   1.218                  }
   1.219              }
   1.220 -            for (File child : file.listFiles()) {
   1.221 -                recurseSubfoldersLookingForAntProjects(child);
   1.222 +            List<File> kids = Arrays.asList(file.listFiles(File::isDirectory));
   1.223 +            if (kids.size() > 5) {
   1.224 +                for (File child : file.listFiles()) {
   1.225 +                    recurseSubfoldersLookingForAntProjects(child);
   1.226 +                }
   1.227 +            } else {
   1.228 +                kids.parallelStream().forEach((File f) -> {
   1.229 +                    recurseSubfoldersLookingForAntProjects(f);
   1.230 +                });
   1.231              }
   1.232          }
   1.233      }
   1.234 @@ -385,6 +478,7 @@
   1.235          return xprop;
   1.236      }
   1.237  
   1.238 +    @Override
   1.239      public boolean isAggressive() {
   1.240          return aggressive;
   1.241      }
   1.242 @@ -413,9 +507,29 @@
   1.243          return omitAbstract;
   1.244      }
   1.245  
   1.246 -    public boolean isExcluded(String qname) {
   1.247 -        for (String ex : exclude) {
   1.248 -            if (qname.startsWith(ex)) {
   1.249 +    private static final Pattern ANONYMOUS = Pattern.compile(".*?\\$\\.\\d+.*");
   1.250 +
   1.251 +    public boolean isExcluded(CharSequence qname) {
   1.252 +        if (exclude.size() > 0) {
   1.253 +            if (qname instanceof ComparableCharSequence) {
   1.254 +                ComparableCharSequence ccs = (ComparableCharSequence) qname;
   1.255 +                for (String ex : exclude) {
   1.256 +                    if (ccs.startsWith(ex)) {
   1.257 +                        return true;
   1.258 +                    }
   1.259 +                }
   1.260 +            } else {
   1.261 +                String qn = qname.toString();
   1.262 +                for (String ex : exclude) {
   1.263 +                    if (qn.startsWith(ex)) {
   1.264 +                        return true;
   1.265 +                    }
   1.266 +                }
   1.267 +            }
   1.268 +        }
   1.269 +        if (isIgnoreAnonymous()) {
   1.270 +            Matcher m = ANONYMOUS.matcher(qname);
   1.271 +            if (m.find()) {
   1.272                  return true;
   1.273              }
   1.274          }
   1.275 @@ -432,7 +546,7 @@
   1.276                  + "and can output graphs of what methods / classes / packages (or all of the above) call each other\nwithin that"
   1.277                  + "source tree."
   1.278                  + "\n\nUsage:\njava -jar callgraph.jar ");
   1.279 -        for (Command c : commands) {
   1.280 +        for (Command c : COMMANDS) {
   1.281              if (c.optional) {
   1.282                  sb.append('[');
   1.283              }
   1.284 @@ -447,7 +561,7 @@
   1.285          }
   1.286          sb.append("dir1 [dir2 dir3 ...]");
   1.287          sb.append('\n');
   1.288 -        for (Command c : commands) {
   1.289 +        for (Command c : COMMANDS) {
   1.290              sb.append("\n\t");
   1.291              sb.append("--").append(c.name).append(" / -").append(c.shortcut).append(" :\t").append(c.help());
   1.292          }
   1.293 @@ -460,6 +574,11 @@
   1.294          return sb.toString();
   1.295      }
   1.296  
   1.297 +    @Override
   1.298 +    public boolean isIgnoreSinglePackage() {
   1.299 +        return ignoreShallowPackages;
   1.300 +    }
   1.301 +
   1.302      private static abstract class Command {
   1.303  
   1.304          protected final String name;
   1.305 @@ -658,7 +777,7 @@
   1.306      private static final class OmitAbstractCommand extends Command {
   1.307  
   1.308          OmitAbstractCommand() {
   1.309 -            super(CMD_OMIT_ABSTRACT, "a", true, false);
   1.310 +            super(CMD_OMIT_ABSTRACT, "b", true, false);
   1.311          }
   1.312  
   1.313          @Override
   1.314 @@ -691,6 +810,44 @@
   1.315          }
   1.316      }
   1.317  
   1.318 +    private static final class IgnoreShallowPackagesCommand extends Command {
   1.319 +
   1.320 +        IgnoreShallowPackagesCommand() {
   1.321 +            super(CMD_IGNORE_SINGLE_PACKAGE, "h", true, false);
   1.322 +        }
   1.323 +
   1.324 +        @Override
   1.325 +        protected int doParse(int i, String[] args, Arguments toSet) {
   1.326 +            toSet.ignoreShallowPackages = true;
   1.327 +            return 1;
   1.328 +        }
   1.329 +
   1.330 +        @Override
   1.331 +        protected String help() {
   1.332 +            return "Ignore classes in packages just below the default package, "
   1.333 +                    + "a pattern frequently used in demo code.";
   1.334 +        }
   1.335 +    }
   1.336 +
   1.337 +    private static final class IgnoreAnonymousClassesCommand extends Command {
   1.338 +
   1.339 +        IgnoreAnonymousClassesCommand() {
   1.340 +            super(CMD_IGNORE_ANONYMOUS, "y", true, false);
   1.341 +        }
   1.342 +
   1.343 +        @Override
   1.344 +        protected int doParse(int i, String[] args, Arguments toSet) {
   1.345 +            toSet.ignoreAnonymousClasses = true;
   1.346 +            return 1;
   1.347 +        }
   1.348 +
   1.349 +        @Override
   1.350 +        protected String help() {
   1.351 +            return "Ignore classes in packages just below the default package, "
   1.352 +                    + "a pattern frequently used in demo code.";
   1.353 +        }
   1.354 +    }
   1.355 +
   1.356      private static final class QuietCommand extends Command {
   1.357  
   1.358          QuietCommand() {
   1.359 @@ -705,7 +862,7 @@
   1.360  
   1.361          @Override
   1.362          protected String help() {
   1.363 -            return "Supress writing the graph to the standard output";
   1.364 +            return "Ignore anonymous classes, e.g. com.foo.Bar.$4";
   1.365          }
   1.366      }
   1.367  
   1.368 @@ -785,6 +942,7 @@
   1.369              }
   1.370              for (String s : args[i + 1].split(",")) {
   1.371                  toSet.exclude.add(s);
   1.372 +                System.out.println("EXCLUDE '" + s + "'");
   1.373              }
   1.374              return 2;
   1.375          }
   1.376 @@ -807,7 +965,7 @@
   1.377                  throw new IllegalArgumentException("--exclude or -e present but no exclusion list present");
   1.378              }
   1.379              for (String s : args[i + 1].split(",")) {
   1.380 -                toSet.ignore.add(s);
   1.381 +                toSet.ignore.add(s.trim());
   1.382              }
   1.383              return 2;
   1.384          }
     2.1 --- a/callgraph/src/main/java/org/netbeans/lib/callgraph/Callgraph.java	Sat Sep 03 02:41:36 2016 -0400
     2.2 +++ b/callgraph/src/main/java/org/netbeans/lib/callgraph/Callgraph.java	Wed Apr 12 05:37:43 2017 -0400
     2.3 @@ -1,4 +1,4 @@
     2.4 -/* 
     2.5 +/*
     2.6   * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
     2.7   *
     2.8   * Copyright (C) 1997-2016 Oracle and/or its affiliates. All rights reserved.
     2.9 @@ -44,17 +44,7 @@
    2.10  package org.netbeans.lib.callgraph;
    2.11  
    2.12  import org.netbeans.lib.callgraph.Arguments.InvalidArgumentsException;
    2.13 -import static org.netbeans.lib.callgraph.CallgraphControl.CMD_CLASSGRAPH;
    2.14 -import static org.netbeans.lib.callgraph.CallgraphControl.CMD_DISABLE_EIGHT_BIT_STRINGS;
    2.15 -import static org.netbeans.lib.callgraph.CallgraphControl.CMD_EXCLUDE;
    2.16 -import static org.netbeans.lib.callgraph.CallgraphControl.CMD_MAVEN;
    2.17 -import static org.netbeans.lib.callgraph.CallgraphControl.CMD_METHODGRAPH;
    2.18 -import static org.netbeans.lib.callgraph.CallgraphControl.CMD_NOSELF;
    2.19 -import static org.netbeans.lib.callgraph.CallgraphControl.CMD_OMIT_ABSTRACT;
    2.20 -import static org.netbeans.lib.callgraph.CallgraphControl.CMD_PACKAGEGRAPH;
    2.21 -import static org.netbeans.lib.callgraph.CallgraphControl.CMD_QUIET;
    2.22 -import static org.netbeans.lib.callgraph.CallgraphControl.CMD_REVERSE;
    2.23 -import static org.netbeans.lib.callgraph.CallgraphControl.CMD_SIMPLE;
    2.24 +import static org.netbeans.lib.callgraph.CallgraphControl.*;
    2.25  import org.netbeans.lib.callgraph.io.JavaFilesIterator;
    2.26  import org.netbeans.lib.callgraph.javac.JavacRunner;
    2.27  import org.netbeans.lib.callgraph.javac.SourceElement;
    2.28 @@ -64,12 +54,17 @@
    2.29  import java.io.IOException;
    2.30  import java.io.PrintStream;
    2.31  import java.util.ArrayList;
    2.32 +import java.util.Collection;
    2.33  import java.util.Collections;
    2.34  import java.util.HashSet;
    2.35  import java.util.Iterator;
    2.36 +import java.util.LinkedHashSet;
    2.37  import java.util.LinkedList;
    2.38  import java.util.List;
    2.39  import java.util.Set;
    2.40 +import java.util.TreeSet;
    2.41 +import java.util.concurrent.ExecutionException;
    2.42 +import java.util.concurrent.ForkJoinPool;
    2.43  import java.util.concurrent.atomic.AtomicReference;
    2.44  import java.util.function.Consumer;
    2.45  
    2.46 @@ -100,6 +95,20 @@
    2.47  
    2.48      private Callgraph() {
    2.49      }
    2.50 +    
    2.51 +    static final class UH  implements Thread.UncaughtExceptionHandler {
    2.52 +
    2.53 +        @Override
    2.54 +        public void uncaughtException(Thread t, Throwable e) {
    2.55 +            if (e instanceof ExecutionException && e.getCause() != null) {
    2.56 +                e = e.getCause();
    2.57 +            }
    2.58 +            e.printStackTrace(System.err);
    2.59 +            System.err.flush();
    2.60 +            System.exit(1);
    2.61 +        }
    2.62 +        
    2.63 +    }
    2.64  
    2.65      /**
    2.66       * Configure a Callgraph to build.
    2.67 @@ -110,18 +119,26 @@
    2.68          return new Callgraph();
    2.69      }
    2.70  
    2.71 -    public static void main(String[] args) throws IOException {
    2.72 -        CallgraphControl arguments = null;
    2.73 -        try {
    2.74 -            arguments = new Arguments(args);
    2.75 -        } catch (InvalidArgumentsException ex) {
    2.76 -            // this will be a help message describing usage and the invalid
    2.77 -            // arguments
    2.78 -            System.err.println(ex.getMessage());
    2.79 -            System.exit(1);
    2.80 -        }
    2.81 -        assert arguments != null;
    2.82 -        invoke(arguments, arguments.isVerbose() ? new LoggingListener() : null);
    2.83 +    public static void main(String[] args) throws Exception {
    2.84 +        int threads = Runtime.getRuntime().availableProcessors() * 4;
    2.85 +        // Threads will spend most of their time blocked waiting for the
    2.86 +        // I/O controller to shovel data from disk, so we want more threads
    2.87 +        // than we actually have CPUs
    2.88 +        new ForkJoinPool(threads, ForkJoinPool.defaultForkJoinWorkerThreadFactory, new UH(), false)
    2.89 +                .submit(() -> {
    2.90 +                    CallgraphControl arguments = null;
    2.91 +                    try {
    2.92 +                        arguments = new Arguments(args);
    2.93 +                    } catch (InvalidArgumentsException ex) {
    2.94 +                        // this will be a help message describing usage and the invalid
    2.95 +                        // arguments
    2.96 +                        System.err.println(ex.getMessage());
    2.97 +                        System.exit(1);
    2.98 +                    }
    2.99 +                    assert arguments != null;
   2.100 +                    invoke(arguments, arguments.isVerbose() ? new LoggingListener() : null);
   2.101 +                    return null;
   2.102 +                }).get();
   2.103      }
   2.104  
   2.105      /**
   2.106 @@ -131,25 +148,38 @@
   2.107       * @return The list of all methods found, sorted by qname
   2.108       * @throws IOException If i/o fails
   2.109       */
   2.110 -    static List<SourceElement> invoke(CallgraphControl arguments, Listener listener) throws IOException {
   2.111 +    static Collection<SourceElement> invoke(CallgraphControl arguments, Listener listener) throws IOException {
   2.112          SourcesInfo info = new SourcesInfo(arguments.isDisableEightBitStrings(), arguments.isAggressive());
   2.113          // Build an iterable of all Java sources (without collecting them all ahead of time)
   2.114          List<Iterable<File>> iterables = new LinkedList<>();
   2.115          for (File folder : arguments) {
   2.116 -            iterables.add(JavaFilesIterator.iterable(folder));
   2.117 +            iterables.add(JavaFilesIterator.iterable(folder, arguments));
   2.118          }
   2.119          // The thing that will run javac
   2.120 -        JavacRunner runner = new JavacRunner(info, MergeIterator.toIterable(iterables), listener);
   2.121 +        JavacRunner runner = new JavacRunner(info, MergeIterator.toIterable(iterables), listener, arguments.isIgnoreSinglePackage(), arguments.isIgnoreAbstract(), arguments.isIgnoreAnonymous());
   2.122          AtomicReference<File> lastFile = new AtomicReference<>();
   2.123 -        Consumer<File> monitor = lastFile::set;
   2.124 +        int[] count = new int[1];
   2.125 +        Consumer<File> monitor = new Consumer<File>() { //lastFile::set;
   2.126 +
   2.127 +            @Override
   2.128 +            public void accept(File t) {
   2.129 +                lastFile.set(t);
   2.130 +                if (listener != null && count[0] % 100 == 0) {
   2.131 +                    listener.onStep("Scanned " + count[0] + " source files...");
   2.132 +                }
   2.133 +                count[0]++;
   2.134 +            }
   2.135 +        };
   2.136 +
   2.137 +        if (listener != null) {
   2.138 +            listener.onStep("Scan " + arguments.folders().size() + " source roots.");
   2.139 +        }
   2.140          // run javac
   2.141          Set<SourceElement> allElements = runner.go(monitor, lastFile);
   2.142 -
   2.143 -        List<SourceElement> all = new ArrayList<>(allElements);
   2.144 -        // Sort, so textual output is more human-friendly
   2.145 -        Collections.sort(all);
   2.146 +        allElements = new TreeSet<>(allElements);
   2.147 +//        Map<CharSequence, List<Object>> packageLineForCharSequence = new TreeMap<>();
   2.148          // Now write files and print output
   2.149 -        if (!all.isEmpty()) {
   2.150 +        if (!allElements.isEmpty()) {
   2.151              PrintStream outStream = createPrintStreamIfNotNull(arguments.methodGraphFile());
   2.152              PrintStream packageStream = createPrintStreamIfNotNull(arguments.packageGraphFile());
   2.153              PrintStream classStream = createPrintStreamIfNotNull(arguments.classGraphFile());
   2.154 @@ -159,14 +189,14 @@
   2.155              List<Object> clazz = new ArrayList<>(5);
   2.156              CharSequence lastClass = null;
   2.157  
   2.158 -            List<Object> pkg = new ArrayList<>(5);
   2.159 +            Set<Object> pkg = new LinkedHashSet<>(5);
   2.160              CharSequence lastPackage = null;
   2.161 -            
   2.162 +            SourceElement last = null;
   2.163              try {
   2.164                  // Iterate every method
   2.165                  outer:
   2.166 -                for (SourceElement sce : all) {
   2.167 -                    if (arguments.isExcluded(sce.qname().toString())) { // Ignore matches
   2.168 +                for (SourceElement sce : allElements) {
   2.169 +                    if (arguments.isExcluded(sce.qname()) || arguments.isExcluded(sce.typeName())) { // Ignore matches
   2.170                          continue;
   2.171                      }
   2.172                      List<SourceElement> outbounds = new ArrayList<>(arguments.isReverse() ? sce.getInboundReferences() : sce.getOutboundReferences());
   2.173 @@ -176,7 +206,9 @@
   2.174                      CharSequence currClazz = arguments.isShortNames() ? sce.typeName() : info.strings.concat(sce.packageName(), info.strings.DOT, sce.typeName());
   2.175                      if (!currClazz.equals(lastClass)) {
   2.176                          if (classStream != null) {
   2.177 -                            writeLine(clazz, info, emittedClassLines, classStream);
   2.178 +                            if (!arguments.isIgnoreAnonymous() || !arguments.isExcluded(currClazz)) {
   2.179 +                                writeLine(clazz, info, emittedClassLines, classStream);
   2.180 +                            }
   2.181                          }
   2.182                          clazz.clear();
   2.183                          lastClass = currClazz;
   2.184 @@ -188,9 +220,6 @@
   2.185                          }
   2.186                      }
   2.187                      CharSequence currPkg = sce.packageName();
   2.188 -                    if (pkg.isEmpty()) {
   2.189 -                        pkg.add(currPkg);
   2.190 -                    }
   2.191                      if (!currPkg.equals(lastPackage)) {
   2.192                          if (packageStream != null) {
   2.193                              writeLine(pkg, info, emittedPackageLines, packageStream);
   2.194 @@ -198,8 +227,12 @@
   2.195                          lastPackage = currPkg;
   2.196                          pkg.clear();
   2.197                      }
   2.198 +                    if (pkg.isEmpty()) {
   2.199 +                        pkg.add(currPkg);
   2.200 +                    }
   2.201 +                    last = sce;
   2.202                      for (SourceElement outbound : outbounds) {
   2.203 -                        if (arguments.isExcluded(outbound.qname().toString())) { // Ignore matches
   2.204 +                        if (arguments.isExcluded(outbound.qname()) || arguments.isExcluded(outbound.typeName())) { // Ignore matches
   2.205                              continue;
   2.206                          }
   2.207                          // If we are ignoring abstract methods, do that - has no effect on classes
   2.208 @@ -223,7 +256,7 @@
   2.209                          }
   2.210                          // Build the package graph output if necessary
   2.211                          if (packageStream != null) {
   2.212 -                            if (!outbound.packageName().equals(currPkg) || arguments.isSelfReferences()) {
   2.213 +                            if (!outbound.packageName().equals(currPkg) && !pkg.contains(outbound.packageName())) {
   2.214                                  pkg.add(outbound.packageName());
   2.215                              }
   2.216                          }
   2.217 @@ -232,7 +265,6 @@
   2.218                              CharSequence type1 = sce.typeName();
   2.219                              CharSequence type2 = outbound.typeName();
   2.220                              if (!arguments.isShortNames()) {
   2.221 -//                                type1 = info.strings.concat(sce.packageName(), info.strings.DOT, type1);
   2.222                                  type2 = info.strings.concat(outbound.packageName(), info.strings.DOT, type2);
   2.223                              }
   2.224                              if (!type1.equals(type2) && !clazz.contains(type2)) {
   2.225 @@ -275,9 +307,10 @@
   2.226                  }
   2.227              }
   2.228          }
   2.229 -        return all;
   2.230 +        return allElements;
   2.231      }
   2.232 -    private static void writeLine(List<Object> clazz, SourcesInfo info, Set<CharSequence> emittedClassLines, PrintStream classStream) {
   2.233 +
   2.234 +    private static void writeLine(Collection<Object> clazz, SourcesInfo info, Set<CharSequence> emittedClassLines, PrintStream classStream) {
   2.235          if (!clazz.isEmpty()) {
   2.236              CharSequence cs = info.strings.concatQuoted(clazz);
   2.237              if (!emittedClassLines.contains(cs)) {
   2.238 @@ -346,7 +379,7 @@
   2.239       * @return Sorted set of source elements
   2.240       * @throws IOException If i/o fails
   2.241       */
   2.242 -    public List<SourceElement> run() throws IOException {
   2.243 +    public Collection<SourceElement> run() throws IOException {
   2.244          return Callgraph.invoke(build(), listener);
   2.245      }
   2.246  
   2.247 @@ -437,19 +470,20 @@
   2.248  
   2.249          @Override
   2.250          public void onFinish() {
   2.251 -            System.out.println("Done.");
   2.252 +            System.err.println("Done.");
   2.253          }
   2.254  
   2.255          @Override
   2.256 -        public void onStartActivity(String activity, int steps) {
   2.257 -            if (steps > 0) {
   2.258 -                System.out.println(activity + " (" + steps + " steps)");
   2.259 -            }
   2.260 +        public void onStartActivity(CharSequence activity, int steps) {
   2.261 +//            if (steps > 0) {
   2.262 +//            System.err.println(activity + " (" + steps + " steps)");
   2.263 +//            }
   2.264 +            System.err.println(activity);
   2.265          }
   2.266  
   2.267          @Override
   2.268 -        public void onStep(String step) {
   2.269 -            System.out.println("\t" + step);
   2.270 +        public void onStep(CharSequence step) {
   2.271 +            System.err.println("\t" + step);
   2.272          }
   2.273      }
   2.274  }
     3.1 --- a/callgraph/src/main/java/org/netbeans/lib/callgraph/CallgraphControl.java	Sat Sep 03 02:41:36 2016 -0400
     3.2 +++ b/callgraph/src/main/java/org/netbeans/lib/callgraph/CallgraphControl.java	Wed Apr 12 05:37:43 2017 -0400
     3.3 @@ -45,7 +45,7 @@
     3.4  package org.netbeans.lib.callgraph;
     3.5  
     3.6  import java.io.File;
     3.7 -import java.util.Iterator;
     3.8 +import java.io.FileFilter;
     3.9  import java.util.Set;
    3.10  
    3.11  /**
    3.12 @@ -53,7 +53,7 @@
    3.13   *
    3.14   * @author Tim Boudreau
    3.15   */
    3.16 -interface CallgraphControl extends Iterable<File> {
    3.17 +interface CallgraphControl extends Iterable<File>, FileFilter {
    3.18  
    3.19      static final String CMD_NOSELF = "noself";
    3.20      static final String CMD_SIMPLE = "simple";
    3.21 @@ -72,6 +72,8 @@
    3.22      static final String CMD_OMIT_ABSTRACT = "omit_abstract";
    3.23      static final String CMD_DISABLE_EIGHT_BIT_STRINGS = "use_java_strings";
    3.24      static final String CMD_REVERSE = "reverse";
    3.25 +    static final String CMD_IGNORE_SINGLE_PACKAGE = "ignore_shallow_packages";
    3.26 +    static final String CMD_IGNORE_ANONYMOUS = "ignore_anonymous";
    3.27  
    3.28      boolean isDisableEightBitStrings();
    3.29  
    3.30 @@ -97,15 +99,21 @@
    3.31  
    3.32      boolean isShortNames();
    3.33  
    3.34 +    boolean isIgnoreSinglePackage();
    3.35 +
    3.36 +    boolean isIgnoreAbstract();
    3.37 +
    3.38      File methodGraphFile();
    3.39  
    3.40      File packageGraphFile();
    3.41  
    3.42 -    boolean isExcluded(String qname);
    3.43 +    boolean isExcluded(CharSequence qname);
    3.44  
    3.45      boolean isVerbose();
    3.46  
    3.47      boolean isOmitAbstract();
    3.48  
    3.49      boolean isReverse();
    3.50 +
    3.51 +    public boolean isIgnoreAnonymous();
    3.52  }
     4.1 --- a/callgraph/src/main/java/org/netbeans/lib/callgraph/Listener.java	Sat Sep 03 02:41:36 2016 -0400
     4.2 +++ b/callgraph/src/main/java/org/netbeans/lib/callgraph/Listener.java	Wed Apr 12 05:37:43 2017 -0400
     4.3 @@ -56,7 +56,7 @@
     4.4  
     4.5      void onFinish();
     4.6  
     4.7 -    void onStartActivity(String activity, int steps);
     4.8 +    void onStartActivity(CharSequence activity, int steps);
     4.9  
    4.10 -    void onStep(String step);
    4.11 +    void onStep(CharSequence step);
    4.12  }
     5.1 --- a/callgraph/src/main/java/org/netbeans/lib/callgraph/io/JavaFilesIterator.java	Sat Sep 03 02:41:36 2016 -0400
     5.2 +++ b/callgraph/src/main/java/org/netbeans/lib/callgraph/io/JavaFilesIterator.java	Wed Apr 12 05:37:43 2017 -0400
     5.3 @@ -62,18 +62,23 @@
     5.4  
     5.5      private final LinkedList<Iterator<File>> stack = new LinkedList<>();
     5.6      private File next;
     5.7 +    private final FileFilter ignoreFilter;
     5.8  
     5.9 -    public JavaFilesIterator(File root) throws IOException {
    5.10 +    public JavaFilesIterator(File root, FileFilter ignoreFilter) throws IOException {
    5.11 +        if (ignoreFilter == null) {
    5.12 +            throw new IllegalArgumentException("Null filter");
    5.13 +        }
    5.14 +        this.ignoreFilter = ignoreFilter;
    5.15          Iterator<File> base = list(root);
    5.16          stack.push(base);
    5.17      }
    5.18  
    5.19 -    public static Iterable<File> iterable(final File root) {
    5.20 +    public static Iterable<File> iterable(final File root, FileFilter ignoreFilter) {
    5.21          return () -> {
    5.22              try {
    5.23 -                return new JavaFilesIterator(root);
    5.24 +                return new JavaFilesIterator(root, ignoreFilter);
    5.25              } catch (IOException ex) {
    5.26 -                throw new IllegalStateException(root + "");
    5.27 +                throw new IllegalStateException(root + "", ex);
    5.28              }
    5.29          };
    5.30      }
    5.31 @@ -131,9 +136,20 @@
    5.32  
    5.33      @Override
    5.34      public boolean accept(File file) {
    5.35 -        return (file.isDirectory() && !file.getName().startsWith(".")) 
    5.36 -                || ((file.isFile() && file.canRead() && file.getName().endsWith(".java") 
    5.37 -                && !file.getName().equals("package-info.java")));
    5.38 +        if (file == null) {
    5.39 +            return false;
    5.40 +        }
    5.41 +        if (file.getName() == null) {
    5.42 +            return false;
    5.43 +        }
    5.44 +        boolean nonHiddenFolder = file.isDirectory() && !file.getName().startsWith(".");
    5.45 +        boolean isJavaFile = file.isFile() && file.canRead() && file.getName().endsWith(".java") ;
    5.46 +        boolean isNotPackageInfo = !"package-info.java".equals(file.getName());
    5.47 +        return nonHiddenFolder || ((isJavaFile && isNotPackageInfo) && ignoreFilter.accept(file));
    5.48 +//        return (
    5.49 +//                file.isDirectory() && !file.getName().startsWith(".")) 
    5.50 +//                || ((file.isFile() && file.canRead() && file.getName().endsWith(".java") 
    5.51 +//                    && !file.getName().equals("package-info.java")));
    5.52      }
    5.53  
    5.54      @Override
     6.1 --- a/callgraph/src/main/java/org/netbeans/lib/callgraph/javac/ElementFinder.java	Sat Sep 03 02:41:36 2016 -0400
     6.2 +++ b/callgraph/src/main/java/org/netbeans/lib/callgraph/javac/ElementFinder.java	Wed Apr 12 05:37:43 2017 -0400
     6.3 @@ -50,7 +50,9 @@
     6.4  import com.sun.source.util.TreePath;
     6.5  import com.sun.source.util.TreeScanner;
     6.6  import com.sun.source.util.Trees;
     6.7 -import java.lang.reflect.Modifier;
     6.8 +import java.util.regex.Matcher;
     6.9 +import java.util.regex.Pattern;
    6.10 +import javax.lang.model.element.Modifier;
    6.11  import javax.lang.model.element.Element;
    6.12  import javax.lang.model.element.ElementKind;
    6.13  import javax.lang.model.type.TypeMirror;
    6.14 @@ -60,21 +62,29 @@
    6.15   * 
    6.16   * @author Tim Boudreau
    6.17   */
    6.18 -final class ElementFinder extends TreeScanner<Void, SourcesInfo> {
    6.19 +final class ElementFinder extends TreeScanner<SourceElement, SourcesInfo> {
    6.20  
    6.21      private final CompilationUnitTree cc;
    6.22      private final Trees trees;
    6.23 +    private SourceElement last;
    6.24 +    private final boolean ignoreShallow;
    6.25 +    private final boolean ignoreAnonymous;
    6.26 +    private final boolean ignoreAbstract;
    6.27  
    6.28 -    public ElementFinder(CompilationUnitTree cc, Trees trees) {
    6.29 +    public ElementFinder(CompilationUnitTree cc, Trees trees, boolean ignoreShallow, boolean ignoreAbstract, boolean ignoreAnonymous) {
    6.30          this.cc = cc;
    6.31          this.trees = trees;
    6.32 +        this.ignoreShallow = ignoreShallow;
    6.33 +        this.ignoreAbstract = ignoreAbstract;
    6.34 +        this.ignoreAnonymous = ignoreAnonymous;
    6.35      }
    6.36  
    6.37      @Override
    6.38 -    public Void visitMethod(MethodTree tree, SourcesInfo info) {
    6.39 +    public SourceElement visitMethod(MethodTree tree, SourcesInfo info) {
    6.40          String nm = tree.getName().toString();
    6.41 -        addItem(tree, info, SourceElementKind.METHOD, nm);
    6.42 -        return super.visitMethod(tree, info);
    6.43 +        SourceElement last = addItem(tree, info, SourceElementKind.METHOD, nm);
    6.44 +        super.visitMethod(tree, info);
    6.45 +        return this.last = last;
    6.46      }
    6.47  
    6.48      // uncomment to also deal in fields
    6.49 @@ -85,16 +95,36 @@
    6.50  //        addItem(tree, set, SceneObjectKind.FIELD, nm);
    6.51  //        return super.visitVariable(tree, set);
    6.52  //    }
    6.53 -    private void addItem(Tree tree, SourcesInfo info, SourceElementKind kind, String nm) {
    6.54 +//    private static final Pattern ANONYMOUS = Pattern.compile(".*\\$\\d+$");
    6.55 +    private SourceElement addItem(Tree tree, SourcesInfo info, SourceElementKind kind, String nm) {
    6.56          TreePath path = TreePath.getPath(cc, tree);
    6.57          Element el = trees.getElement(path);
    6.58          if (el != null && (el.getKind() == ElementKind.FIELD || el.getKind() == ElementKind.METHOD)) {
    6.59 -            boolean abstrakt = el.getKind() == ElementKind.METHOD && el.getModifiers().contains(Modifier.ABSTRACT);
    6.60 +            boolean abstrakt = el.getKind() == ElementKind.METHOD 
    6.61 +                    && el.getModifiers().contains(Modifier.ABSTRACT);
    6.62 +            if (ignoreAbstract && abstrakt) {
    6.63 +                return null;
    6.64 +            }
    6.65              TypeMirror mirror = el.asType();
    6.66 +            
    6.67 +//            if (ignoreAnonymous && mirror != null) {
    6.68 +//                System.out.println(mirror.toString());
    6.69 +//                Matcher m = ANONYMOUS.matcher(mirror.toString());
    6.70 +//                if (m.find()) {
    6.71 +//                    System.out.println("IGNORE " + mirror.toString());
    6.72 +//                    return null;
    6.73 +//                }
    6.74 +//            }
    6.75 +            
    6.76              String typeStr = mirror != null ? mirror.toString() : "void";
    6.77 +            if (ignoreShallow && typeStr.indexOf('.') == typeStr.lastIndexOf('.')) {
    6.78 +                return null;
    6.79 +            }
    6.80              SourceElement nue = new SourceElement(kind, path, nm, typeStr, info, abstrakt);
    6.81              info.allElements.add(nue);
    6.82              info.elements.put(el, nue);
    6.83 +            return nue;
    6.84          }
    6.85 +        return null;
    6.86      }
    6.87  }
     7.1 --- a/callgraph/src/main/java/org/netbeans/lib/callgraph/javac/JavacRunner.java	Sat Sep 03 02:41:36 2016 -0400
     7.2 +++ b/callgraph/src/main/java/org/netbeans/lib/callgraph/javac/JavacRunner.java	Wed Apr 12 05:37:43 2017 -0400
     7.3 @@ -56,7 +56,9 @@
     7.4  import java.util.LinkedList;
     7.5  import java.util.List;
     7.6  import java.util.Locale;
     7.7 +import java.util.Objects;
     7.8  import java.util.Set;
     7.9 +import java.util.concurrent.atomic.AtomicInteger;
    7.10  import java.util.concurrent.atomic.AtomicReference;
    7.11  import java.util.function.Consumer;
    7.12  import javax.tools.Diagnostic;
    7.13 @@ -79,15 +81,21 @@
    7.14      private final Iterable<? extends File> outdir = Collections.singleton(new File(System.getProperty("java.io.tmpdir")));
    7.15      private final Listener listener;
    7.16      private final SourcesInfo info;
    7.17 +    private final boolean ignoreShallow;
    7.18 +    private final boolean ignoreAbstract;
    7.19 +    private boolean ignoreAnonymous;
    7.20  
    7.21 -    public JavacRunner(SourcesInfo info, Iterable<File> files) {
    7.22 -        this(info, files, new NullListener());
    7.23 +    public JavacRunner(SourcesInfo info, Iterable<File> files, boolean ignoreShallow, boolean ignoreAbstract, boolean ignoreAnonymous) {
    7.24 +        this(info, files, new NullListener(), ignoreShallow, ignoreAbstract, ignoreAnonymous);
    7.25      }
    7.26  
    7.27 -    public JavacRunner(SourcesInfo info, Iterable<File> files, Listener listener) {
    7.28 +    public JavacRunner(SourcesInfo info, Iterable<File> files, Listener listener, boolean ignoreShallow, boolean ignoreAbstract, boolean ignoreAnonymous) {
    7.29          this.files = files;
    7.30          this.listener = listener == null ? new NullListener() : listener;
    7.31          this.info = info;
    7.32 +        this.ignoreAnonymous = ignoreAnonymous;
    7.33 +        this.ignoreShallow = ignoreShallow;
    7.34 +        this.ignoreAbstract = ignoreAbstract;
    7.35      }
    7.36  
    7.37      private Iterable<? extends JavaFileObject> javaFileObjects(StandardJavaFileManager m, Consumer<File> monitor) {
    7.38 @@ -168,15 +176,21 @@
    7.39          }
    7.40      }
    7.41  
    7.42 +    @SuppressWarnings("UseSpecificCatch")
    7.43      private SourcesInfo parse(JavacTaskImpl task, AtomicReference<File> lastFile) throws IOException {
    7.44          listener.onStartActivity("Finding and parsing Java sources", -1);
    7.45          JavacTrees trees = JavacTrees.instance(task.getContext());
    7.46          List<CompilationUnitTree> units = new LinkedList<>();
    7.47          // We need to cache these because calling parse() twice is an error
    7.48 +        listener.onStartActivity("Parsing sources with javac", -1);
    7.49 +        int ct = 0;
    7.50          for (CompilationUnitTree tree : task.parse()) {
    7.51              units.add(tree);
    7.52 +            if (++ct % 1000 == 0) {
    7.53 +                listener.onStep("Parsed " + ct + " sources so far...");
    7.54 +            }
    7.55          }
    7.56 -        listener.onStartActivity("Attributing Java sources", -1);
    7.57 +        listener.onStartActivity("Attributing " + units.size() + " Java sources", -1);
    7.58          try {
    7.59              task.analyze(); // Run attribution - the compiler flags we have set will have it not abort on unresolvable classes
    7.60          } catch (Throwable err) {
    7.61 @@ -186,18 +200,48 @@
    7.62              try {
    7.63                  listener.onStartActivity("Cataloging methods", units.size());
    7.64                  // First pass, find all methods in all classes
    7.65 -                for (CompilationUnitTree tree : units) {
    7.66 -                    listener.onStep(tree.getSourceFile().getName());
    7.67 -                    ElementFinder elementFinder = new ElementFinder(tree, trees);
    7.68 -                    elementFinder.scan(tree, info);
    7.69 -                }
    7.70 +                final AtomicInteger count = new AtomicInteger();
    7.71 +                units.parallelStream().forEach(new Consumer<CompilationUnitTree>() {
    7.72 +                    private final ThreadLocal<CharSequence> pkg = new ThreadLocal<>();
    7.73 +
    7.74 +                    @Override
    7.75 +                    public void accept(CompilationUnitTree tree) {
    7.76 +                        ElementFinder elementFinder = new ElementFinder(tree, trees, ignoreShallow, ignoreAbstract, ignoreAnonymous);
    7.77 +                        SourceElement last = elementFinder.scan(tree, info);
    7.78 +                        if (listener != null && !(listener instanceof NullListener)) {
    7.79 +                            if (last != null && !Objects.equals(last.packageName(), pkg.get())) {
    7.80 +                                pkg.equals(last.packageName());
    7.81 +                                listener.onStep(pkg.get() + " scanned");
    7.82 +                            }
    7.83 +                            if (count.getAndIncrement() % 1000 == 0) {
    7.84 +                                listener.onStep(count.get() + " of " + units.size()
    7.85 +                                        + " classes scanned for methods, "
    7.86 +                                        + info.allElements.size() + " edges found so far...");
    7.87 +                            }
    7.88 +                        }
    7.89 +                    }
    7.90 +                });
    7.91 +
    7.92                  listener.onStartActivity("Finding usages", units.size());
    7.93 +                count.set(0);
    7.94                  // Second pass, run find usages on every method we found
    7.95 -                for (CompilationUnitTree tree : units) {
    7.96 -                    listener.onStep(tree.getSourceFile().getName());
    7.97 +//                for (CompilationUnitTree tree : units) {
    7.98 +////                    listener.onStep(tree.getSourceFile().getName());
    7.99 +//                    UsageFinder usageFinder = new UsageFinder(tree, trees);
   7.100 +//                    usageFinder.scan(tree, info);
   7.101 +//                    if (count++ % 1000 == 0) {
   7.102 +//                        listener.onStep(count + " classes searched for usages, " + info.edgeCount() + " found so far...");
   7.103 +//                    }
   7.104 +//                }
   7.105 +                units.parallelStream().forEach((tree) -> {
   7.106                      UsageFinder usageFinder = new UsageFinder(tree, trees);
   7.107                      usageFinder.scan(tree, info);
   7.108 -                }
   7.109 +                    if (listener != null && !(listener instanceof NullListener)) {
   7.110 +                        if (count.getAndIncrement() % 1000 == 0) {
   7.111 +                            listener.onStep(count.get() + " classes searched for usages, " + info.edgeCount() + " found so far...");
   7.112 +                        }
   7.113 +                    }
   7.114 +                });
   7.115              } finally {
   7.116                  task.finish();
   7.117              }
   7.118 @@ -230,12 +274,12 @@
   7.119          }
   7.120  
   7.121          @Override
   7.122 -        public void onStartActivity(String activity, int steps) {
   7.123 +        public void onStartActivity(CharSequence activity, int steps) {
   7.124  
   7.125          }
   7.126  
   7.127          @Override
   7.128 -        public void onStep(String step) {
   7.129 +        public void onStep(CharSequence step) {
   7.130  
   7.131          }
   7.132      }
     8.1 --- a/callgraph/src/main/java/org/netbeans/lib/callgraph/javac/SourceElement.java	Sat Sep 03 02:41:36 2016 -0400
     8.2 +++ b/callgraph/src/main/java/org/netbeans/lib/callgraph/javac/SourceElement.java	Wed Apr 12 05:37:43 2017 -0400
     8.3 @@ -41,13 +41,13 @@
     8.4   * Version 2 license, then the option applies only if the new code is
     8.5   * made subject to such option by the copyright holder.
     8.6   */
     8.7 -
     8.8  package org.netbeans.lib.callgraph.javac;
     8.9  
    8.10  import com.sun.source.util.TreePath;
    8.11  import java.util.Collections;
    8.12  import java.util.HashSet;
    8.13  import java.util.Set;
    8.14 +import java.util.concurrent.ConcurrentSkipListSet;
    8.15  import org.netbeans.lib.callgraph.util.ComparableCharSequence;
    8.16  
    8.17  /**
    8.18 @@ -59,8 +59,8 @@
    8.19  public final class SourceElement implements Comparable<SourceElement> {
    8.20  
    8.21      private final ComparableCharSequence name;
    8.22 -    private final Set<SourceElement> inbound = new HashSet<>();
    8.23 -    private final Set<SourceElement> outbound = new HashSet<>();
    8.24 +    private volatile Set<SourceElement> inbound;
    8.25 +    private volatile Set<SourceElement> outbound = new HashSet<>();
    8.26      private final SourceElementKind kind;
    8.27      private final ComparableCharSequence type;
    8.28      private final ComparableCharSequence typeName;
    8.29 @@ -79,8 +79,8 @@
    8.30          this.typeName = info.strings.create(info.nameOf(handle));
    8.31          parameters = info.strings.create(info.parametersOf(handle));
    8.32          packageName = info.strings.create(info.packageNameOf(handle));
    8.33 -        qname = info.strings.concat(packageName, info.strings.DOT, typeName(), info.strings.DOT, getName(), parameters());;
    8.34 -        shortName = info.strings.concat(typeName(), info.strings.DOT, getName(), parameters());
    8.35 +        qname = info.strings.concat(packageName, info.strings.DOT, typeName(), info.strings.DOT, name, parameters());;
    8.36 +        shortName = info.strings.concat(typeName(), info.strings.DOT, name, parameters());
    8.37          this.abstrakt = abstrakt;
    8.38      }
    8.39  
    8.40 @@ -93,21 +93,41 @@
    8.41      }
    8.42  
    8.43      public synchronized Set<SourceElement> getOutboundReferences() {
    8.44 -        return Collections.unmodifiableSet(outbound);
    8.45 +        return outbound == null ? EMPTY : Collections.unmodifiableSet(outbound);
    8.46      }
    8.47  
    8.48 +    private static final Set<SourceElement> EMPTY = Collections.emptySet();
    8.49 +
    8.50      public synchronized Set<SourceElement> getInboundReferences() {
    8.51 -        return Collections.unmodifiableSet(inbound);
    8.52 +        return inbound == null ? EMPTY : Collections.unmodifiableSet(inbound);
    8.53      }
    8.54  
    8.55 -    synchronized void addOutboundReference(SourceElement item) {
    8.56 +    void addOutboundReference(SourceElement item) {
    8.57          if (item != this) {
    8.58 +            Set<SourceElement> outbound = this.outbound;
    8.59 +            if (outbound == null) {
    8.60 +                synchronized (this) {
    8.61 +                    outbound = this.outbound;
    8.62 +                    if (outbound == null) {
    8.63 +                        this.outbound = outbound = new ConcurrentSkipListSet<>();
    8.64 +                    }
    8.65 +                }
    8.66 +            }
    8.67              outbound.add(item);
    8.68          }
    8.69      }
    8.70  
    8.71      synchronized void addInboundReference(SourceElement item) {
    8.72          if (item != this) {
    8.73 +            Set<SourceElement> inbound = this.inbound;
    8.74 +            if (inbound == null) {
    8.75 +                synchronized (this) {
    8.76 +                    inbound = this.inbound;
    8.77 +                    if (inbound == null) {
    8.78 +                        this.inbound = inbound = new ConcurrentSkipListSet<>();
    8.79 +                    }
    8.80 +                }
    8.81 +            }
    8.82              inbound.add(item);
    8.83          }
    8.84      }
    8.85 @@ -127,7 +147,7 @@
    8.86      public ComparableCharSequence parameters() {
    8.87          return parameters;
    8.88      }
    8.89 -    
    8.90 +
    8.91      public boolean isAbstract() {
    8.92          return abstrakt;
    8.93      }
    8.94 @@ -160,10 +180,9 @@
    8.95          return qname().equals(other.qname());
    8.96      }
    8.97  
    8.98 -    int hash = 0;
    8.99      @Override
   8.100      public int hashCode() {
   8.101 -        return hash == 0 ? hash = 37 * qname().hashCode() : hash;
   8.102 +        return 37 * qname().hashCode();
   8.103      }
   8.104  
   8.105      @Override
     9.1 --- a/callgraph/src/main/java/org/netbeans/lib/callgraph/javac/SourcesInfo.java	Sat Sep 03 02:41:36 2016 -0400
     9.2 +++ b/callgraph/src/main/java/org/netbeans/lib/callgraph/javac/SourcesInfo.java	Wed Apr 12 05:37:43 2017 -0400
     9.3 @@ -52,13 +52,15 @@
     9.4  import com.sun.source.tree.VariableTree;
     9.5  import com.sun.source.util.TreePath;
     9.6  import java.util.ArrayList;
     9.7 -import java.util.HashMap;
     9.8 -import java.util.HashSet;
     9.9  import java.util.Iterator;
    9.10 +import java.util.LinkedList;
    9.11  import java.util.List;
    9.12  import java.util.Map;
    9.13  import java.util.Set;
    9.14 +import java.util.concurrent.ConcurrentHashMap;
    9.15 +import java.util.concurrent.ConcurrentSkipListSet;
    9.16  import javax.lang.model.element.Element;
    9.17 +import org.netbeans.lib.callgraph.util.ComparableCharSequence;
    9.18  
    9.19  /**
    9.20   * Container for shared state that is gathered during the parse and discarded
    9.21 @@ -68,15 +70,27 @@
    9.22   */
    9.23  public final class SourcesInfo implements AutoCloseable {
    9.24  
    9.25 -    public final Set<SourceElement> allElements = new HashSet<>();
    9.26 -    public final Map<Element, SourceElement> elements = new HashMap<>();
    9.27 +    public final Set<SourceElement> allElements = new ConcurrentSkipListSet<>();
    9.28 +    public final Map<Element, SourceElement> elements = new ConcurrentHashMap<>();
    9.29  
    9.30 -    private final Map<ClassTree, List<ClassTree>> inners = new HashMap<>();
    9.31 +    private final Map<ClassTree, List<ClassTree>> inners = new ConcurrentHashMap<>();
    9.32  
    9.33      public final EightBitStrings strings;
    9.34 +    private final ComparableCharSequence DOLLARS_DOT;
    9.35 +    private final ComparableCharSequence DEFAULT_PACKAGE;
    9.36 +    private final ComparableCharSequence OPEN_PARENS;
    9.37 +    private final ComparableCharSequence COMMA;
    9.38 +    private final ComparableCharSequence CLOSE_PARENS;
    9.39 +    private final ComparableCharSequence OPEN_CLOSE_PARENS;
    9.40  
    9.41      public SourcesInfo(boolean eightBitStringsDisabled, boolean aggressive) {
    9.42          this.strings = new EightBitStrings(eightBitStringsDisabled, aggressive);
    9.43 +        DOLLARS_DOT = strings.create("$.");
    9.44 +        DEFAULT_PACKAGE = strings.create("<defaultPackage>");
    9.45 +        OPEN_PARENS = strings.create("(");
    9.46 +        CLOSE_PARENS = strings.create("}");
    9.47 +        OPEN_CLOSE_PARENS = strings.create("()");
    9.48 +        COMMA = strings.create(",");
    9.49      }
    9.50  
    9.51      // XXX rewrite to not hold references to ClassTree, just names,
    9.52 @@ -92,34 +106,48 @@
    9.53          } while (path != null && !CLASS_TREE_KINDS.contains(path.getLeaf().getKind()));
    9.54          return path;
    9.55      }
    9.56 +    
    9.57 +    int edgeCount() {
    9.58 +        int ct = 0;
    9.59 +        for (SourceElement e : allElements) {
    9.60 +            ct += e.getInboundReferences().size() + e.getOutboundReferences().size();
    9.61 +        }
    9.62 +        return ct;
    9.63 +    }
    9.64  
    9.65 -    String parametersOf(TreePath path) {
    9.66 +    ComparableCharSequence parametersOf(TreePath path) {
    9.67          if (path.getLeaf() instanceof MethodTree) {
    9.68              MethodTree method = (MethodTree) path.getLeaf();
    9.69 -            StringBuilder paramsString = new StringBuilder("(");
    9.70 -            for (Iterator<? extends VariableTree> it = method.getParameters().iterator(); it.hasNext();) {
    9.71 +            List<? extends VariableTree> params = method.getParameters();
    9.72 +            if (params.isEmpty()) {
    9.73 +                return OPEN_CLOSE_PARENS;
    9.74 +            }
    9.75 +            List<CharSequence> l = new ArrayList<>(((params.size() -1) * 2) + 2);
    9.76 +            l.add(OPEN_PARENS);
    9.77 +            for (Iterator<? extends VariableTree> it = params.iterator(); it.hasNext();) {
    9.78                  VariableTree variable = it.next();
    9.79 -                paramsString.append(variable.getType().toString());
    9.80 +                l.add(strings.create(variable.getType().toString()));
    9.81                  if (it.hasNext()) {
    9.82 -                    paramsString.append(',');
    9.83 +                    l.add(COMMA);
    9.84                  }
    9.85              }
    9.86 -            return paramsString.append(")").toString();
    9.87 +            l.add(CLOSE_PARENS);
    9.88 +            return strings.concat(l.toArray(new CharSequence[l.size()]));
    9.89          } else {
    9.90 -            return "";
    9.91 +            return ComparableCharSequence.EMPTY;
    9.92          }
    9.93      }
    9.94  
    9.95 -    String packageNameOf(TreePath path) {
    9.96 +    ComparableCharSequence packageNameOf(TreePath path) {
    9.97          ExpressionTree pkgName = path.getCompilationUnit().getPackageName();
    9.98 -        return pkgName == null ? "<defaultPackage>" : pkgName.toString();
    9.99 +        return pkgName == null ? DEFAULT_PACKAGE : strings.create(pkgName.toString());
   9.100      }
   9.101  
   9.102 -    String nameOf(TreePath tree) {
   9.103 +    ComparableCharSequence nameOf(TreePath tree) {
   9.104          TreePath containingClass = containingClassOf(tree);
   9.105          ClassTree clazz = (ClassTree) containingClass.getLeaf();
   9.106 -        String name = clazz.getSimpleName().toString();
   9.107 -        if (name.isEmpty()) {
   9.108 +        ComparableCharSequence name = strings.create(clazz.getSimpleName());
   9.109 +        if (name.length() == 0) {
   9.110              // Recursively generate $1, $2 names, handling the case of
   9.111              // mutiple nesting
   9.112              name = nameOf(containingClass);
   9.113 @@ -127,11 +155,11 @@
   9.114              ClassTree nestingParent = (ClassTree) nestedIn.getLeaf();
   9.115              List<ClassTree> innerClasses = inners.get(nestingParent);
   9.116              if (innerClasses == null) {
   9.117 -                innerClasses = new ArrayList<>(3);
   9.118 +                innerClasses = new LinkedList<>();
   9.119                  inners.put(nestingParent, innerClasses);
   9.120              }
   9.121              innerClasses.add(clazz);
   9.122 -            name = name + ".$" + innerClasses.size();
   9.123 +            return strings.concat(name, DOLLARS_DOT, Integer.toString(innerClasses.size()));
   9.124          }
   9.125          return name;
   9.126      }
    10.1 --- a/callgraph/src/main/java/org/netbeans/lib/callgraph/util/ComparableCharSequence.java	Sat Sep 03 02:41:36 2016 -0400
    10.2 +++ b/callgraph/src/main/java/org/netbeans/lib/callgraph/util/ComparableCharSequence.java	Wed Apr 12 05:37:43 2017 -0400
    10.3 @@ -10,4 +10,92 @@
    10.4      public default int compareTo(CharSequence o) {
    10.5          return EightBitStrings.compareCharSequences(this, o);
    10.6      }
    10.7 +    
    10.8 +    public default boolean startsWith(CharSequence seq) {
    10.9 +        int myLen = length();
   10.10 +        int seqLength = seq.length();
   10.11 +        if (seqLength > myLen) {
   10.12 +            return false;
   10.13 +        }
   10.14 +        for (int i = seqLength-1; i >= 0; i--) {
   10.15 +            if (charAt(i) != seq.charAt(i)) {
   10.16 +                return false;
   10.17 +            }
   10.18 +        }
   10.19 +        return true;
   10.20 +    }
   10.21 +
   10.22 +    public default int indexOf(char c) {
   10.23 +        int len = length();
   10.24 +        if (len > 0) {
   10.25 +            for (int i = 0; i < len; i++) {
   10.26 +                if (c == charAt(i)) {
   10.27 +                    return i;
   10.28 +                }
   10.29 +            }
   10.30 +        }
   10.31 +        return -1;
   10.32 +    }
   10.33 +
   10.34 +    public default int lastIndexOf(char c) {
   10.35 +        int len = length();
   10.36 +        if (len > 0) {
   10.37 +            for (int i = len - 1; i >= 0; i--) {
   10.38 +                if (c == charAt(i)) {
   10.39 +                    return i;
   10.40 +                }
   10.41 +            }
   10.42 +        }
   10.43 +        return -1;
   10.44 +    }
   10.45 +
   10.46 +    static final ComparableCharSequence EMPTY = new ComparableCharSequence() {
   10.47 +        @Override
   10.48 +        public int compareTo(CharSequence o) {
   10.49 +            return o.length() == 0 ? 0 : -1;
   10.50 +        }
   10.51 +
   10.52 +        @Override
   10.53 +        public int indexOf(char c) {
   10.54 +            return -1;
   10.55 +        }
   10.56 +
   10.57 +        @Override
   10.58 +        public int lastIndexOf(char c) {
   10.59 +            return -1;
   10.60 +        }
   10.61 +
   10.62 +        @Override
   10.63 +        public int length() {
   10.64 +            return 0;
   10.65 +        }
   10.66 +
   10.67 +        @Override
   10.68 +        public char charAt(int index) {
   10.69 +            throw new ArrayIndexOutOfBoundsException("0 length string but"
   10.70 +                    + " requested char " + index);
   10.71 +        }
   10.72 +
   10.73 +        @Override
   10.74 +        public CharSequence subSequence(int start, int end) {
   10.75 +            if (start == 0 && end == 0) {
   10.76 +                return this;
   10.77 +            }
   10.78 +            throw new ArrayIndexOutOfBoundsException("0 length string but"
   10.79 +                    + " requested substring " + start + " -> " + end);
   10.80 +
   10.81 +        }
   10.82 +
   10.83 +        public boolean equals(Object o) {
   10.84 +            return o instanceof CharSequence && ((CharSequence) o).length() == 0;
   10.85 +        }
   10.86 +
   10.87 +        public int hashCode() {
   10.88 +            return 0;
   10.89 +        }
   10.90 +        
   10.91 +        public String toString() {
   10.92 +            return "";
   10.93 +        }
   10.94 +    };
   10.95  }
    11.1 --- a/callgraph/src/main/java/org/netbeans/lib/callgraph/util/EightBitStrings.java	Sat Sep 03 02:41:36 2016 -0400
    11.2 +++ b/callgraph/src/main/java/org/netbeans/lib/callgraph/util/EightBitStrings.java	Wed Apr 12 05:37:43 2017 -0400
    11.3 @@ -49,6 +49,7 @@
    11.4  import java.nio.ByteBuffer;
    11.5  import java.nio.charset.CharacterCodingException;
    11.6  import java.nio.charset.Charset;
    11.7 +import java.nio.charset.CharsetDecoder;
    11.8  import java.util.ArrayList;
    11.9  import java.util.Arrays;
   11.10  import java.util.Collection;
   11.11 @@ -66,6 +67,7 @@
   11.12  public class EightBitStrings {
   11.13  
   11.14      private static final Charset UTF = Charset.forName("UTF-8");
   11.15 +    private static final Charset ASCII = Charset.forName("US-ASCII");
   11.16  
   11.17      private final InternTable INTERN_TABLE = new InternTable();
   11.18      public final CharSequence DOT = create(".");
   11.19 @@ -74,18 +76,23 @@
   11.20      public final CharSequence QUOTE_SPACE = create("\" ");
   11.21      public final CharSequence CLOSE_OPEN_QUOTE = create("\" \"");
   11.22  
   11.23 +    private static final boolean ascii = !Boolean.getBoolean("utf");
   11.24      private final boolean disabled;
   11.25      private final boolean aggressive;
   11.26  
   11.27      public EightBitStrings(boolean disabled) {
   11.28 -        this (disabled, false);
   11.29 +        this(disabled, false);
   11.30      }
   11.31  
   11.32      public EightBitStrings(boolean disabled, boolean aggressive) {
   11.33          this.disabled = disabled;
   11.34          this.aggressive = aggressive;
   11.35      }
   11.36 -    
   11.37 +
   11.38 +    public static Charset charset() {
   11.39 +        return ascii ? ASCII : UTF;
   11.40 +    }
   11.41 +
   11.42      public void clear() {
   11.43          INTERN_TABLE.dispose();
   11.44      }
   11.45 @@ -114,16 +121,19 @@
   11.46          } else if (aggressive) {
   11.47              List<CharSequence> nue = new ArrayList<>(seqs.length + (seqs.length / 2));
   11.48              for (CharSequence seq : seqs) {
   11.49 +                if (seq == ComparableCharSequence.EMPTY) {
   11.50 +                    continue;
   11.51 +                }
   11.52                  int ln = seq.length();
   11.53                  StringBuilder sb = new StringBuilder();
   11.54 -                for(int i=0; i < ln; i++) {
   11.55 +                for (int i = 0; i < ln; i++) {
   11.56                      char c = seq.charAt(i);
   11.57                      if (Character.isLetter(c) || Character.isDigit(c)) {
   11.58                          sb.append(c);
   11.59                      } else {
   11.60                          nue.add(sb.toString());
   11.61 -                        sb = new StringBuilder();
   11.62 -                        nue.add(new String(new char[] { c }));
   11.63 +                        sb.setLength(0);
   11.64 +                        nue.add(new String(new char[]{c}));
   11.65                      }
   11.66                  }
   11.67                  if (sb.length() > 0) {
   11.68 @@ -162,7 +172,7 @@
   11.69                  Object c = it.next();
   11.70                  if (c instanceof CharSequence) {
   11.71                      quoted.add(QUOTE);
   11.72 -                    quoted.add((CharSequence)c);
   11.73 +                    quoted.add((CharSequence) c);
   11.74                      if (it.hasNext()) {
   11.75                          quoted.add(QUOTE_SPACE);
   11.76                      } else {
   11.77 @@ -173,14 +183,18 @@
   11.78                      quoted.add(SPACE);
   11.79                  }
   11.80              }
   11.81 -            return new Concatenation(quoted.toArray(new CharSequence[quoted.size()]));
   11.82 +            Concatenation result = new Concatenation(quoted.toArray(new CharSequence[quoted.size()]));
   11.83 +            if (result.entries.length == 1) {
   11.84 +                return result.entries[0];
   11.85 +            }
   11.86 +            return result;
   11.87          }
   11.88      }
   11.89  
   11.90      private static byte[] toBytes(CharSequence seq) {
   11.91          try {
   11.92              ByteArrayOutputStream out = new ByteArrayOutputStream();
   11.93 -            out.write(seq.toString().getBytes(UTF));
   11.94 +            out.write(seq.toString().getBytes(charset()));
   11.95              return out.toByteArray();
   11.96          } catch (IOException ex) {
   11.97              throw new IllegalArgumentException(ex);
   11.98 @@ -189,7 +203,7 @@
   11.99  
  11.100      private static CharSequence toCharSequence(byte[] bytes) {
  11.101          try {
  11.102 -            return UTF.newDecoder().decode(ByteBuffer.wrap(bytes));
  11.103 +            return charset().newDecoder().decode(ByteBuffer.wrap(bytes));
  11.104          } catch (CharacterCodingException ex) {
  11.105              throw new IllegalArgumentException(ex);
  11.106          }
  11.107 @@ -198,14 +212,14 @@
  11.108      int internTableSize() {
  11.109          return INTERN_TABLE.last + 1;
  11.110      }
  11.111 -    
  11.112 +
  11.113      List<CharSequence> dumpInternTable() {
  11.114          return INTERN_TABLE.dumpInternTable();
  11.115      }
  11.116  
  11.117      static class InternTable {
  11.118  
  11.119 -        private static final int SIZE_INCREMENT = 50;
  11.120 +        private static final int SIZE_INCREMENT = 150;
  11.121  
  11.122          private int last = -1;
  11.123          private Entry[] entries = new Entry[SIZE_INCREMENT];
  11.124 @@ -223,20 +237,28 @@
  11.125              // here.  This is slower than a HashMap (we sort on insert so
  11.126              // we can binary search later), but involves far fewer allocations
  11.127              Entry entry = new Entry(toBytes(seq), (short) seq.length());
  11.128 -            int offset = last == -1 ? -1 : Arrays.binarySearch(entries, 0, last + 1, entry);
  11.129 -            if (offset > 0) {
  11.130 -                return entries[offset];
  11.131 +            synchronized (this) {
  11.132 +                int offset = last == -1 ? -1 : Arrays.binarySearch(entries, 0, last + 1, entry);
  11.133 +                if (offset > 0) {
  11.134 +                    return entries[offset];
  11.135 +                }
  11.136 +                if (last == entries.length - 1) {
  11.137 +                    Entry[] nue = new Entry[entries.length + SIZE_INCREMENT];
  11.138 +                    System.arraycopy(entries, 0, nue, 0, entries.length);
  11.139 +                    entries = nue;
  11.140 +                }
  11.141 +                entries[++last] = entry;
  11.142 +                try {
  11.143 +                    Arrays.sort(entries, 0, last + 1);
  11.144 +                } catch (IllegalArgumentException e) {
  11.145 +                    throw new AssertionError("Broken sorting '" + seq
  11.146 +                            + "' into array for item " + last
  11.147 +                            + ". Full table: " + dumpInternTable(), e);
  11.148 +                }
  11.149              }
  11.150 -            if (last == entries.length - 1) {
  11.151 -                Entry[] nue = new Entry[entries.length + SIZE_INCREMENT];
  11.152 -                System.arraycopy(entries, 0, nue, 0, entries.length);
  11.153 -                entries = nue;
  11.154 -            }
  11.155 -            entries[++last] = entry;
  11.156 -            Arrays.sort(entries, 0, last + 1);
  11.157              return entry;
  11.158          }
  11.159 -        
  11.160 +
  11.161          List<CharSequence> dumpInternTable() {
  11.162              return Arrays.asList(entries);
  11.163          }
  11.164 @@ -254,16 +276,28 @@
  11.165                  this.length = length;
  11.166              }
  11.167  
  11.168 +            int hash = 0;
  11.169 +
  11.170              public int hashCode() {
  11.171 +                if (hash != 0) {
  11.172 +                    return hash;
  11.173 +                }
  11.174                  int h = 0;
  11.175                  if (h == 0 && bytes.length > 0) {
  11.176 -                    CharSequence val = toChars();
  11.177 -                    int max = val.length();
  11.178 -                    for (int i = 0; i < max; i++) {
  11.179 -                        h = 31 * h + val.charAt(i);
  11.180 +                    if (ascii) {
  11.181 +                        int max = bytes.length;
  11.182 +                        for (int i = 0; i < max; i++) {
  11.183 +                            h = 31 * h + ((char) bytes[i]);
  11.184 +                        }
  11.185 +                    } else {
  11.186 +                        CharSequence val = toChars();
  11.187 +                        int max = val.length();
  11.188 +                        for (int i = 0; i < max; i++) {
  11.189 +                            h = 31 * h + val.charAt(i);
  11.190 +                        }
  11.191                      }
  11.192                  }
  11.193 -                return h;
  11.194 +                return hash = h;
  11.195              }
  11.196  
  11.197              @Override
  11.198 @@ -277,6 +311,9 @@
  11.199                      if (other.bytes.length < bytes.length) {
  11.200                          return false;
  11.201                      }
  11.202 +                    // XXX if two strings with different unicode encodings,
  11.203 +                    // such as numeric encoding of ascii chars in one,
  11.204 +                    // will give the wrong answer
  11.205                      return Arrays.equals(bytes, other.bytes);
  11.206                  } else if (o instanceof CharSequence) {
  11.207                      return charSequencesEqual(this, (CharSequence) o);
  11.208 @@ -285,10 +322,7 @@
  11.209                  }
  11.210              }
  11.211  
  11.212 -            public int compare(Entry o) {
  11.213 -                if (o == this) {
  11.214 -                    return 0;
  11.215 -                }
  11.216 +            public int compareChars(Entry o) {
  11.217                  int max = Math.min(bytes.length, o.bytes.length);
  11.218                  for (int i = 0; i < max; i++) {
  11.219                      if (bytes[i] > o.bytes[i]) {
  11.220 @@ -297,6 +331,17 @@
  11.221                          return -1;
  11.222                      }
  11.223                  }
  11.224 +                return 0;
  11.225 +            }
  11.226 +
  11.227 +            public int compare(Entry o) {
  11.228 +                if (o == this) {
  11.229 +                    return 0;
  11.230 +                }
  11.231 +                int result = compareChars(o);
  11.232 +                if (result != 0) {
  11.233 +                    return result;
  11.234 +                }
  11.235                  if (bytes.length == o.bytes.length) {
  11.236                      return 0;
  11.237                  } else if (bytes.length > o.bytes.length) {
  11.238 @@ -308,7 +353,7 @@
  11.239  
  11.240              @Override
  11.241              public String toString() {
  11.242 -                return new String(bytes, UTF);
  11.243 +                return new String(bytes, charset());
  11.244              }
  11.245  
  11.246              @Override
  11.247 @@ -322,11 +367,17 @@
  11.248  
  11.249              @Override
  11.250              public char charAt(int index) {
  11.251 +                if (ascii) {
  11.252 +                    return (char) bytes[index];
  11.253 +                }
  11.254                  return toCharSequence(bytes).charAt(index);
  11.255              }
  11.256  
  11.257              @Override
  11.258              public CharSequence subSequence(int start, int end) {
  11.259 +                if (ascii) {
  11.260 +                    return new String(bytes, start, end - start);
  11.261 +                }
  11.262                  return toCharSequence(bytes).subSequence(start, end);
  11.263              }
  11.264  
  11.265 @@ -347,11 +398,17 @@
  11.266          int aLength = a.length();
  11.267          int bLength = b.length();
  11.268          int max = Math.min(aLength, bLength);
  11.269 -        for (int i = 0; i < max; i++) {
  11.270 -            if (a.charAt(i) > b.charAt(i)) {
  11.271 -                return 1;
  11.272 -            } else if (a.charAt(i) < b.charAt(i)) {
  11.273 -                return -1;
  11.274 +        if (ascii && a instanceof InternTable.Entry && b instanceof InternTable.Entry) {
  11.275 +            InternTable.Entry ae = (InternTable.Entry) a;
  11.276 +            InternTable.Entry be = (InternTable.Entry) b;
  11.277 +            return ae.compare(be);
  11.278 +        } else {
  11.279 +            for (int i = 0; i < max; i++) {
  11.280 +                if (a.charAt(i) > b.charAt(i)) {
  11.281 +                    return 1;
  11.282 +                } else if (a.charAt(i) < b.charAt(i)) {
  11.283 +                    return -1;
  11.284 +                }
  11.285              }
  11.286          }
  11.287          if (aLength == bLength) {
  11.288 @@ -364,6 +421,7 @@
  11.289      }
  11.290  
  11.291      static boolean debug;
  11.292 +
  11.293      class Concatenation implements ComparableCharSequence, Comparable<CharSequence> {
  11.294  
  11.295          private final InternTable.Entry[] entries;
  11.296 @@ -403,7 +461,9 @@
  11.297                      return e.charAt(index);
  11.298                  }
  11.299              }
  11.300 -            throw new IndexOutOfBoundsException(index + " of " + length() + " in " + this + " with entries " + Arrays.asList(entries));
  11.301 +            throw new IndexOutOfBoundsException(index + " of "
  11.302 +                    + length() + " in " + this + " with entries "
  11.303 +                    + Arrays.asList(entries));
  11.304          }
  11.305  
  11.306          @Override
  11.307 @@ -417,13 +477,14 @@
  11.308                  sb.append(e);
  11.309              }
  11.310              if (debug) {
  11.311 -                sb.append(" - ").append (Arrays.asList(entries));
  11.312 +                sb.append(" - ").append(Arrays.asList(entries));
  11.313              }
  11.314              return sb.toString();
  11.315          }
  11.316  
  11.317          private int hash = 0;
  11.318  
  11.319 +        @Override
  11.320          public int hashCode() {
  11.321              int h = hash;
  11.322              if (h == 0 && length() > 0) {
  11.323 @@ -472,11 +533,33 @@
  11.324  
  11.325          @Override
  11.326          public int compareTo(CharSequence o) {
  11.327 +            if (o instanceof Concatenation) {
  11.328 +                Concatenation other = (Concatenation) o;
  11.329 +                int ec = Math.min(entries.length, other.entries.length);
  11.330 +                if (ec > 0) {
  11.331 +                    int res = entries[0].compareChars(other.entries[0]);
  11.332 +                    if (res != 0) {
  11.333 +                        return res;
  11.334 +                    }
  11.335 +                }
  11.336 +            }
  11.337              return compareCharSequences(this, o);
  11.338          }
  11.339      }
  11.340  
  11.341      private static boolean charSequencesEqual(CharSequence a, CharSequence b) {
  11.342 +        if (ascii && a instanceof InternTable.Entry && b instanceof InternTable.Entry) {
  11.343 +            return Arrays.equals(((InternTable.Entry) a).bytes, ((InternTable.Entry) b).bytes);
  11.344 +        }
  11.345 +        if (a instanceof Concatenation && b instanceof Concatenation) {
  11.346 +            Concatenation ca = (Concatenation) a;
  11.347 +            Concatenation cb = (Concatenation) b;
  11.348 +            if (ca.entries.length > 0 && cb.entries.length > 0) {
  11.349 +                if (ca.entries[0].compareChars(cb.entries[0]) != 0) {
  11.350 +                    return false;
  11.351 +                }
  11.352 +            }
  11.353 +        }
  11.354          int maxA = a.length();
  11.355          int maxB = b.length();
  11.356          if (maxA != maxB) {
  11.357 @@ -497,7 +580,7 @@
  11.358          private final String s;
  11.359  
  11.360          public StringWrapper(String s) {
  11.361 -            this.s = s;
  11.362 +            this.s = s.intern();
  11.363          }
  11.364  
  11.365          public int length() {
  11.366 @@ -558,7 +641,10 @@
  11.367              if (!(anObject instanceof CharSequence)) {
  11.368                  return false;
  11.369              }
  11.370 -            return charSequencesEqual(this, (CharSequence) anObject);
  11.371 +            if (anObject instanceof String) {
  11.372 +                return s.equals(anObject);
  11.373 +            }
  11.374 +            return s.contentEquals((CharSequence) anObject);
  11.375          }
  11.376  
  11.377          public boolean contentEquals(StringBuffer sb) {
  11.378 @@ -777,6 +863,5 @@
  11.379          public String intern() {
  11.380              return s.intern();
  11.381          }
  11.382 -
  11.383      }
  11.384  }
    12.1 --- a/callgraph/src/test/java/org/netbeans/lib/callgraph/util/SmallStringTest.java	Sat Sep 03 02:41:36 2016 -0400
    12.2 +++ b/callgraph/src/test/java/org/netbeans/lib/callgraph/util/SmallStringTest.java	Wed Apr 12 05:37:43 2017 -0400
    12.3 @@ -9,7 +9,6 @@
    12.4  import java.util.Set;
    12.5  import org.junit.Test;
    12.6  import static org.junit.Assert.*;
    12.7 -import org.netbeans.lib.callgraph.util.EightBitStrings.Concatenation;
    12.8  
    12.9  /**
   12.10   *
   12.11 @@ -86,7 +85,8 @@
   12.12          for (ComparableCharSequence c1 : new ComparableCharSequence[]{a, b, c, d, e}) {
   12.13              for (ComparableCharSequence c2 : new ComparableCharSequence[]{a, b, c, d, e}) {
   12.14                  assertEquals(c1, c2);
   12.15 -                assertEquals(0, c1.compareTo(c2));
   12.16 +                int comp = c1.compareTo(c2);
   12.17 +                assertEquals("'" + c1 + "/ sort with '" + c2 + "' wrongly sorts to " + comp, 0, comp);
   12.18              }
   12.19          }
   12.20  
   12.21 @@ -94,7 +94,7 @@
   12.22          assertEquals("\"a\" \"b\" \"c\" \"d\"", cs.toString());
   12.23  
   12.24          strings = new EightBitStrings(true);
   12.25 -        a = strings.concat("a", "b", "c", "d");
   12.26 +        a = strings.concat("a", "b", "c", "d", "");
   12.27          b = strings.concat("a", "b", "cd");
   12.28          c = strings.concat("ab", "cd");
   12.29          d = strings.concat("a", "bcd");
   12.30 @@ -112,7 +112,7 @@
   12.31          }
   12.32  
   12.33          strings = new EightBitStrings(false);
   12.34 -        a = strings.concat(strings.create("abc"), strings.create("def"));
   12.35 +        a = strings.concat(strings.create("abc"), strings.create("def"), strings.create(""));
   12.36          b = strings.concat("bcd", strings.create("efg"));
   12.37          c = strings.concat("cde", strings.create("fgh"));
   12.38          d = strings.create("defghi");
   12.39 @@ -121,7 +121,7 @@
   12.40          Collections.sort(l);
   12.41          assertEquals(l, Arrays.asList(a, b, c, d, e));
   12.42      }
   12.43 -    
   12.44 +
   12.45      List<String> stringsOf(List<CharSequence> cs) {
   12.46          List<String> result = new ArrayList<>();
   12.47          for (CharSequence c : cs) {
   12.48 @@ -132,7 +132,7 @@
   12.49          }
   12.50          return result;
   12.51      }
   12.52 -    
   12.53 +
   12.54      @Test
   12.55      public void testAggressive() {
   12.56          EightBitStrings strings = new EightBitStrings(false, true);
   12.57 @@ -159,13 +159,48 @@
   12.58      }
   12.59  
   12.60      @Test
   12.61 +    public void testMoreSorting() {
   12.62 +        EightBitStrings strings = new EightBitStrings(false, true);
   12.63 +        char[] chars = "QWERTYUIOPASDFGHJKLZXCVBNM<>?:}\"!@#$%^&*()_+1234567890qwertyuiopasdfghjklzxcvbnm,./;'[]=-\\|`~ \t".toCharArray();
   12.64 +        Arrays.sort(chars);
   12.65 +        Random r = new Random(5);
   12.66 +        List<String> l = new ArrayList<>();
   12.67 +        for (int i = 1; i < 5; i++) {
   12.68 +            for (int j = 0; j < chars.length - (i + 1); j++) {
   12.69 +                StringBuilder sb = new StringBuilder();
   12.70 +                for (int k = 0; k < i; k++) {
   12.71 +                    if (k == 0) {
   12.72 +                        sb.append(chars[k]);
   12.73 +                    } else {
   12.74 +                        sb.append(chars[r.nextInt(chars.length)]);
   12.75 +                    }
   12.76 +                }
   12.77 +                l.add(sb.toString());
   12.78 +            }
   12.79 +            if (i == 4) {
   12.80 +                l.add("");
   12.81 +            }
   12.82 +        }
   12.83 +        
   12.84 +        List<ComparableCharSequence> ll = new ArrayList<>();
   12.85 +        for (String s : l) {
   12.86 +            ll.add(strings.create(s));
   12.87 +        }
   12.88 +        Collections.sort(l);
   12.89 +        Collections.sort(ll);
   12.90 +        for (int i=0; i < l.size(); i++) {
   12.91 +            assertEquals("At " + i + " bad order:\n" + l + "\n" + ll, l.get(i), ll.get(i).toString());
   12.92 +        }
   12.93 +    }
   12.94 +
   12.95 +    @Test
   12.96      public void testConcatQuoted() {
   12.97          EightBitStrings strings = new EightBitStrings(false);
   12.98          List<Object> l = Arrays.asList(strings.create("hey"), false, 23, strings.create("bar"), "baz");
   12.99          CharSequence cc = strings.concatQuoted(l);
  12.100          assertEquals("\"hey\" false 23 \"bar\" \"baz\"", cc.toString());
  12.101      }
  12.102 -    
  12.103 +
  12.104      private static String randomString(int len) {
  12.105          char[] c = new char[len];
  12.106          for (int i = 0; i < c.length; i++) {