| /* |
| * 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.analysis; |
| |
| import java.awt.Dialog; |
| import java.awt.event.ActionEvent; |
| import java.awt.event.ActionListener; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.IdentityHashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import java.util.Set; |
| import java.util.concurrent.atomic.AtomicBoolean; |
| import java.util.concurrent.atomic.AtomicReference; |
| import java.util.logging.Level; |
| import java.util.logging.Logger; |
| import java.util.prefs.Preferences; |
| import javax.swing.JButton; |
| import javax.swing.SwingUtilities; |
| import org.netbeans.api.annotations.common.NonNull; |
| import org.netbeans.api.annotations.common.NullAllowed; |
| import org.netbeans.api.fileinfo.NonRecursiveFolder; |
| import org.netbeans.api.java.classpath.ClassPath; |
| import org.netbeans.api.java.classpath.GlobalPathRegistry; |
| import org.netbeans.api.progress.ProgressHandle; |
| import org.netbeans.api.progress.ProgressHandleFactory; |
| import org.netbeans.api.project.FileOwnerQuery; |
| import org.netbeans.api.project.Project; |
| import org.netbeans.modules.analysis.RunAnalysisPanel.DialogState; |
| import org.netbeans.modules.analysis.RunAnalysisPanel.FutureWarnings; |
| import org.netbeans.modules.analysis.spi.AnalysisScopeProvider; |
| import org.netbeans.modules.analysis.spi.Analyzer; |
| import org.netbeans.modules.analysis.spi.Analyzer.AnalyzerFactory; |
| import org.netbeans.modules.analysis.spi.Analyzer.Context; |
| import org.netbeans.modules.analysis.spi.Analyzer.MissingPlugin; |
| import org.netbeans.modules.analysis.spi.Analyzer.Result; |
| import org.netbeans.modules.analysis.ui.AnalysisProblemNode; |
| import org.netbeans.modules.analysis.ui.AnalysisResultTopComponent; |
| import org.netbeans.modules.analysis.ui.RequiredPluginsNode; |
| import org.netbeans.modules.parsing.spi.indexing.PathRecognizer; |
| import org.netbeans.modules.refactoring.api.Scope; |
| import org.netbeans.spi.editor.hints.ErrorDescription; |
| import org.openide.DialogDescriptor; |
| import org.openide.DialogDisplayer; |
| import org.openide.filesystems.FileObject; |
| import org.openide.nodes.Node; |
| import org.openide.util.HelpCtx; |
| import org.openide.util.Lookup; |
| import org.openide.util.NbBundle.Messages; |
| import org.openide.util.RequestProcessor; |
| import org.openide.util.Utilities; |
| import org.openide.util.lookup.Lookups; |
| |
| /** |
| * |
| * @author lahvac |
| */ |
| public class RunAnalysis { |
| |
| private static final Logger LOG = Logger.getLogger(RunAnalysis.class.getName()); |
| private static final RequestProcessor WORKER = new RequestProcessor(RunAnalysisAction.class.getName(), 1, false, false); |
| private static final int MAX_WORK = 1000; |
| |
| public static void showDialogAndRunAnalysis() { |
| showDialogAndRunAnalysis(Lookups.fixed(Utilities.actionsGlobalContext().lookupAll(Object.class).toArray(new Object[0])), null); |
| } |
| |
| @Messages({"BN_Inspect=Inspect", |
| "BN_Cancel=Cancel", |
| "TL_Inspect=Inspect"}) |
| public static void showDialogAndRunAnalysis(final Lookup context, DialogState startingState) { |
| final ProgressHandle progress = ProgressHandleFactory.createHandle("Analyzing...", null, null); |
| final JButton runAnalysis = new JButton(Bundle.BN_Inspect()); |
| final RunAnalysisPanel rap = new RunAnalysisPanel(progress, context, runAnalysis, startingState); |
| JButton cancel = new JButton(Bundle.BN_Cancel()); |
| HelpCtx helpCtx = new HelpCtx("org.netbeans.modules.analysis.RunAnalysis"); |
| DialogDescriptor dd = new DialogDescriptor(rap, Bundle.TL_Inspect(), true, new Object[] {runAnalysis, cancel}, runAnalysis, DialogDescriptor.DEFAULT_ALIGN, helpCtx, null); |
| dd.setClosingOptions(new Object[0]); |
| final Dialog d = DialogDisplayer.getDefault().createDialog(dd); |
| final AtomicBoolean doCancel = new AtomicBoolean(); |
| final AtomicReference<Analyzer> currentlyRunning = new AtomicReference<Analyzer>(); |
| |
| runAnalysis.addActionListener(new ActionListener() { |
| @Override public void actionPerformed(ActionEvent e) { |
| final long analysisStart = System.currentTimeMillis(); |
| |
| runAnalysis.setEnabled(false); |
| |
| final AnalyzerFactory toRun = rap.getSelectedAnalyzer(); |
| final Configuration configuration = rap.getConfiguration(); |
| final String singleWarningId = rap.getSingleWarningId(); |
| final Collection<? extends AnalyzerFactory> analyzers = rap.getAnalyzers(); |
| final DialogState dialogState = rap.getDialogState(); |
| final FutureWarnings analyzerId2Description = rap.getAnalyzerId2Description(); |
| |
| rap.started(); |
| progress.start(); |
| |
| WORKER.post(new Runnable() { |
| @Override public void run() { |
| dialogState.save(); |
| |
| Scope scope = rap.getSelectedScope(doCancel); |
| |
| progress.switchToDeterminate(MAX_WORK); |
| |
| final Map<AnalyzerFactory, List<ErrorDescription>> result = new HashMap<AnalyzerFactory, List<ErrorDescription>>(); |
| final Map<ErrorDescription, Project> errorsToProjects = new IdentityHashMap<>(); |
| Collection<MissingPlugin> missingPlugins = new ArrayList<MissingPlugin>(); |
| Collection<AnalysisProblem> additionalProblems = new ArrayList<AnalysisProblem>(); |
| |
| if (toRun == null) { |
| int doneSoFar = 0; |
| int bucketSize = MAX_WORK / analyzers.size(); |
| for (AnalyzerFactory analyzer : analyzers) { |
| if (doCancel.get()) break; |
| doRunAnalyzer(analyzer, scope, progress, doneSoFar, bucketSize, result, errorsToProjects, missingPlugins, additionalProblems); |
| doneSoFar += bucketSize; |
| } |
| } else if (!doCancel.get()) { |
| doRunAnalyzer(toRun, scope, progress, 0, MAX_WORK, result, errorsToProjects, missingPlugins, additionalProblems); |
| } |
| |
| final Collection<Node> extraNodes = new ArrayList<Node>(); |
| |
| if (!missingPlugins.isEmpty()) { |
| extraNodes.add(new RequiredPluginsNode(missingPlugins)); |
| } |
| |
| for (AnalysisProblem p : additionalProblems) { |
| extraNodes.add(new AnalysisProblemNode(p)); |
| } |
| |
| SwingUtilities.invokeLater(new Runnable() { |
| @Override public void run() { |
| if (!doCancel.get()) { |
| AnalysisResultTopComponent resultWindow = AnalysisResultTopComponent.findInstance(); |
| resultWindow.setData(context, dialogState, new AnalysisResult(result, errorsToProjects, analyzerId2Description, extraNodes)); |
| resultWindow.open(); |
| resultWindow.requestActive(); |
| } |
| |
| d.setVisible(false); |
| d.dispose(); |
| |
| LOG.log(Level.FINE, "Total analysis time: {0}", System.currentTimeMillis() - analysisStart); |
| } |
| }); |
| } |
| |
| private void doRunAnalyzer(AnalyzerFactory analyzer, Scope scope, ProgressHandle handle, int bucketStart, int bucketSize, final Map<AnalyzerFactory, List<ErrorDescription>> result, final Map<ErrorDescription, Project> errorsToProjects, Collection<MissingPlugin> missingPlugins, Collection<AnalysisProblem> additionalProblems) { |
| List<ErrorDescription> current = new ArrayList<ErrorDescription>(); |
| Preferences settings = configuration != null ? configuration.getPreferences().node(SPIAccessor.ACCESSOR.getAnalyzerId(analyzer)) : null; |
| Context context = SPIAccessor.ACCESSOR.createContext(scope, settings, singleWarningId, handle, bucketStart, bucketSize); |
| Result resCollector = SPIAccessor.ACCESSOR.createResult(current, errorsToProjects, additionalProblems); |
| Collection<? extends MissingPlugin> requiredPlugins = analyzer.requiredPlugins(context); |
| if (!requiredPlugins.isEmpty()) { |
| missingPlugins.addAll(requiredPlugins); |
| return ; |
| } |
| Analyzer a = analyzer.createAnalyzer(context, resCollector); |
| currentlyRunning.set(a); |
| if (doCancel.get()) return; |
| long s = System.currentTimeMillis(); |
| for (ErrorDescription ed : a.analyze()) { |
| current.add(ed); |
| } |
| LOG.log(Level.FINE, "Analysis by {0} took {1}", new Object[] {SPIAccessor.ACCESSOR.getAnalyzerDisplayName(analyzer), System.currentTimeMillis() - s}); |
| currentlyRunning.set(null); |
| if (!current.isEmpty()) |
| result.put(analyzer, current); |
| additionalProblems.addAll(SPIAccessor.ACCESSOR.getAnalysisProblems(context)); |
| } |
| }); |
| } |
| }); |
| |
| cancel.addActionListener(new ActionListener() { |
| @Override public void actionPerformed(ActionEvent e) { |
| doCancel.set(true); |
| Analyzer a = currentlyRunning.get(); |
| |
| if (a != null) a.cancel(); |
| |
| d.setVisible(false); |
| d.dispose(); |
| } |
| }); |
| |
| d.setVisible(true); |
| } |
| |
| |
| static Map<Project, Map<FileObject, ClassPath>> projects2RegisteredContent(AtomicBoolean cancel) { |
| Set<String> sourceIds = new HashSet<String>(); |
| |
| for (PathRecognizer pr : Lookup.getDefault().lookupAll(PathRecognizer.class)) { |
| Set<String> ids = pr.getSourcePathIds(); |
| |
| if (ids == null) continue; |
| |
| sourceIds.addAll(ids); |
| } |
| |
| Map<Project, Map<FileObject, ClassPath>> sourceRoots = new IdentityHashMap<Project, Map<FileObject, ClassPath>>(); |
| |
| for (String id : sourceIds) { |
| for (ClassPath sCP : GlobalPathRegistry.getDefault().getPaths(id)) { |
| for (FileObject root : sCP.getRoots()) { |
| if (cancel.get()) return null; |
| Project owner = FileOwnerQuery.getOwner(root); |
| |
| if (owner != null) { |
| Map<FileObject, ClassPath> projectSources = sourceRoots.get(owner); |
| |
| if (projectSources == null) { |
| sourceRoots.put(owner, projectSources = new HashMap<FileObject, ClassPath>()); |
| } |
| |
| projectSources.put(root, sCP); |
| } |
| } |
| } |
| } |
| |
| return sourceRoots; |
| } |
| |
| static Scope addProjectToScope(Project p, Scope target, AtomicBoolean cancel, Map<Project, Map<FileObject, ClassPath>> projects2RegisteredContent) { |
| Scope projectScope = p.getLookup().lookup(Scope.class); |
| |
| if (projectScope == null) { |
| AnalysisScopeProvider scopeProvider = p.getLookup().lookup(AnalysisScopeProvider.class); |
| |
| projectScope = scopeProvider != null ? scopeProvider.getScope() : null; |
| } |
| |
| if (projectScope != null) { |
| return augment(target, projectScope.getSourceRoots(), projectScope.getFolders(), projectScope.getFiles()); |
| } |
| |
| Map<FileObject, ClassPath> roots = projects2RegisteredContent.get(p); |
| |
| if (roots != null) { |
| for (Entry<FileObject, ClassPath> e : roots.entrySet()) { |
| if (cancel.get()) return null; |
| target = augment(target, Collections.singletonList(e.getKey()), null, null); |
| } |
| } |
| |
| return target; |
| } |
| |
| private static @NonNull Scope augment(@NonNull Scope source, @NullAllowed Collection<FileObject> sourceRoots, |
| @NullAllowed Collection<NonRecursiveFolder> folders, |
| @NullAllowed Collection<FileObject> files) { |
| Collection<FileObject> sourceRootsSet = new HashSet<FileObject>(source.getSourceRoots()); |
| if (sourceRoots != null) { |
| sourceRootsSet.addAll(sourceRoots); |
| } |
| Collection<FileObject> filesSet = new HashSet<FileObject>(source.getFiles()); |
| if (files != null) { |
| filesSet.addAll(files); |
| } |
| Collection<NonRecursiveFolder> foldersSet = new HashSet<NonRecursiveFolder>(source.getFolders()); |
| if (folders != null) { |
| foldersSet.addAll(folders); |
| } |
| return Scope.create(sourceRootsSet, foldersSet, filesSet); |
| } |
| } |