Adding --group option to the Jackpot tool
authorjlahoda
Sun, 11 Oct 2015 22:02:22 +0200
changeset 1013f54d0ef8a0aa
parent 1012 63bfd41aa3e0
child 1014 516c317376b6
Adding --group option to the Jackpot tool
cmdline/tool/nbproject/project.properties
cmdline/tool/src/org/netbeans/modules/jackpot30/cmdline/Main.java
cmdline/tool/test/unit/src/org/netbeans/modules/jackpot30/cmdline/MainTest.java
     1.1 --- a/cmdline/tool/nbproject/project.properties	Sun Sep 20 10:49:35 2015 +0200
     1.2 +++ b/cmdline/tool/nbproject/project.properties	Sun Oct 11 22:02:22 2015 +0200
     1.3 @@ -1,4 +1,4 @@
     1.4 -javac.source=1.6
     1.5 +javac.source=1.7
     1.6  javac.compilerargs=-Xlint -Xlint:-serial
     1.7  cp.extra=../../lib/jopt-simple/jopt-simple-3.2.jar
     1.8  spec.version.base=1.16.0
     2.1 --- a/cmdline/tool/src/org/netbeans/modules/jackpot30/cmdline/Main.java	Sun Sep 20 10:49:35 2015 +0200
     2.2 +++ b/cmdline/tool/src/org/netbeans/modules/jackpot30/cmdline/Main.java	Sun Oct 11 22:02:22 2015 +0200
     2.3 @@ -65,6 +65,7 @@
     2.4  import java.util.concurrent.atomic.AtomicBoolean;
     2.5  import java.util.logging.Level;
     2.6  import java.util.logging.Logger;
     2.7 +import java.util.prefs.AbstractPreferences;
     2.8  import java.util.prefs.BackingStoreException;
     2.9  import java.util.prefs.Preferences;
    2.10  import java.util.regex.Pattern;
    2.11 @@ -82,7 +83,6 @@
    2.12  import org.netbeans.api.java.classpath.GlobalPathRegistry;
    2.13  import org.netbeans.api.java.source.CompilationController;
    2.14  import org.netbeans.api.java.source.ModificationResult;
    2.15 -import org.netbeans.api.project.ui.*;
    2.16  import org.netbeans.core.startup.MainLookup;
    2.17  import org.netbeans.modules.jackpot30.cmdline.lib.Utils;
    2.18  import org.netbeans.modules.jackpot30.ui.settings.XMLHintPreferences;
    2.19 @@ -117,7 +117,6 @@
    2.20  import org.openide.filesystems.FileUtil;
    2.21  import org.openide.util.Exceptions;
    2.22  import org.openide.util.Lookup;
    2.23 -import org.openide.util.NbPreferences;
    2.24  import org.openide.util.RequestProcessor;
    2.25  import org.openide.util.lookup.Lookups;
    2.26  import org.openide.util.lookup.ProxyLookup;
    2.27 @@ -144,16 +143,14 @@
    2.28  
    2.29          OptionParser parser = new OptionParser();
    2.30  //        ArgumentAcceptingOptionSpec<File> projects = parser.accepts("project", "project(s) to refactor").withRequiredArg().withValuesSeparatedBy(File.pathSeparatorChar).ofType(File.class);
    2.31 -        ArgumentAcceptingOptionSpec<File> classpath = parser.accepts("classpath", "classpath").withRequiredArg().withValuesSeparatedBy(File.pathSeparatorChar).ofType(File.class);
    2.32 -        ArgumentAcceptingOptionSpec<File> bootclasspath = parser.accepts("bootclasspath", "bootclasspath").withRequiredArg().withValuesSeparatedBy(File.pathSeparatorChar).ofType(File.class);
    2.33 -        ArgumentAcceptingOptionSpec<File> sourcepath = parser.accepts("sourcepath", "sourcepath").withRequiredArg().withValuesSeparatedBy(File.pathSeparatorChar).ofType(File.class);
    2.34 +        GroupOptions globalGroupOptions = setupGroupParser(parser);
    2.35          ArgumentAcceptingOptionSpec<File> cache = parser.accepts("cache", "a cache directory to store working data").withRequiredArg().ofType(File.class);
    2.36          ArgumentAcceptingOptionSpec<File> out = parser.accepts("out", "output diff").withRequiredArg().ofType(File.class);
    2.37          ArgumentAcceptingOptionSpec<File> configFile = parser.accepts("config-file", "configuration file").withRequiredArg().ofType(File.class);
    2.38          ArgumentAcceptingOptionSpec<String> hint = parser.accepts("hint", "hint name").withRequiredArg().ofType(String.class);
    2.39          ArgumentAcceptingOptionSpec<String> config = parser.accepts("config", "configurations").withRequiredArg().ofType(String.class);
    2.40 -        ArgumentAcceptingOptionSpec<String> source = parser.accepts("source", "source level").withRequiredArg().ofType(String.class).defaultsTo(SOURCE_LEVEL_DEFAULT);
    2.41          ArgumentAcceptingOptionSpec<File> hintFile = parser.accepts("hint-file", "file with rules that should be performed").withRequiredArg().ofType(File.class);
    2.42 +        ArgumentAcceptingOptionSpec<String> group = parser.accepts("group", "specify roots to process alongside with their classpath").withRequiredArg().ofType(String.class);
    2.43  
    2.44          parser.accepts("list", "list all known hints");
    2.45          parser.accepts("progress", "show progress");
    2.46 @@ -196,10 +193,7 @@
    2.47              }
    2.48          }
    2.49  
    2.50 -        ClassPath bootCP = createClassPath(parsed.has(bootclasspath) ? parsed.valuesOf(bootclasspath) : null, createDefaultBootClassPath());
    2.51 -        ClassPath compileCP = createClassPath(parsed.has(classpath) ? parsed.valuesOf(classpath) : null, ClassPath.EMPTY);
    2.52 -        final ClassPath sourceCP = createClassPath(parsed.has(sourcepath) ? parsed.valuesOf(sourcepath) : null, ClassPathSupport.createClassPath(roots.toArray(new FileObject[0])));
    2.53 -        final ClassPath binaryCP = ClassPathSupport.createProxyClassPath(bootCP, compileCP);
    2.54 +        final RootConfiguration globalRootConfiguration = new RootConfiguration(parsed, globalGroupOptions);
    2.55  
    2.56          if (parsed.has("show-gui")) {
    2.57              if (parsed.has(configFile)) {
    2.58 @@ -208,7 +202,7 @@
    2.59                      SwingUtilities.invokeAndWait(new Runnable() {
    2.60                          @Override public void run() {
    2.61                              try {
    2.62 -                                showGUICustomizer(settingsFile, binaryCP, sourceCP);
    2.63 +                                showGUICustomizer(settingsFile, globalRootConfiguration.binaryCP, globalRootConfiguration.sourceCP);
    2.64                              } catch (IOException ex) {
    2.65                                  Exceptions.printStackTrace(ex);
    2.66                              } catch (BackingStoreException ex) {
    2.67 @@ -261,52 +255,36 @@
    2.68              org.netbeans.api.project.ui.OpenProjects.getDefault().getOpenProjects();
    2.69              RepositoryUpdater.getDefault().start(false);
    2.70  
    2.71 -            if (roots.isEmpty() && !parsed.has("list")) {
    2.72 -                System.err.println("no source roots to work on");
    2.73 -                return 1;
    2.74 -            }
    2.75 -
    2.76 -            Iterable<? extends HintDescription> hints;
    2.77 -            
    2.78              if (parsed.has("list")) {
    2.79 -                printHints(sourceCP, binaryCP);
    2.80 +                printHints(globalRootConfiguration.sourceCP, globalRootConfiguration.binaryCP);
    2.81                  return 0;
    2.82              }
    2.83  
    2.84 -            Preferences settingsFromConfigFile;
    2.85 +            int totalGroups = parsed.valuesOf(group).size() + (!globalRootConfiguration.rootFolders.isEmpty() ? 1 : 0);
    2.86 +
    2.87 +            ProgressHandleWrapper progress = parsed.has("progress") ? new ProgressHandleWrapper(new ConsoleProgressHandleAbstraction(), ProgressHandleWrapper.prepareParts(totalGroups)) : new ProgressHandleWrapper(1);
    2.88 +
    2.89              Preferences hintSettingsPreferences;
    2.90 -            HintsSettings hintSettings;
    2.91              boolean apply;
    2.92 +            boolean runDeclarative;
    2.93  
    2.94              if (parsed.has(configFile)) {
    2.95 +                Preferences settingsFromConfigFile;
    2.96                  settingsFromConfigFile = XMLHintPreferences.from(parsed.valueOf(configFile));
    2.97 -                hintSettings = HintsSettings.createPreferencesBasedHintsSettings(hintSettingsPreferences = settingsFromConfigFile.node("settings"), false, null);
    2.98 +                hintSettingsPreferences = settingsFromConfigFile.node("settings");
    2.99                  apply = settingsFromConfigFile.getBoolean("apply", false);
   2.100 -            } else {
   2.101 -                settingsFromConfigFile = null;
   2.102 -                hintSettings = HintsSettings.createPreferencesBasedHintsSettings(hintSettingsPreferences = NbPreferences.root().node("tempSettings"), false, null);
   2.103 -                apply = false;
   2.104 -            }
   2.105 -
   2.106 -            if (parsed.has(hint)) {
   2.107 -                if (settingsFromConfigFile != null) {
   2.108 +                runDeclarative = settingsFromConfigFile.getBoolean("runDeclarative", true);
   2.109 +                if (parsed.has(hint)) {
   2.110                      System.err.println("cannot specify --hint and --config-file together");
   2.111                      return 1;
   2.112 -                }
   2.113 -                hints = findHints(sourceCP, binaryCP, parsed.valueOf(hint), hintSettings);
   2.114 -            } else if (parsed.has(hintFile)) {
   2.115 -                if (settingsFromConfigFile != null) {
   2.116 +                } else if (parsed.has(hintFile)) {
   2.117                      System.err.println("cannot specify --hint-file and --config-file together");
   2.118                      return 1;
   2.119                  }
   2.120 -                FileObject hintFileFO = FileUtil.toFileObject(parsed.valueOf(hintFile));
   2.121 -                assert hintFileFO != null;
   2.122 -                hints = PatternConvertor.create(hintFileFO.asText());
   2.123 -                for (HintDescription hd : hints) {
   2.124 -                    hintSettings.setEnabled(hd.getMetadata(), true);
   2.125 -                }
   2.126              } else {
   2.127 -                hints = readHints(sourceCP, binaryCP, hintSettings, hintSettingsPreferences, settingsFromConfigFile != null ? settingsFromConfigFile.getBoolean("runDeclarative", true) : true);
   2.128 +                hintSettingsPreferences = null;
   2.129 +                apply = false;
   2.130 +                runDeclarative = true;
   2.131              }
   2.132  
   2.133              if (parsed.has(config) && !parsed.has(hint)) {
   2.134 @@ -314,88 +292,35 @@
   2.135                  return 1;
   2.136              }
   2.137  
   2.138 -            if (parsed.has(config)) {
   2.139 -                Iterator<? extends HintDescription> hit = hints.iterator();
   2.140 -                HintDescription hd = hit.next();
   2.141 -
   2.142 -                if (hit.hasNext()) {
   2.143 -                    System.err.println("--config cannot specified when more than one hint is specified");
   2.144 -
   2.145 -                    return 1;
   2.146 -                }
   2.147 -
   2.148 -                Preferences prefs = hintSettings.getHintPreferences(hd.getMetadata());
   2.149 -
   2.150 -                boolean stop = false;
   2.151 -
   2.152 -                for (String c : parsed.valuesOf(config)) {
   2.153 -                    int assign = c.indexOf('=');
   2.154 -
   2.155 -                    if (assign == (-1)) {
   2.156 -                        System.err.println("configuration option is missing '=' (" + c + ")");
   2.157 -                        stop = true;
   2.158 -                        continue;
   2.159 -                    }
   2.160 -
   2.161 -                    prefs.put(c.substring(0, assign), c.substring(assign + 1));
   2.162 -                }
   2.163 -
   2.164 -                if (stop) {
   2.165 -                    return 1;
   2.166 -                }
   2.167 -            }
   2.168 -
   2.169 -            String sourceLevel = parsed.valueOf(source);
   2.170 -
   2.171 -            if (!Pattern.compile(ACCEPTABLE_SOURCE_LEVEL_PATTERN).matcher(sourceLevel).matches()) {
   2.172 -                System.err.println("unrecognized source level specification: " + sourceLevel);
   2.173 -                return 1;
   2.174 -            }
   2.175 -
   2.176              if (parsed.has(OPTION_NO_APPLY)) {
   2.177                  apply = false;
   2.178              } else if (parsed.has(OPTION_APPLY)) {
   2.179                  apply = true;
   2.180              }
   2.181 -            
   2.182 -            if (apply && !hints.iterator().hasNext()) {
   2.183 -                System.err.println("no hints specified");
   2.184 +
   2.185 +            GroupResult result = GroupResult.NOTHING_TO_DO;
   2.186 +
   2.187 +            try (Writer outS = parsed.has(out) ? new BufferedWriter(new OutputStreamWriter(new FileOutputStream(parsed.valueOf(out)))) : null) {
   2.188 +                GlobalConfiguration globalConfig = new GlobalConfiguration(hintSettingsPreferences, apply, runDeclarative, parsed.valueOf(hint), parsed.valueOf(hintFile), outS, parsed.has(OPTION_FAIL_ON_WARNINGS));
   2.189 +
   2.190 +                result = result.join(handleGroup(parsed, globalGroupOptions, progress, globalConfig, parsed.valuesOf(config)));
   2.191 +
   2.192 +                for (String groupValue : parsed.valuesOf(group)) {
   2.193 +                    OptionParser groupParser = new OptionParser();
   2.194 +                    GroupOptions groupOptions = setupGroupParser(groupParser);
   2.195 +                    OptionSet parsedGroup = groupParser.parse(splitGroupArg(groupValue));
   2.196 +                    result = result.join(handleGroup(parsedGroup, groupOptions, progress, globalConfig, parsed.valuesOf(config)));
   2.197 +                }
   2.198 +            }
   2.199 +
   2.200 +            progress.finish();
   2.201 +
   2.202 +            if (result == GroupResult.NOTHING_TO_DO) {
   2.203 +                System.err.println("no source roots to work on");
   2.204                  return 1;
   2.205              }
   2.206  
   2.207 -            Object[] register2Lookup = new Object[] {
   2.208 -                new ClassPathProviderImpl(bootCP, compileCP, sourceCP),
   2.209 -                new JavaPathRecognizer(),
   2.210 -                new SourceLevelQueryImpl(sourceCP, sourceLevel)
   2.211 -            };
   2.212 -
   2.213 -            try {
   2.214 -                for (Object toRegister : register2Lookup) {
   2.215 -                    MainLookup.register(toRegister);
   2.216 -                }
   2.217 -
   2.218 -                ProgressHandleWrapper progress = parsed.has("progress") ? new ProgressHandleWrapper(new ConsoleProgressHandleAbstraction(), 1) : new ProgressHandleWrapper(1);
   2.219 -
   2.220 -                if (apply) {
   2.221 -                    apply(hints, rootFolders.toArray(new Folder[0]), progress, hintSettings, parsed.valueOf(out));
   2.222 -
   2.223 -                    return 0;
   2.224 -                } else {
   2.225 -                    WarningsAndErrors wae = new WarningsAndErrors();
   2.226 -
   2.227 -                    findOccurrences(hints, rootFolders.toArray(new Folder[0]), progress, hintSettings, wae);
   2.228 -
   2.229 -                    if (wae.errors != 0 || (wae.warnings != 0 && parsed.has(OPTION_FAIL_ON_WARNINGS))) {
   2.230 -                        return 1;
   2.231 -                    } else {
   2.232 -                        return 0;
   2.233 -                    }
   2.234 -                }
   2.235 -            } finally {
   2.236 -                for (Object toUnRegister : register2Lookup) {
   2.237 -                    MainLookup.unregister(toUnRegister);
   2.238 -                }
   2.239 -            }
   2.240 +            return result == GroupResult.SUCCESS ? 0 : 1;
   2.241          } catch (Throwable e) {
   2.242              e.printStackTrace();
   2.243              throw new IllegalStateException(e);
   2.244 @@ -411,6 +336,28 @@
   2.245          }
   2.246      }
   2.247  
   2.248 +    private static GroupOptions setupGroupParser(OptionParser parser) {
   2.249 +        return new GroupOptions(parser.accepts("classpath", "classpath").withRequiredArg().withValuesSeparatedBy(File.pathSeparatorChar).ofType(File.class),
   2.250 +                                parser.accepts("bootclasspath", "bootclasspath").withRequiredArg().withValuesSeparatedBy(File.pathSeparatorChar).ofType(File.class),
   2.251 +                                parser.accepts("sourcepath", "sourcepath").withRequiredArg().withValuesSeparatedBy(File.pathSeparatorChar).ofType(File.class),
   2.252 +                                parser.accepts("source", "source level").withRequiredArg().ofType(String.class).defaultsTo(SOURCE_LEVEL_DEFAULT));
   2.253 +    }
   2.254 +
   2.255 +    private static final class GroupOptions {
   2.256 +        private final ArgumentAcceptingOptionSpec<File> classpath;
   2.257 +        private final ArgumentAcceptingOptionSpec<File> bootclasspath;
   2.258 +        private final ArgumentAcceptingOptionSpec<File> sourcepath;
   2.259 +        private final ArgumentAcceptingOptionSpec<String> source;
   2.260 +
   2.261 +        public GroupOptions(ArgumentAcceptingOptionSpec<File> classpath, ArgumentAcceptingOptionSpec<File> bootclasspath, ArgumentAcceptingOptionSpec<File> sourcepath, ArgumentAcceptingOptionSpec<String> source) {
   2.262 +            this.classpath = classpath;
   2.263 +            this.bootclasspath = bootclasspath;
   2.264 +            this.sourcepath = sourcepath;
   2.265 +            this.source = source;
   2.266 +        }
   2.267 +
   2.268 +    }
   2.269 +
   2.270      private static Map<HintMetadata, Collection<? extends HintDescription>> listHints(ClassPath sourceFrom, ClassPath binaryFrom) {
   2.271          Map<HintMetadata, Collection<? extends HintDescription>> result = new HashMap<HintMetadata, Collection<? extends HintDescription>>();
   2.272  
   2.273 @@ -420,6 +367,194 @@
   2.274  
   2.275          return result;
   2.276      }
   2.277 +
   2.278 +    private static GroupResult handleGroup(OptionSet parsed, GroupOptions groupOptions, ProgressHandleWrapper w, GlobalConfiguration globalConfig, List<String> config) throws IOException {
   2.279 +        ProgressHandleWrapper progress = w.startNextPartWithEmbedding(1);
   2.280 +        Iterable<? extends HintDescription> hints;
   2.281 +
   2.282 +        RootConfiguration rootConfiguration = new RootConfiguration(parsed, groupOptions);
   2.283 +
   2.284 +        if (rootConfiguration.rootFolders.isEmpty()) {
   2.285 +            return GroupResult.NOTHING_TO_DO;
   2.286 +        }
   2.287 +
   2.288 +        Preferences settings = globalConfig.configurationPreferences != null ? globalConfig.configurationPreferences : new MemoryPreferences();
   2.289 +        HintsSettings hintSettings = HintsSettings.createPreferencesBasedHintsSettings(settings, false, null);
   2.290 +
   2.291 +        if (globalConfig.hint != null) {
   2.292 +            hints = findHints(rootConfiguration.sourceCP, rootConfiguration.binaryCP, globalConfig.hint, hintSettings);
   2.293 +        } else if (globalConfig.hintFile != null) {
   2.294 +            FileObject hintFileFO = FileUtil.toFileObject(globalConfig.hintFile);
   2.295 +            assert hintFileFO != null;
   2.296 +            hints = PatternConvertor.create(hintFileFO.asText());
   2.297 +            for (HintDescription hd : hints) {
   2.298 +                hintSettings.setEnabled(hd.getMetadata(), true);
   2.299 +            }
   2.300 +        } else {
   2.301 +            hints = readHints(rootConfiguration.sourceCP, rootConfiguration.binaryCP, hintSettings, settings, globalConfig.runDeclarative);
   2.302 +        }
   2.303 +
   2.304 +        if (config != null && !config.isEmpty()) {
   2.305 +            Iterator<? extends HintDescription> hit = hints.iterator();
   2.306 +            HintDescription hd = hit.next();
   2.307 +
   2.308 +            if (hit.hasNext()) {
   2.309 +                System.err.println("--config cannot specified when more than one hint is specified");
   2.310 +
   2.311 +                return GroupResult.FAILURE;
   2.312 +            }
   2.313 +
   2.314 +            Preferences prefs = hintSettings.getHintPreferences(hd.getMetadata());
   2.315 +
   2.316 +            boolean stop = false;
   2.317 +
   2.318 +            for (String c : config) {
   2.319 +                int assign = c.indexOf('=');
   2.320 +
   2.321 +                if (assign == (-1)) {
   2.322 +                    System.err.println("configuration option is missing '=' (" + c + ")");
   2.323 +                    stop = true;
   2.324 +                    continue;
   2.325 +                }
   2.326 +
   2.327 +                prefs.put(c.substring(0, assign), c.substring(assign + 1));
   2.328 +            }
   2.329 +
   2.330 +            if (stop) {
   2.331 +                return GroupResult.FAILURE;
   2.332 +            }
   2.333 +        }
   2.334 +
   2.335 +        String sourceLevel = parsed.valueOf(groupOptions.source);
   2.336 +
   2.337 +        if (!Pattern.compile(ACCEPTABLE_SOURCE_LEVEL_PATTERN).matcher(sourceLevel).matches()) {
   2.338 +            System.err.println("unrecognized source level specification: " + sourceLevel);
   2.339 +            return GroupResult.FAILURE;
   2.340 +        }
   2.341 +
   2.342 +        if (globalConfig.apply && !hints.iterator().hasNext()) {
   2.343 +            System.err.println("no hints specified");
   2.344 +            return GroupResult.FAILURE;
   2.345 +        }
   2.346 +
   2.347 +        Object[] register2Lookup = new Object[] {
   2.348 +            new ClassPathProviderImpl(rootConfiguration.bootCP, rootConfiguration.compileCP, rootConfiguration.sourceCP),
   2.349 +            new JavaPathRecognizer(),
   2.350 +            new SourceLevelQueryImpl(rootConfiguration.sourceCP, sourceLevel)
   2.351 +        };
   2.352 +
   2.353 +        try {
   2.354 +            for (Object toRegister : register2Lookup) {
   2.355 +                MainLookup.register(toRegister);
   2.356 +            }
   2.357 +
   2.358 +            if (globalConfig.apply) {
   2.359 +                apply(hints, rootConfiguration.rootFolders.toArray(new Folder[0]), progress, hintSettings, globalConfig.out);
   2.360 +
   2.361 +                return GroupResult.SUCCESS;
   2.362 +            } else {
   2.363 +                WarningsAndErrors wae = new WarningsAndErrors();
   2.364 +
   2.365 +                findOccurrences(hints, rootConfiguration.rootFolders.toArray(new Folder[0]), progress, hintSettings, wae);
   2.366 +
   2.367 +                if (wae.errors != 0 || (wae.warnings != 0 && globalConfig.failOnWarnings)) {
   2.368 +                    return GroupResult.FAILURE;
   2.369 +                } else {
   2.370 +                    return GroupResult.SUCCESS;
   2.371 +                }
   2.372 +            }
   2.373 +        } finally {
   2.374 +            for (Object toUnRegister : register2Lookup) {
   2.375 +                MainLookup.unregister(toUnRegister);
   2.376 +            }
   2.377 +        }
   2.378 +    }
   2.379 +
   2.380 +    private static class MemoryPreferences extends AbstractPreferences {
   2.381 +
   2.382 +        private final Map<String, String> values = new HashMap<>();
   2.383 +        private final Map<String, MemoryPreferences> nodes = new HashMap<>();
   2.384 +
   2.385 +        public MemoryPreferences() {
   2.386 +            this(null, "");
   2.387 +        }
   2.388 +
   2.389 +        public MemoryPreferences(MemoryPreferences parent, String name) {
   2.390 +            super(parent, name);
   2.391 +        }
   2.392 +        @Override
   2.393 +        protected void putSpi(String key, String value) {
   2.394 +            values.put(key, value);
   2.395 +        }
   2.396 +
   2.397 +        @Override
   2.398 +        protected String getSpi(String key) {
   2.399 +            return values.get(key);
   2.400 +        }
   2.401 +
   2.402 +        @Override
   2.403 +        protected void removeSpi(String key) {
   2.404 +            values.remove(key);
   2.405 +        }
   2.406 +
   2.407 +        @Override
   2.408 +        protected void removeNodeSpi() throws BackingStoreException {
   2.409 +            ((MemoryPreferences) parent()).nodes.remove(name());
   2.410 +        }
   2.411 +
   2.412 +        @Override
   2.413 +        protected String[] keysSpi() throws BackingStoreException {
   2.414 +            return values.keySet().toArray(new String[0]);
   2.415 +        }
   2.416 +
   2.417 +        @Override
   2.418 +        protected String[] childrenNamesSpi() throws BackingStoreException {
   2.419 +            return nodes.keySet().toArray(new String[0]);
   2.420 +        }
   2.421 +
   2.422 +        @Override
   2.423 +        protected AbstractPreferences childSpi(String name) {
   2.424 +            MemoryPreferences result = nodes.get(name);
   2.425 +
   2.426 +            if (result == null) {
   2.427 +                nodes.put(name, result = new MemoryPreferences(this, name));
   2.428 +            }
   2.429 +
   2.430 +            return result;
   2.431 +        }
   2.432 +
   2.433 +        @Override
   2.434 +        protected void syncSpi() throws BackingStoreException {
   2.435 +        }
   2.436 +
   2.437 +        @Override
   2.438 +        protected void flushSpi() throws BackingStoreException {
   2.439 +        }
   2.440 +    }
   2.441 +
   2.442 +    private enum GroupResult {
   2.443 +        NOTHING_TO_DO {
   2.444 +            @Override
   2.445 +            public GroupResult join(GroupResult other) {
   2.446 +                return other;
   2.447 +            }
   2.448 +        },
   2.449 +        SUCCESS {
   2.450 +            @Override
   2.451 +            public GroupResult join(GroupResult other) {
   2.452 +                if (other == FAILURE) return other;
   2.453 +                return this;
   2.454 +            }
   2.455 +        },
   2.456 +        FAILURE {
   2.457 +            @Override
   2.458 +            public GroupResult join(GroupResult other) {
   2.459 +                return this;
   2.460 +            }
   2.461 +        };
   2.462 +
   2.463 +        public abstract GroupResult join(GroupResult other);
   2.464 +    }
   2.465      
   2.466      private static Iterable<? extends HintDescription> findHints(ClassPath sourceFrom, ClassPath binaryFrom, String name, HintsSettings toEnableIn) {
   2.467          List<HintDescription> descs = new LinkedList<HintDescription>();
   2.468 @@ -480,7 +615,7 @@
   2.469          BatchResult occurrences = BatchSearch.findOccurrences(descs, Scopes.specifiedFoldersScope(sourceRoot), w, settings);
   2.470  
   2.471          List<MessageImpl> problems = new LinkedList<MessageImpl>();
   2.472 -        BatchSearch.getVerifiedSpans(occurrences, progress, new VerifiedSpansCallBack() {
   2.473 +        BatchSearch.getVerifiedSpans(occurrences, w, new VerifiedSpansCallBack() {
   2.474              @Override public void groupStarted() {}
   2.475              @Override public boolean spansVerified(CompilationController wc, Resource r, Collection<? extends ErrorDescription> hints) throws Exception {
   2.476                  for (ErrorDescription ed : hints) {
   2.477 @@ -525,7 +660,7 @@
   2.478          System.out.println(b);
   2.479      }
   2.480  
   2.481 -    private static void apply(Iterable<? extends HintDescription> descs, Folder[] sourceRoot, ProgressHandleWrapper progress, HintsSettings settings, File out) throws IOException {
   2.482 +    private static void apply(Iterable<? extends HintDescription> descs, Folder[] sourceRoot, ProgressHandleWrapper progress, HintsSettings settings, Writer out) throws IOException {
   2.483          ProgressHandleWrapper w = progress.startNextPartWithEmbedding(1, 1);
   2.484          BatchResult occurrences = BatchSearch.findOccurrences(descs, Scopes.specifiedFoldersScope(sourceRoot), w, settings);
   2.485  
   2.486 @@ -533,20 +668,8 @@
   2.487          Collection<ModificationResult> diffs = BatchUtilities.applyFixes(occurrences, w, new AtomicBoolean(), problems);
   2.488  
   2.489          if (out != null) {
   2.490 -            Writer outS = null;
   2.491 -
   2.492 -            try {
   2.493 -                outS = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(out)));
   2.494 -
   2.495 -                for (ModificationResult mr : diffs) {
   2.496 -                    org.netbeans.modules.jackpot30.indexing.batch.BatchUtilities.exportDiff(mr, null, outS);
   2.497 -                }
   2.498 -            } finally {
   2.499 -                try {
   2.500 -                    outS.close();
   2.501 -                } catch (IOException ex) {
   2.502 -                    Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
   2.503 -                }
   2.504 +            for (ModificationResult mr : diffs) {
   2.505 +                org.netbeans.modules.jackpot30.indexing.batch.BatchUtilities.exportDiff(mr, null, out);
   2.506              }
   2.507          } else {
   2.508              for (ModificationResult mr : diffs) {
   2.509 @@ -642,11 +765,92 @@
   2.510          }
   2.511      }
   2.512  
   2.513 +    static String[] splitGroupArg(String arg) {
   2.514 +        List<String> result = new ArrayList<>();
   2.515 +        StringBuilder currentPart = new StringBuilder();
   2.516 +
   2.517 +        for (int i = 0; i < arg.length(); i++) {
   2.518 +            switch (arg.charAt(i)) {
   2.519 +                case '\\':
   2.520 +                    if (++i < arg.length()) {
   2.521 +                        currentPart.append(arg.charAt(i));
   2.522 +                    }
   2.523 +                    break;
   2.524 +                case ' ':
   2.525 +                    if (currentPart.length() > 0) {
   2.526 +                        result.add(currentPart.toString());
   2.527 +                        currentPart.delete(0, currentPart.length());
   2.528 +                    }
   2.529 +                    break;
   2.530 +                default:
   2.531 +                    currentPart.append(arg.charAt(i));
   2.532 +                    break;
   2.533 +            }
   2.534 +        }
   2.535 +
   2.536 +        if (currentPart.length() > 0) {
   2.537 +            result.add(currentPart.toString());
   2.538 +        }
   2.539 +
   2.540 +        return result.toArray(new String[0]);
   2.541 +    }
   2.542 +
   2.543      private static final class WarningsAndErrors {
   2.544          private int warnings;
   2.545          private int errors;
   2.546      }
   2.547  
   2.548 +    private static final class RootConfiguration {
   2.549 +        private final List<Folder> rootFolders;
   2.550 +        private final ClassPath bootCP;
   2.551 +        private final ClassPath compileCP;
   2.552 +        private final ClassPath sourceCP;
   2.553 +        private final ClassPath binaryCP;
   2.554 +
   2.555 +        public RootConfiguration(OptionSet parsed, GroupOptions groupOptions) throws IOException {
   2.556 +            this.rootFolders = new ArrayList<>();
   2.557 +
   2.558 +            List<FileObject> roots = new ArrayList<>();
   2.559 +
   2.560 +            for (String sr : parsed.nonOptionArguments()) {
   2.561 +                File r = new File(sr);
   2.562 +                FileObject root = FileUtil.toFileObject(r);
   2.563 +
   2.564 +                if (root != null) {
   2.565 +                    roots.add(root);
   2.566 +                    rootFolders.add(new Folder(root));
   2.567 +                }
   2.568 +            }
   2.569 +
   2.570 +            this.bootCP = createClassPath(parsed.has(groupOptions.bootclasspath) ? parsed.valuesOf(groupOptions.bootclasspath) : null, createDefaultBootClassPath());
   2.571 +            this.compileCP = createClassPath(parsed.has(groupOptions.classpath) ? parsed.valuesOf(groupOptions.classpath) : null, ClassPath.EMPTY);
   2.572 +            this.sourceCP = createClassPath(parsed.has(groupOptions.sourcepath) ? parsed.valuesOf(groupOptions.sourcepath) : null, ClassPathSupport.createClassPath(roots.toArray(new FileObject[0])));
   2.573 +            this.binaryCP = ClassPathSupport.createProxyClassPath(bootCP, compileCP);
   2.574 +        }
   2.575 +
   2.576 +    }
   2.577 +
   2.578 +    private static final class GlobalConfiguration {
   2.579 +        private final Preferences configurationPreferences;
   2.580 +        private final boolean apply;
   2.581 +        private final boolean runDeclarative;
   2.582 +        private final String hint;
   2.583 +        private final File hintFile;
   2.584 +        private final Writer out;
   2.585 +        private final boolean failOnWarnings;
   2.586 +
   2.587 +        public GlobalConfiguration(Preferences configurationPreferences, boolean apply, boolean runDeclarative, String hint, File hintFile, Writer out, boolean failOnWarnings) {
   2.588 +            this.configurationPreferences = configurationPreferences;
   2.589 +            this.apply = apply;
   2.590 +            this.runDeclarative = runDeclarative;
   2.591 +            this.hint = hint;
   2.592 +            this.hintFile = hintFile;
   2.593 +            this.out = out;
   2.594 +            this.failOnWarnings = failOnWarnings;
   2.595 +        }
   2.596 +
   2.597 +    }
   2.598 +
   2.599      @ServiceProvider(service=Lookup.class)
   2.600      public static final class LookupProviderImpl extends ProxyLookup {
   2.601  
     3.1 --- a/cmdline/tool/test/unit/src/org/netbeans/modules/jackpot30/cmdline/MainTest.java	Sun Sep 20 10:49:35 2015 +0200
     3.2 +++ b/cmdline/tool/test/unit/src/org/netbeans/modules/jackpot30/cmdline/MainTest.java	Sun Oct 11 22:02:22 2015 +0200
     3.3 @@ -427,6 +427,48 @@
     3.4                        "--fail-on-warnings");
     3.5      }
     3.6  
     3.7 +    public void testGroups() throws Exception {
     3.8 +        doRunCompiler(null,
     3.9 +                      "${workdir}/src1/test/Test.java:4: warning: [test] test\n" +
    3.10 +                      "        boolean b1 = c.size() == 0;\n" +
    3.11 +                      "                     ^\n" +
    3.12 +                      "${workdir}/src2/test/Test.java:5: warning: [test] test\n" +
    3.13 +                      "        boolean b2 = c.size() != 0;\n" +
    3.14 +                      "                     ^\n",
    3.15 +                      null,
    3.16 +                      "cp1/META-INF/upgrade/test.hint",
    3.17 +                      "$coll.size() == 0 :: $coll instanceof java.util.Collection;;",
    3.18 +                      "src1/test/Test.java",
    3.19 +                      "package test;\n" +
    3.20 +                      "public class Test {\n" +
    3.21 +                      "    private void test(java.util.Collection c) {\n" +
    3.22 +                      "        boolean b1 = c.size() == 0;\n" +
    3.23 +                      "        boolean b2 = c.size() != 0;\n" +
    3.24 +                      "    }\n" +
    3.25 +                      "}\n",
    3.26 +                      "cp2/META-INF/upgrade/test.hint",
    3.27 +                      "$coll.size() != 0 :: $coll instanceof java.util.Collection;;",
    3.28 +                      "src2/test/Test.java",
    3.29 +                      "package test;\n" +
    3.30 +                      "public class Test {\n" +
    3.31 +                      "    private void test(java.util.Collection c) {\n" +
    3.32 +                      "        boolean b1 = c.size() == 0;\n" +
    3.33 +                      "        boolean b2 = c.size() != 0;\n" +
    3.34 +                      "    }\n" +
    3.35 +                      "}\n",
    3.36 +                      null,
    3.37 +                      DONT_APPEND_PATH,
    3.38 +                      "--group",
    3.39 +                      "--classpath ${workdir}/cp1 ${workdir}/src1",
    3.40 +                      "--group",
    3.41 +                      "--classpath ${workdir}/cp2 ${workdir}/src2");
    3.42 +    }
    3.43 +
    3.44 +    public void testGroupsParamEscape() throws Exception {
    3.45 +        assertEquals(Arrays.asList("a b", "a\\b"),
    3.46 +                     Arrays.asList(Main.splitGroupArg("a\\ b a\\\\b")));
    3.47 +    }
    3.48 +
    3.49      private static final String DONT_APPEND_PATH = new String("DONT_APPEND_PATH");
    3.50  
    3.51      private void doRunCompiler(String golden, String stdOut, String stdErr, String... fileContentAndExtraOptions) throws Exception {
    3.52 @@ -482,7 +524,9 @@
    3.53  
    3.54          reallyRunCompiler(wd, exitcode, output, options.toArray(new String[0]));
    3.55  
    3.56 -        assertEquals(golden, TestUtilities.copyFileToString(source));
    3.57 +        if (golden != null) {
    3.58 +            assertEquals(golden, TestUtilities.copyFileToString(source));
    3.59 +        }
    3.60  
    3.61          if (stdOut != null) {
    3.62              assertEquals(stdOut, output[0].replaceAll(Pattern.quote(wd.getAbsolutePath()), Matcher.quoteReplacement("${workdir}")));