Adding ability to run (custom) tests.
2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
4 * Copyright 2010-2011 Sun Microsystems, Inc. All rights reserved.
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]"
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.
37 * Portions Copyrighted 2010-2011 Sun Microsystems, Inc.
40 package org.netbeans.modules.jackpot30.cmdline;
42 import java.awt.BorderLayout;
43 import java.awt.event.ActionEvent;
44 import java.awt.event.ActionListener;
45 import java.io.BufferedWriter;
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;
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;
64 import java.util.Map.Entry;
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;
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]*";
147 public static void main(String... args) throws IOException, ClassNotFoundException {
148 System.exit(compile(args));
151 public static int compile(String... args) throws IOException, ClassNotFoundException {
152 System.setProperty("netbeans.user", "/tmp/tmp-foo");
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);
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");
178 parsed = parser.parse(args);
179 } catch (OptionException ex) {
180 System.err.println(ex.getLocalizedMessage());
181 parser.printHelpOn(System.out);
185 if (!parsed.has("debug")) {
189 if (parsed.has("help")) {
190 parser.printHelpOn(System.out);
194 List<FileObject> roots = new ArrayList<FileObject>();
195 List<Folder> rootFolders = new ArrayList<Folder>();
197 for (String sr : parsed.nonOptionArguments()) {
198 File r = new File(sr);
199 FileObject root = FileUtil.toFileObject(r);
203 rootFolders.add(new Folder(root));
207 final List<RootConfiguration> groups = new ArrayList<>();
209 groups.add(new RootConfiguration(parsed, globalGroupOptions));
211 for (String groupValue : parsed.valuesOf(group)) {
212 OptionParser groupParser = new OptionParser();
213 GroupOptions groupOptions = setupGroupParser(groupParser);
214 OptionSet parsedGroup = groupParser.parse(splitGroupArg(groupValue));
216 groups.add(new RootConfiguration(parsedGroup, groupOptions));
219 if (parsed.has("show-gui")) {
220 if (parsed.has(configFile)) {
221 final File settingsFile = parsed.valueOf(configFile);
223 SwingUtilities.invokeAndWait(new Runnable() {
224 @Override public void run() {
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);
235 } catch (InterruptedException ex) {
236 Exceptions.printStackTrace(ex);
237 } catch (InvocationTargetException ex) {
238 Exceptions.printStackTrace(ex);
243 System.err.println("show-gui requires config-file");
248 File cacheDir = parsed.valueOf(cache);
249 boolean deleteCacheDir = false;
252 if (cacheDir == null) {
253 cacheDir = File.createTempFile("jackpot", "cache");
255 if (!(deleteCacheDir = cacheDir.mkdirs())) {
256 System.err.println("cannot create temporary cache");
261 if (cacheDir.isFile()) {
262 System.err.println("cache directory exists and is a file");
266 String[] cacheDirContent = cacheDir.list();
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");
275 CacheFolder.setCacheFolder(FileUtil.toFileObject(FileUtil.normalizeFile(cacheDir)));
277 org.netbeans.api.project.ui.OpenProjects.getDefault().getOpenProjects();
278 RepositoryUpdater.getDefault().start(false);
280 if (parsed.has("list")) {
281 Pair<ClassPath, ClassPath> sourceAndBinaryCP = jointSourceAndBinaryCP(groups);
282 printHints(sourceAndBinaryCP.first(),
283 sourceAndBinaryCP.second());
289 for (RootConfiguration groupConfig : groups) {
290 if (!groupConfig.rootFolders.isEmpty()) totalGroups++;
293 ProgressHandleWrapper progress = parsed.has("progress") ? new ProgressHandleWrapper(new ConsoleProgressHandleAbstraction(), ProgressHandleWrapper.prepareParts(totalGroups)) : new ProgressHandleWrapper(1);
295 Preferences hintSettingsPreferences;
297 boolean runDeclarative;
298 boolean runDeclarativeTests;
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");
310 } else if (parsed.has(hintFile)) {
311 System.err.println("cannot specify --hint-file and --config-file together");
315 hintSettingsPreferences = null;
317 runDeclarative = true;
318 runDeclarativeTests = parsed.has(RUN_TESTS);
321 if (parsed.has(config) && !parsed.has(hint)) {
322 System.err.println("--config cannot specified when no hint is specified");
326 if (parsed.has(OPTION_NO_APPLY)) {
328 } else if (parsed.has(OPTION_APPLY)) {
332 GroupResult result = GroupResult.NOTHING_TO_DO;
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));
337 for (RootConfiguration groupConfig : groups) {
338 result = result.join(handleGroup(groupConfig, progress, globalConfig, parsed.valuesOf(config)));
344 if (result == GroupResult.NOTHING_TO_DO) {
345 System.err.println("no source roots to work on");
349 if (result == GroupResult.NO_HINTS_FOUND) {
350 System.err.println("no hints specified");
354 return result == GroupResult.SUCCESS ? 0 : 1;
355 } catch (Throwable e) {
357 throw new IllegalStateException(e);
359 if (deleteCacheDir) {
360 FileObject cacheDirFO = FileUtil.toFileObject(cacheDir);
362 if (cacheDirFO != null) {
363 //TODO: would be better to do j.i.File.delete():
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()));
377 return Pair.of(ClassPathSupport.createClassPath(sourceRoots.toArray(new FileObject[0])),
378 ClassPathSupport.createClassPath(binaryRoots.toArray(new FileObject[0])));
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));
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;
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;
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>>();
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());
413 private static GroupResult handleGroup(RootConfiguration rootConfiguration, ProgressHandleWrapper w, GlobalConfiguration globalConfig, List<String> config) throws IOException {
414 Iterable<? extends HintDescription> hints;
416 if (rootConfiguration.rootFolders.isEmpty()) {
417 return GroupResult.NOTHING_TO_DO;
420 WarningsAndErrors wae = new WarningsAndErrors();
422 ProgressHandleWrapper progress = w.startNextPartWithEmbedding(1);
423 Preferences settings = globalConfig.configurationPreferences != null ? globalConfig.configurationPreferences : new MemoryPreferences();
424 HintsSettings hintSettings = HintsSettings.createPreferencesBasedHintsSettings(settings, false, null);
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);
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);
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");
449 for (HintMetadata hm : DeclarativeHintRegistry.parseHintFile(hintFile).keySet()) {
450 if (enabledHints.contains(hm.id)) {
451 testFiles.put(c, hintFile);
458 for (Entry<FileObject, FileObject> e : testFiles.entrySet()) {
459 TestCase[] testCases = TestParser.parse(e.getKey().asText()); //XXX: encoding
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));
472 } catch (Exception ex) {
473 ex.printStackTrace();
479 if (config != null && !config.isEmpty()) {
480 Iterator<? extends HintDescription> hit = hints.iterator();
481 HintDescription hd = hit.next();
484 System.err.println("--config cannot specified when more than one hint is specified");
486 return GroupResult.FAILURE;
489 Preferences prefs = hintSettings.getHintPreferences(hd.getMetadata());
491 boolean stop = false;
493 for (String c : config) {
494 int assign = c.indexOf('=');
496 if (assign == (-1)) {
497 System.err.println("configuration option is missing '=' (" + c + ")");
502 prefs.put(c.substring(0, assign), c.substring(assign + 1));
506 return GroupResult.FAILURE;
510 String sourceLevel = rootConfiguration.sourceLevel;
512 if (!Pattern.compile(ACCEPTABLE_SOURCE_LEVEL_PATTERN).matcher(sourceLevel).matches()) {
513 System.err.println("unrecognized source level specification: " + sourceLevel);
514 return GroupResult.FAILURE;
517 if (globalConfig.apply && !hints.iterator().hasNext()) {
518 return GroupResult.NO_HINTS_FOUND;
521 Object[] register2Lookup = new Object[] {
522 new ClassPathProviderImpl(rootConfiguration.bootCP, rootConfiguration.compileCP, rootConfiguration.sourceCP),
523 new JavaPathRecognizer(),
524 new SourceLevelQueryImpl(rootConfiguration.sourceCP, sourceLevel)
528 for (Object toRegister : register2Lookup) {
529 MainLookup.register(toRegister);
532 if (globalConfig.apply) {
533 apply(hints, rootConfiguration.rootFolders.toArray(new Folder[0]), progress, hintSettings, globalConfig.out);
535 return GroupResult.SUCCESS; //TODO: WarningsAndErrors?
537 findOccurrences(hints, rootConfiguration.rootFolders.toArray(new Folder[0]), progress, hintSettings, wae);
539 if (wae.errors != 0 || (wae.warnings != 0 && globalConfig.failOnWarnings)) {
540 return GroupResult.FAILURE;
542 return GroupResult.SUCCESS;
546 for (Object toUnRegister : register2Lookup) {
547 MainLookup.unregister(toUnRegister);
552 private static class MemoryPreferences extends AbstractPreferences {
554 private final Map<String, String> values = new HashMap<>();
555 private final Map<String, MemoryPreferences> nodes = new HashMap<>();
557 public MemoryPreferences() {
561 public MemoryPreferences(MemoryPreferences parent, String name) {
565 protected void putSpi(String key, String value) {
566 values.put(key, value);
570 protected String getSpi(String key) {
571 return values.get(key);
575 protected void removeSpi(String key) {
580 protected void removeNodeSpi() throws BackingStoreException {
581 ((MemoryPreferences) parent()).nodes.remove(name());
585 protected String[] keysSpi() throws BackingStoreException {
586 return values.keySet().toArray(new String[0]);
590 protected String[] childrenNamesSpi() throws BackingStoreException {
591 return nodes.keySet().toArray(new String[0]);
595 protected AbstractPreferences childSpi(String name) {
596 MemoryPreferences result = nodes.get(name);
598 if (result == null) {
599 nodes.put(name, result = new MemoryPreferences(this, name));
606 protected void syncSpi() throws BackingStoreException {
610 protected void flushSpi() throws BackingStoreException {
614 private enum GroupResult {
617 public GroupResult join(GroupResult other) {
623 public GroupResult join(GroupResult other) {
624 if (other == NOTHING_TO_DO) return this;
630 public GroupResult join(GroupResult other) {
631 if (other == FAILURE) return other;
637 public GroupResult join(GroupResult other) {
642 public abstract GroupResult join(GroupResult other);
645 private static Iterable<? extends HintDescription> findHints(ClassPath sourceFrom, ClassPath binaryFrom, String name, HintsSettings toEnableIn) {
646 List<HintDescription> descs = new LinkedList<HintDescription>();
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);
658 private static Iterable<? extends HintDescription> allHints(ClassPath sourceFrom, ClassPath binaryFrom, HintsSettings toEnableIn) {
659 List<HintDescription> descs = new LinkedList<HintDescription>();
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);
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>();
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());
682 if (/*XXX: hack*/toEnableInPreferencesHack.node(entry.getKey().id).getBoolean("enabled", declarativeEnabledByDefault)) {
683 descs.addAll(entry.getValue());
691 private static final Logger TOP_LOGGER = Logger.getLogger("");
693 private static void prepareLoggers() {
694 TOP_LOGGER.setLevel(Level.OFF);
695 System.setProperty("RepositoryUpdate.increasedLogLevel", "OFF");
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);
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);
712 @Override public void groupFinished() {}
713 @Override public void cannotVerifySpan(Resource r) {
714 //TODO: ignored - what to do?
716 }, problems, new AtomicBoolean());
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();
725 for (int i = 0; i < column; i++) {
726 if (Character.isWhitespace(line.charAt(i))) {
727 b.append(line.charAt(i));
735 String idDisplayName = Utils.categoryName(error.getId(), id2DisplayName);
737 if (error.getSeverity() == Severity.ERROR) {
741 severity = "warning";
744 System.out.println(FileUtil.getFileDisplayName(error.getFile()) + ":" + (lineNumber + 1) + ": " + severity + ": " + idDisplayName + error.getDescription());
745 System.out.println(line);
746 System.out.println(b);
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);
753 List<MessageImpl> problems = new LinkedList<MessageImpl>();
754 Collection<ModificationResult> diffs = BatchUtilities.applyFixes(occurrences, w, new AtomicBoolean(), problems);
757 for (ModificationResult mr : diffs) {
758 org.netbeans.modules.jackpot30.indexing.batch.BatchUtilities.exportDiff(mr, null, out);
761 for (ModificationResult mr : diffs) {
767 private static void printHints(ClassPath sourceFrom, ClassPath binaryFrom) throws IOException {
768 Set<String> hints = new TreeSet<String>();
770 for (Entry<HintMetadata, Collection<? extends HintDescription>> e : listHints(sourceFrom, binaryFrom).entrySet()) {
771 hints.add(e.getKey().displayName);
774 for (String h : hints) {
775 System.out.println(h);
779 private static ClassPath createDefaultBootClassPath() throws IOException {
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")));
785 for (String path : paths) {
786 File f = new File(path);
791 FileObject fo = FileUtil.toFileObject(FileUtil.normalizeFile(f));
793 if (FileUtil.isArchiveFile(fo)) {
794 fo = FileUtil.getArchiveRoot(fo);
798 urls.add(fo.getURL());
802 return ClassPathSupport.createClassPath(urls.toArray(new URL[0]));
803 } catch (FileStateInvalidException e) {
808 private static ClassPath createClassPath(Iterable<? extends File> roots, ClassPath def) {
809 if (roots == null) return def;
811 List<URL> rootURLs = new ArrayList<URL>();
813 for (File r : roots) {
814 rootURLs.add(FileUtil.urlForArchiveOrDir(r));
817 return ClassPathSupport.createClassPath(rootURLs.toArray(new URL[0]));
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");
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());
836 JPanel customizer = new JPanel(new BorderLayout());
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");
843 jop.selectInitialValue();
844 dialog.setVisible(true);
847 Object result = jop.getValue();
849 if (result.equals(JOptionPane.OK_OPTION)) {
854 static String[] splitGroupArg(String arg) {
855 List<String> result = new ArrayList<>();
856 StringBuilder currentPart = new StringBuilder();
858 for (int i = 0; i < arg.length(); i++) {
859 switch (arg.charAt(i)) {
861 if (++i < arg.length()) {
862 currentPart.append(arg.charAt(i));
866 if (currentPart.length() > 0) {
867 result.add(currentPart.toString());
868 currentPart.delete(0, currentPart.length());
872 currentPart.append(arg.charAt(i));
877 if (currentPart.length() > 0) {
878 result.add(currentPart.toString());
881 return result.toArray(new String[0]);
884 private static final class WarningsAndErrors {
885 private int warnings;
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;
897 public RootConfiguration(OptionSet parsed, GroupOptions groupOptions) throws IOException {
898 this.rootFolders = new ArrayList<>();
900 List<FileObject> roots = new ArrayList<>();
902 for (String sr : parsed.nonOptionArguments()) {
903 File r = new File(sr);
904 FileObject root = FileUtil.toFileObject(r);
908 rootFolders.add(new Folder(root));
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);
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;
931 public GlobalConfiguration(Preferences configurationPreferences, boolean apply, boolean runDeclarative, boolean runDeclarativeTests, String hint, File hintFile, Writer out, boolean failOnWarnings) {
932 this.configurationPreferences = configurationPreferences;
934 this.runDeclarative = runDeclarative;
935 this.runDeclarativeTests = runDeclarativeTests;
937 this.hintFile = hintFile;
939 this.failOnWarnings = failOnWarnings;
944 @ServiceProvider(service=Lookup.class)
945 public static final class LookupProviderImpl extends ProxyLookup {
947 public LookupProviderImpl() {
948 super(Lookups.forPath("Services/AntBasedProjectTypes"));
952 public static final class ClassPathProviderImpl implements ClassPathProvider {
953 private final ClassPath boot;
954 private final ClassPath compile;
955 private final ClassPath source;
957 public ClassPathProviderImpl(ClassPath boot, ClassPath compile, ClassPath source) {
959 this.compile = compile;
960 this.source = source;
964 public ClassPath findClassPath(FileObject file, String type) {
965 if (source.findOwnerRoot(file) != null) {
966 if (ClassPath.BOOT.equals(type)) {
968 } else if (ClassPath.COMPILE.equals(type)) {
970 } else if (ClassPath.SOURCE.equals(type)) {
979 public static final class SourceLevelQueryImpl implements SourceLevelQueryImplementation2 {
980 private final ClassPath sourceCP;
981 private final Result sourceLevel;
983 public SourceLevelQueryImpl(ClassPath sourceCP, final String sourceLevel) {
984 this.sourceCP = sourceCP;
985 this.sourceLevel = new Result() {
986 @Override public String getSourceLevel() {
989 @Override public void addChangeListener(ChangeListener listener) {}
990 @Override public void removeChangeListener(ChangeListener listener) {}
995 public Result getSourceLevel(FileObject javaFile) {
996 if (sourceCP.findOwnerRoot(javaFile) != null) {
1005 private static final class ConsoleProgressHandleAbstraction implements ProgressHandleAbstraction {
1007 private final int width = 80 - 2;
1009 private int total = -1;
1010 private int current = 0;
1012 public ConsoleProgressHandleAbstraction() {
1016 public synchronized void start(int totalWork) {
1017 if (total != (-1)) throw new UnsupportedOperationException();
1023 public synchronized void progress(int currentWorkDone) {
1024 current = currentWorkDone;
1029 public void progress(String message) {
1033 public synchronized void finish() {
1035 RequestProcessor.getDefault().post(new Runnable() {
1039 System.out.println();
1044 private void update() {
1045 RequestProcessor.getDefault().post(new Runnable() {
1053 private int currentShownDone = -1;
1055 private void doUpdate(boolean moveCaret) {
1058 synchronized(this) {
1059 done = (int) ((((double) width) / total) * current);
1061 if (done == currentShownDone) {
1065 currentShownDone = done;
1068 int todo = width - done;
1069 PrintStream pw = System.out;
1074 while (done-- > 0) {
1078 while (todo-- > 0) {