diff options
Diffstat (limited to 'platform/structuralsearch/source/com/intellij/structuralsearch/inspection')
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); + } + } +} |