cmdline/tool/src/org/netbeans/modules/jackpot30/cmdline/Main.java
author jlahoda
Sun, 12 Feb 2017 10:11:52 +0100
changeset 1041 b03a880d538e
parent 1016 02ad9fe4588c
permissions -rw-r--r--
Adding ability to run (custom) tests.
     1 /*
     2  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
     3  *
     4  * Copyright 2010-2011 Sun Microsystems, Inc. All rights reserved.
     5  *
     6  * The contents of this file are subject to the terms of either the GNU
     7  * General Public License Version 2 only ("GPL") or the Common
     8  * Development and Distribution License("CDDL") (collectively, the
     9  * "License"). You may not use this file except in compliance with the
    10  * License. You can obtain a copy of the License at
    11  * http://www.netbeans.org/cddl-gplv2.html
    12  * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
    13  * specific language governing permissions and limitations under the
    14  * License.  When distributing the software, include this License Header
    15  * Notice in each file and include the License file at
    16  * nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
    17  * particular file as subject to the "Classpath" exception as provided
    18  * by Sun in the GPL Version 2 section of the License file that
    19  * accompanied this code. If applicable, add the following below the
    20  * License Header, with the fields enclosed by brackets [] replaced by
    21  * your own identifying information:
    22  * "Portions Copyrighted [year] [name of copyright owner]"
    23  *
    24  * If you wish your version of this file to be governed by only the CDDL
    25  * or only the GPL Version 2, indicate your decision by adding
    26  * "[Contributor] elects to include this software in this distribution
    27  * under the [CDDL or GPL Version 2] license." If you do not indicate a
    28  * single choice of license, a recipient has the option to distribute
    29  * your version of this file under either the CDDL, the GPL Version 2 or
    30  * to extend the choice of license to its licensees as provided above.
    31  * However, if you add GPL Version 2 code and therefore, elected the GPL
    32  * Version 2 license, then the option applies only if the new code is
    33  * made subject to such option by the copyright holder.
    34  *
    35  * Contributor(s):
    36  *
    37  * Portions Copyrighted 2010-2011 Sun Microsystems, Inc.
    38  */
    39 
    40 package org.netbeans.modules.jackpot30.cmdline;
    41 
    42 import java.awt.BorderLayout;
    43 import java.awt.event.ActionEvent;
    44 import java.awt.event.ActionListener;
    45 import java.io.BufferedWriter;
    46 import java.io.File;
    47 import java.io.FileOutputStream;
    48 import java.io.IOException;
    49 import java.io.OutputStreamWriter;
    50 import java.io.PrintStream;
    51 import java.io.Writer;
    52 import java.lang.reflect.InvocationTargetException;
    53 import java.net.URL;
    54 import java.util.ArrayList;
    55 import java.util.Arrays;
    56 import java.util.Collection;
    57 import java.util.Collections;
    58 import java.util.HashMap;
    59 import java.util.HashSet;
    60 import java.util.Iterator;
    61 import java.util.LinkedList;
    62 import java.util.List;
    63 import java.util.Map;
    64 import java.util.Map.Entry;
    65 import java.util.Set;
    66 import java.util.TreeSet;
    67 import java.util.concurrent.atomic.AtomicBoolean;
    68 import java.util.logging.Level;
    69 import java.util.logging.Logger;
    70 import java.util.prefs.AbstractPreferences;
    71 import java.util.prefs.BackingStoreException;
    72 import java.util.prefs.Preferences;
    73 import java.util.regex.Pattern;
    74 import javax.swing.JCheckBox;
    75 import javax.swing.JDialog;
    76 import javax.swing.JOptionPane;
    77 import javax.swing.JPanel;
    78 import javax.swing.SwingUtilities;
    79 import javax.swing.event.ChangeListener;
    80 import joptsimple.ArgumentAcceptingOptionSpec;
    81 import joptsimple.OptionException;
    82 import joptsimple.OptionParser;
    83 import joptsimple.OptionSet;
    84 import org.netbeans.api.java.classpath.ClassPath;
    85 import org.netbeans.api.java.classpath.GlobalPathRegistry;
    86 import org.netbeans.api.java.source.CompilationController;
    87 import org.netbeans.api.java.source.ModificationResult;
    88 import org.netbeans.core.startup.MainLookup;
    89 import org.netbeans.modules.jackpot30.cmdline.lib.Utils;
    90 import org.netbeans.modules.jackpot30.ui.settings.XMLHintPreferences;
    91 import org.netbeans.modules.java.hints.declarative.DeclarativeHintRegistry;
    92 import org.netbeans.modules.java.hints.declarative.test.TestParser;
    93 import org.netbeans.modules.java.hints.declarative.test.TestParser.TestCase;
    94 import org.netbeans.modules.java.hints.declarative.test.TestPerformer;
    95 import org.netbeans.modules.java.hints.jackpot.spi.PatternConvertor;
    96 import org.netbeans.modules.java.hints.providers.spi.HintDescription;
    97 import org.netbeans.modules.java.hints.providers.spi.HintMetadata;
    98 import org.netbeans.modules.java.hints.spiimpl.MessageImpl;
    99 import org.netbeans.modules.java.hints.spiimpl.RulesManager;
   100 import org.netbeans.modules.java.hints.spiimpl.batch.BatchSearch;
   101 import org.netbeans.modules.java.hints.spiimpl.batch.BatchSearch.BatchResult;
   102 import org.netbeans.modules.java.hints.spiimpl.batch.BatchSearch.Folder;
   103 import org.netbeans.modules.java.hints.spiimpl.batch.BatchSearch.Resource;
   104 import org.netbeans.modules.java.hints.spiimpl.batch.BatchSearch.VerifiedSpansCallBack;
   105 import org.netbeans.modules.java.hints.spiimpl.batch.BatchUtilities;
   106 import org.netbeans.modules.java.hints.spiimpl.batch.ProgressHandleWrapper;
   107 import org.netbeans.modules.java.hints.spiimpl.batch.ProgressHandleWrapper.ProgressHandleAbstraction;
   108 import org.netbeans.modules.java.hints.spiimpl.batch.Scopes;
   109 import org.netbeans.modules.java.hints.spiimpl.options.HintsPanel;
   110 import org.netbeans.modules.java.hints.spiimpl.options.HintsSettings;
   111 import org.netbeans.modules.java.hints.spiimpl.refactoring.Utilities.ClassPathBasedHintWrapper;
   112 import org.netbeans.modules.java.source.parsing.JavaPathRecognizer;
   113 import org.netbeans.modules.parsing.impl.indexing.CacheFolder;
   114 import org.netbeans.modules.parsing.impl.indexing.RepositoryUpdater;
   115 import org.netbeans.spi.editor.hints.ErrorDescription;
   116 import org.netbeans.spi.editor.hints.ErrorDescriptionFactory;
   117 import org.netbeans.spi.editor.hints.Fix;
   118 import org.netbeans.spi.editor.hints.Severity;
   119 import org.netbeans.spi.java.classpath.ClassPathProvider;
   120 import org.netbeans.spi.java.classpath.support.ClassPathSupport;
   121 import org.netbeans.spi.java.hints.Hint.Kind;
   122 import org.netbeans.spi.java.queries.SourceLevelQueryImplementation2;
   123 import org.openide.filesystems.FileObject;
   124 import org.openide.filesystems.FileStateInvalidException;
   125 import org.openide.filesystems.FileUtil;
   126 import org.openide.util.Exceptions;
   127 import org.openide.util.Lookup;
   128 import org.openide.util.Pair;
   129 import org.openide.util.RequestProcessor;
   130 import org.openide.util.lookup.Lookups;
   131 import org.openide.util.lookup.ProxyLookup;
   132 import org.openide.util.lookup.ServiceProvider;
   133 
   134 /**
   135  *
   136  * @author lahvac
   137  */
   138 public class Main {
   139 
   140     private static final String OPTION_APPLY = "apply";
   141     private static final String OPTION_NO_APPLY = "no-apply";
   142     private static final String OPTION_FAIL_ON_WARNINGS = "fail-on-warnings";
   143     private static final String RUN_TESTS = "run-tests";
   144     private static final String SOURCE_LEVEL_DEFAULT = "1.7";
   145     private static final String ACCEPTABLE_SOURCE_LEVEL_PATTERN = "(1\\.)?[2-9][0-9]*";
   146     
   147     public static void main(String... args) throws IOException, ClassNotFoundException {
   148         System.exit(compile(args));
   149     }
   150 
   151     public static int compile(String... args) throws IOException, ClassNotFoundException {
   152         System.setProperty("netbeans.user", "/tmp/tmp-foo");
   153 
   154         OptionParser parser = new OptionParser();
   155 //        ArgumentAcceptingOptionSpec<File> projects = parser.accepts("project", "project(s) to refactor").withRequiredArg().withValuesSeparatedBy(File.pathSeparatorChar).ofType(File.class);
   156         GroupOptions globalGroupOptions = setupGroupParser(parser);
   157         ArgumentAcceptingOptionSpec<File> cache = parser.accepts("cache", "a cache directory to store working data").withRequiredArg().ofType(File.class);
   158         ArgumentAcceptingOptionSpec<File> out = parser.accepts("out", "output diff").withRequiredArg().ofType(File.class);
   159         ArgumentAcceptingOptionSpec<File> configFile = parser.accepts("config-file", "configuration file").withRequiredArg().ofType(File.class);
   160         ArgumentAcceptingOptionSpec<String> hint = parser.accepts("hint", "hint name").withRequiredArg().ofType(String.class);
   161         ArgumentAcceptingOptionSpec<String> config = parser.accepts("config", "configurations").withRequiredArg().ofType(String.class);
   162         ArgumentAcceptingOptionSpec<File> hintFile = parser.accepts("hint-file", "file with rules that should be performed").withRequiredArg().ofType(File.class);
   163         ArgumentAcceptingOptionSpec<String> group = parser.accepts("group", "specify roots to process alongside with their classpath").withRequiredArg().ofType(String.class);
   164 
   165         parser.accepts("list", "list all known hints");
   166         parser.accepts("progress", "show progress");
   167         parser.accepts("debug", "enable debugging loggers");
   168         parser.accepts("help", "prints this help");
   169         parser.accepts(OPTION_NO_APPLY, "do not apply changes - only print locations were the hint would be applied");
   170         parser.accepts(OPTION_APPLY, "apply changes");
   171         parser.accepts("show-gui", "show configuration dialog");
   172         parser.accepts(OPTION_FAIL_ON_WARNINGS, "fail when warnings are detected");
   173         parser.accepts(RUN_TESTS, "run tests for declarative rules that were used");
   174 
   175         OptionSet parsed;
   176 
   177         try {
   178             parsed = parser.parse(args);
   179         } catch (OptionException ex) {
   180             System.err.println(ex.getLocalizedMessage());
   181             parser.printHelpOn(System.out);
   182             return 1;
   183         }
   184 
   185         if (!parsed.has("debug")) {
   186             prepareLoggers();
   187         }
   188 
   189         if (parsed.has("help")) {
   190             parser.printHelpOn(System.out);
   191             return 0;
   192         }
   193 
   194         List<FileObject> roots = new ArrayList<FileObject>();
   195         List<Folder> rootFolders = new ArrayList<Folder>();
   196 
   197         for (String sr : parsed.nonOptionArguments()) {
   198             File r = new File(sr);
   199             FileObject root = FileUtil.toFileObject(r);
   200 
   201             if (root != null) {
   202                 roots.add(root);
   203                 rootFolders.add(new Folder(root));
   204             }
   205         }
   206 
   207         final List<RootConfiguration> groups = new ArrayList<>();
   208 
   209         groups.add(new RootConfiguration(parsed, globalGroupOptions));
   210 
   211         for (String groupValue : parsed.valuesOf(group)) {
   212             OptionParser groupParser = new OptionParser();
   213             GroupOptions groupOptions = setupGroupParser(groupParser);
   214             OptionSet parsedGroup = groupParser.parse(splitGroupArg(groupValue));
   215 
   216             groups.add(new RootConfiguration(parsedGroup, groupOptions));
   217         }
   218 
   219         if (parsed.has("show-gui")) {
   220             if (parsed.has(configFile)) {
   221                 final File settingsFile = parsed.valueOf(configFile);
   222                 try {
   223                     SwingUtilities.invokeAndWait(new Runnable() {
   224                         @Override public void run() {
   225                             try {
   226                                 Pair<ClassPath, ClassPath> sourceAndBinaryCP = jointSourceAndBinaryCP(groups);
   227                                 showGUICustomizer(settingsFile, sourceAndBinaryCP.second(), sourceAndBinaryCP.first());
   228                             } catch (IOException ex) {
   229                                 Exceptions.printStackTrace(ex);
   230                             } catch (BackingStoreException ex) {
   231                                 Exceptions.printStackTrace(ex);
   232                             }
   233                         }
   234                     });
   235                 } catch (InterruptedException ex) {
   236                     Exceptions.printStackTrace(ex);
   237                 } catch (InvocationTargetException ex) {
   238                     Exceptions.printStackTrace(ex);
   239                 }
   240 
   241                 return 0;
   242             } else {
   243                 System.err.println("show-gui requires config-file");
   244                 return 1;
   245             }
   246         }
   247 
   248         File cacheDir = parsed.valueOf(cache);
   249         boolean deleteCacheDir = false;
   250 
   251         try {
   252             if (cacheDir == null) {
   253                 cacheDir = File.createTempFile("jackpot", "cache");
   254                 cacheDir.delete();
   255                 if (!(deleteCacheDir = cacheDir.mkdirs())) {
   256                     System.err.println("cannot create temporary cache");
   257                     return 1;
   258                 }
   259             }
   260 
   261             if (cacheDir.isFile()) {
   262                 System.err.println("cache directory exists and is a file");
   263                 return 1;
   264             }
   265 
   266             String[] cacheDirContent = cacheDir.list();
   267 
   268             if (cacheDirContent != null && cacheDirContent.length > 0 && !new File(cacheDir, "segments").exists()) {
   269                 System.err.println("cache directory is not empty, but was not created by this tool");
   270                 return 1;
   271             }
   272 
   273             cacheDir.mkdirs();
   274 
   275             CacheFolder.setCacheFolder(FileUtil.toFileObject(FileUtil.normalizeFile(cacheDir)));
   276 
   277             org.netbeans.api.project.ui.OpenProjects.getDefault().getOpenProjects();
   278             RepositoryUpdater.getDefault().start(false);
   279 
   280             if (parsed.has("list")) {
   281                 Pair<ClassPath, ClassPath> sourceAndBinaryCP = jointSourceAndBinaryCP(groups);
   282                 printHints(sourceAndBinaryCP.first(),
   283                            sourceAndBinaryCP.second());
   284                 return 0;
   285             }
   286 
   287             int totalGroups = 0;
   288 
   289             for (RootConfiguration groupConfig : groups) {
   290                 if (!groupConfig.rootFolders.isEmpty()) totalGroups++;
   291             }
   292 
   293             ProgressHandleWrapper progress = parsed.has("progress") ? new ProgressHandleWrapper(new ConsoleProgressHandleAbstraction(), ProgressHandleWrapper.prepareParts(totalGroups)) : new ProgressHandleWrapper(1);
   294 
   295             Preferences hintSettingsPreferences;
   296             boolean apply;
   297             boolean runDeclarative;
   298             boolean runDeclarativeTests;
   299 
   300             if (parsed.has(configFile)) {
   301                 Preferences settingsFromConfigFile;
   302                 settingsFromConfigFile = XMLHintPreferences.from(parsed.valueOf(configFile));
   303                 hintSettingsPreferences = settingsFromConfigFile.node("settings");
   304                 apply = settingsFromConfigFile.getBoolean("apply", false);
   305                 runDeclarative = settingsFromConfigFile.getBoolean("runDeclarative", true);
   306                 runDeclarativeTests = settingsFromConfigFile.getBoolean("runDeclarativeTests", false);
   307                 if (parsed.has(hint)) {
   308                     System.err.println("cannot specify --hint and --config-file together");
   309                     return 1;
   310                 } else if (parsed.has(hintFile)) {
   311                     System.err.println("cannot specify --hint-file and --config-file together");
   312                     return 1;
   313                 }
   314             } else {
   315                 hintSettingsPreferences = null;
   316                 apply = false;
   317                 runDeclarative = true;
   318                 runDeclarativeTests = parsed.has(RUN_TESTS);
   319             }
   320 
   321             if (parsed.has(config) && !parsed.has(hint)) {
   322                 System.err.println("--config cannot specified when no hint is specified");
   323                 return 1;
   324             }
   325 
   326             if (parsed.has(OPTION_NO_APPLY)) {
   327                 apply = false;
   328             } else if (parsed.has(OPTION_APPLY)) {
   329                 apply = true;
   330             }
   331 
   332             GroupResult result = GroupResult.NOTHING_TO_DO;
   333 
   334             try (Writer outS = parsed.has(out) ? new BufferedWriter(new OutputStreamWriter(new FileOutputStream(parsed.valueOf(out)))) : null) {
   335                 GlobalConfiguration globalConfig = new GlobalConfiguration(hintSettingsPreferences, apply, runDeclarative, runDeclarativeTests, parsed.valueOf(hint), parsed.valueOf(hintFile), outS, parsed.has(OPTION_FAIL_ON_WARNINGS));
   336 
   337                 for (RootConfiguration groupConfig : groups) {
   338                     result = result.join(handleGroup(groupConfig, progress, globalConfig, parsed.valuesOf(config)));
   339                 }
   340             }
   341 
   342             progress.finish();
   343 
   344             if (result == GroupResult.NOTHING_TO_DO) {
   345                 System.err.println("no source roots to work on");
   346                 return 1;
   347             }
   348 
   349             if (result == GroupResult.NO_HINTS_FOUND) {
   350                 System.err.println("no hints specified");
   351                 return 1;
   352             }
   353 
   354             return result == GroupResult.SUCCESS ? 0 : 1;
   355         } catch (Throwable e) {
   356             e.printStackTrace();
   357             throw new IllegalStateException(e);
   358         } finally {
   359             if (deleteCacheDir) {
   360                 FileObject cacheDirFO = FileUtil.toFileObject(cacheDir);
   361 
   362                 if (cacheDirFO != null) {
   363                     //TODO: would be better to do j.i.File.delete():
   364                     cacheDirFO.delete();
   365                 }
   366             }
   367         }
   368     }
   369 
   370     private static Pair<ClassPath, ClassPath> jointSourceAndBinaryCP(List<RootConfiguration> groups) {
   371         Set<FileObject> sourceRoots = new HashSet<>();
   372         Set<FileObject> binaryRoots = new HashSet<>();
   373         for (RootConfiguration groupConfig : groups) {
   374             sourceRoots.addAll(Arrays.asList(groupConfig.sourceCP.getRoots()));
   375             binaryRoots.addAll(Arrays.asList(groupConfig.binaryCP.getRoots()));
   376         }
   377         return Pair.of(ClassPathSupport.createClassPath(sourceRoots.toArray(new FileObject[0])),
   378                        ClassPathSupport.createClassPath(binaryRoots.toArray(new FileObject[0])));
   379     }
   380 
   381     private static GroupOptions setupGroupParser(OptionParser parser) {
   382         return new GroupOptions(parser.accepts("classpath", "classpath").withRequiredArg().withValuesSeparatedBy(File.pathSeparatorChar).ofType(File.class),
   383                                 parser.accepts("bootclasspath", "bootclasspath").withRequiredArg().withValuesSeparatedBy(File.pathSeparatorChar).ofType(File.class),
   384                                 parser.accepts("sourcepath", "sourcepath").withRequiredArg().withValuesSeparatedBy(File.pathSeparatorChar).ofType(File.class),
   385                                 parser.accepts("source", "source level").withRequiredArg().ofType(String.class).defaultsTo(SOURCE_LEVEL_DEFAULT));
   386     }
   387 
   388     private static final class GroupOptions {
   389         private final ArgumentAcceptingOptionSpec<File> classpath;
   390         private final ArgumentAcceptingOptionSpec<File> bootclasspath;
   391         private final ArgumentAcceptingOptionSpec<File> sourcepath;
   392         private final ArgumentAcceptingOptionSpec<String> source;
   393 
   394         public GroupOptions(ArgumentAcceptingOptionSpec<File> classpath, ArgumentAcceptingOptionSpec<File> bootclasspath, ArgumentAcceptingOptionSpec<File> sourcepath, ArgumentAcceptingOptionSpec<String> source) {
   395             this.classpath = classpath;
   396             this.bootclasspath = bootclasspath;
   397             this.sourcepath = sourcepath;
   398             this.source = source;
   399         }
   400 
   401     }
   402 
   403     private static Map<HintMetadata, Collection<? extends HintDescription>> listHints(ClassPath sourceFrom, ClassPath binaryFrom) {
   404         Map<HintMetadata, Collection<? extends HintDescription>> result = new HashMap<HintMetadata, Collection<? extends HintDescription>>();
   405 
   406         for (Entry<HintMetadata, ? extends Collection<? extends HintDescription>> entry: RulesManager.getInstance().readHints(null, Arrays.asList(sourceFrom, binaryFrom), null).entrySet()) {
   407             result.put(entry.getKey(), entry.getValue());
   408         }
   409 
   410         return result;
   411     }
   412 
   413     private static GroupResult handleGroup(RootConfiguration rootConfiguration, ProgressHandleWrapper w, GlobalConfiguration globalConfig, List<String> config) throws IOException {
   414         Iterable<? extends HintDescription> hints;
   415 
   416         if (rootConfiguration.rootFolders.isEmpty()) {
   417             return GroupResult.NOTHING_TO_DO;
   418         }
   419 
   420         WarningsAndErrors wae = new WarningsAndErrors();
   421 
   422         ProgressHandleWrapper progress = w.startNextPartWithEmbedding(1);
   423         Preferences settings = globalConfig.configurationPreferences != null ? globalConfig.configurationPreferences : new MemoryPreferences();
   424         HintsSettings hintSettings = HintsSettings.createPreferencesBasedHintsSettings(settings, false, null);
   425 
   426         if (globalConfig.hint != null) {
   427             hints = findHints(rootConfiguration.sourceCP, rootConfiguration.binaryCP, globalConfig.hint, hintSettings);
   428         } else if (globalConfig.hintFile != null) {
   429             FileObject hintFileFO = FileUtil.toFileObject(globalConfig.hintFile);
   430             assert hintFileFO != null;
   431             hints = PatternConvertor.create(hintFileFO.asText());
   432             for (HintDescription hd : hints) {
   433                 hintSettings.setEnabled(hd.getMetadata(), true);
   434             }
   435         } else {
   436             hints = readHints(rootConfiguration.sourceCP, rootConfiguration.binaryCP, hintSettings, settings, globalConfig.runDeclarative);
   437             if (globalConfig.runDeclarativeTests) {
   438                 Set<String> enabledHints = new HashSet<>();
   439                 for (HintDescription desc : hints) {
   440                     enabledHints.add(desc.getMetadata().id);
   441                 }
   442                 ClassPath combined = ClassPathSupport.createProxyClassPath(rootConfiguration.sourceCP, rootConfiguration.binaryCP);
   443                 Map<FileObject, FileObject> testFiles = new HashMap<>();
   444                 for (FileObject upgrade : combined.findAllResources("META-INF/upgrade")) {
   445                     for (FileObject c : upgrade.getChildren()) {
   446                         if (c.getExt().equals("test")) {
   447                             FileObject hintFile = FileUtil.findBrother(c, "hint");
   448 
   449                             for (HintMetadata hm : DeclarativeHintRegistry.parseHintFile(hintFile).keySet()) {
   450                                 if (enabledHints.contains(hm.id)) {
   451                                     testFiles.put(c, hintFile);
   452                                     break;
   453                                 }
   454                             }
   455                         }
   456                     }
   457                 }
   458                 for (Entry<FileObject, FileObject> e : testFiles.entrySet()) {
   459                     TestCase[] testCases = TestParser.parse(e.getKey().asText()); //XXX: encoding
   460                     try {
   461                         Map<TestCase, Collection<String>> testResult = TestPerformer.performTest(e.getValue(), e.getKey(), testCases, new AtomicBoolean());
   462                         for (TestCase tc : testCases) {
   463                             List<String> expected = Arrays.asList(tc.getResults());
   464                             List<String> actual = new ArrayList<>(testResult.get(tc));
   465                             if (!expected.equals(actual)) {
   466                                 int pos = tc.getTestCaseStart();
   467                                 String id = "test-failure";
   468                                 ErrorDescription ed = ErrorDescriptionFactory.createErrorDescription(id, Severity.ERROR, "Actual results did not match the expected test results. Actual results: " + expected, null, ErrorDescriptionFactory.lazyListForFixes(Collections.<Fix>emptyList()), e.getKey(), pos, pos);
   469                                 print(ed, wae, Collections.singletonMap(id, id));
   470                             }
   471                         }
   472                     } catch (Exception ex) {
   473                         ex.printStackTrace();
   474                     }
   475                 }
   476             }
   477         }
   478 
   479         if (config != null && !config.isEmpty()) {
   480             Iterator<? extends HintDescription> hit = hints.iterator();
   481             HintDescription hd = hit.next();
   482 
   483             if (hit.hasNext()) {
   484                 System.err.println("--config cannot specified when more than one hint is specified");
   485 
   486                 return GroupResult.FAILURE;
   487             }
   488 
   489             Preferences prefs = hintSettings.getHintPreferences(hd.getMetadata());
   490 
   491             boolean stop = false;
   492 
   493             for (String c : config) {
   494                 int assign = c.indexOf('=');
   495 
   496                 if (assign == (-1)) {
   497                     System.err.println("configuration option is missing '=' (" + c + ")");
   498                     stop = true;
   499                     continue;
   500                 }
   501 
   502                 prefs.put(c.substring(0, assign), c.substring(assign + 1));
   503             }
   504 
   505             if (stop) {
   506                 return GroupResult.FAILURE;
   507             }
   508         }
   509 
   510         String sourceLevel = rootConfiguration.sourceLevel;
   511 
   512         if (!Pattern.compile(ACCEPTABLE_SOURCE_LEVEL_PATTERN).matcher(sourceLevel).matches()) {
   513             System.err.println("unrecognized source level specification: " + sourceLevel);
   514             return GroupResult.FAILURE;
   515         }
   516 
   517         if (globalConfig.apply && !hints.iterator().hasNext()) {
   518             return GroupResult.NO_HINTS_FOUND;
   519         }
   520 
   521         Object[] register2Lookup = new Object[] {
   522             new ClassPathProviderImpl(rootConfiguration.bootCP, rootConfiguration.compileCP, rootConfiguration.sourceCP),
   523             new JavaPathRecognizer(),
   524             new SourceLevelQueryImpl(rootConfiguration.sourceCP, sourceLevel)
   525         };
   526 
   527         try {
   528             for (Object toRegister : register2Lookup) {
   529                 MainLookup.register(toRegister);
   530             }
   531 
   532             if (globalConfig.apply) {
   533                 apply(hints, rootConfiguration.rootFolders.toArray(new Folder[0]), progress, hintSettings, globalConfig.out);
   534 
   535                 return GroupResult.SUCCESS; //TODO: WarningsAndErrors?
   536             } else {
   537                 findOccurrences(hints, rootConfiguration.rootFolders.toArray(new Folder[0]), progress, hintSettings, wae);
   538 
   539                 if (wae.errors != 0 || (wae.warnings != 0 && globalConfig.failOnWarnings)) {
   540                     return GroupResult.FAILURE;
   541                 } else {
   542                     return GroupResult.SUCCESS;
   543                 }
   544             }
   545         } finally {
   546             for (Object toUnRegister : register2Lookup) {
   547                 MainLookup.unregister(toUnRegister);
   548             }
   549         }
   550     }
   551 
   552     private static class MemoryPreferences extends AbstractPreferences {
   553 
   554         private final Map<String, String> values = new HashMap<>();
   555         private final Map<String, MemoryPreferences> nodes = new HashMap<>();
   556 
   557         public MemoryPreferences() {
   558             this(null, "");
   559         }
   560 
   561         public MemoryPreferences(MemoryPreferences parent, String name) {
   562             super(parent, name);
   563         }
   564         @Override
   565         protected void putSpi(String key, String value) {
   566             values.put(key, value);
   567         }
   568 
   569         @Override
   570         protected String getSpi(String key) {
   571             return values.get(key);
   572         }
   573 
   574         @Override
   575         protected void removeSpi(String key) {
   576             values.remove(key);
   577         }
   578 
   579         @Override
   580         protected void removeNodeSpi() throws BackingStoreException {
   581             ((MemoryPreferences) parent()).nodes.remove(name());
   582         }
   583 
   584         @Override
   585         protected String[] keysSpi() throws BackingStoreException {
   586             return values.keySet().toArray(new String[0]);
   587         }
   588 
   589         @Override
   590         protected String[] childrenNamesSpi() throws BackingStoreException {
   591             return nodes.keySet().toArray(new String[0]);
   592         }
   593 
   594         @Override
   595         protected AbstractPreferences childSpi(String name) {
   596             MemoryPreferences result = nodes.get(name);
   597 
   598             if (result == null) {
   599                 nodes.put(name, result = new MemoryPreferences(this, name));
   600             }
   601 
   602             return result;
   603         }
   604 
   605         @Override
   606         protected void syncSpi() throws BackingStoreException {
   607         }
   608 
   609         @Override
   610         protected void flushSpi() throws BackingStoreException {
   611         }
   612     }
   613 
   614     private enum GroupResult {
   615         NOTHING_TO_DO {
   616             @Override
   617             public GroupResult join(GroupResult other) {
   618                 return other;
   619             }
   620         },
   621         NO_HINTS_FOUND {
   622             @Override
   623             public GroupResult join(GroupResult other) {
   624                 if (other == NOTHING_TO_DO) return this;
   625                 return other;
   626             }
   627         },
   628         SUCCESS {
   629             @Override
   630             public GroupResult join(GroupResult other) {
   631                 if (other == FAILURE) return other;
   632                 return this;
   633             }
   634         },
   635         FAILURE {
   636             @Override
   637             public GroupResult join(GroupResult other) {
   638                 return this;
   639             }
   640         };
   641 
   642         public abstract GroupResult join(GroupResult other);
   643     }
   644     
   645     private static Iterable<? extends HintDescription> findHints(ClassPath sourceFrom, ClassPath binaryFrom, String name, HintsSettings toEnableIn) {
   646         List<HintDescription> descs = new LinkedList<HintDescription>();
   647 
   648         for (Entry<HintMetadata, Collection<? extends HintDescription>> e : listHints(sourceFrom, binaryFrom).entrySet()) {
   649             if (e.getKey().displayName.equals(name)) {
   650                 descs.addAll(e.getValue());
   651                 toEnableIn.setEnabled(e.getKey(), true);
   652             }
   653         }
   654 
   655         return descs;
   656     }
   657 
   658     private static Iterable<? extends HintDescription> allHints(ClassPath sourceFrom, ClassPath binaryFrom, HintsSettings toEnableIn) {
   659         List<HintDescription> descs = new LinkedList<HintDescription>();
   660 
   661         for (Entry<HintMetadata, Collection<? extends HintDescription>> e : listHints(sourceFrom, binaryFrom).entrySet()) {
   662             if (e.getKey().kind != Kind.INSPECTION) continue;
   663             if (!e.getKey().enabled) continue;
   664             descs.addAll(e.getValue());
   665             toEnableIn.setEnabled(e.getKey(), true);
   666         }
   667 
   668         return descs;
   669     }
   670 
   671     private static Iterable<? extends HintDescription> readHints(ClassPath sourceFrom, ClassPath binaryFrom, HintsSettings toEnableIn, Preferences toEnableInPreferencesHack, boolean declarativeEnabledByDefault) {
   672         Map<HintMetadata, ? extends Collection<? extends HintDescription>> hardcoded = RulesManager.getInstance().readHints(null, Arrays.<ClassPath>asList(), null);
   673         Map<HintMetadata, ? extends Collection<? extends HintDescription>> all = RulesManager.getInstance().readHints(null, Arrays.asList(sourceFrom, binaryFrom), null);
   674         List<HintDescription> descs = new LinkedList<HintDescription>();
   675 
   676         for (Entry<HintMetadata, ? extends Collection<? extends HintDescription>> entry: all.entrySet()) {
   677             if (hardcoded.containsKey(entry.getKey())) {
   678                 if (toEnableIn.isEnabled(entry.getKey())) {
   679                     descs.addAll(entry.getValue());
   680                 }
   681             } else {
   682                 if (/*XXX: hack*/toEnableInPreferencesHack.node(entry.getKey().id).getBoolean("enabled", declarativeEnabledByDefault)) {
   683                     descs.addAll(entry.getValue());
   684                 }
   685             }
   686         }
   687 
   688         return descs;
   689     }
   690 
   691     private static final Logger TOP_LOGGER = Logger.getLogger("");
   692 
   693     private static void prepareLoggers() {
   694         TOP_LOGGER.setLevel(Level.OFF);
   695         System.setProperty("RepositoryUpdate.increasedLogLevel", "OFF");
   696     }
   697     
   698     private static void findOccurrences(Iterable<? extends HintDescription> descs, Folder[] sourceRoot, ProgressHandleWrapper progress, HintsSettings settings, final WarningsAndErrors wae) throws IOException {
   699         final Map<String, String> id2DisplayName = Utils.computeId2DisplayName(descs);
   700         ProgressHandleWrapper w = progress.startNextPartWithEmbedding(1, 1);
   701         BatchResult occurrences = BatchSearch.findOccurrences(descs, Scopes.specifiedFoldersScope(sourceRoot), w, settings);
   702 
   703         List<MessageImpl> problems = new LinkedList<MessageImpl>();
   704         BatchSearch.getVerifiedSpans(occurrences, w, new VerifiedSpansCallBack() {
   705             @Override public void groupStarted() {}
   706             @Override public boolean spansVerified(CompilationController wc, Resource r, Collection<? extends ErrorDescription> hints) throws Exception {
   707                 for (ErrorDescription ed : hints) {
   708                     print(ed, wae, id2DisplayName);
   709                 }
   710                 return true;
   711             }
   712             @Override public void groupFinished() {}
   713             @Override public void cannotVerifySpan(Resource r) {
   714                 //TODO: ignored - what to do?
   715             }
   716         }, problems, new AtomicBoolean());
   717     }
   718 
   719     private static void print(ErrorDescription error, WarningsAndErrors wae, Map<String, String> id2DisplayName) throws IOException {
   720         int lineNumber = error.getRange().getBegin().getLine();
   721         String line = error.getFile().asLines().get(lineNumber);
   722         int column = error.getRange().getBegin().getColumn();
   723         StringBuilder b = new StringBuilder();
   724 
   725         for (int i = 0; i < column; i++) {
   726             if (Character.isWhitespace(line.charAt(i))) {
   727                 b.append(line.charAt(i));
   728             } else {
   729                 b.append(' ');
   730             }
   731         }
   732 
   733         b.append('^');
   734 
   735         String idDisplayName = Utils.categoryName(error.getId(), id2DisplayName);
   736         String severity;
   737         if (error.getSeverity() == Severity.ERROR) {
   738             severity = "error";
   739             wae.errors++;
   740         } else {
   741             severity = "warning";
   742             wae.warnings++;
   743         }
   744         System.out.println(FileUtil.getFileDisplayName(error.getFile()) + ":" + (lineNumber + 1) + ": " + severity + ": " + idDisplayName + error.getDescription());
   745         System.out.println(line);
   746         System.out.println(b);
   747     }
   748 
   749     private static void apply(Iterable<? extends HintDescription> descs, Folder[] sourceRoot, ProgressHandleWrapper progress, HintsSettings settings, Writer out) throws IOException {
   750         ProgressHandleWrapper w = progress.startNextPartWithEmbedding(1, 1);
   751         BatchResult occurrences = BatchSearch.findOccurrences(descs, Scopes.specifiedFoldersScope(sourceRoot), w, settings);
   752 
   753         List<MessageImpl> problems = new LinkedList<MessageImpl>();
   754         Collection<ModificationResult> diffs = BatchUtilities.applyFixes(occurrences, w, new AtomicBoolean(), problems);
   755 
   756         if (out != null) {
   757             for (ModificationResult mr : diffs) {
   758                 org.netbeans.modules.jackpot30.indexing.batch.BatchUtilities.exportDiff(mr, null, out);
   759             }
   760         } else {
   761             for (ModificationResult mr : diffs) {
   762                 mr.commit();
   763             }
   764         }
   765     }
   766 
   767     private static void printHints(ClassPath sourceFrom, ClassPath binaryFrom) throws IOException {
   768         Set<String> hints = new TreeSet<String>();
   769 
   770         for (Entry<HintMetadata, Collection<? extends HintDescription>> e : listHints(sourceFrom, binaryFrom).entrySet()) {
   771             hints.add(e.getKey().displayName);
   772         }
   773 
   774         for (String h : hints) {
   775             System.out.println(h);
   776         }
   777     }
   778 
   779     private static ClassPath createDefaultBootClassPath() throws IOException {
   780         try {
   781             String cp = System.getProperty("sun.boot.class.path");
   782             List<URL> urls = new ArrayList<URL>();
   783             String[] paths = cp.split(Pattern.quote(System.getProperty("path.separator")));
   784 
   785             for (String path : paths) {
   786                 File f = new File(path);
   787 
   788                 if (!f.canRead())
   789                     continue;
   790 
   791                 FileObject fo = FileUtil.toFileObject(FileUtil.normalizeFile(f));
   792 
   793                 if (FileUtil.isArchiveFile(fo)) {
   794                     fo = FileUtil.getArchiveRoot(fo);
   795                 }
   796 
   797                 if (fo != null) {
   798                     urls.add(fo.getURL());
   799                 }
   800             }
   801 
   802             return ClassPathSupport.createClassPath(urls.toArray(new URL[0]));
   803         } catch (FileStateInvalidException e) {
   804             throw e;
   805         }
   806     }
   807 
   808     private static ClassPath createClassPath(Iterable<? extends File> roots, ClassPath def) {
   809         if (roots == null) return def;
   810 
   811         List<URL> rootURLs = new ArrayList<URL>();
   812 
   813         for (File r : roots) {
   814             rootURLs.add(FileUtil.urlForArchiveOrDir(r));
   815         }
   816 
   817         return ClassPathSupport.createClassPath(rootURLs.toArray(new URL[0]));
   818     }
   819 
   820     private static void showGUICustomizer(File settingsFile, ClassPath binaryCP, ClassPath sourceCP) throws IOException, BackingStoreException {
   821         GlobalPathRegistry.getDefault().register(ClassPath.COMPILE, new ClassPath[] {binaryCP});
   822         GlobalPathRegistry.getDefault().register(ClassPath.SOURCE, new ClassPath[] {sourceCP});
   823         ClassPathBasedHintWrapper hints = new ClassPathBasedHintWrapper();
   824         final Preferences p = XMLHintPreferences.from(settingsFile);
   825         JPanel hintPanel = new HintsPanel(p.node("settings"), hints, true);
   826         final JCheckBox runDeclarativeHints = new JCheckBox("Always Run Declarative Rules");
   827 
   828         runDeclarativeHints.setToolTipText("Always run the declarative rules found on classpath? (Only those selected above will be run when unchecked.)");
   829         runDeclarativeHints.setSelected(p.getBoolean("runDeclarative", true));
   830         runDeclarativeHints.addActionListener(new ActionListener() {
   831             @Override public void actionPerformed(ActionEvent e) {
   832                 p.putBoolean("runDeclarative", runDeclarativeHints.isSelected());
   833             }
   834         });
   835 
   836         JPanel customizer = new JPanel(new BorderLayout());
   837 
   838         customizer.add(hintPanel, BorderLayout.CENTER);
   839         customizer.add(runDeclarativeHints, BorderLayout.SOUTH);
   840         JOptionPane jop = new JOptionPane(customizer, JOptionPane.PLAIN_MESSAGE);
   841         JDialog dialog = jop.createDialog("Select Hints");
   842 
   843         jop.selectInitialValue();
   844         dialog.setVisible(true);
   845         dialog.dispose();
   846 
   847         Object result = jop.getValue();
   848 
   849         if (result.equals(JOptionPane.OK_OPTION)) {
   850             p.flush();
   851         }
   852     }
   853 
   854     static String[] splitGroupArg(String arg) {
   855         List<String> result = new ArrayList<>();
   856         StringBuilder currentPart = new StringBuilder();
   857 
   858         for (int i = 0; i < arg.length(); i++) {
   859             switch (arg.charAt(i)) {
   860                 case '\\':
   861                     if (++i < arg.length()) {
   862                         currentPart.append(arg.charAt(i));
   863                     }
   864                     break;
   865                 case ' ':
   866                     if (currentPart.length() > 0) {
   867                         result.add(currentPart.toString());
   868                         currentPart.delete(0, currentPart.length());
   869                     }
   870                     break;
   871                 default:
   872                     currentPart.append(arg.charAt(i));
   873                     break;
   874             }
   875         }
   876 
   877         if (currentPart.length() > 0) {
   878             result.add(currentPart.toString());
   879         }
   880 
   881         return result.toArray(new String[0]);
   882     }
   883 
   884     private static final class WarningsAndErrors {
   885         private int warnings;
   886         private int errors;
   887     }
   888 
   889     private static final class RootConfiguration {
   890         private final List<Folder> rootFolders;
   891         private final ClassPath bootCP;
   892         private final ClassPath compileCP;
   893         private final ClassPath sourceCP;
   894         private final ClassPath binaryCP;
   895         private final String    sourceLevel;
   896 
   897         public RootConfiguration(OptionSet parsed, GroupOptions groupOptions) throws IOException {
   898             this.rootFolders = new ArrayList<>();
   899 
   900             List<FileObject> roots = new ArrayList<>();
   901 
   902             for (String sr : parsed.nonOptionArguments()) {
   903                 File r = new File(sr);
   904                 FileObject root = FileUtil.toFileObject(r);
   905 
   906                 if (root != null) {
   907                     roots.add(root);
   908                     rootFolders.add(new Folder(root));
   909                 }
   910             }
   911 
   912             this.bootCP = createClassPath(parsed.has(groupOptions.bootclasspath) ? parsed.valuesOf(groupOptions.bootclasspath) : null, createDefaultBootClassPath());
   913             this.compileCP = createClassPath(parsed.has(groupOptions.classpath) ? parsed.valuesOf(groupOptions.classpath) : null, ClassPath.EMPTY);
   914             this.sourceCP = createClassPath(parsed.has(groupOptions.sourcepath) ? parsed.valuesOf(groupOptions.sourcepath) : null, ClassPathSupport.createClassPath(roots.toArray(new FileObject[0])));
   915             this.binaryCP = ClassPathSupport.createProxyClassPath(bootCP, compileCP);
   916             this.sourceLevel = parsed.valueOf(groupOptions.source);
   917         }
   918 
   919     }
   920 
   921     private static final class GlobalConfiguration {
   922         private final Preferences configurationPreferences;
   923         private final boolean apply;
   924         private final boolean runDeclarative;
   925         private final boolean runDeclarativeTests;
   926         private final String hint;
   927         private final File hintFile;
   928         private final Writer out;
   929         private final boolean failOnWarnings;
   930 
   931         public GlobalConfiguration(Preferences configurationPreferences, boolean apply, boolean runDeclarative, boolean runDeclarativeTests, String hint, File hintFile, Writer out, boolean failOnWarnings) {
   932             this.configurationPreferences = configurationPreferences;
   933             this.apply = apply;
   934             this.runDeclarative = runDeclarative;
   935             this.runDeclarativeTests = runDeclarativeTests;
   936             this.hint = hint;
   937             this.hintFile = hintFile;
   938             this.out = out;
   939             this.failOnWarnings = failOnWarnings;
   940         }
   941 
   942     }
   943 
   944     @ServiceProvider(service=Lookup.class)
   945     public static final class LookupProviderImpl extends ProxyLookup {
   946 
   947         public LookupProviderImpl() {
   948             super(Lookups.forPath("Services/AntBasedProjectTypes"));
   949         }
   950     }
   951 
   952     public static final class ClassPathProviderImpl implements ClassPathProvider {
   953         private final ClassPath boot;
   954         private final ClassPath compile;
   955         private final ClassPath source;
   956 
   957         public ClassPathProviderImpl(ClassPath boot, ClassPath compile, ClassPath source) {
   958             this.boot = boot;
   959             this.compile = compile;
   960             this.source = source;
   961         }
   962 
   963         @Override
   964         public ClassPath findClassPath(FileObject file, String type) {
   965             if (source.findOwnerRoot(file) != null) {
   966                 if (ClassPath.BOOT.equals(type)) {
   967                     return boot;
   968                 } else if (ClassPath.COMPILE.equals(type)) {
   969                     return compile;
   970                 } else  if (ClassPath.SOURCE.equals(type)) {
   971                     return source;
   972                 }
   973             }
   974 
   975             return null;
   976         }
   977     }
   978 
   979     public static final class SourceLevelQueryImpl implements SourceLevelQueryImplementation2 {
   980         private final ClassPath sourceCP;
   981         private final Result sourceLevel;
   982 
   983         public SourceLevelQueryImpl(ClassPath sourceCP, final String sourceLevel) {
   984             this.sourceCP = sourceCP;
   985             this.sourceLevel = new Result() {
   986                 @Override public String getSourceLevel() {
   987                     return sourceLevel;
   988                 }
   989                 @Override public void addChangeListener(ChangeListener listener) {}
   990                 @Override public void removeChangeListener(ChangeListener listener) {}
   991             };
   992         }
   993 
   994         @Override
   995         public Result getSourceLevel(FileObject javaFile) {
   996             if (sourceCP.findOwnerRoot(javaFile) != null) {
   997                 return sourceLevel;
   998             } else {
   999                 return null;
  1000             }
  1001         }
  1002 
  1003     }
  1004 
  1005     private static final class ConsoleProgressHandleAbstraction implements ProgressHandleAbstraction {
  1006 
  1007         private final int width = 80 - 2;
  1008 
  1009         private int total = -1;
  1010         private int current = 0;
  1011 
  1012         public ConsoleProgressHandleAbstraction() {
  1013         }
  1014 
  1015         @Override
  1016         public synchronized void start(int totalWork) {
  1017             if (total != (-1)) throw new UnsupportedOperationException();
  1018             total = totalWork;
  1019             update();
  1020         }
  1021 
  1022         @Override
  1023         public synchronized void progress(int currentWorkDone) {
  1024             current = currentWorkDone;
  1025             update();
  1026         }
  1027 
  1028         @Override
  1029         public void progress(String message) {
  1030         }
  1031 
  1032         @Override
  1033         public synchronized void finish() {
  1034             current = total;
  1035             RequestProcessor.getDefault().post(new Runnable() {
  1036                 @Override
  1037                 public void run() {
  1038                     doUpdate(false);
  1039                     System.out.println();
  1040                 }
  1041             });
  1042         }
  1043 
  1044         private void update() {
  1045             RequestProcessor.getDefault().post(new Runnable() {
  1046                 @Override
  1047                 public void run() {
  1048                     doUpdate(true);
  1049                 }
  1050             });
  1051         }
  1052 
  1053         private int currentShownDone = -1;
  1054 
  1055         private void doUpdate(boolean moveCaret) {
  1056             int done;
  1057 
  1058             synchronized(this) {
  1059                 done = (int) ((((double) width) / total) * current);
  1060 
  1061                 if (done == currentShownDone) {
  1062                     return;
  1063                 }
  1064 
  1065                 currentShownDone = done;
  1066             }
  1067             
  1068             int todo = width - done;
  1069             PrintStream pw = System.out;
  1070 
  1071             pw.print("[");
  1072 
  1073 
  1074             while (done-- > 0) {
  1075                 pw.print("=");
  1076             }
  1077 
  1078             while (todo-- > 0) {
  1079                 pw.print(" ");
  1080             }
  1081 
  1082             pw.print("]");
  1083 
  1084             if (moveCaret)
  1085                 pw.print("\r");
  1086         }
  1087 
  1088     }
  1089 
  1090 }