summaryrefslogtreecommitdiff
path: root/platform/structuralsearch/source/com/intellij/structuralsearch/inspection
diff options
context:
space:
mode:
Diffstat (limited to 'platform/structuralsearch/source/com/intellij/structuralsearch/inspection')
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/inspection/highlightTemplate/SSBasedInspection.java186
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/inspection/highlightTemplate/SSBasedInspectionCompiledPatternsCache.java78
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/inspection/highlightTemplate/SSBasedInspectionOptions.java256
3 files changed, 520 insertions, 0 deletions
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/inspection/highlightTemplate/SSBasedInspection.java b/platform/structuralsearch/source/com/intellij/structuralsearch/inspection/highlightTemplate/SSBasedInspection.java
new file mode 100644
index 000000000000..7a38ddb2ea65
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/inspection/highlightTemplate/SSBasedInspection.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright 2000-2014 JetBrains s.r.o.
+ *
+ * Licensed 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 com.intellij.structuralsearch.inspection.highlightTemplate;
+
+import com.intellij.codeInsight.FileModificationService;
+import com.intellij.codeInspection.*;
+import com.intellij.dupLocator.iterators.CountingNodeIterator;
+import com.intellij.notification.Notification;
+import com.intellij.notification.NotificationType;
+import com.intellij.notification.Notifications;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.InvalidDataException;
+import com.intellij.openapi.util.Pair;
+import com.intellij.openapi.util.WriteExternalException;
+import com.intellij.profile.codeInspection.InspectionProfileManager;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiElementVisitor;
+import com.intellij.structuralsearch.MatchResult;
+import com.intellij.structuralsearch.Matcher;
+import com.intellij.structuralsearch.SSRBundle;
+import com.intellij.structuralsearch.StructuralSearchException;
+import com.intellij.structuralsearch.impl.matcher.MatchContext;
+import com.intellij.structuralsearch.impl.matcher.MatcherImpl;
+import com.intellij.structuralsearch.impl.matcher.filters.LexicalNodesFilter;
+import com.intellij.structuralsearch.impl.matcher.iterators.SsrFilteringNodeIterator;
+import com.intellij.structuralsearch.plugin.replace.ReplacementInfo;
+import com.intellij.structuralsearch.plugin.replace.impl.Replacer;
+import com.intellij.structuralsearch.plugin.replace.ui.ReplaceConfiguration;
+import com.intellij.structuralsearch.plugin.ui.Configuration;
+import com.intellij.structuralsearch.plugin.ui.ConfigurationManager;
+import com.intellij.structuralsearch.plugin.ui.SearchContext;
+import com.intellij.util.PairProcessor;
+import org.jdom.Element;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.annotations.TestOnly;
+
+import javax.swing.*;
+import java.util.*;
+
+/**
+ * @author cdr
+ */
+public class SSBasedInspection extends LocalInspectionTool {
+ static final String SHORT_NAME = "SSBasedInspection";
+ private List<Configuration> myConfigurations = new ArrayList<Configuration>();
+ private Set<String> myProblemsReported = new HashSet<String>(1);
+
+ public void writeSettings(@NotNull Element node) throws WriteExternalException {
+ ConfigurationManager.writeConfigurations(node, myConfigurations, Collections.<Configuration>emptyList());
+ }
+
+ public void readSettings(@NotNull Element node) throws InvalidDataException {
+ myProblemsReported.clear();
+ myConfigurations.clear();
+ ConfigurationManager.readConfigurations(node, myConfigurations, new ArrayList<Configuration>());
+ }
+
+ @NotNull
+ public String getGroupDisplayName() {
+ return GENERAL_GROUP_NAME;
+ }
+
+ @NotNull
+ public String getDisplayName() {
+ return SSRBundle.message("SSRInspection.display.name");
+ }
+
+ @NotNull
+ @NonNls
+ public String getShortName() {
+ return SHORT_NAME;
+ }
+
+ @NotNull
+ @Override
+ public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, final boolean isOnTheFly) {
+ final MatcherImpl.CompiledOptions compiledOptions =
+ SSBasedInspectionCompiledPatternsCache.getCompiledOptions(holder.getProject());
+
+ if (compiledOptions == null) return super.buildVisitor(holder, isOnTheFly);
+
+ return new PsiElementVisitor() {
+ final List<Pair<MatchContext,Configuration>> contexts = compiledOptions.getMatchContexts();
+ final Matcher matcher = new Matcher(holder.getManager().getProject());
+ final PairProcessor<MatchResult, Configuration> processor = new PairProcessor<MatchResult, Configuration>() {
+ public boolean process(MatchResult matchResult, Configuration configuration) {
+ PsiElement element = matchResult.getMatch();
+ String name = configuration.getName();
+ LocalQuickFix fix = createQuickFix(holder.getManager().getProject(), matchResult, configuration);
+ holder.registerProblem(
+ holder.getManager().createProblemDescriptor(element, name, fix, ProblemHighlightType.GENERIC_ERROR_OR_WARNING, isOnTheFly)
+ );
+ return true;
+ }
+ };
+
+ @Override
+ public void visitElement(PsiElement element) {
+ if (LexicalNodesFilter.getInstance().accepts(element)) return;
+ final SsrFilteringNodeIterator matchedNodes = new SsrFilteringNodeIterator(element);
+ for (Pair<MatchContext, Configuration> pair : contexts) {
+ Configuration configuration = pair.second;
+ MatchContext context = pair.first;
+
+ if (MatcherImpl.checkIfShouldAttemptToMatch(context, matchedNodes)) {
+ final int nodeCount = context.getPattern().getNodeCount();
+ try {
+ matcher.processMatchesInElement(context, configuration, new CountingNodeIterator(nodeCount, matchedNodes), processor);
+ }
+ catch (StructuralSearchException e) {
+ if (myProblemsReported.add(configuration.getName())) { // don't overwhelm the user with messages
+ Notifications.Bus.notify(new Notification(SSRBundle.message("structural.search.title"),
+ SSRBundle.message("template.problem", configuration.getName()),
+ e.getMessage(),
+ NotificationType.ERROR), element.getProject());
+ }
+ }
+ matchedNodes.reset();
+ }
+ }
+ }
+ };
+ }
+
+ private static LocalQuickFix createQuickFix(final Project project, final MatchResult matchResult, final Configuration configuration) {
+ if (!(configuration instanceof ReplaceConfiguration)) return null;
+ ReplaceConfiguration replaceConfiguration = (ReplaceConfiguration)configuration;
+ final Replacer replacer = new Replacer(project, replaceConfiguration.getOptions());
+ final ReplacementInfo replacementInfo = replacer.buildReplacement(matchResult);
+
+ return new LocalQuickFix() {
+ @NotNull
+ public String getName() {
+ return SSRBundle.message("SSRInspection.replace.with", replacementInfo.getReplacement());
+ }
+
+ public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
+ PsiElement element = descriptor.getPsiElement();
+ if (element != null && FileModificationService.getInstance().preparePsiElementsForWrite(element)) {
+ replacer.replace(replacementInfo);
+ }
+ }
+
+ @NotNull
+ public String getFamilyName() {
+ return SSRBundle.message("SSRInspection.family.name");
+ }
+ };
+ }
+
+ @Nullable
+ public JComponent createOptionsPanel() {
+ return new SSBasedInspectionOptions(myConfigurations){
+ public void configurationsChanged(final SearchContext searchContext) {
+ super.configurationsChanged(searchContext);
+ SSBasedInspectionCompiledPatternsCache.precompileConfigurations(searchContext.getProject(), SSBasedInspection.this);
+ InspectionProfileManager.getInstance().fireProfileChanged(null);
+ }
+ }.getComponent();
+ }
+
+ @TestOnly
+ public void setConfigurations(final List<Configuration> configurations, final Project project) {
+ myConfigurations = configurations;
+ SSBasedInspectionCompiledPatternsCache.setCompiledOptions(project, configurations);
+ }
+
+ public List<Configuration> getConfigurations() {
+ return myConfigurations;
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/inspection/highlightTemplate/SSBasedInspectionCompiledPatternsCache.java b/platform/structuralsearch/source/com/intellij/structuralsearch/inspection/highlightTemplate/SSBasedInspectionCompiledPatternsCache.java
new file mode 100644
index 000000000000..2d6335fe10ce
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/inspection/highlightTemplate/SSBasedInspectionCompiledPatternsCache.java
@@ -0,0 +1,78 @@
+package com.intellij.structuralsearch.inspection.highlightTemplate;
+
+import com.intellij.codeInspection.InspectionProfile;
+import com.intellij.codeInspection.ex.InspectionToolWrapper;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.startup.StartupActivity;
+import com.intellij.openapi.util.Key;
+import com.intellij.profile.codeInspection.InspectionProjectProfileManager;
+import com.intellij.structuralsearch.Matcher;
+import com.intellij.structuralsearch.impl.matcher.MatcherImpl;
+import com.intellij.structuralsearch.plugin.ui.Configuration;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.annotations.TestOnly;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @author Eugene.Kudelevsky
+ */
+public class SSBasedInspectionCompiledPatternsCache implements StartupActivity {
+ private static final Key<MatcherImpl.CompiledOptions> COMPILED_OPTIONS_KEY = Key.create("SSR_INSPECTION_COMPILED_OPTIONS_KEY");
+
+ @Override
+ public void runActivity(@NotNull final Project project) {
+ precompileConfigurations(project, null);
+ }
+
+ static void precompileConfigurations(final Project project, @Nullable final SSBasedInspection ssBasedInspection) {
+ if (project.isDisposed()) {
+ return;
+ }
+ final MatcherImpl.CompiledOptions currentCompiledOptions = getCompiledOptions(project);
+
+ final SSBasedInspection inspection = ssBasedInspection != null ? ssBasedInspection : getInspection(project);
+ if (inspection == null) {
+ return;
+ }
+
+ List<Configuration> configurations = inspection.getConfigurations();
+ if (configurations == null) {
+ configurations = Collections.emptyList();
+ }
+
+ if ((currentCompiledOptions == null || currentCompiledOptions.getMatchContexts().isEmpty()) &&
+ configurations.isEmpty()) {
+ return;
+ }
+
+ final Matcher matcher = new Matcher(project);
+ final MatcherImpl.CompiledOptions compiledOptions = matcher.precompileOptions(configurations);
+
+ if (compiledOptions != null) {
+ project.putUserData(COMPILED_OPTIONS_KEY, compiledOptions);
+ }
+ }
+
+ @Nullable
+ private static SSBasedInspection getInspection(@NotNull Project project) {
+ final InspectionProfile profile = InspectionProjectProfileManager.getInstance(project).getInspectionProfile();
+ final InspectionToolWrapper entry = profile.getInspectionTool(SSBasedInspection.SHORT_NAME, project);
+
+ return entry == null ? null : (SSBasedInspection)entry.getTool();
+ }
+
+ @Nullable
+ static MatcherImpl.CompiledOptions getCompiledOptions(@NotNull Project project) {
+ return project.getUserData(COMPILED_OPTIONS_KEY);
+ }
+
+ @TestOnly
+ static void setCompiledOptions(@NotNull Project project, @NotNull List<Configuration> configurations) {
+ final Matcher matcher = new Matcher(project);
+ project.putUserData(COMPILED_OPTIONS_KEY,
+ matcher.precompileOptions(configurations));
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/inspection/highlightTemplate/SSBasedInspectionOptions.java b/platform/structuralsearch/source/com/intellij/structuralsearch/inspection/highlightTemplate/SSBasedInspectionOptions.java
new file mode 100644
index 000000000000..8dd7211eefb4
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/inspection/highlightTemplate/SSBasedInspectionOptions.java
@@ -0,0 +1,256 @@
+/*
+ * Copyright 2000-2014 JetBrains s.r.o.
+ *
+ * Licensed 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 com.intellij.structuralsearch.inspection.highlightTemplate;
+
+import com.intellij.ide.DataManager;
+import com.intellij.openapi.actionSystem.ActionManager;
+import com.intellij.openapi.actionSystem.AnAction;
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.openapi.actionSystem.DefaultActionGroup;
+import com.intellij.openapi.ui.popup.JBPopupFactory;
+import com.intellij.structuralsearch.SSRBundle;
+import com.intellij.structuralsearch.plugin.replace.ui.ReplaceConfiguration;
+import com.intellij.structuralsearch.plugin.replace.ui.ReplaceDialog;
+import com.intellij.structuralsearch.plugin.ui.*;
+import com.intellij.ui.AnActionButton;
+import com.intellij.ui.AnActionButtonRunnable;
+import com.intellij.ui.DoubleClickListener;
+import com.intellij.ui.ToolbarDecorator;
+import com.intellij.ui.components.JBList;
+import org.jdom.Element;
+import org.jetbrains.annotations.NonNls;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.MouseEvent;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * @author cdr
+ */
+public class SSBasedInspectionOptions {
+ private JBList myTemplatesList;
+ // for externalization
+ private final List<Configuration> myConfigurations;
+
+ public SSBasedInspectionOptions(final List<Configuration> configurations) {
+ myConfigurations = configurations;
+ myTemplatesList = new JBList(new MyListModel());
+ myTemplatesList.setCellRenderer(new DefaultListCellRenderer() {
+ public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
+ JLabel component = (JLabel)super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
+ Configuration configuration = myConfigurations.get(index);
+ component.setText(configuration.getName());
+ return component;
+ }
+ });
+ }
+
+ private static void copyConfiguration(final Configuration configuration, final Configuration newConfiguration) {
+ @NonNls Element temp = new Element("temp");
+ configuration.writeExternal(temp);
+ newConfiguration.readExternal(temp);
+ }
+
+ interface SearchDialogFactory {
+ SearchDialog createDialog(SearchContext searchContext);
+ }
+
+ private void addTemplate(SearchDialogFactory searchDialogFactory) {
+ SearchDialog dialog = createDialog(searchDialogFactory);
+ dialog.show();
+ if (!dialog.isOK()) return;
+ Configuration configuration = dialog.getConfiguration();
+
+ if (configuration.getName() == null || configuration.getName().equals(SearchDialog.USER_DEFINED)) {
+ String name = dialog.showSaveTemplateAsDialog();
+
+ if (name != null) {
+ name = ConfigurationManager.findAppropriateName(myConfigurations, name, dialog.getProject());
+ }
+ if (name == null) return;
+ configuration.setName(name);
+ }
+ myConfigurations.add(configuration);
+
+ configurationsChanged(dialog.getSearchContext());
+ }
+
+ private static SearchDialog createDialog(final SearchDialogFactory searchDialogFactory) {
+ SearchContext searchContext = createSearchContext();
+ return searchDialogFactory.createDialog(searchContext);
+ }
+
+ private static SearchContext createSearchContext() {
+ AnActionEvent event = new AnActionEvent(null, DataManager.getInstance().getDataContext(),
+ "", new DefaultActionGroup().getTemplatePresentation(), ActionManager.getInstance(), 0);
+ return SearchContext.buildFromDataContext(event.getDataContext());
+ }
+
+ public void configurationsChanged(final SearchContext searchContext) {
+ ((MyListModel)myTemplatesList.getModel()).fireContentsChanged();
+ }
+
+ public JPanel getComponent() {
+ JPanel panel = new JPanel(new BorderLayout());
+ panel.add(new JLabel(SSRBundle.message("SSRInspection.selected.templates")));
+ panel.add(
+ ToolbarDecorator.createDecorator(myTemplatesList)
+ .setAddAction(new AnActionButtonRunnable() {
+ @Override
+ public void run(AnActionButton button) {
+ final AnAction[] children = new AnAction[]{
+ new AnAction(SSRBundle.message("SSRInspection.add.search.template.button")) {
+ @Override
+ public void actionPerformed(AnActionEvent e) {
+ addTemplate(new SearchDialogFactory() {
+ public SearchDialog createDialog(SearchContext searchContext) {
+ return new SearchDialog(searchContext, false, false);
+ }
+ });
+ }
+ },
+ new AnAction(SSRBundle.message("SSRInspection.add.replace.template.button")) {
+ @Override
+ public void actionPerformed(AnActionEvent e) {
+ addTemplate(new SearchDialogFactory() {
+ public SearchDialog createDialog(SearchContext searchContext) {
+ return new ReplaceDialog(searchContext, false, false);
+ }
+ });
+ }
+ }
+ };
+ JBPopupFactory.getInstance().createActionGroupPopup(null, new DefaultActionGroup(children),
+ DataManager.getInstance()
+ .getDataContext(button.getContextComponent()),
+ JBPopupFactory.ActionSelectionAid.SPEEDSEARCH, true)
+ .show(button.getPreferredPopupPoint());
+ }
+ }).setEditAction(new AnActionButtonRunnable() {
+ @Override
+ public void run(AnActionButton button) {
+ performEditAction();
+ }
+ }).setRemoveAction(new AnActionButtonRunnable() {
+ @Override
+ public void run(AnActionButton button) {
+ Object[] selected = myTemplatesList.getSelectedValues();
+ for (Object o : selected) {
+ Configuration configuration = (Configuration)o;
+ Iterator<Configuration> iterator = myConfigurations.iterator();
+ while (iterator.hasNext()) {
+ Configuration configuration1 = iterator.next();
+ if (configuration1.getName().equals(configuration.getName())) {
+ iterator.remove();
+ }
+ }
+ }
+ configurationsChanged(createSearchContext());
+ }
+ }).setMoveUpAction(new AnActionButtonRunnable() {
+ @Override
+ public void run(AnActionButton button) {
+ performMoveUpDown(false);
+ }
+ }).setMoveDownAction(new AnActionButtonRunnable() {
+ @Override
+ public void run(AnActionButton button) {
+ performMoveUpDown(true);
+ }
+ }).createPanel()
+ );
+ new DoubleClickListener() {
+ @Override
+ protected boolean onDoubleClick(MouseEvent e) {
+ performEditAction();
+ return true;
+ }
+ }.installOn(myTemplatesList);
+ return panel;
+ }
+
+ private void performMoveUpDown(boolean down) {
+ final int[] indices = myTemplatesList.getSelectedIndices();
+ if (indices.length == 0) return;
+ final int delta = down ? 1 : -1;
+ myTemplatesList.removeSelectionInterval(0, myConfigurations.size() - 1);
+ for (int i = down ? indices[indices.length - 1] : 0;
+ down ? i >= 0 : i < indices.length;
+ i -= delta) {
+ final int index = indices[i];
+ final Configuration temp = myConfigurations.get(index);
+ myConfigurations.set(index, myConfigurations.get(index + delta));
+ myConfigurations.set(index + delta, temp);
+ myTemplatesList.addSelectionInterval(index + delta, index + delta);
+ }
+ final int index = down ? myTemplatesList.getMaxSelectionIndex() : myTemplatesList.getMinSelectionIndex();
+ final Rectangle cellBounds = myTemplatesList.getCellBounds(index, index);
+ if (cellBounds != null) {
+ myTemplatesList.scrollRectToVisible(cellBounds);
+ }
+ }
+
+ private void performEditAction() {
+ final Configuration configuration = (Configuration)myTemplatesList.getSelectedValue();
+ if (configuration == null) return;
+
+ SearchDialog dialog = createDialog(new SearchDialogFactory() {
+ public SearchDialog createDialog(SearchContext searchContext) {
+ if (configuration instanceof SearchConfiguration) {
+ return new SearchDialog(searchContext, false, false) {
+ public Configuration createConfiguration() {
+ SearchConfiguration newConfiguration = new SearchConfiguration();
+ copyConfiguration(configuration, newConfiguration);
+ return newConfiguration;
+ }
+ };
+ }
+ else {
+ return new ReplaceDialog(searchContext, false, false) {
+ public Configuration createConfiguration() {
+ ReplaceConfiguration newConfiguration = new ReplaceConfiguration();
+ copyConfiguration(configuration, newConfiguration);
+ return newConfiguration;
+ }
+ };
+ }
+ }
+ });
+ dialog.setValuesFromConfig(configuration);
+ dialog.setUseLastConfiguration(true);
+ dialog.show();
+ if (!dialog.isOK()) return;
+ Configuration newConfiguration = dialog.getConfiguration();
+ copyConfiguration(newConfiguration, configuration);
+ configurationsChanged(dialog.getSearchContext());
+ }
+
+ private class MyListModel extends AbstractListModel {
+ public int getSize() {
+ return myConfigurations.size();
+ }
+
+ public Object getElementAt(int index) {
+ return index < myConfigurations.size() ? myConfigurations.get(index) : null;
+ }
+
+ public void fireContentsChanged() {
+ fireContentsChanged(myTemplatesList, -1, -1);
+ }
+ }
+}