| /** |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| package org.netbeans.modules.jackpot30.cmdline; |
| |
| import java.io.BufferedWriter; |
| import java.io.File; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.io.OutputStreamWriter; |
| import java.io.PrintStream; |
| import java.io.UncheckedIOException; |
| import java.io.Writer; |
| import java.lang.reflect.InvocationTargetException; |
| import java.net.URL; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import java.util.Set; |
| import java.util.TreeSet; |
| import java.util.concurrent.atomic.AtomicBoolean; |
| import java.util.logging.Level; |
| import java.util.logging.Logger; |
| import java.util.prefs.AbstractPreferences; |
| import java.util.prefs.BackingStoreException; |
| import java.util.prefs.Preferences; |
| import java.util.regex.Pattern; |
| import java.util.stream.Collectors; |
| import javax.swing.SwingUtilities; |
| import javax.swing.event.ChangeListener; |
| import joptsimple.ArgumentAcceptingOptionSpec; |
| import joptsimple.OptionException; |
| import joptsimple.OptionParser; |
| import joptsimple.OptionSet; |
| import org.netbeans.api.java.classpath.ClassPath; |
| import org.netbeans.api.java.source.CompilationController; |
| import org.netbeans.api.java.source.ModificationResult; |
| import org.netbeans.modules.editor.tools.storage.api.ToolPreferences; |
| import org.netbeans.modules.jackpot30.cmdline.lib.Utils; |
| //import org.netbeans.modules.jackpot30.ui.settings.XMLHintPreferences; |
| import org.netbeans.modules.java.hints.declarative.DeclarativeHintRegistry; |
| import org.netbeans.modules.java.hints.declarative.test.TestParser; |
| import org.netbeans.modules.java.hints.declarative.test.TestParser.TestCase; |
| import org.netbeans.modules.java.hints.declarative.test.TestPerformer; |
| import org.netbeans.modules.java.hints.declarative.test.TestPerformer.TestClassPathProvider; |
| import org.netbeans.modules.java.hints.jackpot.spi.PatternConvertor; |
| import org.netbeans.modules.java.hints.providers.spi.HintDescription; |
| import org.netbeans.modules.java.hints.providers.spi.HintMetadata; |
| import org.netbeans.modules.java.hints.providers.spi.HintMetadata.Options; |
| import org.netbeans.modules.java.hints.spiimpl.MessageImpl; |
| import org.netbeans.modules.java.hints.spiimpl.RulesManager; |
| import org.netbeans.modules.java.hints.spiimpl.batch.BatchSearch; |
| import org.netbeans.modules.java.hints.spiimpl.batch.BatchSearch.BatchResult; |
| import org.netbeans.modules.java.hints.spiimpl.batch.BatchSearch.Folder; |
| import org.netbeans.modules.java.hints.spiimpl.batch.BatchSearch.Resource; |
| import org.netbeans.modules.java.hints.spiimpl.batch.BatchSearch.VerifiedSpansCallBack; |
| import org.netbeans.modules.java.hints.spiimpl.batch.BatchUtilities; |
| import org.netbeans.modules.java.hints.spiimpl.batch.ProgressHandleWrapper; |
| import org.netbeans.modules.java.hints.spiimpl.batch.ProgressHandleWrapper.ProgressHandleAbstraction; |
| import org.netbeans.modules.java.hints.spiimpl.batch.Scopes; |
| import org.netbeans.modules.java.hints.spiimpl.options.HintsSettings; |
| import org.netbeans.modules.parsing.impl.indexing.CacheFolder; |
| import org.netbeans.modules.parsing.impl.indexing.RepositoryUpdater; |
| import org.netbeans.modules.refactoring.spi.RefactoringElementImplementation; |
| import org.netbeans.spi.editor.hints.ErrorDescription; |
| import org.netbeans.spi.editor.hints.ErrorDescriptionFactory; |
| import org.netbeans.spi.editor.hints.Fix; |
| import org.netbeans.spi.editor.hints.Severity; |
| import org.netbeans.spi.java.classpath.ClassPathProvider; |
| import org.netbeans.spi.java.classpath.support.ClassPathSupport; |
| import org.netbeans.spi.java.hints.Hint.Kind; |
| import org.netbeans.spi.java.queries.SourceLevelQueryImplementation2; |
| import org.openide.filesystems.FileObject; |
| import org.openide.filesystems.FileUtil; |
| import org.openide.util.Exceptions; |
| import org.openide.util.Lookup; |
| import org.openide.util.Pair; |
| import org.openide.util.RequestProcessor; |
| import org.openide.util.lookup.Lookups; |
| import org.openide.util.lookup.ProxyLookup; |
| import org.openide.util.lookup.ServiceProvider; |
| |
| /** |
| * |
| * @author lahvac |
| */ |
| public class Main { |
| |
| private static final String OPTION_APPLY = "apply"; |
| private static final String OPTION_NO_APPLY = "no-apply"; |
| private static final String OPTION_FAIL_ON_WARNINGS = "fail-on-warnings"; |
| private static final String RUN_TESTS = "run-tests"; |
| private static final String SOURCE_LEVEL_DEFAULT = "1.7"; |
| private static final String ACCEPTABLE_SOURCE_LEVEL_PATTERN = "(1\\.)?[2-9][0-9]*"; |
| |
| public static void main(String... args) throws IOException, ClassNotFoundException { |
| System.exit(compile(args)); |
| } |
| |
| public static int compile(String... args) throws IOException, ClassNotFoundException { |
| try { |
| Class.forName("javax.lang.model.element.ModuleElement"); |
| } catch (ClassNotFoundException ex) { |
| System.err.println("Error: no suitable javac found, please run on JDK 11+."); |
| return 1; |
| } |
| System.setProperty("netbeans.user", "/tmp/tmp-foo"); |
| System.setProperty("SourcePath.no.source.filter", "true"); |
| |
| OptionParser parser = new OptionParser(); |
| // ArgumentAcceptingOptionSpec<File> projects = parser.accepts("project", "project(s) to refactor").withRequiredArg().withValuesSeparatedBy(File.pathSeparatorChar).ofType(File.class); |
| GroupOptions globalGroupOptions = setupGroupParser(parser); |
| ArgumentAcceptingOptionSpec<File> cache = parser.accepts("cache", "a cache directory to store working data").withRequiredArg().ofType(File.class); |
| ArgumentAcceptingOptionSpec<File> out = parser.accepts("out", "output diff").withRequiredArg().ofType(File.class); |
| ArgumentAcceptingOptionSpec<File> configFile = parser.accepts("config-file", "configuration file").withRequiredArg().ofType(File.class); |
| ArgumentAcceptingOptionSpec<String> hint = parser.accepts("hint", "hint name").withRequiredArg().ofType(String.class); |
| ArgumentAcceptingOptionSpec<String> config = parser.accepts("config", "configurations").withRequiredArg().ofType(String.class); |
| ArgumentAcceptingOptionSpec<File> hintFile = parser.accepts("hint-file", "file with rules that should be performed").withRequiredArg().ofType(File.class); |
| ArgumentAcceptingOptionSpec<String> group = parser.accepts("group", "specify roots to process alongside with their classpath").withRequiredArg().ofType(String.class); |
| |
| parser.accepts("list", "list all known hints"); |
| parser.accepts("progress", "show progress"); |
| parser.accepts("debug", "enable debugging loggers"); |
| parser.accepts("help", "prints this help"); |
| parser.accepts(OPTION_NO_APPLY, "do not apply changes - only print locations were the hint would be applied"); |
| parser.accepts(OPTION_APPLY, "apply changes"); |
| parser.accepts("show-gui", "show configuration dialog"); |
| parser.accepts(OPTION_FAIL_ON_WARNINGS, "fail when warnings are detected"); |
| parser.accepts(RUN_TESTS, "run tests for declarative rules that were used"); |
| |
| OptionSet parsed; |
| |
| try { |
| parsed = parser.parse(args); |
| } catch (OptionException ex) { |
| System.err.println(ex.getLocalizedMessage()); |
| parser.printHelpOn(System.out); |
| return 1; |
| } |
| |
| if (!parsed.has("debug")) { |
| prepareLoggers(); |
| } |
| |
| if (parsed.has("help")) { |
| parser.printHelpOn(System.out); |
| return 0; |
| } |
| |
| List<FileObject> roots = new ArrayList<FileObject>(); |
| List<Folder> rootFolders = new ArrayList<Folder>(); |
| |
| for (String sr : parsed.nonOptionArguments()) { |
| File r = new File(sr); |
| FileObject root = FileUtil.toFileObject(r); |
| |
| if (root != null) { |
| roots.add(root); |
| rootFolders.add(new Folder(root)); |
| } |
| } |
| |
| final List<RootConfiguration> groups = new ArrayList<>(); |
| |
| groups.add(new RootConfiguration(parsed, globalGroupOptions)); |
| |
| for (String groupValue : parsed.valuesOf(group)) { |
| OptionParser groupParser = new OptionParser(); |
| GroupOptions groupOptions = setupGroupParser(groupParser); |
| OptionSet parsedGroup = groupParser.parse(splitGroupArg(groupValue)); |
| |
| groups.add(new RootConfiguration(parsedGroup, groupOptions)); |
| } |
| |
| if (parsed.has("show-gui")) { |
| if (parsed.has(configFile)) { |
| final File settingsFile = parsed.valueOf(configFile); |
| try { |
| SwingUtilities.invokeAndWait(new Runnable() { |
| @Override public void run() { |
| try { |
| Pair<ClassPath, ClassPath> sourceAndBinaryCP = jointSourceAndBinaryCP(groups); |
| showGUICustomizer(settingsFile, sourceAndBinaryCP.second(), sourceAndBinaryCP.first()); |
| } catch (IOException ex) { |
| Exceptions.printStackTrace(ex); |
| } catch (BackingStoreException ex) { |
| Exceptions.printStackTrace(ex); |
| } |
| } |
| }); |
| } catch (InterruptedException ex) { |
| Exceptions.printStackTrace(ex); |
| } catch (InvocationTargetException ex) { |
| Exceptions.printStackTrace(ex); |
| } |
| |
| return 0; |
| } else { |
| System.err.println("show-gui requires config-file"); |
| return 1; |
| } |
| } |
| |
| File cacheDir = parsed.valueOf(cache); |
| boolean deleteCacheDir = false; |
| |
| try { |
| if (cacheDir == null) { |
| cacheDir = File.createTempFile("jackpot", "cache"); |
| cacheDir.delete(); |
| if (!(deleteCacheDir = cacheDir.mkdirs())) { |
| System.err.println("cannot create temporary cache"); |
| return 1; |
| } |
| } |
| |
| if (cacheDir.isFile()) { |
| System.err.println("cache directory exists and is a file"); |
| return 1; |
| } |
| |
| String[] cacheDirContent = cacheDir.list(); |
| |
| if (cacheDirContent != null && cacheDirContent.length > 0 && !new File(cacheDir, "segments").exists()) { |
| System.err.println("cache directory is not empty, but was not created by this tool"); |
| return 1; |
| } |
| |
| cacheDir.mkdirs(); |
| |
| CacheFolder.setCacheFolder(FileUtil.toFileObject(FileUtil.normalizeFile(cacheDir))); |
| |
| org.netbeans.api.project.ui.OpenProjects.getDefault().getOpenProjects(); |
| RepositoryUpdater.getDefault().start(false); |
| |
| if (parsed.has("list")) { |
| Pair<ClassPath, ClassPath> sourceAndBinaryCP = jointSourceAndBinaryCP(groups); |
| printHints(sourceAndBinaryCP.first(), |
| sourceAndBinaryCP.second()); |
| return 0; |
| } |
| |
| int totalGroups = 0; |
| |
| for (RootConfiguration groupConfig : groups) { |
| if (!groupConfig.rootFolders.isEmpty()) totalGroups++; |
| } |
| |
| ProgressHandleWrapper progress = parsed.has("progress") ? new ProgressHandleWrapper(new ConsoleProgressHandleAbstraction(), ProgressHandleWrapper.prepareParts(totalGroups)) : new ProgressHandleWrapper(1); |
| |
| Preferences hintSettingsPreferences; |
| boolean apply; |
| boolean runDeclarative; |
| boolean runDeclarativeTests; |
| boolean useDefaultEnabledSetting; |
| |
| if (parsed.has(configFile)) { |
| ToolPreferences toolPrefs = ToolPreferences.from(parsed.valueOf(configFile).toURI()); |
| hintSettingsPreferences = toolPrefs.getPreferences("hints", "text/x-java"); |
| Preferences toolSettings = toolPrefs.getPreferences("standalone", "text/x-java"); |
| apply = toolSettings.getBoolean("apply", false); |
| runDeclarative = toolSettings.getBoolean("runDeclarative", true); |
| runDeclarativeTests = toolSettings.getBoolean("runDeclarativeTests", false); |
| useDefaultEnabledSetting = true; //TODO: read from the configuration file? |
| if (parsed.has(hint)) { |
| System.err.println("cannot specify --hint and --config-file together"); |
| return 1; |
| } else if (parsed.has(hintFile)) { |
| System.err.println("cannot specify --hint-file and --config-file together"); |
| return 1; |
| } |
| } else { |
| hintSettingsPreferences = null; |
| apply = false; |
| runDeclarative = true; |
| runDeclarativeTests = parsed.has(RUN_TESTS); |
| useDefaultEnabledSetting = false; |
| } |
| |
| if (parsed.has(config) && !parsed.has(hint)) { |
| System.err.println("--config cannot specified when no hint is specified"); |
| return 1; |
| } |
| |
| if (parsed.has(OPTION_NO_APPLY)) { |
| apply = false; |
| } else if (parsed.has(OPTION_APPLY)) { |
| apply = true; |
| } |
| |
| GroupResult result = GroupResult.NOTHING_TO_DO; |
| |
| try (Writer outS = parsed.has(out) ? new BufferedWriter(new OutputStreamWriter(new FileOutputStream(parsed.valueOf(out)))) : null) { |
| GlobalConfiguration globalConfig = new GlobalConfiguration(hintSettingsPreferences, apply, runDeclarative, runDeclarativeTests, useDefaultEnabledSetting, parsed.valueOf(hint), parsed.valueOf(hintFile), outS, parsed.has(OPTION_FAIL_ON_WARNINGS)); |
| |
| for (RootConfiguration groupConfig : groups) { |
| result = result.join(handleGroup(groupConfig, progress, globalConfig, parsed.valuesOf(config))); |
| } |
| } |
| |
| progress.finish(); |
| |
| if (result == GroupResult.NOTHING_TO_DO) { |
| System.err.println("no source roots to work on"); |
| return 1; |
| } |
| |
| if (result == GroupResult.NO_HINTS_FOUND) { |
| System.err.println("no hints specified"); |
| return 1; |
| } |
| |
| return result == GroupResult.SUCCESS ? 0 : 1; |
| } catch (Throwable e) { |
| e.printStackTrace(); |
| throw new IllegalStateException(e); |
| } finally { |
| if (deleteCacheDir) { |
| FileObject cacheDirFO = FileUtil.toFileObject(cacheDir); |
| |
| if (cacheDirFO != null) { |
| //TODO: would be better to do j.i.File.delete(): |
| cacheDirFO.delete(); |
| } |
| } |
| } |
| } |
| |
| private static Pair<ClassPath, ClassPath> jointSourceAndBinaryCP(List<RootConfiguration> groups) { |
| Set<FileObject> sourceRoots = new HashSet<>(); |
| Set<FileObject> binaryRoots = new HashSet<>(); |
| for (RootConfiguration groupConfig : groups) { |
| sourceRoots.addAll(Arrays.asList(groupConfig.sourceCP.getRoots())); |
| binaryRoots.addAll(Arrays.asList(groupConfig.binaryCP.getRoots())); |
| } |
| return Pair.of(ClassPathSupport.createClassPath(sourceRoots.toArray(new FileObject[0])), |
| ClassPathSupport.createClassPath(binaryRoots.toArray(new FileObject[0]))); |
| } |
| |
| private static GroupOptions setupGroupParser(OptionParser parser) { |
| return new GroupOptions(parser.accepts("classpath", "classpath").withRequiredArg().withValuesSeparatedBy(File.pathSeparatorChar).ofType(File.class), |
| parser.accepts("bootclasspath", "bootclasspath").withRequiredArg().withValuesSeparatedBy(File.pathSeparatorChar).ofType(File.class), |
| parser.accepts("sourcepath", "sourcepath").withRequiredArg().withValuesSeparatedBy(File.pathSeparatorChar).ofType(File.class), |
| parser.accepts("source", "source level").withRequiredArg().ofType(String.class).defaultsTo(SOURCE_LEVEL_DEFAULT)); |
| } |
| |
| private static final class GroupOptions { |
| private final ArgumentAcceptingOptionSpec<File> classpath; |
| private final ArgumentAcceptingOptionSpec<File> bootclasspath; |
| private final ArgumentAcceptingOptionSpec<File> sourcepath; |
| private final ArgumentAcceptingOptionSpec<String> source; |
| |
| public GroupOptions(ArgumentAcceptingOptionSpec<File> classpath, ArgumentAcceptingOptionSpec<File> bootclasspath, ArgumentAcceptingOptionSpec<File> sourcepath, ArgumentAcceptingOptionSpec<String> source) { |
| this.classpath = classpath; |
| this.bootclasspath = bootclasspath; |
| this.sourcepath = sourcepath; |
| this.source = source; |
| } |
| |
| } |
| |
| private static Map<HintMetadata, Collection<? extends HintDescription>> listHints(ClassPath sourceFrom, ClassPath binaryFrom) { |
| Map<HintMetadata, Collection<? extends HintDescription>> result = new HashMap<HintMetadata, Collection<? extends HintDescription>>(); |
| |
| for (Entry<HintMetadata, ? extends Collection<? extends HintDescription>> entry: RulesManager.getInstance().readHints(null, Arrays.asList(sourceFrom, binaryFrom), null).entrySet()) { |
| result.put(entry.getKey(), entry.getValue()); |
| } |
| |
| return result; |
| } |
| |
| private static GroupResult handleGroup(RootConfiguration rootConfiguration, ProgressHandleWrapper w, GlobalConfiguration globalConfig, List<String> config) throws IOException { |
| Iterable<? extends HintDescription> hints; |
| |
| if (rootConfiguration.rootFolders.isEmpty()) { |
| return GroupResult.NOTHING_TO_DO; |
| } |
| |
| WarningsAndErrors wae = new WarningsAndErrors(); |
| |
| ProgressHandleWrapper progress = w.startNextPartWithEmbedding(1); |
| Preferences settings = globalConfig.configurationPreferences != null ? globalConfig.configurationPreferences : new MemoryPreferences(); |
| HintsSettings hintSettings = HintsSettings.createPreferencesBasedHintsSettings(settings, globalConfig.useDefaultEnabledSetting, null); |
| |
| if (globalConfig.hint != null) { |
| hints = findHints(rootConfiguration.sourceCP, rootConfiguration.binaryCP, globalConfig.hint, hintSettings); |
| } else if (globalConfig.hintFile != null) { |
| FileObject hintFileFO = FileUtil.toFileObject(globalConfig.hintFile); |
| assert hintFileFO != null; |
| hints = PatternConvertor.create(hintFileFO.asText()); |
| for (HintDescription hd : hints) { |
| hintSettings.setEnabled(hd.getMetadata(), true); |
| } |
| } else { |
| hints = readHints(rootConfiguration.sourceCP, rootConfiguration.binaryCP, hintSettings, settings, globalConfig.runDeclarative); |
| if (globalConfig.runDeclarativeTests) { |
| Set<String> enabledHints = new HashSet<>(); |
| for (HintDescription desc : hints) { |
| enabledHints.add(desc.getMetadata().id); |
| } |
| ClassPath combined = ClassPathSupport.createProxyClassPath(rootConfiguration.sourceCP, rootConfiguration.binaryCP); |
| Map<FileObject, FileObject> testFiles = new HashMap<>(); |
| for (FileObject upgrade : combined.findAllResources("META-INF/upgrade")) { |
| for (FileObject c : upgrade.getChildren()) { |
| if (c.getExt().equals("test")) { |
| FileObject hintFile = FileUtil.findBrother(c, "hint"); |
| |
| for (HintMetadata hm : DeclarativeHintRegistry.parseHintFile(hintFile).keySet()) { |
| if (enabledHints.contains(hm.id)) { |
| testFiles.put(c, hintFile); |
| break; |
| } |
| } |
| } |
| } |
| } |
| for (Entry<FileObject, FileObject> e : testFiles.entrySet()) { |
| TestCase[] testCases = TestParser.parse(e.getKey().asText()); //XXX: encoding |
| try { |
| Map<TestCase, Collection<String>> testResult = TestPerformer.performTest(e.getValue(), e.getKey(), testCases, new AtomicBoolean()); |
| for (TestCase tc : testCases) { |
| List<String> expected = Arrays.asList(tc.getResults()); |
| List<String> actual = new ArrayList<>(testResult.get(tc)); |
| if (!expected.equals(actual)) { |
| int pos = tc.getTestCaseStart(); |
| String id = "test-failure"; |
| 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); |
| print(ed, wae, Collections.singletonMap(id, id)); |
| } |
| } |
| } catch (Exception ex) { |
| ex.printStackTrace(); |
| } |
| } |
| } |
| } |
| |
| if (config != null && !config.isEmpty()) { |
| Iterator<? extends HintDescription> hit = hints.iterator(); |
| HintDescription hd = hit.next(); |
| |
| if (hit.hasNext()) { |
| System.err.println("--config cannot specified when more than one hint is specified"); |
| |
| return GroupResult.FAILURE; |
| } |
| |
| Preferences prefs = hintSettings.getHintPreferences(hd.getMetadata()); |
| |
| boolean stop = false; |
| |
| for (String c : config) { |
| int assign = c.indexOf('='); |
| |
| if (assign == (-1)) { |
| System.err.println("configuration option is missing '=' (" + c + ")"); |
| stop = true; |
| continue; |
| } |
| |
| prefs.put(c.substring(0, assign), c.substring(assign + 1)); |
| } |
| |
| if (stop) { |
| return GroupResult.FAILURE; |
| } |
| } |
| |
| String sourceLevel = rootConfiguration.sourceLevel; |
| |
| if (!Pattern.compile(ACCEPTABLE_SOURCE_LEVEL_PATTERN).matcher(sourceLevel).matches()) { |
| System.err.println("unrecognized source level specification: " + sourceLevel); |
| return GroupResult.FAILURE; |
| } |
| |
| if (globalConfig.apply && !hints.iterator().hasNext()) { |
| return GroupResult.NO_HINTS_FOUND; |
| } |
| |
| RootConfiguration prevConfig = currentRootConfiguration.get(); |
| |
| try { |
| currentRootConfiguration.set(rootConfiguration); |
| |
| try { |
| if (globalConfig.apply) { |
| apply(hints, rootConfiguration.rootFolders.toArray(new Folder[0]), progress, hintSettings, globalConfig.out); |
| |
| return GroupResult.SUCCESS; //TODO: WarningsAndErrors? |
| } else { |
| findOccurrences(hints, rootConfiguration.rootFolders.toArray(new Folder[0]), progress, hintSettings, wae); |
| |
| if (wae.errors != 0 || (wae.warnings != 0 && globalConfig.failOnWarnings)) { |
| return GroupResult.FAILURE; |
| } else { |
| return GroupResult.SUCCESS; |
| } |
| } |
| } catch (IOException t) { |
| throw new UncheckedIOException(t); |
| } |
| } finally { |
| currentRootConfiguration.set(prevConfig); |
| } |
| } |
| |
| private static class MemoryPreferences extends AbstractPreferences { |
| |
| private final Map<String, String> values = new HashMap<>(); |
| private final Map<String, MemoryPreferences> nodes = new HashMap<>(); |
| |
| public MemoryPreferences() { |
| this(null, ""); |
| } |
| |
| public MemoryPreferences(MemoryPreferences parent, String name) { |
| super(parent, name); |
| } |
| @Override |
| protected void putSpi(String key, String value) { |
| values.put(key, value); |
| } |
| |
| @Override |
| protected String getSpi(String key) { |
| return values.get(key); |
| } |
| |
| @Override |
| protected void removeSpi(String key) { |
| values.remove(key); |
| } |
| |
| @Override |
| protected void removeNodeSpi() throws BackingStoreException { |
| ((MemoryPreferences) parent()).nodes.remove(name()); |
| } |
| |
| @Override |
| protected String[] keysSpi() throws BackingStoreException { |
| return values.keySet().toArray(new String[0]); |
| } |
| |
| @Override |
| protected String[] childrenNamesSpi() throws BackingStoreException { |
| return nodes.keySet().toArray(new String[0]); |
| } |
| |
| @Override |
| protected AbstractPreferences childSpi(String name) { |
| MemoryPreferences result = nodes.get(name); |
| |
| if (result == null) { |
| nodes.put(name, result = new MemoryPreferences(this, name)); |
| } |
| |
| return result; |
| } |
| |
| @Override |
| protected void syncSpi() throws BackingStoreException { |
| } |
| |
| @Override |
| protected void flushSpi() throws BackingStoreException { |
| } |
| } |
| |
| private enum GroupResult { |
| NOTHING_TO_DO { |
| @Override |
| public GroupResult join(GroupResult other) { |
| return other; |
| } |
| }, |
| NO_HINTS_FOUND { |
| @Override |
| public GroupResult join(GroupResult other) { |
| if (other == NOTHING_TO_DO) return this; |
| return other; |
| } |
| }, |
| SUCCESS { |
| @Override |
| public GroupResult join(GroupResult other) { |
| if (other == FAILURE) return other; |
| return this; |
| } |
| }, |
| FAILURE { |
| @Override |
| public GroupResult join(GroupResult other) { |
| return this; |
| } |
| }; |
| |
| public abstract GroupResult join(GroupResult other); |
| } |
| |
| private static Iterable<? extends HintDescription> findHints(ClassPath sourceFrom, ClassPath binaryFrom, String name, HintsSettings toEnableIn) { |
| List<HintDescription> descs = new LinkedList<HintDescription>(); |
| |
| for (Entry<HintMetadata, Collection<? extends HintDescription>> e : listHints(sourceFrom, binaryFrom).entrySet()) { |
| if (e.getKey().displayName.equals(name)) { |
| descs.addAll(e.getValue()); |
| toEnableIn.setEnabled(e.getKey(), true); |
| } |
| } |
| |
| return descs; |
| } |
| |
| private static Iterable<? extends HintDescription> allHints(ClassPath sourceFrom, ClassPath binaryFrom, HintsSettings toEnableIn) { |
| List<HintDescription> descs = new LinkedList<HintDescription>(); |
| |
| for (Entry<HintMetadata, Collection<? extends HintDescription>> e : listHints(sourceFrom, binaryFrom).entrySet()) { |
| if (e.getKey().kind != Kind.INSPECTION) continue; |
| if (!e.getKey().enabled) continue; |
| descs.addAll(e.getValue()); |
| toEnableIn.setEnabled(e.getKey(), true); |
| } |
| |
| return descs; |
| } |
| |
| private static Iterable<? extends HintDescription> readHints(ClassPath sourceFrom, ClassPath binaryFrom, HintsSettings toEnableIn, Preferences toEnableInPreferencesHack, boolean declarativeEnabledByDefault) { |
| Map<HintMetadata, ? extends Collection<? extends HintDescription>> hardcoded = RulesManager.getInstance().readHints(null, Arrays.<ClassPath>asList(), null); |
| Map<HintMetadata, ? extends Collection<? extends HintDescription>> all = RulesManager.getInstance().readHints(null, Arrays.asList(sourceFrom, binaryFrom), null); |
| List<HintDescription> descs = new LinkedList<HintDescription>(); |
| |
| for (Entry<HintMetadata, ? extends Collection<? extends HintDescription>> entry: all.entrySet()) { |
| if (hardcoded.containsKey(entry.getKey())) { |
| if (toEnableIn.isEnabled(entry.getKey()) && entry.getKey().kind == Kind.INSPECTION && !entry.getKey().options.contains(Options.NO_BATCH)) { |
| descs.addAll(entry.getValue()); |
| } |
| } else { |
| if (/*XXX: hack*/toEnableInPreferencesHack.node(entry.getKey().id).getBoolean("enabled", declarativeEnabledByDefault)) { |
| descs.addAll(entry.getValue()); |
| } |
| } |
| } |
| |
| return descs; |
| } |
| |
| private static final Logger TOP_LOGGER = Logger.getLogger(""); |
| |
| private static void prepareLoggers() { |
| TOP_LOGGER.setLevel(Level.OFF); |
| System.setProperty("RepositoryUpdate.increasedLogLevel", "OFF"); |
| } |
| |
| private static void findOccurrences(Iterable<? extends HintDescription> descs, Folder[] sourceRoot, ProgressHandleWrapper progress, HintsSettings settings, final WarningsAndErrors wae) throws IOException { |
| final Map<String, String> id2DisplayName = Utils.computeId2DisplayName(descs); |
| ProgressHandleWrapper w = progress.startNextPartWithEmbedding(1, 1); |
| BatchResult occurrences = BatchSearch.findOccurrences(descs, Scopes.specifiedFoldersScope(sourceRoot), w, settings); |
| |
| List<MessageImpl> problems = new LinkedList<MessageImpl>(); |
| BatchSearch.getVerifiedSpans(occurrences, w, new VerifiedSpansCallBack() { |
| @Override public void groupStarted() {} |
| @Override public boolean spansVerified(CompilationController wc, Resource r, Collection<? extends ErrorDescription> hints) throws Exception { |
| hints = hints.stream() |
| .sorted((ed1, ed2) -> ed1.getRange().getBegin().getOffset() - ed2.getRange().getBegin().getOffset()) |
| .collect(Collectors.toList()); |
| for (ErrorDescription ed : hints) { |
| print(ed, wae, id2DisplayName); |
| } |
| return true; |
| } |
| @Override public void groupFinished() {} |
| @Override public void cannotVerifySpan(Resource r) { |
| //TODO: ignored - what to do? |
| } |
| }, true, problems, new AtomicBoolean()); |
| } |
| |
| private static void print(ErrorDescription error, WarningsAndErrors wae, Map<String, String> id2DisplayName) throws IOException { |
| int lineNumber = error.getRange().getBegin().getLine(); |
| String line = error.getFile().asLines().get(lineNumber); |
| int column = error.getRange().getBegin().getColumn(); |
| StringBuilder b = new StringBuilder(); |
| |
| for (int i = 0; i < column; i++) { |
| if (Character.isWhitespace(line.charAt(i))) { |
| b.append(line.charAt(i)); |
| } else { |
| b.append(' '); |
| } |
| } |
| |
| b.append('^'); |
| |
| String idDisplayName = Utils.categoryName(error.getId(), id2DisplayName); |
| String severity; |
| if (error.getSeverity() == Severity.ERROR) { |
| severity = "error"; |
| wae.errors++; |
| } else { |
| severity = "warning"; |
| wae.warnings++; |
| } |
| System.out.println(FileUtil.getFileDisplayName(error.getFile()) + ":" + (lineNumber + 1) + ": " + severity + ": " + idDisplayName + error.getDescription()); |
| System.out.println(line); |
| System.out.println(b); |
| } |
| |
| private static void apply(Iterable<? extends HintDescription> descs, Folder[] sourceRoot, ProgressHandleWrapper progress, HintsSettings settings, Writer out) throws IOException { |
| ProgressHandleWrapper w = progress.startNextPartWithEmbedding(1, 1); |
| BatchResult occurrences = BatchSearch.findOccurrences(descs, Scopes.specifiedFoldersScope(sourceRoot), w, settings); |
| |
| List<MessageImpl> problems = new LinkedList<MessageImpl>(); |
| Collection<ModificationResult> diffs = BatchUtilities.applyFixes(occurrences, w, new AtomicBoolean(), new ArrayList<RefactoringElementImplementation>(), null, true, problems); |
| |
| if (out != null) { |
| for (ModificationResult mr : diffs) { |
| //XXX: |
| // org.netbeans.modules.jackpot30.indexing.batch.BatchUtilities.exportDiff(mr, null, out); |
| } |
| } else { |
| for (ModificationResult mr : diffs) { |
| mr.commit(); |
| } |
| } |
| } |
| |
| private static void printHints(ClassPath sourceFrom, ClassPath binaryFrom) throws IOException { |
| Set<String> hints = new TreeSet<String>(); |
| |
| for (Entry<HintMetadata, Collection<? extends HintDescription>> e : listHints(sourceFrom, binaryFrom).entrySet()) { |
| hints.add(e.getKey().displayName); |
| } |
| |
| for (String h : hints) { |
| System.out.println(h); |
| } |
| } |
| |
| private static ClassPath createClassPath(Iterable<? extends File> roots, ClassPath def) { |
| if (roots == null) return def; |
| |
| List<URL> rootURLs = new ArrayList<URL>(); |
| |
| for (File r : roots) { |
| rootURLs.add(FileUtil.urlForArchiveOrDir(r)); |
| } |
| |
| return ClassPathSupport.createClassPath(rootURLs.toArray(new URL[0])); |
| } |
| |
| private static void showGUICustomizer(File settingsFile, ClassPath binaryCP, ClassPath sourceCP) throws IOException, BackingStoreException { |
| // GlobalPathRegistry.getDefault().register(ClassPath.COMPILE, new ClassPath[] {binaryCP}); |
| // GlobalPathRegistry.getDefault().register(ClassPath.SOURCE, new ClassPath[] {sourceCP}); |
| // ClassPathBasedHintWrapper hints = new ClassPathBasedHintWrapper(); |
| // final Preferences p = XMLHintPreferences.from(settingsFile); |
| // JPanel hintPanel = new HintsPanel(p.node("settings"), hints, true); |
| // final JCheckBox runDeclarativeHints = new JCheckBox("Always Run Declarative Rules"); |
| // |
| // runDeclarativeHints.setToolTipText("Always run the declarative rules found on classpath? (Only those selected above will be run when unchecked.)"); |
| // runDeclarativeHints.setSelected(p.getBoolean("runDeclarative", true)); |
| // runDeclarativeHints.addActionListener(new ActionListener() { |
| // @Override public void actionPerformed(ActionEvent e) { |
| // p.putBoolean("runDeclarative", runDeclarativeHints.isSelected()); |
| // } |
| // }); |
| // |
| // JPanel customizer = new JPanel(new BorderLayout()); |
| // |
| // customizer.add(hintPanel, BorderLayout.CENTER); |
| // customizer.add(runDeclarativeHints, BorderLayout.SOUTH); |
| // JOptionPane jop = new JOptionPane(customizer, JOptionPane.PLAIN_MESSAGE); |
| // JDialog dialog = jop.createDialog("Select Hints"); |
| // |
| // jop.selectInitialValue(); |
| // dialog.setVisible(true); |
| // dialog.dispose(); |
| // |
| // Object result = jop.getValue(); |
| // |
| // if (result.equals(JOptionPane.OK_OPTION)) { |
| // p.flush(); |
| // } |
| } |
| |
| static String[] splitGroupArg(String arg) { |
| List<String> result = new ArrayList<>(); |
| StringBuilder currentPart = new StringBuilder(); |
| |
| for (int i = 0; i < arg.length(); i++) { |
| switch (arg.charAt(i)) { |
| case '\\': |
| if (++i < arg.length()) { |
| currentPart.append(arg.charAt(i)); |
| } |
| break; |
| case ' ': |
| if (currentPart.length() > 0) { |
| result.add(currentPart.toString()); |
| currentPart.delete(0, currentPart.length()); |
| } |
| break; |
| default: |
| currentPart.append(arg.charAt(i)); |
| break; |
| } |
| } |
| |
| if (currentPart.length() > 0) { |
| result.add(currentPart.toString()); |
| } |
| |
| return result.toArray(new String[0]); |
| } |
| |
| private static final class WarningsAndErrors { |
| private int warnings; |
| private int errors; |
| } |
| |
| private static final class RootConfiguration { |
| private final List<Folder> rootFolders; |
| private final ClassPath bootCP; |
| private final ClassPath compileCP; |
| private final ClassPath sourceCP; |
| private final ClassPath binaryCP; |
| private final String sourceLevel; |
| |
| public RootConfiguration(OptionSet parsed, GroupOptions groupOptions) throws IOException { |
| this.rootFolders = new ArrayList<>(); |
| |
| List<FileObject> roots = new ArrayList<>(); |
| |
| for (String sr : parsed.nonOptionArguments()) { |
| File r = new File(sr); |
| FileObject root = FileUtil.toFileObject(r); |
| |
| if (root != null) { |
| roots.add(root); |
| rootFolders.add(new Folder(root)); |
| } |
| } |
| |
| this.bootCP = createClassPath(parsed.has(groupOptions.bootclasspath) ? parsed.valuesOf(groupOptions.bootclasspath) : null, Utils.createDefaultBootClassPath()); |
| this.compileCP = createClassPath(parsed.has(groupOptions.classpath) ? parsed.valuesOf(groupOptions.classpath) : null, ClassPath.EMPTY); |
| this.sourceCP = createClassPath(parsed.has(groupOptions.sourcepath) ? parsed.valuesOf(groupOptions.sourcepath) : null, ClassPathSupport.createClassPath(roots.toArray(new FileObject[0]))); |
| this.binaryCP = ClassPathSupport.createProxyClassPath(bootCP, compileCP); |
| this.sourceLevel = parsed.valueOf(groupOptions.source); |
| } |
| |
| } |
| |
| private static final class GlobalConfiguration { |
| private final Preferences configurationPreferences; |
| private final boolean apply; |
| private final boolean runDeclarative; |
| private final boolean runDeclarativeTests; |
| private final boolean useDefaultEnabledSetting; |
| private final String hint; |
| private final File hintFile; |
| private final Writer out; |
| private final boolean failOnWarnings; |
| |
| public GlobalConfiguration(Preferences configurationPreferences, boolean apply, boolean runDeclarative, boolean runDeclarativeTests, boolean useDefaultEnabledSetting, String hint, File hintFile, Writer out, boolean failOnWarnings) { |
| this.configurationPreferences = configurationPreferences; |
| this.apply = apply; |
| this.runDeclarative = runDeclarative; |
| this.runDeclarativeTests = runDeclarativeTests; |
| this.useDefaultEnabledSetting = useDefaultEnabledSetting; |
| this.hint = hint; |
| this.hintFile = hintFile; |
| this.out = out; |
| this.failOnWarnings = failOnWarnings; |
| } |
| |
| } |
| |
| @ServiceProvider(service=Lookup.class) |
| public static final class LookupProviderImpl extends ProxyLookup { |
| |
| public LookupProviderImpl() { |
| super(Lookups.forPath("Services/AntBasedProjectTypes")); |
| } |
| } |
| |
| private static final ThreadLocal<RootConfiguration> currentRootConfiguration = new ThreadLocal<>(); |
| |
| @ServiceProvider(service=ClassPathProvider.class, position=100) |
| public static final class ClassPathProviderImpl implements ClassPathProvider { |
| |
| @Override |
| public ClassPath findClassPath(FileObject file, String type) { |
| RootConfiguration rootConfiguration = currentRootConfiguration.get(); |
| |
| if (rootConfiguration == null) { |
| return null; |
| } |
| |
| if (rootConfiguration.sourceCP.findOwnerRoot(file) != null) { |
| if (ClassPath.BOOT.equals(type)) { |
| return rootConfiguration.bootCP; |
| } else if (ClassPath.COMPILE.equals(type)) { |
| return rootConfiguration.compileCP; |
| } else if (ClassPath.SOURCE.equals(type)) { |
| return rootConfiguration.sourceCP; |
| } |
| } |
| |
| return null; |
| } |
| } |
| |
| @ServiceProvider(service=SourceLevelQueryImplementation2.class, position=100) |
| public static final class SourceLevelQueryImpl implements SourceLevelQueryImplementation2 { |
| |
| @Override |
| public Result getSourceLevel(FileObject javaFile) { |
| RootConfiguration rootConfiguration = currentRootConfiguration.get(); |
| |
| if (rootConfiguration == null) { |
| return null; |
| } |
| |
| if (rootConfiguration.sourceCP.findOwnerRoot(javaFile) != null) { |
| return new Result() { |
| @Override public String getSourceLevel() { |
| return rootConfiguration.sourceLevel; |
| } |
| @Override public void addChangeListener(ChangeListener listener) {} |
| @Override public void removeChangeListener(ChangeListener listener) {} |
| }; |
| } else { |
| return null; |
| } |
| } |
| |
| } |
| |
| private static final class ConsoleProgressHandleAbstraction implements ProgressHandleAbstraction { |
| |
| private final int width = 80 - 2; |
| |
| private int total = -1; |
| private int current = 0; |
| |
| public ConsoleProgressHandleAbstraction() { |
| } |
| |
| @Override |
| public synchronized void start(int totalWork) { |
| if (total != (-1)) throw new UnsupportedOperationException(); |
| total = totalWork; |
| update(); |
| } |
| |
| @Override |
| public synchronized void progress(int currentWorkDone) { |
| current = currentWorkDone; |
| update(); |
| } |
| |
| @Override |
| public void progress(String message) { |
| } |
| |
| @Override |
| public synchronized void finish() { |
| current = total; |
| RequestProcessor.getDefault().post(new Runnable() { |
| @Override |
| public void run() { |
| doUpdate(false); |
| System.out.println(); |
| } |
| }); |
| } |
| |
| private void update() { |
| RequestProcessor.getDefault().post(new Runnable() { |
| @Override |
| public void run() { |
| doUpdate(true); |
| } |
| }); |
| } |
| |
| private int currentShownDone = -1; |
| |
| private void doUpdate(boolean moveCaret) { |
| int done; |
| |
| synchronized(this) { |
| done = (int) ((((double) width) / total) * current); |
| |
| if (done == currentShownDone) { |
| return; |
| } |
| |
| currentShownDone = done; |
| } |
| |
| int todo = width - done; |
| PrintStream pw = System.out; |
| |
| pw.print("["); |
| |
| |
| while (done-- > 0) { |
| pw.print("="); |
| } |
| |
| while (todo-- > 0) { |
| pw.print(" "); |
| } |
| |
| pw.print("]"); |
| |
| if (moveCaret) |
| pw.print("\r"); |
| } |
| |
| } |
| |
| @ServiceProvider(service=ClassPathProvider.class, position=9999/*DefaultClassPathProvider has 10000*/) |
| public static final class BCPFallBack implements ClassPathProvider { |
| |
| @Override |
| public ClassPath findClassPath(FileObject file, String type) { |
| //hack, TestClassPathProvider does not have a reasonable position: |
| for (ClassPathProvider p : Lookup.getDefault().lookupAll(ClassPathProvider.class)) { |
| if (p instanceof TestClassPathProvider) { |
| ClassPath cp = ((TestClassPathProvider) p).findClassPath(file, type); |
| |
| if (cp != null) { |
| return cp; |
| } |
| } |
| } |
| |
| if (ClassPath.BOOT.equals(type)) { |
| return Utils.createDefaultBootClassPath(); |
| } |
| return null; |
| } |
| |
| } |
| } |