summaryrefslogtreecommitdiff
path: root/platform/structuralsearch/source/com
diff options
context:
space:
mode:
Diffstat (limited to 'platform/structuralsearch/source/com')
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/DocumentBasedReplaceHandler.java55
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/MalformedPatternException.java11
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/MatchOptions.java334
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/MatchResult.java29
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/MatchResultSink.java35
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/MatchVariableConstraint.java558
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/Matcher.java84
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/MatchingProcess.java13
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/NamedScriptableDefinition.java81
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/PredefinedConfigurationUtil.java34
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/ReplacementVariableDefinition.java14
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/SSRBundle.java32
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/StructuralReplaceHandler.java16
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/StructuralSearchException.java13
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/StructuralSearchProfile.java281
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/StructuralSearchProfileBase.java674
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/StructuralSearchUtil.java169
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/UnsupportedPatternException.java11
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/XmlStructuralSearchProfile.java229
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/CompiledPattern.java182
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/DataProvider.java16
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/GlobalMatchingVisitor.java315
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatchConstraintsSink.java85
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatchContext.java130
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatchPredicateProvider.java16
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatchResultImpl.java207
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatchUtils.java42
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatcherImpl.java737
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatcherImplUtil.java67
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/PatternTreeContext.java8
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/XmlCompiledPattern.java22
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/XmlMatchingVisitor.java130
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/CompileContext.java71
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/DeleteNodesAction.java59
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/FindInFilesOptimizingSearchHelper.java103
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/GlobalCompilingVisitor.java314
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/OptimizingSearchHelper.java27
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/OptimizingSearchHelperBase.java85
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/PatternCompiler.java512
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/StringToConstraintsTransformer.java436
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/TestModeOptimizingSearchHelper.java70
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/XmlCompilingVisitor.java53
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/filters/CompositeFilter.java27
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/filters/DefaultFilter.java16
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/filters/LexicalNodesFilter.java51
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/filters/TagValueFilter.java43
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/filters/XmlLexicalNodesFilter.java19
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/DelegatingHandler.java8
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/LightTopLevelMatchingHandler.java46
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/LiteralWithSubstitutionHandler.java49
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/MatchPredicate.java27
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/MatchingHandler.java271
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/SimpleHandler.java20
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/SkippingHandler.java108
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/SubstitutionHandler.java557
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/SymbolHandler.java21
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/TopLevelMatchingHandler.java85
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/TypedSymbolHandler.java21
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/XmlTextHandler.java23
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/iterators/SsrFilteringNodeIterator.java20
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/AbstractStringBasedPredicate.java22
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/BinaryPredicate.java38
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/ContainsPredicate.java18
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/NotPredicate.java24
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/ReferencePredicate.java31
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/RegExpPredicate.java175
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/ScriptPredicate.java28
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/ScriptSupport.java91
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/WithinPredicate.java38
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/strategies/MatchingStrategy.java13
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/strategies/XmlMatchingStrategy.java42
-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
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/plugin/StructuralReplaceAction.java50
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/plugin/StructuralSearchAction.java49
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/plugin/StructuralSearchPlugin.java111
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/ReplaceOptions.java185
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/ReplacementInfo.java22
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/impl/ParameterInfo.java114
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/impl/ReplacementBuilder.java218
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/impl/ReplacementContext.java57
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/impl/ReplacementInfoImpl.java52
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/impl/Replacer.java426
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/impl/ReplacerUtil.java49
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/ui/ReplaceCommand.java40
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/ui/ReplaceConfiguration.java46
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/ui/ReplaceDialog.java195
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/ui/ReplaceUsageViewContext.java180
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/ui/ReplacementPreviewDialog.java132
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/Configuration.java87
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/ConfigurationCreator.java12
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/ConfigurationManager.java179
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/DialogBase.java152
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/EditVarConstraintsDialog.java635
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/ExistingTemplatesComponent.java333
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/SearchCommand.java142
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/SearchConfiguration.java39
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/SearchContext.java59
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/SearchDialog.java995
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/SearchModel.java29
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/SelectTemplateDialog.java294
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/SubstitutionShortInfoHandler.java114
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/UIUtil.java241
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/UsageViewContext.java190
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/VarConstraints.form350
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/plugin/util/CollectingMatchResultSink.java40
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/plugin/util/SmartPsiPointer.java56
-rw-r--r--platform/structuralsearch/source/com/intellij/tokenindex/AnonymToken.java34
-rw-r--r--platform/structuralsearch/source/com/intellij/tokenindex/IndentToken.java20
-rw-r--r--platform/structuralsearch/source/com/intellij/tokenindex/LanguageTokenizer.java14
-rw-r--r--platform/structuralsearch/source/com/intellij/tokenindex/PathMarkerToken.java41
-rw-r--r--platform/structuralsearch/source/com/intellij/tokenindex/PsiMarkerToken.java19
-rw-r--r--platform/structuralsearch/source/com/intellij/tokenindex/RecursiveTokenizingVisitor.java86
-rw-r--r--platform/structuralsearch/source/com/intellij/tokenindex/TextToken.java39
-rw-r--r--platform/structuralsearch/source/com/intellij/tokenindex/Token.java22
-rw-r--r--platform/structuralsearch/source/com/intellij/tokenindex/TokenIndex.java187
-rw-r--r--platform/structuralsearch/source/com/intellij/tokenindex/TokenIndexKey.java60
-rw-r--r--platform/structuralsearch/source/com/intellij/tokenindex/TokenIndexKeyDescriptor.java43
-rw-r--r--platform/structuralsearch/source/com/intellij/tokenindex/Tokenizer.java13
120 files changed, 15263 insertions, 0 deletions
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/DocumentBasedReplaceHandler.java b/platform/structuralsearch/source/com/intellij/structuralsearch/DocumentBasedReplaceHandler.java
new file mode 100644
index 000000000000..1f319b8f15e5
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/DocumentBasedReplaceHandler.java
@@ -0,0 +1,55 @@
+package com.intellij.structuralsearch;
+
+import com.intellij.openapi.editor.Document;
+import com.intellij.openapi.editor.RangeMarker;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.TextRange;
+import com.intellij.psi.PsiDocumentManager;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiFile;
+import com.intellij.structuralsearch.plugin.replace.ReplaceOptions;
+import com.intellij.structuralsearch.plugin.replace.ReplacementInfo;
+import com.intellij.structuralsearch.plugin.replace.impl.ReplacementInfoImpl;
+import com.intellij.util.containers.HashMap;
+
+import java.util.Map;
+
+/**
+ * @author Eugene.Kudelevsky
+ */
+public class DocumentBasedReplaceHandler extends StructuralReplaceHandler {
+ private final Project myProject;
+ private final Map<ReplacementInfo, RangeMarker> myRangeMarkers = new HashMap<ReplacementInfo, RangeMarker>();
+
+ DocumentBasedReplaceHandler(Project project) {
+ myProject = project;
+ }
+
+ public void replace(ReplacementInfo info, ReplaceOptions options) {
+ if (info.getMatchesCount() == 0) return;
+ assert info instanceof ReplacementInfoImpl;
+ PsiElement element = info.getMatch(0);
+ if (element == null) return;
+ PsiFile file = element instanceof PsiFile ? (PsiFile)element : element.getContainingFile();
+ assert file != null;
+ RangeMarker rangeMarker = myRangeMarkers.get(info);
+ Document document = rangeMarker.getDocument();
+ document.replaceString(rangeMarker.getStartOffset(), rangeMarker.getEndOffset(), info.getReplacement());
+ PsiDocumentManager.getInstance(element.getProject()).commitDocument(document);
+ }
+
+ @Override
+ public void prepare(ReplacementInfo info) {
+ assert info instanceof ReplacementInfoImpl;
+ MatchResult result = ((ReplacementInfoImpl)info).getMatchResult();
+ PsiElement element = result.getMatch();
+ PsiFile file = element instanceof PsiFile ? (PsiFile)element : element.getContainingFile();
+ Document document = PsiDocumentManager.getInstance(myProject).getDocument(file);
+ TextRange textRange = result.getMatchRef().getElement().getTextRange();
+ assert textRange != null;
+ RangeMarker rangeMarker = document.createRangeMarker(textRange);
+ rangeMarker.setGreedyToLeft(true);
+ rangeMarker.setGreedyToRight(true);
+ myRangeMarkers.put(info, rangeMarker);
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/MalformedPatternException.java b/platform/structuralsearch/source/com/intellij/structuralsearch/MalformedPatternException.java
new file mode 100644
index 000000000000..e73eddc19550
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/MalformedPatternException.java
@@ -0,0 +1,11 @@
+package com.intellij.structuralsearch;
+
+import org.jetbrains.annotations.NonNls;
+
+/**
+ * Class to indicate incorrect pattern
+ */
+public class MalformedPatternException extends RuntimeException {
+ public MalformedPatternException() {}
+ public MalformedPatternException(String msg) { super(msg); }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/MatchOptions.java b/platform/structuralsearch/source/com/intellij/structuralsearch/MatchOptions.java
new file mode 100644
index 000000000000..fb22f39990fa
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/MatchOptions.java
@@ -0,0 +1,334 @@
+package com.intellij.structuralsearch;
+
+import com.intellij.lang.Language;
+import com.intellij.openapi.fileTypes.FileType;
+import com.intellij.openapi.util.JDOMExternalizable;
+import com.intellij.psi.search.SearchScope;
+import org.jdom.Attribute;
+import org.jdom.DataConversionException;
+import org.jdom.Element;
+import org.jetbrains.annotations.NonNls;
+
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * match options
+ */
+public class MatchOptions implements JDOMExternalizable, Cloneable {
+ @NonNls private static final String TEXT_ATTRIBUTE_NAME = "text";
+
+ private boolean looseMatching;
+ private boolean distinct;
+ private boolean recursiveSearch;
+ private boolean caseSensitiveMatch;
+ private boolean resultIsContextMatch = false;
+ private FileType myFileType = StructuralSearchUtil.getDefaultFileType();
+ private Language myDialect = null;
+ private int maxMatches = Integer.MAX_VALUE;
+
+ private SearchScope scope;
+ private SearchScope downUpMatchScope;
+ private String searchCriteria = "";
+ private Map<String,MatchVariableConstraint> variableConstraints;
+
+ private String myPatternContext;
+
+ @NonNls private static final String DISTINCT_ATTRIBUTE_NAME = "distinct";
+ @NonNls private static final String RECURSIVE_ATTRIBUTE_NAME = "recursive";
+ @NonNls private static final String CASESENSITIVE_ATTRIBUTE_NAME = "caseInsensitive";
+ //private static final String SCOPE_ATTRIBUTE_NAME = "scope";
+ @NonNls private static final String CONSTRAINT_TAG_NAME = "constraint";
+ @NonNls private static final String FILE_TYPE_ATTR_NAME = "type";
+ @NonNls private static final String DIALECT_ATTR_NAME = "dialect";
+ @NonNls public static final String INSTANCE_MODIFIER_NAME = "Instance";
+ @NonNls public static final String MODIFIER_ANNOTATION_NAME = "Modifier";
+
+ //private static final String UNDEFINED_SCOPE = "undefined";
+
+ public void addVariableConstraint(MatchVariableConstraint constraint) {
+ if (variableConstraints==null) {
+ variableConstraints = new LinkedHashMap<String,MatchVariableConstraint>();
+ }
+ variableConstraints.put( constraint.getName(), constraint );
+ }
+
+ public boolean hasVariableConstraints() {
+ return variableConstraints!=null;
+ }
+
+ public void clearVariableConstraints() {
+ variableConstraints=null;
+ }
+
+ public MatchVariableConstraint getVariableConstraint(String name) {
+ if (variableConstraints!=null) {
+ return variableConstraints.get(name);
+ }
+ return null;
+ }
+
+ public Iterator<String> getVariableConstraintNames() {
+ if (variableConstraints==null) return null;
+ return variableConstraints.keySet().iterator();
+ }
+
+ public void setCaseSensitiveMatch(boolean caseSensitiveMatch) {
+ this.caseSensitiveMatch = caseSensitiveMatch;
+ }
+
+ public boolean isCaseSensitiveMatch() {
+ return caseSensitiveMatch;
+ }
+
+ @SuppressWarnings({"HardCodedStringLiteral"})
+ public String toString() {
+ StringBuffer result = new StringBuffer();
+
+ result.append("match options:\n");
+ result.append("search pattern:\n");
+ result.append(searchCriteria);
+ result.append("\nsearch scope:\n");
+
+ // @TODO print scope
+ //result.append((scopeHandler!=null)?scopeHandler.toString():"undefined scope");
+
+ result.append("\nrecursive:");
+ result.append(recursiveSearch);
+
+ result.append("\ndistinct:");
+ result.append(distinct);
+
+ result.append("\ncasesensitive:");
+ result.append(caseSensitiveMatch);
+
+ return result.toString();
+ }
+
+ public boolean isDistinct() {
+ return distinct;
+ }
+
+ public void setDistinct(boolean distinct) {
+ this.distinct = distinct;
+ }
+
+ public boolean isRecursiveSearch() {
+ return recursiveSearch;
+ }
+
+ public void setRecursiveSearch(boolean recursiveSearch) {
+ this.recursiveSearch = recursiveSearch;
+ }
+
+ public boolean isLooseMatching() {
+ return looseMatching;
+ }
+
+ public void setLooseMatching(boolean looseMatching) {
+ this.looseMatching = looseMatching;
+ }
+
+ public void setSearchPattern(String text) {
+ searchCriteria = text;
+ }
+
+ public String getSearchPattern() {
+ return searchCriteria;
+ }
+
+ public int getMaxMatchesCount() {
+ return maxMatches;
+ }
+
+ public boolean isResultIsContextMatch() {
+ return resultIsContextMatch;
+ }
+
+ public void setResultIsContextMatch(boolean resultIsContextMatch) {
+ this.resultIsContextMatch = resultIsContextMatch;
+ }
+
+ public SearchScope getScope() {
+ return scope;
+ }
+
+ public void setScope(SearchScope scope) {
+ this.scope = scope;
+ }
+
+ public SearchScope getDownUpMatchScope() {
+ return downUpMatchScope;
+ }
+
+ public void setDownUpMatchScope(final SearchScope downUpMatchScope) {
+ this.downUpMatchScope = downUpMatchScope;
+ }
+
+ public void writeExternal(Element element) {
+ element.setAttribute(TEXT_ATTRIBUTE_NAME,getSearchPattern());
+ element.setAttribute(RECURSIVE_ATTRIBUTE_NAME,String.valueOf(recursiveSearch));
+ if (distinct) element.setAttribute(DISTINCT_ATTRIBUTE_NAME,String.valueOf(distinct));
+ element.setAttribute(CASESENSITIVE_ATTRIBUTE_NAME,String.valueOf(caseSensitiveMatch));
+
+ //@TODO serialize scope!
+
+ //if (myFileType != StdFileTypes.JAVA) {
+ element.setAttribute(FILE_TYPE_ATTR_NAME, myFileType.getName());
+ //}
+
+ if (myDialect != null) {
+ element.setAttribute(DIALECT_ATTR_NAME, myDialect.getID());
+ }
+
+ if (variableConstraints!=null) {
+ for (final MatchVariableConstraint matchVariableConstraint : variableConstraints.values()) {
+ if (matchVariableConstraint.isArtificial()) continue;
+ final Element infoElement = new Element(CONSTRAINT_TAG_NAME);
+ element.addContent(infoElement);
+ matchVariableConstraint.writeExternal(infoElement);
+ }
+ }
+ }
+
+ public void readExternal(Element element) {
+ setSearchPattern(element.getAttribute(TEXT_ATTRIBUTE_NAME).getValue());
+
+ Attribute attr = element.getAttribute(RECURSIVE_ATTRIBUTE_NAME);
+ if (attr!=null) {
+ try {
+ recursiveSearch = attr.getBooleanValue();
+ } catch(DataConversionException ignored) {}
+ }
+
+ attr = element.getAttribute(DISTINCT_ATTRIBUTE_NAME);
+ if (attr!=null) {
+ try {
+ distinct = attr.getBooleanValue();
+ } catch(DataConversionException ignored) {}
+ }
+
+ attr = element.getAttribute(CASESENSITIVE_ATTRIBUTE_NAME);
+ if (attr!=null) {
+ try {
+ caseSensitiveMatch = attr.getBooleanValue();
+ } catch(DataConversionException ignored) {}
+ }
+
+ attr = element.getAttribute(FILE_TYPE_ATTR_NAME);
+ if (attr!=null) {
+ String value = attr.getValue();
+ myFileType = getFileTypeByName(value);
+ }
+
+ attr = element.getAttribute(DIALECT_ATTR_NAME);
+ if (attr != null) {
+ myDialect = Language.findLanguageByID(attr.getValue());
+ }
+
+ // @TODO deserialize scope
+
+ List<Element> elements = element.getChildren(CONSTRAINT_TAG_NAME);
+ if (elements!=null && !elements.isEmpty()) {
+ for (final Element element1 : elements) {
+ final MatchVariableConstraint constraint = new MatchVariableConstraint();
+ constraint.readExternal(element1);
+ addVariableConstraint(constraint);
+ }
+ }
+ }
+
+ private static FileType getFileTypeByName(String value) {
+ if (value != null) {
+ for (FileType type : StructuralSearchUtil.getSuitableFileTypes()) {
+ if (value.equals(type.getName())) {
+ return type;
+ }
+ }
+ }
+
+ return StructuralSearchUtil.getDefaultFileType();
+ }
+
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof MatchOptions)) return false;
+
+ final MatchOptions matchOptions = (MatchOptions)o;
+
+ if (caseSensitiveMatch != matchOptions.caseSensitiveMatch) return false;
+ if (distinct != matchOptions.distinct) return false;
+ //if (enableAutoIdentifySearchTarget != matchOptions.enableAutoIdentifySearchTarget) return false;
+ if (looseMatching != matchOptions.looseMatching) return false;
+ if (recursiveSearch != matchOptions.recursiveSearch) return false;
+ // @TODO support scope
+
+ if (searchCriteria != null ? !searchCriteria.equals(matchOptions.searchCriteria) : matchOptions.searchCriteria != null) return false;
+ if (variableConstraints != null ? !variableConstraints.equals(matchOptions.variableConstraints) : matchOptions.variableConstraints !=
+ null) {
+ return false;
+ }
+ if (myFileType != matchOptions.myFileType) {
+ return false;
+ }
+
+ if (myDialect != null ? !myDialect.equals(matchOptions.myDialect) : matchOptions.myDialect != null) {
+ return false;
+ }
+
+ if (myPatternContext != null ? !myPatternContext.equals(matchOptions.myPatternContext) : matchOptions.myPatternContext != null) {
+ return false;
+ }
+
+ return true;
+ }
+
+ public int hashCode() {
+ int result;
+ result = (looseMatching ? 1 : 0);
+ result = 29 * result + (distinct ? 1 : 0);
+ result = 29 * result + (recursiveSearch ? 1 : 0);
+ result = 29 * result + (caseSensitiveMatch ? 1 : 0);
+ // @TODO support scope
+ result = 29 * result + (searchCriteria != null ? searchCriteria.hashCode() : 0);
+ result = 29 * result + (variableConstraints != null ? variableConstraints.hashCode() : 0);
+ if (myFileType != null) result = 29 * result + myFileType.hashCode();
+ if (myDialect != null) result = 29 * result + myDialect.hashCode();
+ return result;
+ }
+
+ public void setFileType(FileType fileType) {
+ myFileType = fileType;
+ }
+
+ public FileType getFileType() {
+ return myFileType;
+ }
+
+ public Language getDialect() {
+ return myDialect;
+ }
+
+ public void setDialect(Language dialect) {
+ myDialect = dialect;
+ }
+
+ public String getPatternContext() {
+ return myPatternContext;
+ }
+
+ public void setPatternContext(String patternContext) {
+ myPatternContext = patternContext;
+ }
+
+ public MatchOptions clone() {
+ try {
+ return (MatchOptions) super.clone();
+ } catch (CloneNotSupportedException e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/MatchResult.java b/platform/structuralsearch/source/com/intellij/structuralsearch/MatchResult.java
new file mode 100644
index 000000000000..c5196e67178d
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/MatchResult.java
@@ -0,0 +1,29 @@
+package com.intellij.structuralsearch;
+
+import com.intellij.psi.PsiElement;
+import com.intellij.structuralsearch.plugin.util.SmartPsiPointer;
+import org.jetbrains.annotations.NonNls;
+
+import java.util.List;
+
+/**
+ * Class describing the match result
+ */
+public abstract class MatchResult {
+ @NonNls public static final String LINE_MATCH = "line";
+ @NonNls public static final String MULTI_LINE_MATCH = "context";
+
+ public abstract String getMatchImage();
+
+ public abstract SmartPsiPointer getMatchRef();
+ public abstract PsiElement getMatch();
+ public abstract int getStart();
+ public abstract int getEnd();
+
+ public abstract String getName();
+
+ public abstract List<MatchResult> getAllSons();
+ public abstract boolean hasSons();
+ public abstract boolean isScopeMatch();
+ public abstract boolean isMultipleMatch();
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/MatchResultSink.java b/platform/structuralsearch/source/com/intellij/structuralsearch/MatchResultSink.java
new file mode 100644
index 000000000000..8a4fdf968a8c
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/MatchResultSink.java
@@ -0,0 +1,35 @@
+package com.intellij.structuralsearch;
+
+import com.intellij.psi.PsiFile;
+import com.intellij.structuralsearch.MatchingProcess;
+import com.intellij.openapi.progress.ProgressIndicator;
+
+/**
+ * Interface for consumers of match results
+ */
+public interface MatchResultSink {
+ /**
+ * Notifies sink about new match
+ * @param result
+ */
+ void newMatch(MatchResult result);
+
+ /**
+ * Notifies sink about starting the matching for given element
+ * @param element the current file
+ */
+ void processFile(PsiFile element);
+
+ /**
+ * Sets the reference to the matching process
+ * @param matchingProcess the matching process reference
+ */
+ void setMatchingProcess(MatchingProcess matchingProcess);
+
+ /**
+ * Notifies sink about end of matching.
+ */
+ void matchingFinished();
+
+ ProgressIndicator getProgressIndicator();
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/MatchVariableConstraint.java b/platform/structuralsearch/source/com/intellij/structuralsearch/MatchVariableConstraint.java
new file mode 100644
index 000000000000..db2aaa901c54
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/MatchVariableConstraint.java
@@ -0,0 +1,558 @@
+package com.intellij.structuralsearch;
+
+import org.jdom.Element;
+import org.jdom.Attribute;
+import org.jdom.DataConversionException;
+import org.jetbrains.annotations.NonNls;
+
+/**
+ * @author Maxim.Mossienko
+ * Date: Mar 19, 2004
+ * Time: 5:36:32 PM
+ */
+public class MatchVariableConstraint extends NamedScriptableDefinition {
+ private String regExp = "";
+ private boolean invertRegExp;
+ private boolean withinHierarchy;
+ private boolean strictlyWithinHierarchy;
+ private boolean wholeWordsOnly;
+ private int minCount = 1;
+ private int maxCount = 1;
+ private boolean readAccess;
+ private boolean invertReadAccess;
+ private boolean writeAccess;
+ private boolean invertWriteAccess;
+ private boolean greedy = true;
+ private boolean reference;
+ private boolean invertReference;
+ private String nameOfReferenceVar = "";
+ private boolean partOfSearchResults;
+ private String nameOfExprType = "";
+ private boolean invertExprType;
+ private boolean exprTypeWithinHierarchy;
+
+ private String nameOfFormalArgType = "";
+ private boolean invertFormalType;
+ private boolean formalArgTypeWithinHierarchy;
+
+ private String withinConstraint = "";
+ private String containsConstraint = "";
+ private boolean invertContainsConstraint;
+ private boolean invertWithinConstraint;
+ private final boolean artificial;
+
+ @NonNls private static final String NAME_OF_REFEENCE_VAR = "nameOfReferenceVar";
+ @NonNls private static final String NAME_OF_EXPRTYPE = "nameOfExprType";
+ @NonNls private static final String NAME_OF_FORMALTYPE = "nameOfFormalType";
+ @NonNls private static final String REGEXP = "regexp";
+ @NonNls private static final String EXPRTYPE_WITHIN_HIERARCHY = "exprTypeWithinHierarchy";
+ @NonNls private static final String FORMALTYPE_WITHIN_HIERARCHY = "formalTypeWithinHierarchy";
+
+ @NonNls private static final String WITHIN_HIERARCHY = "withinHierarchy";
+ @NonNls private static final String MAX_OCCURS = "maxCount";
+ @NonNls private static final String MIN_OCCURS = "minCount";
+
+ @NonNls private static final String NEGATE_NAME_CONDITION = "negateName";
+ @NonNls private static final String NEGATE_EXPRTYPE_CONDITION = "negateExprType";
+ @NonNls private static final String NEGATE_FORMALTYPE_CONDITION = "negateFormalType";
+ @NonNls private static final String NEGATE_READ_CONDITION = "negateRead";
+ @NonNls private static final String NEGATE_WRITE_CONDITION = "negateWrite";
+ @NonNls private static final String NEGATE_CONTAINS_CONDITION = "negateContains";
+ @NonNls private static final String NEGATE_WITHIN_CONDITION = "negateWithin";
+ @NonNls private static final String WITHIN_CONDITION = "within";
+ @NonNls private static final String CONTAINS_CONDITION = "contains";
+ @NonNls private static final String READ = "readAccess";
+ @NonNls private static final String WRITE = "writeAccess";
+ @NonNls private static final String TARGET = "target";
+
+ @NonNls private static final String WHOLE_WORDS_ONLY = "wholeWordsOnly";
+ @NonNls private static final String TRUE = Boolean.TRUE.toString();
+
+ public MatchVariableConstraint() { this(false); }
+ public MatchVariableConstraint(boolean _artificial) { artificial = _artificial; }
+
+ public boolean isGreedy() {
+ return greedy;
+ }
+
+ public void setGreedy(boolean greedy) {
+ this.greedy = greedy;
+ }
+
+ public String getRegExp() {
+ return regExp;
+ }
+
+ public void setRegExp(String regExp) {
+ this.regExp = regExp;
+ }
+
+ public boolean isInvertRegExp() {
+ return invertRegExp;
+ }
+
+ public void setInvertRegExp(boolean invertRegExp) {
+ this.invertRegExp = invertRegExp;
+ }
+
+ public boolean isWithinHierarchy() {
+ return withinHierarchy;
+ }
+
+ public void setWithinHierarchy(boolean withinHierarchy) {
+ this.withinHierarchy = withinHierarchy;
+ }
+
+ public int getMinCount() {
+ return minCount;
+ }
+
+ public void setMinCount(int minCount) {
+ this.minCount = minCount;
+ }
+
+ public int getMaxCount() {
+ return maxCount;
+ }
+
+ public void setMaxCount(int maxCount) {
+ this.maxCount = maxCount;
+ }
+
+ public boolean isReadAccess() {
+ return readAccess;
+ }
+
+ public void setReadAccess(boolean readAccess) {
+ this.readAccess = readAccess;
+ }
+
+ public boolean isInvertReadAccess() {
+ return invertReadAccess;
+ }
+
+ public void setInvertReadAccess(boolean invertReadAccess) {
+ this.invertReadAccess = invertReadAccess;
+ }
+
+ public boolean isWriteAccess() {
+ return writeAccess;
+ }
+
+ public void setWriteAccess(boolean writeAccess) {
+ this.writeAccess = writeAccess;
+ }
+
+ public boolean isInvertWriteAccess() {
+ return invertWriteAccess;
+ }
+
+ public void setInvertWriteAccess(boolean invertWriteAccess) {
+ this.invertWriteAccess = invertWriteAccess;
+ }
+
+ public boolean isPartOfSearchResults() {
+ return partOfSearchResults;
+ }
+
+ public void setPartOfSearchResults(boolean partOfSearchResults) {
+ this.partOfSearchResults = partOfSearchResults;
+ }
+
+ public boolean isReference() {
+ return reference;
+ }
+
+ public void setReference(boolean reference) {
+ this.reference = reference;
+ }
+
+ public boolean isInvertReference() {
+ return invertReference;
+ }
+
+ public void setInvertReference(boolean invertReference) {
+ this.invertReference = invertReference;
+ }
+
+ public String getNameOfReferenceVar() {
+ return nameOfReferenceVar;
+ }
+
+ public void setNameOfReferenceVar(String nameOfReferenceVar) {
+ this.nameOfReferenceVar = nameOfReferenceVar;
+ }
+
+ public boolean isStrictlyWithinHierarchy() {
+ return strictlyWithinHierarchy;
+ }
+
+ public void setStrictlyWithinHierarchy(boolean strictlyWithinHierarchy) {
+ this.strictlyWithinHierarchy = strictlyWithinHierarchy;
+ }
+
+ public String getNameOfExprType() {
+ return nameOfExprType;
+ }
+
+ public void setNameOfExprType(String nameOfExprType) {
+ this.nameOfExprType = nameOfExprType;
+ }
+
+ public boolean isInvertExprType() {
+ return invertExprType;
+ }
+
+ public void setInvertExprType(boolean invertExprType) {
+ this.invertExprType = invertExprType;
+ }
+
+ public boolean isExprTypeWithinHierarchy() {
+ return exprTypeWithinHierarchy;
+ }
+
+ public void setExprTypeWithinHierarchy(boolean exprTypeWithinHierarchy) {
+ this.exprTypeWithinHierarchy = exprTypeWithinHierarchy;
+ }
+
+ public boolean isWholeWordsOnly() {
+ return wholeWordsOnly;
+ }
+
+ public void setWholeWordsOnly(boolean wholeWordsOnly) {
+ this.wholeWordsOnly = wholeWordsOnly;
+ }
+
+ public String getNameOfFormalArgType() {
+ return nameOfFormalArgType;
+ }
+
+ public void setNameOfFormalArgType(String nameOfFormalArgType) {
+ this.nameOfFormalArgType = nameOfFormalArgType;
+ }
+
+ public boolean isInvertFormalType() {
+ return invertFormalType;
+ }
+
+ public void setInvertFormalType(boolean invertFormalType) {
+ this.invertFormalType = invertFormalType;
+ }
+
+ public boolean isFormalArgTypeWithinHierarchy() {
+ return formalArgTypeWithinHierarchy;
+ }
+
+ public void setFormalArgTypeWithinHierarchy(boolean formalArgTypeWithinHierarchy) {
+ this.formalArgTypeWithinHierarchy = formalArgTypeWithinHierarchy;
+ }
+
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof MatchVariableConstraint)) return false;
+ if (!(super.equals(o))) return false;
+
+ final MatchVariableConstraint matchVariableConstraint = (MatchVariableConstraint)o;
+
+ if (exprTypeWithinHierarchy != matchVariableConstraint.exprTypeWithinHierarchy) return false;
+ if (formalArgTypeWithinHierarchy != matchVariableConstraint.formalArgTypeWithinHierarchy) return false;
+ if (greedy != matchVariableConstraint.greedy) return false;
+ if (invertExprType != matchVariableConstraint.invertExprType) return false;
+ if (invertFormalType != matchVariableConstraint.invertFormalType) return false;
+ if (invertReadAccess != matchVariableConstraint.invertReadAccess) return false;
+ if (invertReference != matchVariableConstraint.invertReference) return false;
+ if (invertRegExp != matchVariableConstraint.invertRegExp) return false;
+ if (invertWriteAccess != matchVariableConstraint.invertWriteAccess) return false;
+ if (maxCount != matchVariableConstraint.maxCount) return false;
+ if (minCount != matchVariableConstraint.minCount) return false;
+ if (partOfSearchResults != matchVariableConstraint.partOfSearchResults) return false;
+ if (readAccess != matchVariableConstraint.readAccess) return false;
+ if (reference != matchVariableConstraint.reference) return false;
+ if (strictlyWithinHierarchy != matchVariableConstraint.strictlyWithinHierarchy) return false;
+ if (wholeWordsOnly != matchVariableConstraint.wholeWordsOnly) return false;
+ if (withinHierarchy != matchVariableConstraint.withinHierarchy) return false;
+ if (writeAccess != matchVariableConstraint.writeAccess) return false;
+ if (!nameOfExprType.equals(matchVariableConstraint.nameOfExprType)) return false;
+ if (!nameOfFormalArgType.equals(matchVariableConstraint.nameOfFormalArgType)) return false;
+ if (!nameOfReferenceVar.equals(matchVariableConstraint.nameOfReferenceVar)) return false;
+ if (!regExp.equals(matchVariableConstraint.regExp)) return false;
+ if (!withinConstraint.equals(matchVariableConstraint.withinConstraint)) return false;
+ if (!containsConstraint.equals(matchVariableConstraint.containsConstraint)) return false;
+ if (invertWithinConstraint != matchVariableConstraint.invertWithinConstraint) return false;
+ if (invertContainsConstraint != matchVariableConstraint.invertContainsConstraint) return false;
+
+ return true;
+ }
+
+ public int hashCode() {
+ int result;
+ result = super.hashCode();
+ result = 29 * result + regExp.hashCode();
+ result = 29 * result + (invertRegExp ? 1 : 0);
+ result = 29 * result + (withinHierarchy ? 1 : 0);
+ result = 29 * result + (strictlyWithinHierarchy ? 1 : 0);
+ result = 29 * result + (wholeWordsOnly ? 1 : 0);
+ result = 29 * result + minCount;
+ result = 29 * result + maxCount;
+ result = 29 * result + (readAccess ? 1 : 0);
+ result = 29 * result + (invertReadAccess ? 1 : 0);
+ result = 29 * result + (writeAccess ? 1 : 0);
+ result = 29 * result + (invertWriteAccess ? 1 : 0);
+ result = 29 * result + (greedy ? 1 : 0);
+ result = 29 * result + (reference ? 1 : 0);
+ result = 29 * result + (invertReference ? 1 : 0);
+ result = 29 * result + nameOfReferenceVar.hashCode();
+ result = 29 * result + (partOfSearchResults ? 1 : 0);
+ result = 29 * result + nameOfExprType.hashCode();
+ result = 29 * result + (invertExprType ? 1 : 0);
+ result = 29 * result + (exprTypeWithinHierarchy ? 1 : 0);
+ result = 29 * result + nameOfFormalArgType.hashCode();
+ result = 29 * result + (invertFormalType ? 1 : 0);
+ result = 29 * result + (formalArgTypeWithinHierarchy ? 1 : 0);
+ result = 29 * result + withinConstraint.hashCode();
+ result = 29 * result + containsConstraint.hashCode();
+
+ if (invertContainsConstraint) result = 29 * result + 1;
+ if (invertWithinConstraint) result = 29 * result + 1;
+ return result;
+ }
+
+ public void readExternal(Element element) {
+ super.readExternal(element);
+ Attribute attribute;
+
+ attribute = element.getAttribute(REGEXP);
+ if (attribute != null) {
+ regExp = attribute.getValue();
+ }
+
+ attribute = element.getAttribute(NAME_OF_EXPRTYPE);
+ if (attribute != null) {
+ nameOfExprType = attribute.getValue();
+ }
+
+ attribute = element.getAttribute(NAME_OF_FORMALTYPE);
+ if (attribute != null) {
+ nameOfFormalArgType = attribute.getValue();
+ }
+
+ attribute = element.getAttribute(NAME_OF_REFEENCE_VAR);
+ if (attribute != null) {
+ nameOfReferenceVar = attribute.getValue();
+ }
+
+ attribute = element.getAttribute(WITHIN_HIERARCHY);
+ if (attribute != null) {
+ try {
+ withinHierarchy = attribute.getBooleanValue();
+ }
+ catch (DataConversionException ex) {
+ }
+ }
+
+ attribute = element.getAttribute(EXPRTYPE_WITHIN_HIERARCHY);
+ if (attribute != null) {
+ try {
+ exprTypeWithinHierarchy = attribute.getBooleanValue();
+ }
+ catch (DataConversionException ex) {
+ }
+ }
+
+ attribute = element.getAttribute(FORMALTYPE_WITHIN_HIERARCHY);
+ if (attribute != null) {
+ try {
+ formalArgTypeWithinHierarchy = attribute.getBooleanValue();
+ }
+ catch (DataConversionException ex) {
+ }
+ }
+
+ attribute = element.getAttribute(NEGATE_NAME_CONDITION);
+ if (attribute != null) {
+ try {
+ invertRegExp = attribute.getBooleanValue();
+ }
+ catch (DataConversionException ex) {
+ }
+ }
+
+ attribute = element.getAttribute(NEGATE_EXPRTYPE_CONDITION);
+ if (attribute != null) {
+ try {
+ invertExprType = attribute.getBooleanValue();
+ }
+ catch (DataConversionException ex) {
+ }
+ }
+
+ attribute = element.getAttribute(NEGATE_FORMALTYPE_CONDITION);
+ if (attribute != null) {
+ try {
+ invertFormalType = attribute.getBooleanValue();
+ }
+ catch (DataConversionException ex) {
+ }
+ }
+
+ attribute = element.getAttribute(NEGATE_READ_CONDITION);
+ if (attribute != null) {
+ try {
+ invertReadAccess = attribute.getBooleanValue();
+ }
+ catch (DataConversionException ex) {
+ }
+ }
+
+ attribute = element.getAttribute(NEGATE_WRITE_CONDITION);
+ if (attribute != null) {
+ try {
+ invertWriteAccess = attribute.getBooleanValue();
+ }
+ catch (DataConversionException ex) {
+ }
+ }
+
+ attribute = element.getAttribute(READ);
+ if (attribute != null) {
+ try {
+ readAccess = attribute.getBooleanValue();
+ }
+ catch (DataConversionException ex) {
+ }
+ }
+
+ attribute = element.getAttribute(WRITE);
+ if (attribute != null) {
+ try {
+ writeAccess = attribute.getBooleanValue();
+ }
+ catch (DataConversionException ex) {
+ }
+ }
+
+ attribute = element.getAttribute(TARGET);
+ if (attribute != null) {
+ try {
+ partOfSearchResults = attribute.getBooleanValue();
+ }
+ catch (DataConversionException ex) {
+ }
+ }
+
+ attribute = element.getAttribute(MIN_OCCURS);
+ if (attribute != null) {
+ try {
+ minCount = attribute.getIntValue();
+ }
+ catch (DataConversionException ex) {
+ }
+ }
+
+ attribute = element.getAttribute(MAX_OCCURS);
+ if (attribute != null) {
+ try {
+ maxCount = attribute.getIntValue();
+ }
+ catch (DataConversionException ex) {
+ }
+ }
+
+ attribute = element.getAttribute(WHOLE_WORDS_ONLY);
+ if (attribute != null) {
+ try {
+ wholeWordsOnly = attribute.getBooleanValue();
+ }
+ catch (DataConversionException ex) {
+ }
+ }
+
+ attribute = element.getAttribute(NEGATE_WITHIN_CONDITION);
+ if (attribute != null) {
+ try {
+ invertWithinConstraint = attribute.getBooleanValue();
+ } catch (DataConversionException ex) {}
+ }
+
+ attribute = element.getAttribute(NEGATE_CONTAINS_CONDITION);
+ if (attribute != null) {
+ try {
+ invertContainsConstraint = attribute.getBooleanValue();
+ } catch (DataConversionException ex) {}
+ }
+
+ attribute = element.getAttribute(CONTAINS_CONDITION);
+ if(attribute != null) containsConstraint = attribute.getValue();
+
+ attribute = element.getAttribute(WITHIN_CONDITION);
+ if(attribute != null) withinConstraint = attribute.getValue();
+ }
+
+ public void writeExternal(Element element) {
+ super.writeExternal(element);
+
+ if (regExp.length() > 0) element.setAttribute(REGEXP,regExp);
+ if (nameOfExprType.length() > 0) element.setAttribute(NAME_OF_EXPRTYPE,nameOfExprType);
+ if (nameOfReferenceVar.length() > 0) element.setAttribute(NAME_OF_REFEENCE_VAR,nameOfReferenceVar);
+ if (nameOfFormalArgType.length() > 0) element.setAttribute(NAME_OF_FORMALTYPE,nameOfFormalArgType);
+
+
+ if (withinHierarchy) element.setAttribute(WITHIN_HIERARCHY,TRUE);
+ if (exprTypeWithinHierarchy) element.setAttribute(EXPRTYPE_WITHIN_HIERARCHY,TRUE);
+ if (formalArgTypeWithinHierarchy) element.setAttribute(FORMALTYPE_WITHIN_HIERARCHY,TRUE);
+
+ if (minCount!=1) element.setAttribute(MIN_OCCURS,String.valueOf(minCount));
+ if (maxCount!=1) element.setAttribute(MAX_OCCURS,String.valueOf(maxCount));
+ if (partOfSearchResults) element.setAttribute(TARGET,TRUE);
+ if (readAccess) element.setAttribute(READ,TRUE);
+ if (writeAccess) element.setAttribute(WRITE,TRUE);
+
+ if (invertRegExp) element.setAttribute(NEGATE_NAME_CONDITION,TRUE);
+ if (invertExprType) element.setAttribute(NEGATE_EXPRTYPE_CONDITION,TRUE);
+ if (invertFormalType) element.setAttribute(NEGATE_FORMALTYPE_CONDITION,TRUE);
+ if (invertReadAccess) element.setAttribute(NEGATE_READ_CONDITION,TRUE);
+ if (invertWriteAccess) element.setAttribute(NEGATE_WRITE_CONDITION,TRUE);
+
+ if (wholeWordsOnly) element.setAttribute(WHOLE_WORDS_ONLY,TRUE);
+ if (invertContainsConstraint) element.setAttribute(NEGATE_CONTAINS_CONDITION,TRUE);
+ if (invertWithinConstraint) element.setAttribute(NEGATE_WITHIN_CONDITION,TRUE);
+ element.setAttribute(WITHIN_CONDITION, withinConstraint);
+ element.setAttribute(CONTAINS_CONDITION, containsConstraint);
+ }
+
+ public String getWithinConstraint() {
+ return withinConstraint;
+ }
+
+ public void setWithinConstraint(final String withinConstraint) {
+ this.withinConstraint = withinConstraint;
+ }
+
+ public String getContainsConstraint() {
+ return containsConstraint;
+ }
+
+ public void setContainsConstraint(final String containsConstraint) {
+ this.containsConstraint = containsConstraint;
+ }
+
+ public boolean isInvertContainsConstraint() {
+ return invertContainsConstraint;
+ }
+
+ public void setInvertContainsConstraint(final boolean invertContainsConstraint) {
+ this.invertContainsConstraint = invertContainsConstraint;
+ }
+
+ public boolean isInvertWithinConstraint() {
+ return invertWithinConstraint;
+ }
+
+ public void setInvertWithinConstraint(final boolean invertWithinConstraint) {
+ this.invertWithinConstraint = invertWithinConstraint;
+ }
+
+ public boolean isArtificial() {
+ return artificial;
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/Matcher.java b/platform/structuralsearch/source/com/intellij/structuralsearch/Matcher.java
new file mode 100644
index 000000000000..594686e14d49
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/Matcher.java
@@ -0,0 +1,84 @@
+package com.intellij.structuralsearch;
+
+import com.intellij.openapi.fileTypes.FileType;
+import com.intellij.openapi.project.Project;
+import com.intellij.psi.PsiElement;
+import com.intellij.structuralsearch.impl.matcher.MatcherImpl;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.List;
+
+/**
+ * This class makes program structure tree matching:
+ */
+public class Matcher extends MatcherImpl {
+
+ public Matcher(Project project) {
+ super(project);
+ }
+
+ public Matcher(final Project project, final MatchOptions matchOptions) {
+ super(project, matchOptions);
+ }
+
+ /**
+ * Finds the matches of given pattern starting from given tree element.
+ * @throws MalformedPatternException
+ * @throws UnsupportedPatternException
+ */
+ public void findMatches(MatchResultSink sink,MatchOptions options) throws
+ MalformedPatternException, UnsupportedPatternException
+ {
+ super.findMatches(sink,options);
+ }
+
+ /**
+ * Finds the matches of given pattern starting from given tree element.
+ * @param source string for search
+ * @param pattern to be searched
+ * @return list of matches found
+ * @throws MalformedPatternException
+ * @throws UnsupportedPatternException
+ */
+ public List<MatchResult> testFindMatches(String source,
+ String pattern,
+ MatchOptions options,
+ boolean filePattern,
+ FileType sourceFileType,
+ String sourceExtension,
+ boolean physicalSourceFile)
+ throws MalformedPatternException, UnsupportedPatternException {
+ return super.testFindMatches(source, pattern, options, filePattern, sourceFileType, sourceExtension, physicalSourceFile);
+ }
+
+ public List<MatchResult> testFindMatches(String source, String pattern, MatchOptions options, boolean filePattern)
+ throws MalformedPatternException, UnsupportedPatternException {
+ return super.testFindMatches(source, pattern, options, filePattern);
+ }
+
+ /**
+ * Finds the matches of given pattern starting from given tree element.
+ * @param sink
+ * @param options
+ * @throws MalformedPatternException
+ * @throws UnsupportedPatternException
+ */
+ public void testFindMatches(MatchResultSink sink,MatchOptions options)
+ throws MalformedPatternException, UnsupportedPatternException {
+
+ super.testFindMatches(sink,options);
+ }
+
+ /**
+ * Tests if given element is matched by given pattern starting from target variable. If matching succeeds
+ * then not null match result is returned.
+ * @throws MalformedPatternException
+ * @throws UnsupportedPatternException
+ */
+ @Nullable
+ public MatchResult isMatchedByDownUp(PsiElement element,MatchOptions options) throws
+ MalformedPatternException, UnsupportedPatternException
+ {
+ return super.isMatchedByDownUp(element, options);
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/MatchingProcess.java b/platform/structuralsearch/source/com/intellij/structuralsearch/MatchingProcess.java
new file mode 100644
index 000000000000..6ecb42d800c3
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/MatchingProcess.java
@@ -0,0 +1,13 @@
+package com.intellij.structuralsearch;
+
+/**
+ * Interface of running matching process
+ */
+public interface MatchingProcess {
+ void stop();
+ void pause();
+ void resume();
+
+ boolean isSuspended();
+ boolean isEnded();
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/NamedScriptableDefinition.java b/platform/structuralsearch/source/com/intellij/structuralsearch/NamedScriptableDefinition.java
new file mode 100644
index 000000000000..8c954ab5a0ef
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/NamedScriptableDefinition.java
@@ -0,0 +1,81 @@
+package com.intellij.structuralsearch;
+
+import com.intellij.openapi.util.JDOMExternalizable;
+import org.jdom.Element;
+import org.jdom.Attribute;
+import org.jetbrains.annotations.NonNls;
+
+/**
+ * @author Maxim.Mossienko
+ * Date: 11.06.2009
+ * Time: 12:55:39
+ */
+public class NamedScriptableDefinition implements JDOMExternalizable, Cloneable {
+ @NonNls private static final String NAME = "name";
+ @NonNls private static final String SCRIPT = "script";
+ private String name;
+ private String scriptCodeConstraint = "";
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getScriptCodeConstraint() {
+ return scriptCodeConstraint;
+ }
+
+ public void setScriptCodeConstraint(String scriptCodeConstraint) {
+ this.scriptCodeConstraint = scriptCodeConstraint;
+ }
+
+ public Object clone() {
+ try {
+ return super.clone();
+ } catch(CloneNotSupportedException ex) {
+ return null;
+ }
+ }
+
+ public void readExternal(Element element) {
+ Attribute attribute = element.getAttribute(NAME);
+ if (attribute != null) {
+ name = attribute.getValue();
+ }
+
+ String s = element.getAttributeValue(SCRIPT);
+ if (s != null) {
+ setScriptCodeConstraint(s);
+ }
+ }
+
+ public void writeExternal(Element element) {
+ element.setAttribute(NAME,name);
+ if (scriptCodeConstraint.length() > 0) element.setAttribute(SCRIPT,scriptCodeConstraint);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof NamedScriptableDefinition)) return false;
+
+ NamedScriptableDefinition that = (NamedScriptableDefinition)o;
+
+ if (name != null ? !name.equals(that.name) : that.name != null) return false;
+ if (scriptCodeConstraint != null ? !scriptCodeConstraint.equals(that.scriptCodeConstraint) : that.scriptCodeConstraint != null) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = name != null ? name.hashCode() : 0;
+ result = 31 * result + (scriptCodeConstraint != null ? scriptCodeConstraint.hashCode() : 0);
+ return result;
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/PredefinedConfigurationUtil.java b/platform/structuralsearch/source/com/intellij/structuralsearch/PredefinedConfigurationUtil.java
new file mode 100644
index 000000000000..6e14ce437025
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/PredefinedConfigurationUtil.java
@@ -0,0 +1,34 @@
+package com.intellij.structuralsearch;
+
+import com.intellij.structuralsearch.impl.matcher.MatcherImplUtil;
+import com.intellij.openapi.fileTypes.FileType;
+import com.intellij.openapi.fileTypes.StdFileTypes;
+import com.intellij.structuralsearch.plugin.ui.Configuration;
+import com.intellij.structuralsearch.plugin.ui.SearchConfiguration;
+import org.jetbrains.annotations.NonNls;
+
+public class PredefinedConfigurationUtil {
+
+ public static Configuration createSearchTemplateInfo(String name, @NonNls String criteria, String category) {
+ return createSearchTemplateInfo(name, criteria, category, StdFileTypes.JAVA);
+ }
+
+ public static Configuration createSearchTemplateInfo(String name, @NonNls String criteria, String category, FileType fileType) {
+ final SearchConfiguration config = new SearchConfiguration();
+ config.setPredefined(true);
+ config.setName(name);
+ config.setCategory(category);
+ config.getMatchOptions().setSearchPattern(criteria);
+ config.getMatchOptions().setFileType(fileType);
+ MatcherImplUtil.transform( config.getMatchOptions() );
+
+ return config;
+ }
+
+ public static Configuration createSearchTemplateInfoSimple(String name, @NonNls String criteria, String category) {
+ final Configuration info = createSearchTemplateInfo(name,criteria,category);
+ info.getMatchOptions().setRecursiveSearch(false);
+
+ return info;
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/ReplacementVariableDefinition.java b/platform/structuralsearch/source/com/intellij/structuralsearch/ReplacementVariableDefinition.java
new file mode 100644
index 000000000000..1010db48c1f8
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/ReplacementVariableDefinition.java
@@ -0,0 +1,14 @@
+package com.intellij.structuralsearch;
+
+/**
+ * @author Maxim.Mossienko
+ * Date: Mar 19, 2004
+ * Time: 5:36:32 PM
+ */
+public class ReplacementVariableDefinition extends NamedScriptableDefinition {
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof ReplacementVariableDefinition)) return false;
+ return super.equals(o);
+ }
+} \ No newline at end of file
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/SSRBundle.java b/platform/structuralsearch/source/com/intellij/structuralsearch/SSRBundle.java
new file mode 100644
index 000000000000..8ef278154b24
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/SSRBundle.java
@@ -0,0 +1,32 @@
+package com.intellij.structuralsearch;
+
+import com.intellij.CommonBundle;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.PropertyKey;
+
+import java.lang.ref.Reference;
+import java.lang.ref.SoftReference;
+import java.util.ResourceBundle;
+
+public class SSRBundle {
+
+ public static String message(@NotNull @PropertyKey(resourceBundle = BUNDLE) String key, @NotNull Object... params) {
+ return CommonBundle.message(getBundle(), key, params);
+ }
+
+ private static Reference<ResourceBundle> ourBundle;
+ @NonNls private static final String BUNDLE = "messages.SSRBundle";
+
+ private SSRBundle() {
+ }
+
+ private static ResourceBundle getBundle() {
+ ResourceBundle bundle = com.intellij.reference.SoftReference.dereference(ourBundle);
+ if (bundle == null) {
+ bundle = ResourceBundle.getBundle(BUNDLE);
+ ourBundle = new SoftReference<ResourceBundle>(bundle);
+ }
+ return bundle;
+ }
+} \ No newline at end of file
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/StructuralReplaceHandler.java b/platform/structuralsearch/source/com/intellij/structuralsearch/StructuralReplaceHandler.java
new file mode 100644
index 000000000000..2a9bf219fe37
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/StructuralReplaceHandler.java
@@ -0,0 +1,16 @@
+package com.intellij.structuralsearch;
+
+import com.intellij.psi.PsiElement;
+import com.intellij.structuralsearch.plugin.replace.ReplaceOptions;
+import com.intellij.structuralsearch.plugin.replace.ReplacementInfo;
+
+/**
+ * @author Eugene.Kudelevsky
+ */
+public abstract class StructuralReplaceHandler {
+ public abstract void replace(final ReplacementInfo info, ReplaceOptions options);
+
+ public void prepare(ReplacementInfo info) {}
+
+ public void postProcess(PsiElement affectedElement, ReplaceOptions options) {}
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/StructuralSearchException.java b/platform/structuralsearch/source/com/intellij/structuralsearch/StructuralSearchException.java
new file mode 100644
index 000000000000..f925285054f0
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/StructuralSearchException.java
@@ -0,0 +1,13 @@
+
+package com.intellij.structuralsearch;
+
+/**
+ * @author Bas Leijdekkers
+ */
+public class StructuralSearchException extends RuntimeException {
+ public StructuralSearchException() {}
+
+ public StructuralSearchException(String message) {
+ super(message);
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/StructuralSearchProfile.java b/platform/structuralsearch/source/com/intellij/structuralsearch/StructuralSearchProfile.java
new file mode 100644
index 000000000000..e40683dce04e
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/StructuralSearchProfile.java
@@ -0,0 +1,281 @@
+package com.intellij.structuralsearch;
+
+import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer;
+import com.intellij.codeInsight.template.TemplateContextType;
+import com.intellij.lang.Language;
+import com.intellij.openapi.editor.Document;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.editor.EditorFactory;
+import com.intellij.openapi.editor.ex.EditorEx;
+import com.intellij.openapi.extensions.ExtensionPointName;
+import com.intellij.openapi.fileTypes.FileType;
+import com.intellij.openapi.fileTypes.LanguageFileType;
+import com.intellij.openapi.project.Project;
+import com.intellij.psi.*;
+import com.intellij.structuralsearch.impl.matcher.CompiledPattern;
+import com.intellij.structuralsearch.impl.matcher.GlobalMatchingVisitor;
+import com.intellij.structuralsearch.impl.matcher.PatternTreeContext;
+import com.intellij.structuralsearch.impl.matcher.compiler.GlobalCompilingVisitor;
+import com.intellij.structuralsearch.impl.matcher.filters.LexicalNodesFilter;
+import com.intellij.structuralsearch.plugin.replace.ReplaceOptions;
+import com.intellij.structuralsearch.plugin.replace.impl.ParameterInfo;
+import com.intellij.structuralsearch.plugin.replace.impl.ReplacementBuilder;
+import com.intellij.structuralsearch.plugin.replace.impl.ReplacementContext;
+import com.intellij.structuralsearch.plugin.replace.impl.Replacer;
+import com.intellij.structuralsearch.plugin.ui.Configuration;
+import com.intellij.structuralsearch.plugin.ui.SearchContext;
+import com.intellij.structuralsearch.plugin.ui.UIUtil;
+import com.intellij.util.ArrayUtil;
+import com.intellij.util.LocalTimeCounter;
+import com.intellij.util.containers.ContainerUtil;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+
+/**
+ * @author Eugene.Kudelevsky
+ */
+public abstract class StructuralSearchProfile {
+ public static final ExtensionPointName<StructuralSearchProfile> EP_NAME =
+ ExtensionPointName.create("com.intellij.structuralsearch.profile");
+
+ public abstract void compile(PsiElement[] elements, @NotNull GlobalCompilingVisitor globalVisitor);
+
+ @NotNull
+ public abstract PsiElementVisitor createMatchingVisitor(@NotNull GlobalMatchingVisitor globalVisitor);
+
+ @NotNull
+ public abstract PsiElementVisitor getLexicalNodesFilter(@NotNull LexicalNodesFilter filter);
+
+ @NotNull
+ public abstract CompiledPattern createCompiledPattern();
+
+ public static String getTypeName(FileType fileType) {
+ return fileType.getName().toLowerCase();
+ }
+
+ public abstract boolean canProcess(@NotNull FileType fileType);
+
+ public abstract boolean isMyLanguage(@NotNull Language language);
+
+ public boolean isMyFile(PsiFile file, @NotNull Language language, Language... patternLanguages) {
+ if (isMyLanguage(language) && ArrayUtil.find(patternLanguages, language) >= 0) {
+ return true;
+ }
+ return false;
+ }
+
+ @NotNull
+ public PsiElement[] createPatternTree(@NotNull String text,
+ @NotNull PatternTreeContext context,
+ @NotNull FileType fileType,
+ @Nullable Language language,
+ @Nullable String contextName,
+ @Nullable String extension,
+ @NotNull Project project,
+ boolean physical) {
+ final String ext = extension != null ? extension : fileType.getDefaultExtension();
+ final String name = "__dummy." + ext;
+ final PsiFileFactory factory = PsiFileFactory.getInstance(project);
+
+ final PsiFile file = language == null
+ ? factory.createFileFromText(name, fileType, text, LocalTimeCounter.currentTime(), physical, true)
+ : factory.createFileFromText(name, language, text, physical, true);
+
+ return file != null ? file.getChildren() : PsiElement.EMPTY_ARRAY;
+ }
+
+ @NotNull
+ public PsiElement[] createPatternTree(@NotNull String text,
+ @NotNull PatternTreeContext context,
+ @NotNull FileType fileType,
+ @NotNull Project project,
+ boolean physical) {
+ return createPatternTree(text, context, fileType, null, null, null, project, physical);
+ }
+
+ @NotNull
+ public Editor createEditor(@NotNull SearchContext searchContext,
+ @NotNull FileType fileType,
+ Language dialect,
+ String text,
+ boolean useLastConfiguration) {
+ PsiFile codeFragment = createCodeFragment(searchContext.getProject(), text, null);
+ if (codeFragment == null) {
+ codeFragment = createFileFragment(searchContext, fileType, dialect, text);
+ }
+
+ if (codeFragment != null) {
+ final Document doc = PsiDocumentManager.getInstance(searchContext.getProject()).getDocument(codeFragment);
+ assert doc != null : "code fragment element should be physical";
+ DaemonCodeAnalyzer.getInstance(searchContext.getProject()).setHighlightingEnabled(codeFragment, false);
+ return UIUtil.createEditor(doc, searchContext.getProject(), true, true, getTemplateContextType());
+ }
+
+ final EditorFactory factory = EditorFactory.getInstance();
+ final Document document = factory.createDocument(text);
+ final EditorEx editor = (EditorEx)factory.createEditor(document, searchContext.getProject());
+ editor.getSettings().setFoldingOutlineShown(false);
+ return editor;
+ }
+
+ private static PsiFile createFileFragment(SearchContext searchContext, FileType fileType, Language dialect, String text) {
+ final String name = "__dummy." + fileType.getDefaultExtension();
+ final PsiFileFactory factory = PsiFileFactory.getInstance(searchContext.getProject());
+
+ return dialect == null ?
+ factory.createFileFromText(name, fileType, text, LocalTimeCounter.currentTime(), true, true) :
+ factory.createFileFromText(name, dialect, text, true, true);
+ }
+
+ @Nullable
+ public PsiCodeFragment createCodeFragment(Project project, String text, @Nullable PsiElement context) {
+ return null;
+ }
+
+ @Nullable
+ public Class<? extends TemplateContextType> getTemplateContextTypeClass() {
+ return null;
+ }
+
+ public final TemplateContextType getTemplateContextType() {
+ final Class<? extends TemplateContextType> clazz = getTemplateContextTypeClass();
+ return clazz != null ? ContainerUtil.findInstance(TemplateContextType.EP_NAME.getExtensions(), clazz) : null;
+ }
+
+ @Nullable
+ public FileType detectFileType(@NotNull PsiElement context) {
+ return null;
+ }
+
+ @Nullable
+ public StructuralReplaceHandler getReplaceHandler(@NotNull ReplacementContext context) {
+ return null;
+ }
+
+ public void checkSearchPattern(Project project, MatchOptions options) {
+ }
+
+ public void checkReplacementPattern(Project project, ReplaceOptions options) {
+ String fileType = getTypeName(options.getMatchOptions().getFileType());
+ throw new UnsupportedPatternException(SSRBundle.message("replacement.not.supported.for.filetype", fileType));
+ }
+
+ @NotNull
+ public Language getLanguage(PsiElement element) {
+ return element.getLanguage();
+ }
+
+ // only for nodes not filtered by lexical-nodes filter; they can be by default
+ public boolean canBeVarDelimeter(@NotNull PsiElement element) {
+ return false;
+ }
+
+ public String getText(PsiElement match, int start, int end) {
+ final String matchText = match.getText();
+ if (start==0 && end==-1) return matchText;
+ return matchText.substring(start, end == -1 ? matchText.length() : end);
+ }
+
+ public Class getElementContextByPsi(PsiElement element) {
+ return element.getClass();
+ }
+
+ public String getTypedVarString(PsiElement element) {
+ if (element instanceof PsiNamedElement) {
+ return ((PsiNamedElement)element).getName();
+ }
+ return element.getText();
+ }
+
+ public String getMeaningfulText(PsiElement element) {
+ return getTypedVarString(element);
+ }
+
+ public PsiElement updateCurrentNode(PsiElement node) {
+ return node;
+ }
+
+ public PsiElement extendMatchedByDownUp(PsiElement node) {
+ return node;
+ }
+
+ public PsiElement extendMatchOnePsiFile(PsiElement file) {
+ return file;
+ }
+
+ public LanguageFileType getDefaultFileType(@Nullable LanguageFileType fileType) {
+ return fileType;
+ }
+
+ Configuration[] getPredefinedTemplates() {
+ return Configuration.EMPTY_ARRAY;
+ }
+
+ public void provideAdditionalReplaceOptions(@NotNull PsiElement node, ReplaceOptions options, ReplacementBuilder builder) {}
+
+ public int handleSubstitution(final ParameterInfo info,
+ MatchResult match,
+ StringBuilder result,
+ int offset,
+ HashMap<String, MatchResult> matchMap) {
+ return defaultHandleSubstitution(info, match, result, offset);
+ }
+
+ public static int defaultHandleSubstitution(ParameterInfo info, MatchResult match, StringBuilder result, int offset) {
+ if (info.getName().equals(match.getName())) {
+ String replacementString = match.getMatchImage();
+ boolean forceAddingNewLine = false;
+ if (match.getAllSons().size() > 0 && !match.isScopeMatch()) {
+ // compound matches
+ StringBuilder buf = new StringBuilder();
+
+ for (final MatchResult matchResult : match.getAllSons()) {
+ final PsiElement currentElement = matchResult.getMatch();
+
+ if (buf.length() > 0) {
+ if (info.isArgumentContext()) {
+ buf.append(',');
+ } else {
+ buf.append(' ');
+ }
+ }
+
+ buf.append(matchResult.getMatchImage());
+ forceAddingNewLine = currentElement instanceof PsiComment;
+ }
+ replacementString = buf.toString();
+ } else {
+ if (info.isStatementContext()) {
+ forceAddingNewLine = match.getMatch() instanceof PsiComment;
+ }
+ }
+
+ offset = Replacer.insertSubstitution(result, offset, info, replacementString);
+ if (forceAddingNewLine && info.isStatementContext()) {
+ result.insert(info.getStartIndex() + offset + 1, '\n');
+ offset ++;
+ }
+ }
+ return offset;
+ }
+
+ public int processAdditionalOptions(ParameterInfo info, int offset, StringBuilder result, MatchResult r) {
+ return offset;
+ }
+
+ public boolean isIdentifier(PsiElement element) {
+ return false;
+ }
+
+ public Collection<String> getReservedWords() {
+ return Collections.emptySet();
+ }
+
+ public boolean isDocCommentOwner(PsiElement match) {
+ return false;
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/StructuralSearchProfileBase.java b/platform/structuralsearch/source/com/intellij/structuralsearch/StructuralSearchProfileBase.java
new file mode 100644
index 000000000000..9e9496115809
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/StructuralSearchProfileBase.java
@@ -0,0 +1,674 @@
+package com.intellij.structuralsearch;
+
+import com.intellij.dupLocator.PsiElementRole;
+import com.intellij.dupLocator.equivalence.EquivalenceDescriptor;
+import com.intellij.dupLocator.equivalence.EquivalenceDescriptorProvider;
+import com.intellij.dupLocator.equivalence.MultiChildDescriptor;
+import com.intellij.dupLocator.equivalence.SingleChildDescriptor;
+import com.intellij.dupLocator.iterators.FilteringNodeIterator;
+import com.intellij.dupLocator.iterators.NodeIterator;
+import com.intellij.dupLocator.util.DuplocatorUtil;
+import com.intellij.dupLocator.util.NodeFilter;
+import com.intellij.lang.ASTNode;
+import com.intellij.lang.Language;
+import com.intellij.lang.LanguageParserDefinitions;
+import com.intellij.lang.ParserDefinition;
+import com.intellij.openapi.fileTypes.FileType;
+import com.intellij.openapi.fileTypes.LanguageFileType;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.psi.*;
+import com.intellij.psi.impl.source.tree.LeafElement;
+import com.intellij.psi.tree.IElementType;
+import com.intellij.psi.tree.TokenSet;
+import com.intellij.structuralsearch.impl.matcher.*;
+import com.intellij.structuralsearch.impl.matcher.compiler.GlobalCompilingVisitor;
+import com.intellij.structuralsearch.impl.matcher.compiler.PatternCompiler;
+import com.intellij.structuralsearch.impl.matcher.filters.LexicalNodesFilter;
+import com.intellij.structuralsearch.impl.matcher.handlers.*;
+import com.intellij.structuralsearch.impl.matcher.iterators.SsrFilteringNodeIterator;
+import com.intellij.structuralsearch.impl.matcher.strategies.MatchingStrategy;
+import com.intellij.structuralsearch.plugin.replace.ReplaceOptions;
+import com.intellij.structuralsearch.plugin.replace.impl.ReplacementContext;
+import com.intellij.util.ArrayUtil;
+import com.intellij.util.LocalTimeCounter;
+import com.intellij.util.containers.HashSet;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+/**
+ * @author Eugene.Kudelevsky
+ */
+public abstract class StructuralSearchProfileBase extends StructuralSearchProfile {
+ private static final String DELIMETER_CHARS = ",;.[]{}():";
+ protected static final String PATTERN_PLACEHOLDER = "$$PATTERN_PLACEHOLDER$$";
+ private PsiElementVisitor myLexicalNodesFilter;
+
+ @Override
+ public void compile(PsiElement[] elements, @NotNull final GlobalCompilingVisitor globalVisitor) {
+ final PsiElement topElement = elements[0].getParent();
+ final PsiElement element = elements.length > 1 ? topElement : elements[0];
+
+ element.accept(new MyCompilingVisitor(globalVisitor, topElement));
+
+ element.accept(new PsiRecursiveElementVisitor() {
+ @Override
+ public void visitElement(PsiElement element) {
+ super.visitElement(element);
+ if (DuplocatorUtil.isIgnoredNode(element)) {
+ return;
+ }
+ CompiledPattern pattern = globalVisitor.getContext().getPattern();
+ MatchingHandler handler = pattern.getHandler(element);
+
+ if (!(handler instanceof SubstitutionHandler) &&
+ !(handler instanceof TopLevelMatchingHandler) &&
+ !(handler instanceof LightTopLevelMatchingHandler)) {
+ pattern.setHandler(element, new SkippingHandler(handler));
+ }
+
+ // todo: simplify logic
+
+ /*
+ place skipping handler under top-level handler, because when we skip top-level node we can get non top-level handler, so
+ depth matching won't be done!;
+ */
+ if (handler instanceof LightTopLevelMatchingHandler) {
+ MatchingHandler delegate = ((LightTopLevelMatchingHandler)handler).getDelegate();
+ if (!(delegate instanceof SubstitutionHandler)) {
+ pattern.setHandler(element, new LightTopLevelMatchingHandler(new SkippingHandler(delegate)));
+ }
+ }
+ }
+ });
+
+
+ final Language baseLanguage = element.getContainingFile().getLanguage();
+
+ // todo: try to optimize it: too heavy strategy!
+ globalVisitor.getContext().getPattern().setStrategy(new MatchingStrategy() {
+ @Override
+ public boolean continueMatching(PsiElement start) {
+ Language language = start.getLanguage();
+
+ PsiFile file = start.getContainingFile();
+ if (file != null) {
+ Language fileLanguage = file.getLanguage();
+ if (fileLanguage.isKindOf(language)) {
+ // dialect
+ language = fileLanguage;
+ }
+ }
+
+ return language == baseLanguage;
+ }
+
+ @Override
+ public boolean shouldSkip(PsiElement element, PsiElement elementToMatchWith) {
+ return DuplocatorUtil.shouldSkip(element, elementToMatchWith);
+ }
+ });
+ }
+
+ @NotNull
+ @Override
+ public PsiElementVisitor createMatchingVisitor(@NotNull GlobalMatchingVisitor globalVisitor) {
+ return new MyMatchingVisitor(globalVisitor);
+ }
+
+ @NotNull
+ @Override
+ public PsiElementVisitor getLexicalNodesFilter(@NotNull final LexicalNodesFilter filter) {
+ if (myLexicalNodesFilter == null) {
+ myLexicalNodesFilter = new PsiElementVisitor() {
+ @Override
+ public void visitElement(PsiElement element) {
+ super.visitElement(element);
+ if (DuplocatorUtil.isIgnoredNode(element)) {
+ filter.setResult(true);
+ }
+ }
+ };
+ }
+ return myLexicalNodesFilter;
+ }
+
+ public static boolean containsOnlyDelimeters(String s) {
+ for (int i = 0, n = s.length(); i < n; i++) {
+ if (DELIMETER_CHARS.indexOf(s.charAt(i)) < 0) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @NotNull
+ protected abstract String[] getVarPrefixes();
+
+ @NotNull
+ @Override
+ public CompiledPattern createCompiledPattern() {
+ return new CompiledPattern() {
+
+ @Override
+ protected SubstitutionHandler doCreateSubstitutionHandler(String name, boolean target, int minOccurs, int maxOccurs, boolean greedy) {
+ return new MySubstitutionHandler(name, target, minOccurs, maxOccurs, greedy);
+ }
+
+ @Override
+ public String[] getTypedVarPrefixes() {
+ return getVarPrefixes();
+ }
+
+ @Override
+ public boolean isTypedVar(String str) {
+ for (String prefix : getVarPrefixes()) {
+ if (str.startsWith(prefix)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public String getTypedVarString(PsiElement element) {
+ final PsiElement initialElement = element;
+ PsiElement child = SkippingHandler.getOnlyNonWhitespaceChild(element);
+
+ while (child != element && child != null && !(child instanceof LeafElement)) {
+ element = child;
+ child = SkippingHandler.getOnlyNonWhitespaceChild(element);
+ }
+ return child instanceof LeafElement ? element.getText() : initialElement.getText();
+ }
+ };
+ }
+
+ @Override
+ public boolean canProcess(@NotNull FileType fileType) {
+ return fileType instanceof LanguageFileType &&
+ isMyLanguage(((LanguageFileType)fileType).getLanguage());
+ }
+
+ @Override
+ public boolean isMyLanguage(@NotNull Language language) {
+ return language.isKindOf(getFileType().getLanguage());
+ }
+
+ @NotNull
+ protected abstract LanguageFileType getFileType();
+
+ @NotNull
+ @Override
+ public PsiElement[] createPatternTree(@NotNull String text,
+ @NotNull PatternTreeContext context,
+ @NotNull FileType fileType,
+ @Nullable Language language,
+ @Nullable String contextName,
+ @Nullable String extension,
+ @NotNull Project project,
+ boolean physical) {
+ if (context == PatternTreeContext.Block) {
+ final String strContext = getContext(text, language, contextName);
+ return strContext != null ?
+ parsePattern(project, strContext, text, fileType, language, extension, physical) :
+ PsiElement.EMPTY_ARRAY;
+ }
+ return super.createPatternTree(text, context, fileType, language, contextName, extension, project, physical);
+ }
+
+ @Override
+ public void checkReplacementPattern(Project project, ReplaceOptions options) {
+ final CompiledPattern compiledPattern = PatternCompiler.compilePattern(project, options.getMatchOptions());
+ if (compiledPattern == null) {
+ return;
+ }
+
+ final NodeIterator it = compiledPattern.getNodes();
+ if (!it.hasNext()) {
+ return;
+ }
+
+ final PsiElement root = it.current().getParent();
+
+ if (!checkOptionalChildren(root) ||
+ !checkErrorElements(root)) {
+ throw new UnsupportedPatternException(": Partial and expression patterns are not supported");
+ }
+ }
+
+ private static boolean checkErrorElements(PsiElement element) {
+ final boolean[] result = {true};
+ final int endOffset = element.getTextRange().getEndOffset();
+
+ element.accept(new PsiRecursiveElementWalkingVisitor() {
+ @Override
+ public void visitElement(PsiElement element) {
+ super.visitElement(element);
+
+ if (element instanceof PsiErrorElement && element.getTextRange().getEndOffset() == endOffset) {
+ result[0] = false;
+ }
+ }
+ });
+
+ return result[0];
+ }
+
+ private static boolean checkOptionalChildren(PsiElement root) {
+ final boolean[] result = {true};
+
+ root.accept(new PsiRecursiveElementWalkingVisitor() {
+ @Override
+ public void visitElement(PsiElement element) {
+ super.visitElement(element);
+
+ if (element instanceof LeafElement) {
+ return;
+ }
+
+ final EquivalenceDescriptorProvider provider = EquivalenceDescriptorProvider.getInstance(element);
+ if (provider == null) {
+ return;
+ }
+
+ final EquivalenceDescriptor descriptor = provider.buildDescriptor(element);
+ if (descriptor == null) {
+ return;
+ }
+
+ for (SingleChildDescriptor childDescriptor : descriptor.getSingleChildDescriptors()) {
+ if (childDescriptor.getType() == SingleChildDescriptor.MyType.OPTIONALLY_IN_PATTERN &&
+ childDescriptor.getElement() == null) {
+ result[0] = false;
+ }
+ }
+
+ for (MultiChildDescriptor childDescriptor : descriptor.getMultiChildDescriptors()) {
+ if (childDescriptor.getType() == MultiChildDescriptor.MyType.OPTIONALLY_IN_PATTERN) {
+ PsiElement[] elements = childDescriptor.getElements();
+ if (elements == null || elements.length == 0) {
+ result[0] = false;
+ }
+ }
+ }
+ }
+ });
+ return result[0];
+ }
+
+ @Override
+ public StructuralReplaceHandler getReplaceHandler(@NotNull ReplacementContext context) {
+ return new DocumentBasedReplaceHandler(context.getProject());
+ }
+
+ @NotNull
+ public String[] getContextNames() {
+ return ArrayUtil.EMPTY_STRING_ARRAY;
+ }
+
+ @Nullable
+ protected String getContext(@NotNull String pattern, @Nullable Language language, @Nullable String contextName) {
+ return PATTERN_PLACEHOLDER;
+ }
+
+ private static boolean canBePatternVariable(PsiElement element) {
+ // can be leaf element! (ex. var a = 1 <-> var $a$ = 1)
+ if (element instanceof LeafElement) {
+ return true;
+ }
+
+ while (!(element instanceof LeafElement) && element != null) {
+ element = SkippingHandler.getOnlyNonWhitespaceChild(element);
+ }
+ return element != null;
+ }
+
+ private static boolean isLiteral(PsiElement element) {
+ if (element == null) return false;
+ final ASTNode astNode = element.getNode();
+ if (astNode == null) {
+ return false;
+ }
+ final IElementType elementType = astNode.getElementType();
+ final ParserDefinition parserDefinition = LanguageParserDefinitions.INSTANCE.forLanguage(element.getLanguage());
+ if (parserDefinition != null) {
+ final TokenSet literals = parserDefinition.getStringLiteralElements();
+ return literals.contains(elementType);
+ }
+ return false;
+ }
+
+ private static boolean canBePatternVariableValue(PsiElement element) {
+ // can be leaf element! (ex. var a = 1 <-> var $a$ = 1)
+ return !containsOnlyDelimeters(element.getText());
+ }
+
+ @Override
+ public boolean canBeVarDelimeter(@NotNull PsiElement element) {
+ final ASTNode node = element.getNode();
+ return node != null && getVariableDelimiters().contains(node.getElementType());
+ }
+
+ protected TokenSet getVariableDelimiters() {
+ return TokenSet.EMPTY;
+ }
+
+ public static PsiElement[] parsePattern(Project project,
+ String context,
+ String pattern,
+ FileType fileType,
+ Language language,
+ String extension,
+ boolean physical) {
+ int offset = context.indexOf(PATTERN_PLACEHOLDER);
+
+ final int patternLength = pattern.length();
+ final String patternInContext = context.replace(PATTERN_PLACEHOLDER, pattern);
+
+ final String ext = extension != null ? extension : fileType.getDefaultExtension();
+ final String name = "__dummy." + ext;
+ final PsiFileFactory factory = PsiFileFactory.getInstance(project);
+
+ final PsiFile file = language == null
+ ? factory.createFileFromText(name, fileType, patternInContext, LocalTimeCounter.currentTime(), physical, true)
+ : factory.createFileFromText(name, language, patternInContext, physical, true);
+ if (file == null) {
+ return PsiElement.EMPTY_ARRAY;
+ }
+
+ final List<PsiElement> result = new ArrayList<PsiElement>();
+
+ PsiElement element = file.findElementAt(offset);
+ if (element == null) {
+ return PsiElement.EMPTY_ARRAY;
+ }
+
+ PsiElement topElement = element;
+ element = element.getParent();
+
+ while (element != null) {
+ if (element.getTextRange().getStartOffset() == offset && element.getTextLength() <= patternLength) {
+ topElement = element;
+ }
+ element = element.getParent();
+ }
+
+ if (topElement instanceof PsiFile) {
+ return topElement.getChildren();
+ }
+
+ final int endOffset = offset + patternLength;
+ result.add(topElement);
+ topElement = topElement.getNextSibling();
+
+ while (topElement != null && topElement.getTextRange().getEndOffset() <= endOffset) {
+ result.add(topElement);
+ topElement = topElement.getNextSibling();
+ }
+
+ return result.toArray(new PsiElement[result.size()]);
+ }
+
+ // todo: support expression patterns
+ // todo: support {statement;} = statement; (node has only non-lexical child)
+
+ private static class MyCompilingVisitor extends PsiRecursiveElementVisitor {
+ private final GlobalCompilingVisitor myGlobalVisitor;
+ private final PsiElement myTopElement;
+
+ private Pattern[] mySubstitutionPatterns;
+
+ private MyCompilingVisitor(GlobalCompilingVisitor globalVisitor, PsiElement topElement) {
+ myGlobalVisitor = globalVisitor;
+ myTopElement = topElement;
+ }
+
+ @Override
+ public void visitElement(PsiElement element) {
+ doVisitElement(element);
+
+ if (isLiteral(element)) {
+ visitLiteral(element);
+ }
+ }
+
+ private void doVisitElement(PsiElement element) {
+ CompiledPattern pattern = myGlobalVisitor.getContext().getPattern();
+
+ if (myGlobalVisitor.getCodeBlockLevel() == 0) {
+ initTopLevelElement(element);
+ return;
+ }
+
+ if (canBePatternVariable(element) && pattern.isRealTypedVar(element)) {
+ myGlobalVisitor.handle(element);
+ final MatchingHandler handler = pattern.getHandler(element);
+ handler.setFilter(new NodeFilter() {
+ public boolean accepts(PsiElement other) {
+ return canBePatternVariableValue(other);
+ }
+ });
+
+ super.visitElement(element);
+
+ return;
+ }
+
+ super.visitElement(element);
+
+ if (myGlobalVisitor.getContext().getSearchHelper().doOptimizing() && element instanceof LeafElement) {
+ ParserDefinition parserDefinition = LanguageParserDefinitions.INSTANCE.forLanguage(element.getLanguage());
+ if (parserDefinition != null) {
+ String text = element.getText();
+
+ // todo: support variables inside comments
+ boolean flag = true;
+ if (StringUtil.isJavaIdentifier(text) && flag) {
+ myGlobalVisitor.processTokenizedName(text, true, GlobalCompilingVisitor.OccurenceKind.CODE);
+ }
+ }
+ }
+ }
+
+ private void visitLiteral(PsiElement literal) {
+ String value = literal.getText();
+
+ if (value.length() > 2 &&
+ (value.charAt(0) == '"' && value.charAt(value.length() - 1) == '"') ||
+ (value.charAt(0) == '\'' && value.charAt(value.length() - 1) == '\'')) {
+
+ if (mySubstitutionPatterns == null) {
+ final String[] prefixes = myGlobalVisitor.getContext().getPattern().getTypedVarPrefixes();
+ mySubstitutionPatterns = createPatterns(prefixes);
+ }
+
+ for (Pattern substitutionPattern : mySubstitutionPatterns) {
+ @Nullable MatchingHandler handler =
+ myGlobalVisitor.processPatternStringWithFragments(value, GlobalCompilingVisitor.OccurenceKind.LITERAL, substitutionPattern);
+
+ if (handler != null) {
+ literal.putUserData(CompiledPattern.HANDLER_KEY, handler);
+ break;
+ }
+ }
+ }
+ }
+
+ private static Pattern[] createPatterns(String[] prefixes) {
+ final Pattern[] patterns = new Pattern[prefixes.length];
+
+ for (int i = 0; i < prefixes.length; i++) {
+ final String s = StructuralSearchUtil.shieldSpecialChars(prefixes[0]);
+ patterns[i] = Pattern.compile("\\b(" + s + "\\w+)\\b");
+ }
+ return patterns;
+ }
+
+ private void initTopLevelElement(PsiElement element) {
+ CompiledPattern pattern = myGlobalVisitor.getContext().getPattern();
+
+ PsiElement newElement = SkippingHandler.skipNodeIfNeccessary(element);
+
+ if (element != newElement && newElement != null) {
+ // way to support partial matching (ex. if ($condition$) )
+ newElement.accept(this);
+ pattern.setHandler(element, new LightTopLevelMatchingHandler(pattern.getHandler(element)));
+ }
+ else {
+ myGlobalVisitor.setCodeBlockLevel(myGlobalVisitor.getCodeBlockLevel() + 1);
+
+ for (PsiElement el = element.getFirstChild(); el != null; el = el.getNextSibling()) {
+ if (GlobalCompilingVisitor.getFilter().accepts(el)) {
+ if (el instanceof PsiWhiteSpace) {
+ myGlobalVisitor.addLexicalNode(el);
+ }
+ }
+ else {
+ el.accept(this);
+
+ MatchingHandler matchingHandler = pattern.getHandler(el);
+ pattern.setHandler(el, element == myTopElement ? new TopLevelMatchingHandler(matchingHandler) :
+ new LightTopLevelMatchingHandler(matchingHandler));
+
+ /*
+ do not assign light-top-level handlers through skipping, because it is incorrect;
+ src: if (...) { st1; st2; }
+ pattern: if (...) {$a$;}
+
+ $a$ will have top-level handler, so matching will be considered as correct, although "st2;" is left!
+ */
+ }
+ }
+
+ myGlobalVisitor.setCodeBlockLevel(myGlobalVisitor.getCodeBlockLevel() - 1);
+ pattern.setHandler(element, new TopLevelMatchingHandler(pattern.getHandler(element)));
+ }
+ }
+ }
+
+ private static class MyMatchingVisitor extends PsiElementVisitor {
+ private final GlobalMatchingVisitor myGlobalVisitor;
+
+ private MyMatchingVisitor(GlobalMatchingVisitor globalVisitor) {
+ myGlobalVisitor = globalVisitor;
+ }
+
+ private boolean shouldIgnoreVarNode(PsiElement element) {
+ MatchingHandler handler = myGlobalVisitor.getMatchContext().getPattern().getHandlerSimple(element);
+ if (handler instanceof DelegatingHandler) {
+ handler = ((DelegatingHandler)handler).getDelegate();
+ }
+ return handler instanceof MySubstitutionHandler && ((MySubstitutionHandler)handler).myExceptedNodes.contains(element);
+ }
+
+ @Override
+ public void visitElement(PsiElement element) {
+ super.visitElement(element);
+
+ final EquivalenceDescriptorProvider descriptorProvider = EquivalenceDescriptorProvider.getInstance(element);
+
+ if (descriptorProvider != null) {
+ final EquivalenceDescriptor descriptor1 = descriptorProvider.buildDescriptor(element);
+ final EquivalenceDescriptor descriptor2 = descriptorProvider.buildDescriptor(myGlobalVisitor.getElement());
+
+ if (descriptor1 != null && descriptor2 != null) {
+ final boolean result = DuplocatorUtil
+ .match(descriptor1, descriptor2, myGlobalVisitor, Collections.<PsiElementRole>emptySet(), null);
+ myGlobalVisitor.setResult(result);
+ return;
+ }
+ }
+
+ if (isLiteral(element)) {
+ visitLiteral(element);
+ return;
+ }
+
+ if (canBePatternVariable(element) &&
+ myGlobalVisitor.getMatchContext().getPattern().isRealTypedVar(element) &&
+ !shouldIgnoreVarNode(element)) {
+
+ PsiElement matchedElement = myGlobalVisitor.getElement();
+ PsiElement newElement = SkippingHandler.skipNodeIfNeccessary(matchedElement);
+ while (newElement != matchedElement) {
+ matchedElement = newElement;
+ newElement = SkippingHandler.skipNodeIfNeccessary(matchedElement);
+ }
+
+ myGlobalVisitor.setResult(myGlobalVisitor.handleTypedElement(element, matchedElement));
+ }
+ else if (element instanceof LeafElement) {
+ myGlobalVisitor.setResult(element.getText().equals(myGlobalVisitor.getElement().getText()));
+ }
+ else if (element.getFirstChild() == null && element.getTextLength() == 0) {
+ myGlobalVisitor.setResult(true);
+ }
+ else {
+ PsiElement patternChild = element.getFirstChild();
+ PsiElement matchedChild = myGlobalVisitor.getElement().getFirstChild();
+
+ FilteringNodeIterator patternIterator = new SsrFilteringNodeIterator(patternChild);
+ FilteringNodeIterator matchedIterator = new SsrFilteringNodeIterator(matchedChild);
+
+ boolean matched = myGlobalVisitor.matchSequentially(patternIterator, matchedIterator);
+ myGlobalVisitor.setResult(matched);
+ }
+ }
+
+ public void visitLiteral(PsiElement literal) {
+ final PsiElement l2 = myGlobalVisitor.getElement();
+
+ MatchingHandler handler = (MatchingHandler)literal.getUserData(CompiledPattern.HANDLER_KEY);
+
+ if (handler instanceof SubstitutionHandler) {
+ int offset = 0;
+ int length = l2.getTextLength();
+ final String text = l2.getText();
+
+ if (length > 2 &&
+ (text.charAt(0) == '"' && text.charAt(length - 1) == '"') ||
+ (text.charAt(0) == '\'' && text.charAt(length - 1) == '\'')) {
+ length--;
+ offset++;
+ }
+ myGlobalVisitor.setResult(((SubstitutionHandler)handler).handle(l2, offset, length, myGlobalVisitor.getMatchContext()));
+ }
+ else if (handler != null) {
+ myGlobalVisitor.setResult(handler.match(literal, l2, myGlobalVisitor.getMatchContext()));
+ }
+ else {
+ myGlobalVisitor.setResult(literal.textMatches(l2));
+ }
+ }
+ }
+
+ private static class MySubstitutionHandler extends SubstitutionHandler {
+ final Set<PsiElement> myExceptedNodes;
+
+ public MySubstitutionHandler(String name, boolean target, int minOccurs, int maxOccurs, boolean greedy) {
+ super(name, target, minOccurs, maxOccurs, greedy);
+ myExceptedNodes = new HashSet<PsiElement>();
+ }
+
+ @Override
+ public boolean matchSequentially(NodeIterator nodes, NodeIterator nodes2, MatchContext context) {
+ if (doMatchSequentially(nodes, nodes2, context)) {
+ return true;
+ }
+ final PsiElement current = nodes.current();
+ if (current != null) {
+ myExceptedNodes.add(current);
+ }
+ final boolean result = doMatchSequentiallyBySimpleHandler(nodes, nodes2, context);
+ myExceptedNodes.remove(current);
+ return result;
+ }
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/StructuralSearchUtil.java b/platform/structuralsearch/source/com/intellij/structuralsearch/StructuralSearchUtil.java
new file mode 100644
index 000000000000..b37c10143636
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/StructuralSearchUtil.java
@@ -0,0 +1,169 @@
+package com.intellij.structuralsearch;
+
+import com.intellij.lang.Language;
+import com.intellij.openapi.fileTypes.*;
+import com.intellij.openapi.fileTypes.impl.AbstractFileType;
+import com.intellij.psi.PsiElement;
+import com.intellij.structuralsearch.impl.matcher.MatchUtils;
+import com.intellij.structuralsearch.plugin.ui.Configuration;
+import com.intellij.tokenindex.LanguageTokenizer;
+import com.intellij.tokenindex.Tokenizer;
+import org.jetbrains.annotations.Contract;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.*;
+
+/**
+ * @author Eugene.Kudelevsky
+ */
+public class StructuralSearchUtil {
+ private static LanguageFileType ourDefaultFileType = null;
+
+ public static boolean ourUseUniversalMatchingAlgorithm = false;
+ private static StructuralSearchProfile[] ourNewStyleProfiles;
+ private static List<Configuration> ourPredefinedConfigurations = null;
+
+ private StructuralSearchUtil() {}
+
+ @Nullable
+ public static StructuralSearchProfile getProfileByPsiElement(@NotNull PsiElement element) {
+ return getProfileByLanguage(element.getLanguage());
+ }
+
+ @Contract("null -> false")
+ public static boolean isIdentifier(PsiElement element) {
+ final StructuralSearchProfile profile = getProfileByPsiElement(element);
+ return profile != null && profile.isIdentifier(element);
+ }
+
+ private static StructuralSearchProfile[] getNewStyleProfiles() {
+ if (ourNewStyleProfiles == null) {
+ final List<StructuralSearchProfile> list = new ArrayList<StructuralSearchProfile>();
+
+ for (StructuralSearchProfile profile : StructuralSearchProfile.EP_NAME.getExtensions()) {
+ if (profile instanceof StructuralSearchProfileBase) {
+ list.add(profile);
+ }
+ }
+ list.add(new XmlStructuralSearchProfile());
+ ourNewStyleProfiles = list.toArray(new StructuralSearchProfile[list.size()]);
+ }
+ return ourNewStyleProfiles;
+ }
+
+ private static StructuralSearchProfile[] getProfiles() {
+ return ourUseUniversalMatchingAlgorithm
+ ? getNewStyleProfiles()
+ : StructuralSearchProfile.EP_NAME.getExtensions();
+ }
+
+ public static FileType getDefaultFileType() {
+ if (ourDefaultFileType == null) {
+ for (StructuralSearchProfile profile : getProfiles()) {
+ ourDefaultFileType = profile.getDefaultFileType(ourDefaultFileType);
+ }
+ if (ourDefaultFileType == null) {
+ ourDefaultFileType = StdFileTypes.XML;
+ }
+ }
+ assert isValidFileType(ourDefaultFileType) : "file type not valid for structural search: " + ourDefaultFileType.getName();
+ return ourDefaultFileType;
+ }
+
+ @Nullable
+ public static StructuralSearchProfile getProfileByLanguage(@NotNull Language language) {
+
+ for (StructuralSearchProfile profile : getProfiles()) {
+ if (profile.isMyLanguage(language)) {
+ return profile;
+ }
+ }
+ return null;
+ }
+
+ @Nullable
+ public static Tokenizer getTokenizerForLanguage(@NotNull Language language) {
+ return LanguageTokenizer.INSTANCE.forLanguage(language);
+ }
+
+ public static boolean isTypedVariable(@NotNull final String name) {
+ return name.charAt(0)=='$' && name.charAt(name.length()-1)=='$';
+ }
+
+ @Nullable
+ public static StructuralSearchProfile getProfileByFileType(FileType fileType) {
+
+ for (StructuralSearchProfile profile : getProfiles()) {
+ if (profile.canProcess(fileType)) {
+ return profile;
+ }
+ }
+
+ return null;
+ }
+
+ @NotNull
+ public static FileType[] getSuitableFileTypes() {
+ Set<FileType> allFileTypes = new HashSet<FileType>();
+ Collections.addAll(allFileTypes, FileTypeManager.getInstance().getRegisteredFileTypes());
+ for (Language language : Language.getRegisteredLanguages()) {
+ FileType fileType = language.getAssociatedFileType();
+ if (fileType != null) {
+ allFileTypes.add(fileType);
+ }
+ }
+
+ List<FileType> result = new ArrayList<FileType>();
+ for (FileType fileType : allFileTypes) {
+ if (isValidFileType(fileType)) {
+ result.add(fileType);
+ }
+ }
+
+ return result.toArray(new FileType[result.size()]);
+ }
+
+ private static boolean isValidFileType(FileType fileType) {
+ return fileType != StdFileTypes.GUI_DESIGNER_FORM &&
+ fileType != StdFileTypes.IDEA_MODULE &&
+ fileType != StdFileTypes.IDEA_PROJECT &&
+ fileType != StdFileTypes.IDEA_WORKSPACE &&
+ fileType != FileTypes.ARCHIVE &&
+ fileType != FileTypes.UNKNOWN &&
+ fileType != FileTypes.PLAIN_TEXT &&
+ !(fileType instanceof AbstractFileType) &&
+ !fileType.isBinary() &&
+ !fileType.isReadOnly();
+ }
+
+ public static String shieldSpecialChars(String word) {
+ final StringBuilder buf = new StringBuilder(word.length());
+
+ for (int i = 0; i < word.length(); ++i) {
+ if (MatchUtils.SPECIAL_CHARS.indexOf(word.charAt(i)) != -1) {
+ buf.append("\\");
+ }
+ buf.append(word.charAt(i));
+ }
+
+ return buf.toString();
+ }
+
+ public static List<Configuration> getPredefinedTemplates() {
+ if (ourPredefinedConfigurations == null) {
+ final List<Configuration> result = new ArrayList<Configuration>();
+ for (StructuralSearchProfile profile : getProfiles()) {
+ Collections.addAll(result, profile.getPredefinedTemplates());
+ }
+ Collections.sort(result);
+ ourPredefinedConfigurations = Collections.unmodifiableList(result);
+ }
+ return ourPredefinedConfigurations;
+ }
+
+ public static boolean isDocCommentOwner(PsiElement match) {
+ final StructuralSearchProfile profile = getProfileByPsiElement(match);
+ return profile != null && profile.isDocCommentOwner(match);
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/UnsupportedPatternException.java b/platform/structuralsearch/source/com/intellij/structuralsearch/UnsupportedPatternException.java
new file mode 100644
index 000000000000..1236264118c2
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/UnsupportedPatternException.java
@@ -0,0 +1,11 @@
+package com.intellij.structuralsearch;
+
+/**
+ * Exception about encountering yet unsupported pattern event.
+ */
+public class UnsupportedPatternException extends RuntimeException {
+
+ public UnsupportedPatternException(String _pattern) {
+ super(_pattern);
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/XmlStructuralSearchProfile.java b/platform/structuralsearch/source/com/intellij/structuralsearch/XmlStructuralSearchProfile.java
new file mode 100644
index 000000000000..5e0ca8bde957
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/XmlStructuralSearchProfile.java
@@ -0,0 +1,229 @@
+package com.intellij.structuralsearch;
+
+import com.intellij.codeInsight.template.TemplateContextType;
+import com.intellij.codeInsight.template.XmlContextType;
+import com.intellij.lang.Language;
+import com.intellij.lang.StdLanguages;
+import com.intellij.lang.xml.XMLLanguage;
+import com.intellij.openapi.fileTypes.FileType;
+import com.intellij.openapi.fileTypes.StdFileTypes;
+import com.intellij.openapi.project.Project;
+import com.intellij.psi.*;
+import com.intellij.psi.xml.XmlDocument;
+import com.intellij.psi.xml.XmlFile;
+import com.intellij.psi.xml.XmlTag;
+import com.intellij.psi.xml.XmlText;
+import com.intellij.structuralsearch.impl.matcher.*;
+import com.intellij.structuralsearch.impl.matcher.compiler.GlobalCompilingVisitor;
+import com.intellij.structuralsearch.impl.matcher.compiler.XmlCompilingVisitor;
+import com.intellij.structuralsearch.impl.matcher.filters.LexicalNodesFilter;
+import com.intellij.structuralsearch.impl.matcher.filters.XmlLexicalNodesFilter;
+import com.intellij.structuralsearch.plugin.replace.ReplaceOptions;
+import com.intellij.structuralsearch.plugin.replace.ReplacementInfo;
+import com.intellij.structuralsearch.plugin.replace.impl.ReplacementContext;
+import com.intellij.structuralsearch.plugin.replace.impl.Replacer;
+import com.intellij.structuralsearch.plugin.replace.impl.ReplacerUtil;
+import com.intellij.structuralsearch.plugin.ui.Configuration;
+import com.intellij.util.IncorrectOperationException;
+import com.intellij.util.LocalTimeCounter;
+import com.intellij.xml.util.HtmlUtil;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import static com.intellij.structuralsearch.PredefinedConfigurationUtil.createSearchTemplateInfo;
+
+/**
+ * @author Eugene.Kudelevsky
+ */
+public class XmlStructuralSearchProfile extends StructuralSearchProfile {
+
+ private XmlLexicalNodesFilter myLexicalNodesFilter;
+
+ public void compile(PsiElement[] elements, @NotNull GlobalCompilingVisitor globalVisitor) {
+ elements[0].getParent().accept(new XmlCompilingVisitor(globalVisitor));
+ }
+
+ @NotNull
+ public PsiElementVisitor createMatchingVisitor(@NotNull GlobalMatchingVisitor globalVisitor) {
+ return new XmlMatchingVisitor(globalVisitor);
+ }
+
+ @NotNull
+ @Override
+ public PsiElementVisitor getLexicalNodesFilter(@NotNull LexicalNodesFilter filter) {
+ if (myLexicalNodesFilter == null) {
+ myLexicalNodesFilter = new XmlLexicalNodesFilter(filter);
+ }
+ return myLexicalNodesFilter;
+ }
+
+ @NotNull
+ public CompiledPattern createCompiledPattern() {
+ return new XmlCompiledPattern();
+ }
+
+ @Override
+ public boolean canProcess(@NotNull FileType fileType) {
+ return fileType == StdFileTypes.XML || fileType == StdFileTypes.HTML || fileType == StdFileTypes.JSP ||
+ fileType == StdFileTypes.JSPX || fileType == StdFileTypes.XHTML;
+ }
+
+ public boolean isMyLanguage(@NotNull Language language) {
+ return language instanceof XMLLanguage;
+ }
+
+ @NotNull
+ @Override
+ public PsiElement[] createPatternTree(@NotNull String text,
+ @NotNull PatternTreeContext context,
+ @NotNull FileType fileType,
+ @Nullable Language language,
+ String contextName, @Nullable String extension,
+ @NotNull Project project,
+ boolean physical) {
+ final String ext = extension != null ? extension : fileType.getDefaultExtension();
+ String text1 = context == PatternTreeContext.File ? text : "<QQQ>" + text + "</QQQ>";
+ final PsiFile fileFromText = PsiFileFactory.getInstance(project)
+ .createFileFromText("dummy." + ext, fileType, text1, LocalTimeCounter.currentTime(), physical, true);
+
+ final XmlDocument document = HtmlUtil.getRealXmlDocument(((XmlFile)fileFromText).getDocument());
+ if (context == PatternTreeContext.File) {
+ return new PsiElement[]{document};
+ }
+
+ return document.getRootTag().getValue().getChildren();
+ }
+
+ @Override
+ public Class<? extends TemplateContextType> getTemplateContextTypeClass() {
+ return XmlContextType.class;
+ }
+
+ @NotNull
+ @Override
+ public FileType detectFileType(@NotNull PsiElement context) {
+ PsiFile file = context instanceof PsiFile ? (PsiFile)context : context.getContainingFile();
+ Language contextLanguage = context instanceof PsiFile ? null : context.getLanguage();
+ if (file.getLanguage() == StdLanguages.HTML || (file.getFileType() == StdFileTypes.JSP && contextLanguage == StdLanguages.HTML)) {
+ return StdFileTypes.HTML;
+ }
+ return StdFileTypes.XML;
+ }
+
+ @Override
+ public void checkReplacementPattern(Project project, ReplaceOptions options) {
+ }
+
+ @Override
+ public StructuralReplaceHandler getReplaceHandler(@NotNull ReplacementContext context) {
+ return new MyReplaceHandler(context);
+ }
+
+ private static class MyReplaceHandler extends StructuralReplaceHandler {
+ private final ReplacementContext myContext;
+
+ private MyReplaceHandler(ReplacementContext context) {
+ myContext = context;
+ }
+
+ public void replace(ReplacementInfo info, ReplaceOptions options) {
+ PsiElement elementToReplace = info.getMatch(0);
+ assert elementToReplace != null;
+ PsiElement elementParent = elementToReplace.getParent();
+ String replacementToMake = info.getReplacement();
+ boolean listContext = elementToReplace.getParent() instanceof XmlTag;
+
+ if (listContext) {
+ doReplaceInContext(info, elementToReplace, replacementToMake, elementParent, myContext);
+ }
+
+ PsiElement[] statements = ReplacerUtil.createTreeForReplacement(replacementToMake, PatternTreeContext.Block, myContext);
+
+ if (statements.length > 0) {
+ PsiElement replacement = ReplacerUtil.copySpacesAndCommentsBefore(elementToReplace, statements, replacementToMake, elementParent);
+
+ // preserve comments
+ Replacer.handleComments(elementToReplace, replacement, myContext);
+ elementToReplace.replace(replacement);
+ }
+ else {
+ final PsiElement nextSibling = elementToReplace.getNextSibling();
+ elementToReplace.delete();
+ assert nextSibling != null;
+ if (nextSibling.isValid()) {
+ if (nextSibling instanceof PsiWhiteSpace) {
+ nextSibling.delete();
+ }
+ }
+ }
+ }
+ }
+
+ private static void doReplaceInContext(ReplacementInfo info,
+ PsiElement elementToReplace,
+ String replacementToMake,
+ PsiElement elementParent,
+ ReplacementContext context) {
+ PsiElement[] statements = ReplacerUtil.createTreeForReplacement(replacementToMake, PatternTreeContext.Block, context);
+
+ if (statements.length > 1) {
+ elementParent.addRangeBefore(statements[0], statements[statements.length - 1], elementToReplace);
+ }
+ else if (statements.length == 1) {
+ PsiElement replacement = statements[0];
+
+ Replacer.handleComments(elementToReplace, replacement, context);
+
+ try {
+ elementParent.addBefore(replacement, elementToReplace);
+ }
+ catch (IncorrectOperationException e) {
+ elementToReplace.replace(replacement);
+ }
+ }
+
+ final int matchSize = info.getMatchesCount();
+
+ for (int i = 0; i < matchSize; ++i) {
+ PsiElement element = info.getMatch(i);
+
+ if (element == null) continue;
+ PsiElement firstToDelete = element;
+ PsiElement lastToDelete = element;
+ PsiElement prevSibling = element.getPrevSibling();
+ PsiElement nextSibling = element.getNextSibling();
+
+ if (prevSibling instanceof PsiWhiteSpace) {
+ firstToDelete = prevSibling;
+ }
+ else if (prevSibling == null && nextSibling instanceof PsiWhiteSpace) {
+ lastToDelete = nextSibling;
+ }
+ if (nextSibling instanceof XmlText && i + 1 < matchSize) {
+ final PsiElement next = info.getMatch(i + 1);
+ if (next != null && next == nextSibling.getNextSibling()) {
+ lastToDelete = nextSibling;
+ }
+ }
+ element.getParent().deleteChildRange(firstToDelete, lastToDelete);
+ }
+ }
+
+ @Override
+ Configuration[] getPredefinedTemplates() {
+ return XmlPredefinedConfigurations.createPredefinedTemplates();
+ }
+
+ private static class XmlPredefinedConfigurations {
+ private static final String HTML_XML = SSRBundle.message("xml_html.category");
+
+ private static Configuration[] createPredefinedTemplates() {
+ return new Configuration[]{
+ createSearchTemplateInfo("xml tag", "<'a/>", HTML_XML, StdFileTypes.XML),
+ createSearchTemplateInfo("xml attribute", "<'_tag 'attribute=\"'_value\"/>", HTML_XML, StdFileTypes.XML),
+ createSearchTemplateInfo("xml attribute value", "<'_tag '_attribute=\"'value\"/>", HTML_XML, StdFileTypes.XML),
+ createSearchTemplateInfo("xml/html tag value", "<table>'_content*</table>", HTML_XML, StdFileTypes.HTML),
+ };
+ }
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/CompiledPattern.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/CompiledPattern.java
new file mode 100644
index 000000000000..565f80ac01ce
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/CompiledPattern.java
@@ -0,0 +1,182 @@
+package com.intellij.structuralsearch.impl.matcher;
+
+import com.intellij.dupLocator.iterators.ArrayBackedNodeIterator;
+import com.intellij.dupLocator.iterators.NodeIterator;
+import com.intellij.openapi.util.Key;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.search.SearchScope;
+import com.intellij.psi.util.PsiUtilCore;
+import com.intellij.structuralsearch.StructuralSearchProfile;
+import com.intellij.structuralsearch.StructuralSearchUtil;
+import com.intellij.structuralsearch.impl.matcher.handlers.MatchingHandler;
+import com.intellij.structuralsearch.impl.matcher.handlers.SimpleHandler;
+import com.intellij.structuralsearch.impl.matcher.handlers.SubstitutionHandler;
+import com.intellij.structuralsearch.impl.matcher.strategies.MatchingStrategy;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * Class to hold compiled pattern information
+ */
+public abstract class CompiledPattern {
+ public static final String ALL_CLASS_UNMATCHED_CONTENT_VAR_ARTIFICIAL_NAME = "__class_unmatched__";
+ private SearchScope scope;
+ private NodeIterator nodes;
+ private MatchingStrategy strategy;
+ private PsiElement targetNode;
+ private int optionsHashStamp;
+ private int nodeCount;
+
+ // @todo support this property during matching (how many nodes should be advanced)
+ // when match is not successful (or successful partially)
+ //private int advancement = 1;
+
+ public abstract String[] getTypedVarPrefixes();
+ public abstract boolean isTypedVar(String str);
+
+ public void setTargetNode(final PsiElement element) {
+ targetNode = element;
+ }
+
+ public PsiElement getTargetNode() {
+ return targetNode;
+ }
+
+ public int getOptionsHashStamp() {
+ return optionsHashStamp;
+ }
+
+ public void setOptionsHashStamp(final int optionsHashStamp) {
+ this.optionsHashStamp = optionsHashStamp;
+ }
+
+ public static final Key<Object> HANDLER_KEY = Key.create("ss.handler");
+
+ public MatchingStrategy getStrategy() {
+ return strategy;
+ }
+
+ public void setStrategy(MatchingStrategy strategy) {
+ this.strategy = strategy;
+ }
+
+ public int getNodeCount() {
+ return nodeCount;
+ }
+
+ public NodeIterator getNodes() {
+ return nodes;
+ }
+
+ public void setNodes(List<PsiElement> elements) {
+ this.nodes = new ArrayBackedNodeIterator(PsiUtilCore.toPsiElementArray(elements));
+ this.nodeCount = elements.size();
+ }
+
+ public boolean isTypedVar(final PsiElement element) {
+ return element!=null && isTypedVar( element.getText() );
+ }
+
+ public boolean isRealTypedVar(PsiElement element) {
+ if (element!=null && element.getTextLength()>0) {
+ String str = getTypedVarString(element);
+ if (str.length()==0) {
+ return false;
+ }
+ return isTypedVar( str );
+ } else {
+ return false;
+ }
+ }
+
+ public String getTypedVarString(PsiElement element) {
+ final StructuralSearchProfile profile = StructuralSearchUtil.getProfileByPsiElement(element);
+ if (profile == null) {
+ return element.getText();
+ }
+ return profile.getTypedVarString(element);
+ }
+
+ private final HashMap<Object,MatchingHandler> handlers = new HashMap<Object,MatchingHandler>();
+
+ public MatchingHandler getHandlerSimple(PsiElement node) {
+ return handlers.get(node);
+ }
+
+ private PsiElement last;
+ private MatchingHandler lastHandler;
+
+ public MatchingHandler getHandler(PsiElement node) {
+ if (node==last) {
+ return lastHandler;
+ }
+ MatchingHandler handler = handlers.get(node);
+
+ if (handler==null) {
+ handler = new SimpleHandler();
+ setHandler(node,handler);
+ }
+
+ last = node;
+ lastHandler = handler;
+
+ return handler;
+ }
+
+ public MatchingHandler getHandler(String name) {
+ return handlers.get(name);
+ }
+
+ public void setHandler(PsiElement node, MatchingHandler handler) {
+ last = null;
+ handlers.put(node,handler);
+ }
+
+ public SubstitutionHandler createSubstitutionHandler(
+ String name, String compiledName, boolean target,int minOccurs, int maxOccurs, boolean greedy) {
+
+ SubstitutionHandler handler = (SubstitutionHandler) handlers.get(compiledName);
+ if (handler != null) return handler;
+
+ handler = doCreateSubstitutionHandler(name, target, minOccurs, maxOccurs, greedy);
+
+ handlers.put(compiledName,handler);
+
+ return handler;
+ }
+
+ protected SubstitutionHandler doCreateSubstitutionHandler(String name, boolean target, int minOccurs, int maxOccurs, boolean greedy) {
+ return new SubstitutionHandler(name, target, minOccurs, maxOccurs, greedy);
+ }
+
+ public SearchScope getScope() {
+ return scope;
+ }
+
+ public void setScope(SearchScope scope) {
+ this.scope = scope;
+ }
+
+ public void clearHandlers() {
+ handlers.clear();
+ last = null;
+ lastHandler = null;
+ }
+
+ void clearHandlersState() {
+ for (final MatchingHandler h : handlers.values()) {
+ if (h != null) h.reset();
+ }
+ }
+
+ public boolean isToResetHandler(PsiElement element) {
+ return true;
+ }
+
+ @Nullable
+ public String getAlternativeTextToMatch(PsiElement node, String previousText) {
+ return null;
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/DataProvider.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/DataProvider.java
new file mode 100644
index 000000000000..e7327fad7e5e
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/DataProvider.java
@@ -0,0 +1,16 @@
+package com.intellij.structuralsearch.impl.matcher;
+
+import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.project.Project;
+
+/**
+ * Created by IntelliJ IDEA.
+ * User: maxim
+ * Date: 07.01.2004
+ * Time: 1:06:51
+ * To change this template use Options | File Templates.
+ */
+public interface DataProvider {
+ Editor getEditor();
+ Project getProject();
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/GlobalMatchingVisitor.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/GlobalMatchingVisitor.java
new file mode 100644
index 000000000000..5577db0d8540
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/GlobalMatchingVisitor.java
@@ -0,0 +1,315 @@
+package com.intellij.structuralsearch.impl.matcher;
+
+import com.intellij.dupLocator.AbstractMatchingVisitor;
+import com.intellij.dupLocator.iterators.NodeIterator;
+import com.intellij.dupLocator.util.NodeFilter;
+import com.intellij.lang.Language;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiElementVisitor;
+import com.intellij.structuralsearch.MatchResult;
+import com.intellij.structuralsearch.StructuralSearchProfile;
+import com.intellij.structuralsearch.StructuralSearchUtil;
+import com.intellij.structuralsearch.impl.matcher.filters.LexicalNodesFilter;
+import com.intellij.structuralsearch.impl.matcher.handlers.DelegatingHandler;
+import com.intellij.structuralsearch.impl.matcher.handlers.MatchingHandler;
+import com.intellij.structuralsearch.impl.matcher.handlers.SubstitutionHandler;
+import com.intellij.structuralsearch.plugin.ui.Configuration;
+import com.intellij.structuralsearch.plugin.util.SmartPsiPointer;
+import com.intellij.util.containers.HashMap;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Visitor class to manage pattern matching
+ */
+@SuppressWarnings({"RefusedBequest"})
+public class GlobalMatchingVisitor extends AbstractMatchingVisitor {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.structuralsearch.impl.matcher.GlobalMatchingVisitor");
+
+ // the pattern element for visitor check
+ private PsiElement myElement;
+
+ // the result of matching in visitor
+ private boolean myResult;
+
+ // context of matching
+ private MatchContext matchContext;
+
+ private MatchingHandler myLastHandler;
+
+ private Map<Language, PsiElementVisitor> myLanguage2MatchingVisitor = new HashMap<Language, PsiElementVisitor>(1);
+
+ public PsiElement getElement() {
+ return myElement;
+ }
+
+ public boolean getResult() {
+ return myResult;
+ }
+
+ public void setResult(boolean result) {
+ this.myResult = result;
+ }
+
+ public MatchContext getMatchContext() {
+ return matchContext;
+ }
+
+ @Override
+ protected boolean doMatchInAnyOrder(NodeIterator elements, NodeIterator elements2) {
+ return matchContext.getPattern().getHandler(elements.current()).matchInAnyOrder(
+ elements,
+ elements2,
+ matchContext
+ );
+ }
+
+ @NotNull
+ @Override
+ protected NodeFilter getNodeFilter() {
+ return LexicalNodesFilter.getInstance();
+ }
+
+ public final boolean handleTypedElement(final PsiElement typedElement, final PsiElement match) {
+ MatchingHandler handler = matchContext.getPattern().getHandler(typedElement);
+ final MatchingHandler initialHandler = handler;
+ if (handler instanceof DelegatingHandler) {
+ handler = ((DelegatingHandler)handler).getDelegate();
+ }
+ assert handler instanceof SubstitutionHandler :
+ handler != null ? handler.getClass() : "null" + ' ' + (initialHandler != null ? initialHandler.getClass() : "null");
+
+ return ((SubstitutionHandler)handler).handle(match, matchContext);
+ }
+
+ /**
+ * Identifies the match between given element of program tree and pattern element
+ *
+ * @param el1 the pattern for matching
+ * @param el2 the tree element for matching
+ * @return true if equal and false otherwise
+ */
+ public boolean match(final PsiElement el1, final PsiElement el2) {
+ // null
+ if (el1 == el2) return true;
+ if (el2 == null || el1 == null) {
+ // this a bug!
+ return false;
+ }
+
+ // copy changed data to local stack
+ PsiElement prevElement = myElement;
+ myElement = el2;
+
+ try {
+ /*if (el1 instanceof XmlElement) {
+ el1.accept(myXmlVisitor);
+ }
+ else {
+ el1.accept(myJavaVisitor);
+ }*/
+ PsiElementVisitor visitor = getVisitorForElement(el1);
+ if (visitor != null) {
+ el1.accept(visitor);
+ }
+ }
+ catch (ClassCastException ex) {
+ myResult = false;
+ }
+ finally {
+ myElement = prevElement;
+ }
+
+ return myResult;
+ }
+
+ @Nullable
+ private PsiElementVisitor getVisitorForElement(PsiElement element) {
+ Language language = element.getLanguage();
+ PsiElementVisitor visitor = myLanguage2MatchingVisitor.get(language);
+ if (visitor == null) {
+ visitor = createMatchingVisitor(language);
+ myLanguage2MatchingVisitor.put(language, visitor);
+ }
+ return visitor;
+ }
+
+ @Nullable
+ private PsiElementVisitor createMatchingVisitor(Language language) {
+ StructuralSearchProfile profile = StructuralSearchUtil.getProfileByLanguage(language);
+ if (profile == null) {
+ LOG.warn("there is no StructuralSearchProfile for language " + language.getID());
+ return null;
+ }
+ else {
+ return profile.createMatchingVisitor(this);
+ }
+ }
+
+ /**
+ * Matches tree segments starting with given elements to find equality
+ *
+ * @param nodes the pattern element for matching
+ * @param nodes2 the tree element for matching
+ * @return if they are equal and false otherwise
+ */
+ public boolean matchSequentially(NodeIterator nodes, NodeIterator nodes2) {
+ if (!nodes.hasNext()) {
+ return nodes.hasNext() == nodes2.hasNext();
+ }
+
+ myLastHandler = matchContext.getPattern().getHandler(nodes.current());
+ return myLastHandler.matchSequentially(
+ nodes,
+ nodes2,
+ matchContext
+ );
+ }
+
+ public static boolean continueMatchingSequentially(final NodeIterator nodes, final NodeIterator nodes2, MatchContext matchContext) {
+ if (!nodes.hasNext()) {
+ return nodes.hasNext() == nodes2.hasNext();
+ }
+
+ return matchContext.getPattern().getHandler(nodes.current()).matchSequentially(
+ nodes,
+ nodes2,
+ matchContext
+ );
+ }
+
+ /**
+ * Descents the tree in depth finding matches
+ *
+ * @param elements the element for which the sons are looked for match
+ */
+ public void matchContext(final NodeIterator elements) {
+ final CompiledPattern pattern = matchContext.getPattern();
+ final NodeIterator patternNodes = pattern.getNodes().clone();
+ final MatchResultImpl saveResult = matchContext.hasResult() ? matchContext.getResult() : null;
+ final List<PsiElement> saveMatchedNodes = matchContext.getMatchedNodes();
+
+ try {
+ matchContext.setResult(null);
+ matchContext.setMatchedNodes(null);
+
+ if (!patternNodes.hasNext()) return;
+ final MatchingHandler firstMatchingHandler = pattern.getHandler(patternNodes.current());
+
+ for (; elements.hasNext(); elements.advance()) {
+ final PsiElement elementNode = elements.current();
+
+ boolean matched = firstMatchingHandler.matchSequentially(patternNodes, elements, matchContext);
+
+ if (matched) {
+ MatchingHandler matchingHandler = matchContext.getPattern().getHandler(Configuration.CONTEXT_VAR_NAME);
+ if (matchingHandler != null) {
+ matched = ((SubstitutionHandler)matchingHandler).handle(elementNode, matchContext);
+ }
+ }
+
+ final List<PsiElement> matchedNodes = matchContext.getMatchedNodes();
+
+ if (matched) {
+ dispatchMatched(matchedNodes, matchContext.getResult());
+ }
+
+ matchContext.setMatchedNodes(null);
+ matchContext.setResult(null);
+
+ patternNodes.reset();
+ if (matchedNodes != null && matchedNodes.size() > 0 && matched) {
+ elements.rewind();
+ }
+ }
+ }
+ finally {
+ matchContext.setResult(saveResult);
+ matchContext.setMatchedNodes(saveMatchedNodes);
+ }
+ }
+
+ private void dispatchMatched(final List<PsiElement> matchedNodes, MatchResultImpl result) {
+ if (!matchContext.getOptions().isResultIsContextMatch() && doDispatch(result, result)) return;
+
+ // There is no substitutions so show the context
+
+ processNoSubstitutionMatch(matchedNodes, result);
+ matchContext.getSink().newMatch(result);
+ }
+
+ private boolean doDispatch(final MatchResultImpl result, MatchResultImpl context) {
+ boolean ret = false;
+
+ for (MatchResult _r : result.getAllSons()) {
+ final MatchResultImpl r = (MatchResultImpl)_r;
+
+ if ((r.isScopeMatch() && !r.isTarget()) || r.isMultipleMatch()) {
+ ret |= doDispatch(r, context);
+ }
+ else if (r.isTarget()) {
+ r.setContext(context);
+ matchContext.getSink().newMatch(r);
+ ret = true;
+ }
+ }
+ return ret;
+ }
+
+ private static void processNoSubstitutionMatch(List<PsiElement> matchedNodes, MatchResultImpl result) {
+ boolean complexMatch = matchedNodes.size() > 1;
+ final PsiElement match = matchedNodes.get(0);
+
+ if (!complexMatch) {
+ result.setMatchRef(new SmartPsiPointer(match));
+ result.setMatchImage(match.getText());
+ }
+ else {
+ MatchResultImpl sonresult;
+
+ for (final PsiElement matchStatement : matchedNodes) {
+ result.getMatches().add(
+ sonresult = new MatchResultImpl(
+ MatchResult.LINE_MATCH,
+ matchStatement.getText(),
+ new SmartPsiPointer(matchStatement),
+ true
+ )
+ );
+
+ sonresult.setParent(result);
+ }
+
+ result.setMatchRef(
+ new SmartPsiPointer(match)
+ );
+ result.setMatchImage(
+ match.getText()
+ );
+ result.setName(MatchResult.MULTI_LINE_MATCH);
+ }
+ }
+
+ public void setMatchContext(MatchContext matchContext) {
+ this.matchContext = matchContext;
+ }
+
+ // Matches the sons of given elements to find equality
+ // @param el1 the pattern element for matching
+ // @param el2 the tree element for matching
+ // @return if they are equal and false otherwise
+
+ @Override
+ protected boolean isLeftLooseMatching() {
+ return matchContext.getOptions().isLooseMatching();
+ }
+
+ @Override
+ protected boolean isRightLooseMatching() {
+ return false;
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatchConstraintsSink.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatchConstraintsSink.java
new file mode 100644
index 000000000000..2036c83d56f4
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatchConstraintsSink.java
@@ -0,0 +1,85 @@
+package com.intellij.structuralsearch.impl.matcher;
+
+import com.intellij.psi.PsiElement;
+import com.intellij.structuralsearch.MatchResultSink;
+import com.intellij.structuralsearch.MatchingProcess;
+import com.intellij.structuralsearch.MatchResult;
+import com.intellij.structuralsearch.SSRBundle;
+import com.intellij.psi.PsiFile;
+import com.intellij.openapi.progress.ProgressIndicator;
+
+import javax.swing.*;
+import java.util.HashMap;
+
+/**
+ * Sink to detect
+ */
+class MatchConstraintsSink implements MatchResultSink {
+ private final MatchResultSink delegate;
+ private MatchingProcess process;
+ private final boolean distinct;
+ private final boolean caseSensitive;
+ private int matchCount;
+ private final int maxMatches;
+ private final HashMap<Object, MatchResult> matches = new HashMap<Object, MatchResult>();
+
+ MatchConstraintsSink(MatchResultSink _delegate, int _maxMatches,boolean distinct, boolean _caseSensitive) {
+ delegate = _delegate;
+ maxMatches = _maxMatches;
+ this.distinct = distinct;
+ caseSensitive = _caseSensitive;
+ }
+
+ public void newMatch(MatchResult result) {
+ if (distinct) {
+ String matchImage = result.getMatchImage();
+
+ if (!caseSensitive) matchImage = matchImage.toLowerCase();
+
+ if (matches.get(matchImage)!=null) {
+ return;
+ }
+
+ matches.put(matchImage,result);
+ }
+ else {
+ final PsiElement match = result.getMatch();
+ if (matches.containsKey(match)) {
+ return;
+ }
+ matches.put(match, result);
+ }
+
+ delegate.newMatch(result);
+ ++matchCount;
+
+ if (matchCount==maxMatches) {
+ JOptionPane.showMessageDialog(null, SSRBundle.message("search.produced.too.many.results.message"));
+ process.stop();
+ }
+ }
+
+ /* Notifies sink about starting the matching for given element
+ * @param element the current file
+ * @param task process continuation reference
+ */
+ public void processFile(PsiFile element) {
+ delegate.processFile(element);
+ }
+
+ public void setMatchingProcess(MatchingProcess matchingProcess) {
+ process = matchingProcess;
+ delegate.setMatchingProcess(process);
+ }
+
+ public void matchingFinished() {
+ matchCount = 0;
+ matches.clear();
+ delegate.matchingFinished();
+ }
+
+ public ProgressIndicator getProgressIndicator() {
+ return delegate.getProgressIndicator();
+ }
+
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatchContext.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatchContext.java
new file mode 100644
index 000000000000..c4c8d21e3ab3
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatchContext.java
@@ -0,0 +1,130 @@
+package com.intellij.structuralsearch.impl.matcher;
+
+import com.intellij.psi.PsiElement;
+import com.intellij.structuralsearch.MatchOptions;
+import com.intellij.structuralsearch.MatchResultSink;
+import com.intellij.util.containers.Stack;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Global context of matching process
+ */
+public class MatchContext {
+ private MatchResultSink sink;
+ private final Stack<MatchResultImpl> previousResults = new Stack<MatchResultImpl>();
+ private MatchResultImpl result;
+ private CompiledPattern pattern;
+ private MatchOptions options;
+ private GlobalMatchingVisitor matcher;
+ private boolean shouldRecursivelyMatch = true;
+ private boolean myWithAlternativePatternRoots = true;
+
+ private List<PsiElement> myMatchedNodes;
+
+ public List<PsiElement> getMatchedNodes() {
+ return myMatchedNodes;
+ }
+
+ public void setMatchedNodes(final List<PsiElement> matchedNodes) {
+ myMatchedNodes = matchedNodes;
+ }
+
+ public boolean isWithAlternativePatternRoots() {
+ return myWithAlternativePatternRoots;
+ }
+
+ public void setWithAlternativePatternRoots(boolean withAlternativePatternRoots) {
+ myWithAlternativePatternRoots = withAlternativePatternRoots;
+ }
+
+ public interface MatchedElementsListener {
+ void matchedElements(Collection<PsiElement> matchedElements);
+ void commitUnmatched();
+ }
+
+ private MatchedElementsListener myMatchedElementsListener;
+
+ public void setMatcher(GlobalMatchingVisitor matcher) {
+ this.matcher = matcher;
+ }
+
+ public GlobalMatchingVisitor getMatcher() {
+ return matcher;
+ }
+
+ public MatchOptions getOptions() {
+ return options;
+ }
+
+ public void setOptions(MatchOptions options) {
+ this.options = options;
+ }
+
+ public MatchResultImpl getPreviousResult() {
+ return previousResults.isEmpty() ? null : previousResults.peek();
+ }
+
+ public MatchResultImpl getResult() {
+ if (result==null) result = new MatchResultImpl();
+ return result;
+ }
+
+ public void pushResult() {
+ previousResults.push(result);
+ result = null;
+ }
+
+ public void popResult() {
+ result = previousResults.pop();
+ }
+
+ public void setResult(MatchResultImpl result) {
+ this.result = result;
+ if (result == null) {
+ pattern.clearHandlersState();
+ }
+ }
+
+ public boolean hasResult() {
+ return result!=null;
+ }
+
+ public CompiledPattern getPattern() {
+ return pattern;
+ }
+
+ public void setPattern(CompiledPattern pattern) {
+ this.pattern = pattern;
+ }
+
+ public MatchResultSink getSink() {
+ return sink;
+ }
+
+ public void setSink(MatchResultSink sink) {
+ this.sink = sink;
+ }
+
+ void clear() {
+ result = null;
+ pattern = null;
+ }
+
+ public boolean shouldRecursivelyMatch() {
+ return shouldRecursivelyMatch;
+ }
+
+ public void setShouldRecursivelyMatch(boolean shouldRecursivelyMatch) {
+ this.shouldRecursivelyMatch = shouldRecursivelyMatch;
+ }
+
+ public void setMatchedElementsListener(MatchedElementsListener _matchedElementsListener) {
+ myMatchedElementsListener = _matchedElementsListener;
+ }
+
+ public MatchedElementsListener getMatchedElementsListener() {
+ return myMatchedElementsListener;
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatchPredicateProvider.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatchPredicateProvider.java
new file mode 100644
index 000000000000..b75ffb5d0f5d
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatchPredicateProvider.java
@@ -0,0 +1,16 @@
+package com.intellij.structuralsearch.impl.matcher;
+
+import com.intellij.openapi.extensions.ExtensionPointName;
+import com.intellij.structuralsearch.MatchOptions;
+import com.intellij.structuralsearch.MatchVariableConstraint;
+import com.intellij.structuralsearch.impl.matcher.handlers.MatchPredicate;
+
+import java.util.Set;
+
+public abstract class MatchPredicateProvider {
+ public static final ExtensionPointName<MatchPredicateProvider> EP_NAME = ExtensionPointName.create("com.intellij.structuralsearch.matchPredicateProvider");
+ public abstract void collectPredicates(MatchVariableConstraint constraint,
+ String name,
+ MatchOptions options,
+ Set<MatchPredicate> predicates);
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatchResultImpl.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatchResultImpl.java
new file mode 100644
index 000000000000..8761472d813a
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatchResultImpl.java
@@ -0,0 +1,207 @@
+package com.intellij.structuralsearch.impl.matcher;
+
+import com.intellij.psi.PsiElement;
+import com.intellij.structuralsearch.MatchResult;
+import com.intellij.structuralsearch.plugin.util.SmartPsiPointer;
+import org.jetbrains.annotations.NonNls;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Class describing the match result
+ */
+public final class MatchResultImpl extends MatchResult {
+ private String name;
+ private SmartPsiPointer matchRef;
+ private int start;
+ private int end = -1;
+ private String matchImage;
+ private List<MatchResult> matches;
+ private MatchResult parent;
+ private boolean target;
+
+ @NonNls public static final String DEFAULT_NAME2 = "end of context match";
+ @NonNls public static final String DEFAULT_NAME = "start of context match";
+ private boolean myScopeMatch;
+ private boolean myMultipleMatch;
+ @NonNls private static final String NULL = "null";
+ private MatchResultImpl myContext;
+
+ MatchResultImpl() {
+ }
+
+ public MatchResultImpl(String name, String image, SmartPsiPointer ref, boolean target) {
+ this(name,image,ref,0,-1,target);
+ }
+
+ public MatchResultImpl(String name, String image, SmartPsiPointer ref, int start, int end,boolean target) {
+ matchRef = ref;
+ this.name = name;
+ matchImage = image;
+ this.target = target;
+ this.start = start;
+ this.end = end;
+ }
+
+ public String getMatchImage() {
+ if (matchImage==null) {
+ matchImage = NULL;
+ }
+ return matchImage;
+ }
+
+ public void setParent(MatchResult parent) {
+ this.parent = parent;
+ }
+
+ public SmartPsiPointer getMatchRef() {
+ return matchRef;
+ }
+
+ public PsiElement getMatch() {
+ return matchRef.getElement();
+ }
+
+ public void setMatchRef(SmartPsiPointer matchStart) {
+ matchRef = matchStart;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public List<MatchResult> getMatches() {
+ if (matches==null) matches = new ArrayList<MatchResult>();
+ return matches;
+ }
+
+ public List<MatchResult> getAllSons() {
+ return getMatches();
+ }
+
+ public boolean hasSons() {
+ return matches!=null;
+ }
+
+ public boolean isScopeMatch() {
+ return myScopeMatch;
+ }
+
+ public boolean isMultipleMatch() {
+ return myMultipleMatch;
+ }
+
+ public void clear() {
+ if (matchRef != null) {
+ matchRef.clear();
+ matchRef = null;
+ }
+
+ if (matches != null) {
+ for (final MatchResult match : matches) {
+ ((MatchResultImpl)match).clear();
+ }
+ matches = null;
+ }
+
+ name = null;
+ matchImage = null;
+ }
+
+ public void clearMatches() {
+ matches = null;
+ }
+
+ public void setScopeMatch(final boolean scopeMatch) {
+ myScopeMatch = scopeMatch;
+ }
+
+ public void setMultipleMatch(final boolean multipleMatch) {
+ myMultipleMatch = multipleMatch;
+ }
+
+ public MatchResultImpl findSon(String name) {
+ if (matches!=null) {
+ // @todo this could be performance bottleneck, replace with hash lookup!
+ for (final MatchResult match : matches) {
+ final MatchResultImpl res = (MatchResultImpl)match;
+
+ if (name.equals(res.getName())) {
+ return res;
+ }
+ }
+ }
+ return null;
+ }
+
+ public MatchResultImpl removeSon(String typedVar) {
+ if (matches == null) return null;
+
+ // @todo this could be performance bottleneck, replace with hash lookup!
+ for(Iterator<MatchResult> i=matches.iterator();i.hasNext();) {
+ final MatchResultImpl res = (MatchResultImpl)i.next();
+ if (typedVar.equals(res.getName())) {
+ i.remove();
+ return res;
+ }
+ }
+
+ return null;
+ }
+
+ public void addSon(MatchResultImpl result) {
+ getMatches().add(result);
+ }
+
+ public void setMatchImage(String matchImage) {
+ this.matchImage = matchImage;
+ }
+
+ public boolean isTarget() {
+ return target;
+ }
+
+ public void setTarget(boolean target) {
+ this.target = target;
+ }
+
+ public boolean isMatchImageNull() {
+ return matchImage==null;
+ }
+
+ public int getStart() {
+ return start;
+ }
+
+ public void setStart(int start) {
+ this.start = start;
+ }
+
+ public int getEnd() {
+ return end;
+ }
+
+ public void setEnd(int end) {
+ this.end = end;
+ }
+
+ public void setContext(final MatchResultImpl context) {
+ myContext = context;
+ }
+
+ public MatchResultImpl getContext() {
+ return myContext;
+ }
+
+ @Override
+ public String toString() {
+ return "MatchResultImpl{name='" + name + '\'' + ", matchImage='" + matchImage + '\'' + "}";
+ }
+}
+
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatchUtils.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatchUtils.java
new file mode 100644
index 000000000000..38c31d16524f
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatchUtils.java
@@ -0,0 +1,42 @@
+package com.intellij.structuralsearch.impl.matcher;
+
+import com.intellij.psi.*;
+import org.jetbrains.annotations.NonNls;
+
+/**
+ * Created by IntelliJ IDEA.
+ * User: maxim
+ * Date: 24.12.2003
+ * Time: 22:10:20
+ * To change this template use Options | File Templates.
+ */
+public class MatchUtils {
+ public static final String SPECIAL_CHARS = "*(){}[]^$\\.-|";
+
+ public static final boolean compareWithNoDifferenceToPackage(final String typeImage,@NonNls final String typeImage2) {
+ if (typeImage == null || typeImage2 == null) return typeImage == typeImage2;
+ return typeImage2.endsWith(typeImage) && (
+ typeImage.length() == typeImage2.length() ||
+ typeImage2.charAt(typeImage2.length()-typeImage.length()-1)=='.' // package separator
+ );
+ }
+
+ public static PsiElement getReferencedElement(final PsiElement element) {
+ if (element instanceof PsiReference) {
+ return ((PsiReference)element).resolve();
+ }
+
+ /*if (element instanceof PsiTypeElement) {
+ PsiType type = ((PsiTypeElement)element).getType();
+
+ if (type instanceof PsiArrayType) {
+ type = ((PsiArrayType)type).getComponentType();
+ }
+ if (type instanceof PsiClassType) {
+ return ((PsiClassType)type).resolve();
+ }
+ return null;
+ }*/
+ return element;
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatcherImpl.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatcherImpl.java
new file mode 100644
index 000000000000..d667b068ee48
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatcherImpl.java
@@ -0,0 +1,737 @@
+package com.intellij.structuralsearch.impl.matcher;
+
+import com.intellij.dupLocator.iterators.ArrayBackedNodeIterator;
+import com.intellij.dupLocator.iterators.NodeIterator;
+import com.intellij.lang.Language;
+import com.intellij.lang.StdLanguages;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.application.ModalityState;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.editor.Document;
+import com.intellij.openapi.fileTypes.FileType;
+import com.intellij.openapi.fileTypes.FileTypes;
+import com.intellij.openapi.fileTypes.LanguageFileType;
+import com.intellij.openapi.progress.ProcessCanceledException;
+import com.intellij.openapi.progress.ProgressIndicator;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.roots.ContentIterator;
+import com.intellij.openapi.roots.ProjectFileIndex;
+import com.intellij.openapi.util.Computable;
+import com.intellij.openapi.util.Pair;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.*;
+import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil;
+import com.intellij.psi.search.DelegatingGlobalSearchScope;
+import com.intellij.psi.search.GlobalSearchScope;
+import com.intellij.psi.search.LocalSearchScope;
+import com.intellij.psi.search.SearchScope;
+import com.intellij.structuralsearch.*;
+import com.intellij.structuralsearch.impl.matcher.compiler.PatternCompiler;
+import com.intellij.structuralsearch.impl.matcher.handlers.MatchingHandler;
+import com.intellij.structuralsearch.impl.matcher.handlers.TopLevelMatchingHandler;
+import com.intellij.structuralsearch.impl.matcher.iterators.SsrFilteringNodeIterator;
+import com.intellij.structuralsearch.impl.matcher.strategies.MatchingStrategy;
+import com.intellij.structuralsearch.plugin.ui.Configuration;
+import com.intellij.structuralsearch.plugin.util.CollectingMatchResultSink;
+import com.intellij.util.IncorrectOperationException;
+import com.intellij.util.PairProcessor;
+import com.intellij.util.SmartList;
+import com.intellij.util.indexing.FileBasedIndex;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.lang.ref.SoftReference;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * This class makes program structure tree matching:
+ */
+public class MatcherImpl {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.structuralsearch.impl.matcher.MatcherImpl");
+ // project being worked on
+ private final Project project;
+
+ // context of matching
+ private final MatchContext matchContext;
+ private boolean isTesting;
+
+ // visitor to delegate the real work
+ private final GlobalMatchingVisitor visitor = new GlobalMatchingVisitor();
+ private ProgressIndicator progress;
+ private final TaskScheduler scheduler = new TaskScheduler();
+
+ private int totalFilesToScan;
+ private int scannedFilesCount;
+
+ public MatcherImpl(final Project project, final MatchOptions matchOptions) {
+ this.project = project;
+ matchContext = new MatchContext();
+ matchContext.setMatcher(visitor);
+
+ if (matchOptions != null) {
+ matchContext.setOptions(matchOptions);
+ cacheCompiledPattern(matchOptions, PatternCompiler.compilePattern(project,matchOptions));
+ }
+ }
+
+ static class LastMatchData {
+ CompiledPattern lastPattern;
+ MatchOptions lastOptions;
+ }
+
+ private static SoftReference<LastMatchData> lastMatchData;
+
+ protected MatcherImpl(Project project) {
+ this(project, null);
+ }
+
+ public static void validate(Project project, MatchOptions options) {
+ PsiDocumentManager.getInstance(project).commitAllDocuments();
+
+ synchronized(MatcherImpl.class) {
+ final LastMatchData data = new LastMatchData();
+ data.lastPattern = PatternCompiler.compilePattern(project, options);
+ data.lastOptions = options;
+ lastMatchData = new SoftReference<LastMatchData>(data);
+ }
+
+ final StructuralSearchProfile profile = StructuralSearchUtil.getProfileByFileType(options.getFileType());
+ profile.checkSearchPattern(project, options);
+ }
+
+ public static class CompiledOptions {
+ public final List<Pair<MatchContext, Configuration>> matchContexts;
+
+ public CompiledOptions(final List<Pair<MatchContext, Configuration>> matchContexts) {
+ this.matchContexts = matchContexts;
+ }
+
+ public List<Pair<MatchContext, Configuration>> getMatchContexts() {
+ return matchContexts;
+ }
+ }
+
+ public static boolean checkIfShouldAttemptToMatch(MatchContext context, NodeIterator matchedNodes) {
+ final CompiledPattern pattern = context.getPattern();
+ final NodeIterator patternNodes = pattern.getNodes();
+ try {
+ while (true) {
+ final PsiElement patternNode = patternNodes.current();
+ if (patternNode == null) {
+ return true;
+ }
+ final PsiElement matchedNode = matchedNodes.current();
+ if (matchedNode == null) {
+ return false;
+ }
+ final MatchingHandler matchingHandler = pattern.getHandler(patternNode);
+ if (matchingHandler == null || !matchingHandler.canMatch(patternNode, matchedNode)) {
+ return false;
+ }
+ matchedNodes.advance();
+ patternNodes.advance();
+ }
+ } finally {
+ patternNodes.reset();
+ matchedNodes.reset();
+ }
+ }
+
+ public void processMatchesInElement(MatchContext context, Configuration configuration,
+ NodeIterator matchedNodes,
+ PairProcessor<MatchResult, Configuration> processor) {
+ try {
+ configureOptions(context, configuration, matchedNodes.current(), processor);
+ context.setShouldRecursivelyMatch(false);
+ visitor.matchContext(matchedNodes);
+ } finally {
+ matchedNodes.reset();
+ }
+ }
+
+ public void clearContext() {
+ matchContext.clear();
+ }
+
+ private void configureOptions(MatchContext context,
+ final Configuration configuration,
+ PsiElement psiFile,
+ final PairProcessor<MatchResult, Configuration> processor) {
+ if (psiFile == null) return;
+ LocalSearchScope scope = new LocalSearchScope(psiFile);
+
+ matchContext.clear();
+ matchContext.setMatcher(visitor);
+
+ MatchOptions options = context.getOptions();
+ matchContext.setOptions(options);
+ matchContext.setPattern(context.getPattern());
+ matchContext.setShouldRecursivelyMatch(context.shouldRecursivelyMatch());
+ visitor.setMatchContext(matchContext);
+
+ matchContext.setSink(
+ new MatchConstraintsSink(
+ new MatchResultSink() {
+ public void newMatch(MatchResult result) {
+ processor.process(result, configuration);
+ }
+
+ public void processFile(PsiFile element) {
+ }
+
+ public void setMatchingProcess(MatchingProcess matchingProcess) {
+ }
+
+ public void matchingFinished() {
+ }
+
+ public ProgressIndicator getProgressIndicator() {
+ return null;
+ }
+ },
+ options.getMaxMatchesCount(),
+ options.isDistinct(),
+ options.isCaseSensitiveMatch()
+ )
+ );
+ options.setScope(scope);
+ }
+
+ public CompiledOptions precompileOptions(List<Configuration> configurations) {
+ List<Pair<MatchContext, Configuration>> contexts = new ArrayList<Pair<MatchContext, Configuration>>();
+
+ for (Configuration configuration : configurations) {
+ MatchContext matchContext = new MatchContext();
+ matchContext.setMatcher(visitor);
+ MatchOptions matchOptions = configuration.getMatchOptions();
+ matchContext.setOptions(matchOptions);
+
+ try {
+ CompiledPattern compiledPattern = PatternCompiler.compilePattern(project, matchOptions);
+ matchContext.setPattern(compiledPattern);
+ contexts.add(Pair.create(matchContext, configuration));
+ }
+ catch (UnsupportedPatternException ignored) {}
+ catch (MalformedPatternException ignored) {}
+ }
+ return new CompiledOptions(contexts);
+ }
+
+ Project getProject() {
+ return project;
+ }
+
+ /**
+ * Finds the matches of given pattern starting from given tree element.
+ * @throws MalformedPatternException
+ * @throws UnsupportedPatternException
+ */
+ protected void findMatches(MatchResultSink sink, final MatchOptions options) throws MalformedPatternException, UnsupportedPatternException
+ {
+ CompiledPattern compiledPattern = prepareMatching(sink, options);
+ if (compiledPattern== null) {
+ return;
+ }
+
+ matchContext.getSink().setMatchingProcess( scheduler );
+ scheduler.init();
+ progress = matchContext.getSink().getProgressIndicator();
+
+ if (/*TokenBasedSearcher.canProcess(compiledPattern)*/ false) {
+ //TokenBasedSearcher searcher = new TokenBasedSearcher(this);
+ //searcher.search(compiledPattern);
+ if (isTesting) {
+ matchContext.getSink().matchingFinished();
+ return;
+ }
+ }
+ else {
+ if (isTesting) {
+ // testing mode;
+ final PsiElement[] elements = ((LocalSearchScope)options.getScope()).getScope();
+
+ PsiElement parent = elements[0].getParent();
+ if (elements.length > 0 && matchContext.getPattern().getStrategy().continueMatching(parent != null ? parent : elements[0])) {
+ visitor.matchContext(new SsrFilteringNodeIterator(new ArrayBackedNodeIterator(elements)));
+ }
+ else {
+ for (PsiElement element : elements) {
+ match(element);
+ }
+ }
+
+ matchContext.getSink().matchingFinished();
+ return;
+ }
+ if (!findMatches(options, compiledPattern)) {
+ return;
+ }
+ }
+
+ if (scheduler.getTaskQueueEndAction()==null) {
+ scheduler.setTaskQueueEndAction(
+ new Runnable() {
+ public void run() {
+ matchContext.getSink().matchingFinished();
+ }
+ }
+ );
+ }
+
+ scheduler.executeNext();
+ }
+
+ private boolean findMatches(MatchOptions options, CompiledPattern compiledPattern) {
+ LanguageFileType languageFileType = (LanguageFileType)options.getFileType();
+ final StructuralSearchProfile profile = StructuralSearchUtil.getProfileByLanguage(languageFileType.getLanguage());
+ assert profile != null;
+ PsiElement node = compiledPattern.getNodes().current();
+ final Language ourPatternLanguage = node != null ? profile.getLanguage(node) : ((LanguageFileType)options.getFileType()).getLanguage();
+ final Language ourPatternLanguage2 = ourPatternLanguage == StdLanguages.XML ? StdLanguages.XHTML:null;
+ SearchScope searchScope = compiledPattern.getScope();
+ boolean ourOptimizedScope = searchScope != null;
+ if (!ourOptimizedScope) searchScope = options.getScope();
+
+ if (searchScope instanceof GlobalSearchScope) {
+ final GlobalSearchScope scope = (GlobalSearchScope)searchScope;
+
+ final ContentIterator ci = new ContentIterator() {
+ public boolean processFile(final VirtualFile fileOrDir) {
+ if (!fileOrDir.isDirectory() && scope.contains(fileOrDir) && fileOrDir.getFileType() != FileTypes.UNKNOWN) {
+ ++totalFilesToScan;
+ scheduler.addOneTask(new MatchOneVirtualFile(fileOrDir, profile, ourPatternLanguage, ourPatternLanguage2));
+ }
+ return true;
+ }
+ };
+
+ ApplicationManager.getApplication().runReadAction(new Runnable() {
+ @Override
+ public void run() {
+ FileBasedIndex.getInstance().iterateIndexableFiles(ci, project, progress);
+ }
+ });
+ progress.setText2("");
+ }
+ else {
+ final PsiElement[] elementsToScan = ((LocalSearchScope)searchScope).getScope();
+ totalFilesToScan = elementsToScan.length;
+
+ for (int i = 0; i < elementsToScan.length; ++i) {
+ final PsiElement psiElement = elementsToScan[i];
+
+ if (psiElement == null) continue;
+ final Language language = psiElement.getLanguage();
+
+ PsiFile file = psiElement instanceof PsiFile ? (PsiFile)psiElement : psiElement.getContainingFile();
+
+ if (profile.isMyFile(file, language, ourPatternLanguage, ourPatternLanguage2)) {
+ scheduler.addOneTask(new MatchOnePsiFile(psiElement));
+ }
+ if (ourOptimizedScope) elementsToScan[i] = null; // to prevent long PsiElement reference
+ }
+ }
+ return true;
+ }
+
+ private CompiledPattern prepareMatching(final MatchResultSink sink, final MatchOptions options) {
+ CompiledPattern savedPattern = null;
+
+ if (matchContext.getOptions() == options && matchContext.getPattern() != null &&
+ matchContext.getOptions().hashCode() == matchContext.getPattern().getOptionsHashStamp()) {
+ savedPattern = matchContext.getPattern();
+ }
+
+ matchContext.clear();
+ matchContext.setSink(
+ new MatchConstraintsSink(
+ sink,
+ options.getMaxMatchesCount(),
+ options.isDistinct(),
+ options.isCaseSensitiveMatch()
+ )
+ );
+ matchContext.setOptions(options);
+ matchContext.setMatcher(visitor);
+ visitor.setMatchContext(matchContext);
+
+ CompiledPattern compiledPattern = savedPattern;
+
+ if (compiledPattern == null) {
+
+ synchronized(getClass()) {
+ final LastMatchData data = com.intellij.reference.SoftReference.dereference(lastMatchData);
+ if (data != null && options == data.lastOptions) {
+ compiledPattern = data.lastPattern;
+ }
+ lastMatchData = null;
+ }
+
+ if (compiledPattern==null) {
+ compiledPattern = ApplicationManager.getApplication().runReadAction(new Computable<CompiledPattern>() {
+ @Override
+ public CompiledPattern compute() {
+ return PatternCompiler.compilePattern(project,options);
+ }
+ });
+ }
+ }
+
+ cacheCompiledPattern(options, compiledPattern);
+ return compiledPattern;
+ }
+
+ private void cacheCompiledPattern(final MatchOptions options, final CompiledPattern compiledPattern) {
+ matchContext.setPattern(compiledPattern);
+ compiledPattern.setOptionsHashStamp(options.hashCode());
+ }
+
+ /**
+ * Finds the matches of given pattern starting from given tree element.
+ * @param sink match result destination
+ * @throws MalformedPatternException
+ * @throws UnsupportedPatternException
+ */
+ protected void testFindMatches(MatchResultSink sink, MatchOptions options)
+ throws MalformedPatternException, UnsupportedPatternException {
+ isTesting = true;
+ try {
+ findMatches(sink,options);
+ } finally {
+ isTesting = false;
+ }
+ }
+
+ /**
+ * Finds the matches of given pattern starting from given tree element.
+ * @param source string for search
+ * @param pattern to be searched
+ * @return list of matches found
+ * @throws MalformedPatternException
+ * @throws UnsupportedPatternException
+ */
+ protected List<MatchResult> testFindMatches(String source,
+ String pattern,
+ MatchOptions options,
+ boolean filePattern,
+ FileType sourceFileType,
+ String sourceExtension,
+ boolean physicalSourceFile)
+ throws MalformedPatternException, UnsupportedPatternException {
+
+ CollectingMatchResultSink sink = new CollectingMatchResultSink();
+
+ try {
+ PsiElement[] elements = MatcherImplUtil.createSourceTreeFromText(source,
+ filePattern ? PatternTreeContext.File : PatternTreeContext.Block,
+ sourceFileType,
+ sourceExtension,
+ project, physicalSourceFile);
+
+ options.setSearchPattern(pattern);
+ options.setScope(new LocalSearchScope(elements));
+ testFindMatches(sink, options);
+ }
+ catch (IncorrectOperationException e) {
+ MalformedPatternException exception = new MalformedPatternException();
+ exception.initCause(e);
+ throw exception;
+ }
+
+ return sink.getMatches();
+ }
+
+ protected List<MatchResult> testFindMatches(String source, String pattern, MatchOptions options, boolean filePattern) {
+ return testFindMatches(source, pattern, options, filePattern, options.getFileType(), null, false);
+ }
+
+ class TaskScheduler implements MatchingProcess {
+ private LinkedList<Runnable> tasks = new LinkedList<Runnable>();
+ private boolean ended;
+ private Runnable taskQueueEndAction;
+
+ private boolean suspended;
+
+ public void stop() {
+ ended = true;
+ }
+
+ public void pause() {
+ suspended = true;
+ }
+
+ public void resume() {
+ if (!suspended) return;
+ suspended = false;
+ executeNext();
+ }
+
+ public boolean isSuspended() {
+ return suspended;
+ }
+
+ public boolean isEnded() {
+ return ended;
+ }
+
+ void setTaskQueueEndAction(Runnable taskQueueEndAction) {
+ this.taskQueueEndAction = taskQueueEndAction;
+ }
+ Runnable getTaskQueueEndAction () {
+ return taskQueueEndAction;
+ }
+
+ void addOneTask(Runnable runnable) {
+ tasks.add(runnable);
+ }
+
+ private void executeNext() {
+ while(!suspended && !ended) {
+ if (tasks.isEmpty()) {
+ ended = true;
+ break;
+ }
+
+ final Runnable task = tasks.removeFirst();
+ try {
+ task.run();
+ }
+ catch (ProcessCanceledException e) {
+ ended = true;
+ clearSchedule();
+ throw e;
+ }
+ catch (StructuralSearchException e) {
+ ended = true;
+ clearSchedule();
+ throw e;
+ }
+ catch (Throwable th) {
+ LOG.error(th);
+ }
+ }
+
+ if (ended) clearSchedule();
+ }
+
+ private void init() {
+ ended = false;
+ suspended = false;
+ PsiManager.getInstance(project).startBatchFilesProcessingMode();
+ }
+
+ private void clearSchedule() {
+ if (tasks != null) {
+ taskQueueEndAction.run();
+ if (!project.isDisposed()) {
+ PsiManager.getInstance(project).finishBatchFilesProcessingMode();
+ }
+ tasks = null;
+ }
+ }
+
+ }
+
+ private class MatchOnePsiFile extends MatchOneFile {
+ private PsiElement file;
+
+ MatchOnePsiFile(PsiElement file) {
+ this.file = file;
+ }
+
+ @Nullable
+ @Override
+ protected List<PsiElement> getPsiElementsToProcess() {
+ PsiElement file = this.file;
+ this.file = null;
+ return new SmartList<PsiElement>(file);
+ }
+ }
+
+ private abstract class MatchOneFile implements Runnable {
+ public void run() {
+ List<PsiElement> files = getPsiElementsToProcess();
+
+ if (progress!=null) {
+ progress.setFraction((double)scannedFilesCount/totalFilesToScan);
+ }
+
+ ++scannedFilesCount;
+
+ if (files == null || files.size() == 0) return;
+ final PsiFile psiFile = files.get(0).getContainingFile();
+
+ if (psiFile!=null) {
+ final Runnable action = new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ public void run() {
+ if (project.isDisposed()) return;
+ final PsiDocumentManager manager = PsiDocumentManager.getInstance(project);
+ Document document = manager.getDocument(psiFile);
+ if (document != null) manager.commitDocument(document);
+ }
+ });
+ }
+ };
+
+ if (ApplicationManager.getApplication().isDispatchThread()) {
+ action.run();
+ } else {
+ ApplicationManager.getApplication().invokeAndWait(
+ action,
+ ModalityState.defaultModalityState()
+ );
+ }
+ }
+
+ if (project.isDisposed()) return;
+
+ for(PsiElement file:files) {
+ if (file instanceof PsiFile) {
+ matchContext.getSink().processFile((PsiFile)file);
+ }
+
+ final PsiElement finalFile = file;
+ ApplicationManager.getApplication().runReadAction(
+ new Runnable() {
+ public void run() {
+ PsiElement file = finalFile;
+ if (!file.isValid()) return;
+ file = StructuralSearchUtil.getProfileByLanguage(file.getLanguage()).extendMatchOnePsiFile(file);
+ match(file);
+ }
+ }
+ );
+ }
+ }
+
+ protected abstract @Nullable List<PsiElement> getPsiElementsToProcess();
+ }
+
+ // Initiates the matching process for given element
+ // @param element the current search tree element
+ public void match(PsiElement element) {
+ MatchingStrategy strategy = matchContext.getPattern().getStrategy();
+
+ if (strategy.continueMatching(element)) {
+ visitor.matchContext(new ArrayBackedNodeIterator(new PsiElement[] {element}));
+ return;
+ }
+ for(PsiElement el=element.getFirstChild();el!=null;el=el.getNextSibling()) {
+ match(el);
+ }
+ if (element instanceof PsiLanguageInjectionHost) {
+ InjectedLanguageUtil.enumerate(element, new PsiLanguageInjectionHost.InjectedPsiVisitor() {
+ @Override
+ public void visit(@NotNull PsiFile injectedPsi, @NotNull List<PsiLanguageInjectionHost.Shred> places) {
+ match(injectedPsi);
+ }
+ });
+ }
+ }
+
+ @Nullable
+ protected MatchResult isMatchedByDownUp(PsiElement element, final MatchOptions options) {
+ final CollectingMatchResultSink sink = new CollectingMatchResultSink();
+ CompiledPattern compiledPattern = prepareMatching(sink, options);
+
+ if (compiledPattern== null) {
+ assert false;
+ return null;
+ }
+
+ PsiElement targetNode = compiledPattern.getTargetNode();
+ PsiElement elementToStartMatching = null;
+
+ if (targetNode == null) {
+ targetNode = compiledPattern.getNodes().current();
+ if (targetNode != null) {
+ compiledPattern.getNodes().advance();
+ assert !compiledPattern.getNodes().hasNext();
+ compiledPattern.getNodes().rewind();
+
+ while (element.getClass() != targetNode.getClass()) {
+ element = element.getParent();
+ if (element == null) return null;
+ }
+
+ elementToStartMatching = element;
+ }
+ } else {
+ targetNode = StructuralSearchUtil.getProfileByPsiElement(element).extendMatchedByDownUp(targetNode);
+
+ MatchingHandler handler = null;
+
+ while (element.getClass() == targetNode.getClass() ||
+ compiledPattern.isTypedVar(targetNode) && compiledPattern.getHandler(targetNode).canMatch(targetNode, element)
+ ) {
+ handler = compiledPattern.getHandler(targetNode);
+ handler.setPinnedElement(element);
+ elementToStartMatching = element;
+ if (handler instanceof TopLevelMatchingHandler) break;
+ element = element.getParent();
+ targetNode = targetNode.getParent();
+
+ if (options.isLooseMatching()) {
+ element = StructuralSearchUtil.getProfileByPsiElement(element).updateCurrentNode(element);
+ targetNode = StructuralSearchUtil.getProfileByPsiElement(element).updateCurrentNode(targetNode);
+ }
+ }
+
+ if (!(handler instanceof TopLevelMatchingHandler)) return null;
+ }
+
+ assert targetNode != null : "Could not match down up when no target node";
+
+ match(elementToStartMatching);
+ matchContext.getSink().matchingFinished();
+ final int matchCount = sink.getMatches().size();
+ assert matchCount <= 1;
+ return matchCount > 0 ? sink.getMatches().get(0) : null;
+ }
+
+ private class MatchOneVirtualFile extends MatchOneFile {
+ private final VirtualFile myFileOrDir;
+ private final StructuralSearchProfile myProfile;
+ private final Language myOurPatternLanguage;
+ private final Language myOurPatternLanguage2;
+
+ public MatchOneVirtualFile(VirtualFile fileOrDir,
+ StructuralSearchProfile profile,
+ Language ourPatternLanguage,
+ Language ourPatternLanguage2) {
+ myFileOrDir = fileOrDir;
+ myProfile = profile;
+ myOurPatternLanguage = ourPatternLanguage;
+ myOurPatternLanguage2 = ourPatternLanguage2;
+ }
+
+ @Nullable
+ @Override
+ protected List<PsiElement> getPsiElementsToProcess() {
+ return ApplicationManager.getApplication().runReadAction(new Computable<List<PsiElement>>() {
+ @Override
+ public List<PsiElement> compute() {
+ PsiFile file = PsiManager.getInstance(project).findFile(myFileOrDir);
+ if (file == null) {
+ return null;
+ }
+
+ final FileViewProvider viewProvider = file.getViewProvider();
+ List<PsiElement> elementsToProcess = new SmartList<PsiElement>();
+
+ for(Language lang: viewProvider.getLanguages()) {
+ if (myProfile.isMyFile(file, lang, myOurPatternLanguage, myOurPatternLanguage2)) {
+ elementsToProcess.add(viewProvider.getPsi(lang));
+ }
+ }
+
+ return elementsToProcess;
+ }
+ });
+ }
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatcherImplUtil.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatcherImplUtil.java
new file mode 100644
index 000000000000..ffc9ae78e876
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatcherImplUtil.java
@@ -0,0 +1,67 @@
+package com.intellij.structuralsearch.impl.matcher;
+
+import com.intellij.lang.Language;
+import com.intellij.openapi.fileTypes.FileType;
+import com.intellij.openapi.fileTypes.LanguageFileType;
+import com.intellij.openapi.project.Project;
+import com.intellij.psi.PsiElement;
+import com.intellij.structuralsearch.MatchOptions;
+import com.intellij.structuralsearch.StructuralSearchProfile;
+import com.intellij.structuralsearch.StructuralSearchUtil;
+import com.intellij.structuralsearch.impl.matcher.compiler.PatternCompiler;
+import com.intellij.util.IncorrectOperationException;
+
+/**
+ * Created by IntelliJ IDEA.
+ * User: Maxim.Mossienko
+ * Date: Mar 19, 2004
+ * Time: 6:56:25 PM
+ * To change this template use File | Settings | File Templates.
+ */
+public class MatcherImplUtil {
+
+ public static void transform(MatchOptions options) {
+ if (options.hasVariableConstraints()) return;
+ PatternCompiler.transformOldPattern(options);
+ }
+
+ public static PsiElement[] createTreeFromText(String text, PatternTreeContext context, FileType fileType, Project project)
+ throws IncorrectOperationException {
+ return createTreeFromText(text, context, fileType, null, null, project, false);
+ }
+
+ public static PsiElement[] createSourceTreeFromText(String text,
+ PatternTreeContext context,
+ FileType fileType,
+ String extension,
+ Project project,
+ boolean physical) {
+ if (fileType instanceof LanguageFileType) {
+ Language language = ((LanguageFileType)fileType).getLanguage();
+ StructuralSearchProfile profile = StructuralSearchUtil.getProfileByLanguage(language);
+ if (profile != null) {
+ return profile.createPatternTree(text, context, fileType, null, null, extension, project, physical);
+ }
+ }
+ return PsiElement.EMPTY_ARRAY;
+ }
+
+ public static PsiElement[] createTreeFromText(String text,
+ PatternTreeContext context,
+ FileType fileType,
+ Language language,
+ String contextName,
+ Project project,
+ boolean physical) throws IncorrectOperationException {
+ if (language == null && fileType instanceof LanguageFileType) {
+ language = ((LanguageFileType)fileType).getLanguage();
+ }
+ if (language != null) {
+ StructuralSearchProfile profile = StructuralSearchUtil.getProfileByLanguage(language);
+ if (profile != null) {
+ return profile.createPatternTree(text, context, fileType, language, contextName, null, project, physical);
+ }
+ }
+ return PsiElement.EMPTY_ARRAY;
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/PatternTreeContext.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/PatternTreeContext.java
new file mode 100644
index 000000000000..7d93c9ae82e0
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/PatternTreeContext.java
@@ -0,0 +1,8 @@
+package com.intellij.structuralsearch.impl.matcher;
+
+/**
+* @author Eugene.Kudelevsky
+*/
+public enum PatternTreeContext {
+ File, Block, Class
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/XmlCompiledPattern.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/XmlCompiledPattern.java
new file mode 100644
index 000000000000..0c9f4945e3a5
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/XmlCompiledPattern.java
@@ -0,0 +1,22 @@
+package com.intellij.structuralsearch.impl.matcher;
+
+import com.intellij.structuralsearch.impl.matcher.strategies.XmlMatchingStrategy;
+
+/**
+* @author Eugene.Kudelevsky
+*/
+public class XmlCompiledPattern extends CompiledPattern {
+ private static final String XML_TYPED_VAR_PREFIX = "__";
+
+ public XmlCompiledPattern() {
+ setStrategy(XmlMatchingStrategy.getInstance());
+ }
+
+ public String[] getTypedVarPrefixes() {
+ return new String[] {XML_TYPED_VAR_PREFIX};
+ }
+
+ public boolean isTypedVar(final String str) {
+ return str.startsWith(XML_TYPED_VAR_PREFIX);
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/XmlMatchingVisitor.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/XmlMatchingVisitor.java
new file mode 100644
index 000000000000..d2e1e210534b
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/XmlMatchingVisitor.java
@@ -0,0 +1,130 @@
+package com.intellij.structuralsearch.impl.matcher;
+
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiWhiteSpace;
+import com.intellij.psi.XmlElementVisitor;
+import com.intellij.psi.util.PsiUtilCore;
+import com.intellij.psi.xml.*;
+import com.intellij.structuralsearch.impl.matcher.handlers.MatchingHandler;
+import com.intellij.structuralsearch.impl.matcher.handlers.SubstitutionHandler;
+import com.intellij.dupLocator.iterators.ArrayBackedNodeIterator;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+* @author Eugene.Kudelevsky
+*/
+public class XmlMatchingVisitor extends XmlElementVisitor {
+ private final GlobalMatchingVisitor myMatchingVisitor;
+ private final boolean myCaseSensitive;
+
+ public XmlMatchingVisitor(GlobalMatchingVisitor matchingVisitor) {
+ myMatchingVisitor = matchingVisitor;
+ myCaseSensitive = myMatchingVisitor.getMatchContext().getOptions().isCaseSensitiveMatch();
+ }
+
+ @Override
+ public void visitElement(final PsiElement element) {
+ myMatchingVisitor.setResult(element.textMatches(element));
+ }
+
+ @Override public void visitXmlAttribute(XmlAttribute attribute) {
+ final XmlAttribute another = (XmlAttribute)myMatchingVisitor.getElement();
+ final boolean isTypedVar = myMatchingVisitor.getMatchContext().getPattern().isTypedVar(attribute.getName());
+
+ myMatchingVisitor.setResult(matches(attribute.getName(), another.getName()) || isTypedVar);
+ if (myMatchingVisitor.getResult()) {
+ myMatchingVisitor.setResult(myMatchingVisitor.match(attribute.getValueElement(), another.getValueElement()));
+ }
+
+ if (myMatchingVisitor.getResult() && isTypedVar) {
+ MatchingHandler handler = myMatchingVisitor.getMatchContext().getPattern().getHandler(attribute.getName());
+ myMatchingVisitor.setResult(((SubstitutionHandler)handler).handle(another, myMatchingVisitor.getMatchContext()));
+ }
+ }
+
+ @Override public void visitXmlAttributeValue(XmlAttributeValue value) {
+ final XmlAttributeValue another = (XmlAttributeValue)myMatchingVisitor.getElement();
+ final String text = value.getValue();
+
+ final boolean isTypedVar = myMatchingVisitor.getMatchContext().getPattern().isTypedVar(text);
+ MatchingHandler handler;
+
+ if (isTypedVar && (handler = myMatchingVisitor.getMatchContext().getPattern().getHandler( text )) instanceof SubstitutionHandler) {
+ String text2 = another.getText();
+ int offset = text2.length() > 0 && ( text2.charAt(0) == '"' || text2.charAt(0) == '\'') ? 1:0;
+ myMatchingVisitor.setResult(((SubstitutionHandler)handler).handle(another, offset, text2.length() - offset,
+ myMatchingVisitor.getMatchContext()));
+ } else {
+ myMatchingVisitor.setResult(matches(text, another.getValue()));
+ }
+ }
+
+ @Override public void visitXmlTag(XmlTag tag) {
+ final XmlTag another = (XmlTag)myMatchingVisitor.getElement();
+ final boolean isTypedVar = myMatchingVisitor.getMatchContext().getPattern().isTypedVar(tag.getName());
+
+ myMatchingVisitor.setResult((matches(tag.getName(), another.getName()) || isTypedVar) &&
+ myMatchingVisitor.matchInAnyOrder(tag.getAttributes(), another.getAttributes()));
+
+ if(myMatchingVisitor.getResult()) {
+ final XmlTagChild[] contentChildren = tag.getValue().getChildren();
+
+ if (contentChildren.length > 0) {
+ PsiElement[] patternNodes = contentChildren;
+ PsiElement[] matchedNodes = another.getValue().getChildren();
+
+ if (contentChildren.length != 1) {
+ patternNodes = expandXmlTexts(patternNodes);
+ matchedNodes = expandXmlTexts(matchedNodes);
+ }
+
+ myMatchingVisitor.setResult(myMatchingVisitor.matchSequentially(
+ new ArrayBackedNodeIterator(patternNodes),
+ new ArrayBackedNodeIterator(matchedNodes)
+ ));
+ }
+ }
+
+ if (myMatchingVisitor.getResult() && isTypedVar) {
+ MatchingHandler handler = myMatchingVisitor.getMatchContext().getPattern().getHandler( tag.getName() );
+ myMatchingVisitor.setResult(((SubstitutionHandler)handler).handle(another, myMatchingVisitor.getMatchContext()));
+ }
+ }
+
+ private static PsiElement[] expandXmlTexts(PsiElement[] children) {
+ List<PsiElement> result = new ArrayList<PsiElement>(children.length);
+ for(PsiElement c:children) {
+ if (c instanceof XmlText) {
+ for(PsiElement p:c.getChildren()) {
+ if (!(p instanceof PsiWhiteSpace)) result.add(p);
+ }
+ } else if (!(c instanceof PsiWhiteSpace)) {
+ result.add(c);
+ }
+ }
+ return PsiUtilCore.toPsiElementArray(result);
+ }
+
+ @Override public void visitXmlText(XmlText text) {
+ myMatchingVisitor.setResult(myMatchingVisitor.matchSequentially(text.getFirstChild(), myMatchingVisitor.getElement().getFirstChild()));
+ }
+
+ @Override public void visitXmlToken(XmlToken token) {
+ if (token.getTokenType() == XmlTokenType.XML_DATA_CHARACTERS) {
+ String text = token.getText();
+ final boolean isTypedVar = myMatchingVisitor.getMatchContext().getPattern().isTypedVar(text);
+
+ if (isTypedVar) {
+ myMatchingVisitor.setResult(myMatchingVisitor.handleTypedElement(token, myMatchingVisitor.getElement()));
+ } else {
+ myMatchingVisitor.setResult(matches(text, myMatchingVisitor.getElement().getText()));
+ }
+ }
+ }
+
+ private boolean matches(String a, String b) {
+ return myCaseSensitive ? a.equals(b) : a.equalsIgnoreCase(b);
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/CompileContext.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/CompileContext.java
new file mode 100644
index 000000000000..09758a5e2191
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/CompileContext.java
@@ -0,0 +1,71 @@
+package com.intellij.structuralsearch.impl.matcher.compiler;
+
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.project.Project;
+import com.intellij.structuralsearch.MatchOptions;
+import com.intellij.structuralsearch.impl.matcher.CompiledPattern;
+
+/**
+ * Created by IntelliJ IDEA.
+ * User: maxim
+ * Date: 17.11.2004
+ * Time: 19:26:37
+ * To change this template use File | Settings | File Templates.
+ */
+public class CompileContext {
+ private OptimizingSearchHelper searchHelper;
+
+ private CompiledPattern pattern;
+ private MatchOptions options;
+ private Project project;
+
+ public void clear() {
+ if (searchHelper!=null) searchHelper.clear();
+
+ project = null;
+ pattern = null;
+ options = null;
+ }
+
+ public void init(final CompiledPattern _result, final MatchOptions _options, final Project _project, final boolean _findMatchingFiles) {
+ options = _options;
+ project = _project;
+ pattern = _result;
+
+ searchHelper = ApplicationManager.getApplication().isUnitTestMode() ?
+ new TestModeOptimizingSearchHelper(this) :
+ new FindInFilesOptimizingSearchHelper(this, _findMatchingFiles, _project);
+ }
+
+ public OptimizingSearchHelper getSearchHelper() {
+ return searchHelper;
+ }
+
+ void setSearchHelper(OptimizingSearchHelper searchHelper) {
+ this.searchHelper = searchHelper;
+ }
+
+ public CompiledPattern getPattern() {
+ return pattern;
+ }
+
+ void setPattern(CompiledPattern pattern) {
+ this.pattern = pattern;
+ }
+
+ MatchOptions getOptions() {
+ return options;
+ }
+
+ void setOptions(MatchOptions options) {
+ this.options = options;
+ }
+
+ Project getProject() {
+ return project;
+ }
+
+ void setProject(Project project) {
+ this.project = project;
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/DeleteNodesAction.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/DeleteNodesAction.java
new file mode 100644
index 000000000000..4b5ad7c64545
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/DeleteNodesAction.java
@@ -0,0 +1,59 @@
+package com.intellij.structuralsearch.impl.matcher.compiler;
+
+import com.intellij.psi.PsiElement;
+
+import java.util.ArrayList;
+
+/**
+ * Created by IntelliJ IDEA.
+ * User: maxim
+ * Date: 17.11.2004
+ * Time: 19:24:40
+ * To change this template use File | Settings | File Templates.
+ */
+class DeleteNodesAction implements Runnable {
+ private final ArrayList elements;
+
+ DeleteNodesAction(ArrayList _elements) {
+ elements = _elements;
+ }
+
+ private void delete(PsiElement first, PsiElement last) throws Exception {
+ if (last==first) {
+ first.delete();
+ } else {
+ first.getParent().deleteChildRange(first,last);
+ }
+ }
+ public void run() {
+ try {
+ PsiElement first= null;
+ PsiElement last = null;
+
+ for(int i = 0;i < elements.size(); ++i) {
+ final PsiElement el = (PsiElement)elements.get(i);
+
+ if (!el.isValid()) continue;
+
+ if (first==null) {
+ first = last = el;
+ } else if (last.getNextSibling()==el) {
+ last = el;
+ } else {
+ delete(first,last);
+ first = last = null;
+ --i;
+ continue;
+ }
+ }
+
+ if (first!=null) {
+ delete(first,last);
+ }
+ } catch(Throwable ex) {
+ ex.printStackTrace();
+ } finally {
+ elements.clear();
+ }
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/FindInFilesOptimizingSearchHelper.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/FindInFilesOptimizingSearchHelper.java
new file mode 100644
index 000000000000..ff7f06a36d5b
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/FindInFilesOptimizingSearchHelper.java
@@ -0,0 +1,103 @@
+package com.intellij.structuralsearch.impl.matcher.compiler;
+
+import com.intellij.lang.Language;
+import com.intellij.lang.LanguageNamesValidation;
+import com.intellij.lang.refactoring.NamesValidator;
+import com.intellij.lexer.Lexer;
+import com.intellij.openapi.fileTypes.FileType;
+import com.intellij.openapi.fileTypes.LanguageFileType;
+import com.intellij.openapi.project.Project;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.search.GlobalSearchScope;
+import com.intellij.psi.search.PsiSearchHelper;
+import com.intellij.util.Processor;
+import gnu.trove.THashMap;
+
+import java.util.Set;
+
+/**
+ * @author Maxim.Mossienko
+*/
+class FindInFilesOptimizingSearchHelper extends OptimizingSearchHelperBase {
+ private PsiSearchHelper helper;
+ private THashMap<PsiFile,PsiFile> filesToScan;
+ private THashMap<PsiFile,PsiFile> filesToScan2;
+ private Lexer javaLexer;
+
+ private final boolean findMatchingFiles;
+
+ FindInFilesOptimizingSearchHelper(CompileContext _context, boolean _findMatchngFiles, Project project) {
+ super(_context);
+ findMatchingFiles = _findMatchngFiles;
+
+ if (findMatchingFiles) {
+ helper = PsiSearchHelper.SERVICE.getInstance(project);
+
+ if (filesToScan == null) {
+ filesToScan = new THashMap<PsiFile,PsiFile>();
+ filesToScan2 = new THashMap<PsiFile,PsiFile>();
+ }
+ }
+ }
+
+ public boolean doOptimizing() {
+ return findMatchingFiles;
+ }
+
+ public void clear() {
+ super.clear();
+
+ if (filesToScan != null) {
+ filesToScan.clear();
+ filesToScan2.clear();
+
+ helper = null;
+ }
+ }
+
+ protected void doAddSearchWordInCode(final String refname) {
+ final FileType fileType = context.getOptions().getFileType();
+ final Language language = fileType instanceof LanguageFileType ? ((LanguageFileType)fileType).getLanguage() : Language.ANY;
+ final NamesValidator namesValidator = LanguageNamesValidation.INSTANCE.forLanguage(language);
+ if (namesValidator.isKeyword(refname, context.getProject())) {
+ helper.processAllFilesWithWordInText(refname, (GlobalSearchScope)context.getOptions().getScope(), new MyFileProcessor(), true);
+ } else {
+ helper.processAllFilesWithWord(refname, (GlobalSearchScope)context.getOptions().getScope(), new MyFileProcessor(), true);
+ }
+ }
+
+ protected void doAddSearchWordInText(final String refname) {
+ helper.processAllFilesWithWordInText(refname, (GlobalSearchScope)context.getOptions().getScope(), new MyFileProcessor(), true);
+ }
+
+ protected void doAddSearchWordInComments(final String refname) {
+ helper.processAllFilesWithWordInComments(refname, (GlobalSearchScope)context.getOptions().getScope(), new MyFileProcessor());
+ }
+
+ protected void doAddSearchWordInLiterals(final String refname) {
+ helper.processAllFilesWithWordInLiterals(refname, (GlobalSearchScope)context.getOptions().getScope(), new MyFileProcessor());
+ }
+
+ public void endTransaction() {
+ super.endTransaction();
+ THashMap<PsiFile,PsiFile> map = filesToScan;
+ if (map.size() > 0) map.clear();
+ filesToScan = filesToScan2;
+ filesToScan2 = map;
+ }
+
+ public Set<PsiFile> getFilesSetToScan() {
+ return filesToScan.keySet();
+ }
+
+ private class MyFileProcessor implements Processor<PsiFile> {
+ public boolean process(PsiFile file) {
+ if (scanRequest == 0 ||
+ filesToScan.get(file)!=null) {
+ filesToScan2.put(file,file);
+ }
+ return true;
+ }
+ }
+
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/GlobalCompilingVisitor.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/GlobalCompilingVisitor.java
new file mode 100644
index 000000000000..130751adec67
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/GlobalCompilingVisitor.java
@@ -0,0 +1,314 @@
+package com.intellij.structuralsearch.impl.matcher.compiler;
+
+import com.intellij.dupLocator.util.NodeFilter;
+import com.intellij.openapi.extensions.Extensions;
+import com.intellij.psi.PsiElement;
+import com.intellij.structuralsearch.StructuralSearchProfile;
+import com.intellij.structuralsearch.StructuralSearchUtil;
+import com.intellij.structuralsearch.impl.matcher.filters.CompositeFilter;
+import com.intellij.structuralsearch.impl.matcher.filters.LexicalNodesFilter;
+import com.intellij.structuralsearch.impl.matcher.handlers.LiteralWithSubstitutionHandler;
+import com.intellij.structuralsearch.impl.matcher.handlers.MatchingHandler;
+import com.intellij.structuralsearch.impl.matcher.handlers.SubstitutionHandler;
+import com.intellij.structuralsearch.impl.matcher.predicates.RegExpPredicate;
+import org.jetbrains.annotations.Contract;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import static com.intellij.structuralsearch.MatchOptions.INSTANCE_MODIFIER_NAME;
+import static com.intellij.structuralsearch.MatchOptions.MODIFIER_ANNOTATION_NAME;
+
+/**
+ * @author maxim
+ */
+public class GlobalCompilingVisitor {
+ @NonNls private static final String SUBSTITUTION_PATTERN_STR = "\\b(__\\$_\\w+)\\b";
+ private static final Pattern ourSubstitutionPattern = Pattern.compile(SUBSTITUTION_PATTERN_STR);
+ private static final Set<String> ourReservedWords = new HashSet<String>(Arrays.asList(MODIFIER_ANNOTATION_NAME, INSTANCE_MODIFIER_NAME)) {
+ {
+ for (StructuralSearchProfile profile : Extensions.getExtensions(StructuralSearchProfile.EP_NAME)) {
+ addAll(profile.getReservedWords());
+ }
+ }
+ };
+ private static final Pattern ourAlternativePattern = Pattern.compile("^\\((.+)\\)$");
+ @NonNls private static final String WORD_SEARCH_PATTERN_STR = ".*?\\b(.+?)\\b.*?";
+ private static final Pattern ourWordSearchPattern = Pattern.compile(WORD_SEARCH_PATTERN_STR);
+ private CompileContext context;
+ private final ArrayList<PsiElement> myLexicalNodes = new ArrayList<PsiElement>();
+
+ private int myCodeBlockLevel;
+
+ private static final NodeFilter ourFilter = LexicalNodesFilter.getInstance();
+
+ public static NodeFilter getFilter() {
+ return ourFilter;
+ }
+
+ public void setHandler(PsiElement element, MatchingHandler handler) {
+ MatchingHandler realHandler = context.getPattern().getHandlerSimple(element);
+
+ if (realHandler instanceof SubstitutionHandler) {
+ ((SubstitutionHandler)realHandler).setMatchHandler(handler);
+ }
+ else {
+ // @todo care about composite handler in this case of simple handler!
+ context.getPattern().setHandler(element, handler);
+ }
+ }
+
+ public final void handle(PsiElement element) {
+
+ if ((!ourFilter.accepts(element) ||
+ StructuralSearchUtil.isIdentifier(element)) &&
+ context.getPattern().isRealTypedVar(element) &&
+ context.getPattern().getHandlerSimple(element) == null
+ ) {
+ String name = context.getPattern().getTypedVarString(element);
+ // name is the same for named element (clazz,methods, etc) and token (name of ... itself)
+ // @todo need fix this
+ final SubstitutionHandler handler;
+
+ context.getPattern().setHandler(
+ element,
+ handler = (SubstitutionHandler)context.getPattern().getHandler(name)
+ );
+
+ if (handler != null && context.getOptions().getVariableConstraint(handler.getName()).isPartOfSearchResults()) {
+ handler.setTarget(true);
+ context.getPattern().setTargetNode(element);
+ }
+ }
+ }
+
+ public CompileContext getContext() {
+ return context;
+ }
+
+ public int getCodeBlockLevel() {
+ return myCodeBlockLevel;
+ }
+
+ public void setCodeBlockLevel(int codeBlockLevel) {
+ this.myCodeBlockLevel = codeBlockLevel;
+ }
+
+ static void setFilter(MatchingHandler handler, NodeFilter filter) {
+ if (handler.getFilter() != null &&
+ handler.getFilter().getClass() != filter.getClass()
+ ) {
+ // for constructor we will have the same handler for class and method and tokens itselfa
+ handler.setFilter(
+ new CompositeFilter(
+ filter,
+ handler.getFilter()
+ )
+ );
+ }
+ else {
+ handler.setFilter(filter);
+ }
+ }
+
+ public ArrayList<PsiElement> getLexicalNodes() {
+ return myLexicalNodes;
+ }
+
+ public void addLexicalNode(PsiElement node) {
+ myLexicalNodes.add(node);
+ }
+
+ void compile(PsiElement[] elements, CompileContext context) {
+ myCodeBlockLevel = 0;
+ this.context = context;
+ final StructuralSearchProfile profile = StructuralSearchUtil.getProfileByFileType(context.getOptions().getFileType());
+ assert profile != null;
+ profile.compile(elements, this);
+
+ if (context.getPattern().getStrategy() == null) {
+ System.out.println();
+ }
+ }
+
+ @Nullable
+ public MatchingHandler processPatternStringWithFragments(String pattern, OccurenceKind kind) {
+ return processPatternStringWithFragments(pattern, kind, ourSubstitutionPattern);
+ }
+
+ @Nullable
+ public MatchingHandler processPatternStringWithFragments(String pattern, OccurenceKind kind, Pattern substitutionPattern) {
+ String content;
+
+ if (kind == OccurenceKind.LITERAL) {
+ content = pattern.substring(1, pattern.length() - 1);
+ }
+ else if (kind == OccurenceKind.COMMENT) {
+ content = pattern;
+ }
+ else {
+ return null;
+ }
+
+ @NonNls StringBuilder buf = new StringBuilder(content.length());
+ Matcher matcher = substitutionPattern.matcher(content);
+ List<SubstitutionHandler> handlers = null;
+ int start = 0;
+ String word;
+ boolean hasLiteralContent = false;
+
+ SubstitutionHandler handler = null;
+ while (matcher.find()) {
+ if (handlers == null) handlers = new ArrayList<SubstitutionHandler>();
+ handler = (SubstitutionHandler)getContext().getPattern().getHandler(matcher.group(1));
+ if (handler != null) handlers.add(handler);
+
+ word = content.substring(start, matcher.start());
+
+ if (word.length() > 0) {
+ buf.append(StructuralSearchUtil.shieldSpecialChars(word));
+ hasLiteralContent = true;
+
+ processTokenizedName(word, false, kind);
+ }
+
+ RegExpPredicate predicate = MatchingHandler.getSimpleRegExpPredicate(handler);
+
+ if (predicate == null || !predicate.isWholeWords()) {
+ buf.append("(.*?)");
+ }
+ else {
+ buf.append(".*?\\b(").append(predicate.getRegExp()).append(")\\b.*?");
+ }
+
+ if (isSuitablePredicate(predicate, handler)) {
+ processTokenizedName(predicate.getRegExp(), false, kind);
+ }
+
+ start = matcher.end();
+ }
+
+ word = content.substring(start, content.length());
+
+ if (word.length() > 0) {
+ hasLiteralContent = true;
+ buf.append(StructuralSearchUtil.shieldSpecialChars(word));
+
+ processTokenizedName(word, false, kind);
+ }
+
+ if (hasLiteralContent) {
+ if (kind == OccurenceKind.LITERAL) {
+ buf.insert(0, "[\"']");
+ buf.append("[\"']");
+ }
+ buf.append("$");
+ }
+
+ if (handlers != null) {
+ return hasLiteralContent ? new LiteralWithSubstitutionHandler(buf.toString(), handlers) : handler;
+ }
+
+ return null;
+ }
+
+ @Contract("null,_ -> false")
+ static boolean isSuitablePredicate(RegExpPredicate predicate, SubstitutionHandler handler) {
+ return predicate != null && handler.getMinOccurs() != 0 && predicate.couldBeOptimized();
+ }
+
+ public static void addFilesToSearchForGivenWord(String refname,
+ boolean endTransaction,
+ GlobalCompilingVisitor.OccurenceKind kind,
+ CompileContext compileContext) {
+ if (!compileContext.getSearchHelper().doOptimizing()) {
+ return;
+ }
+ if (ourReservedWords.contains(refname)) return; // skip our special annotations !!!
+
+ boolean addedSomething = false;
+
+ if (kind == GlobalCompilingVisitor.OccurenceKind.CODE) {
+ addedSomething = compileContext.getSearchHelper().addWordToSearchInCode(refname);
+ }
+ else if (kind == GlobalCompilingVisitor.OccurenceKind.COMMENT) {
+ addedSomething = compileContext.getSearchHelper().addWordToSearchInComments(refname);
+ }
+ else if (kind == GlobalCompilingVisitor.OccurenceKind.LITERAL) {
+ addedSomething = compileContext.getSearchHelper().addWordToSearchInLiterals(refname);
+ }
+
+ if (addedSomething && endTransaction) {
+ compileContext.getSearchHelper().endTransaction();
+ }
+ }
+
+ public void processTokenizedName(String name, boolean skipComments, GlobalCompilingVisitor.OccurenceKind kind) {
+ WordTokenizer tokenizer = new WordTokenizer(name);
+ for (Iterator<String> i = tokenizer.iterator(); i.hasNext();) {
+ String nextToken = i.next();
+ if (skipComments &&
+ (nextToken.equals("/*") || nextToken.equals("/**") || nextToken.equals("*/") || nextToken.equals("*") || nextToken.equals("//"))
+ ) {
+ continue;
+ }
+
+ Matcher matcher = ourAlternativePattern.matcher(nextToken);
+ if (matcher.matches()) {
+ StringTokenizer alternatives = new StringTokenizer(matcher.group(1), "|");
+ while (alternatives.hasMoreTokens()) {
+ addFilesToSearchForGivenWord(alternatives.nextToken(), !alternatives.hasMoreTokens(), kind, getContext());
+ }
+ }
+ else {
+ addFilesToSearchForGivenWord(nextToken, true, kind, getContext());
+ }
+ }
+ }
+
+ public enum OccurenceKind {
+ LITERAL, COMMENT, CODE
+ }
+
+ private static class WordTokenizer {
+ private final List<String> myWords = new ArrayList<String>();
+
+ WordTokenizer(String text) {
+ final StringTokenizer tokenizer = new StringTokenizer(text);
+ Matcher matcher = null;
+
+ while (tokenizer.hasMoreTokens()) {
+ String nextToken = tokenizer.nextToken();
+ if (matcher == null) {
+ matcher = ourWordSearchPattern.matcher(nextToken);
+ }
+ else {
+ matcher.reset(nextToken);
+ }
+
+ nextToken = (matcher.matches()) ? matcher.group(1) : nextToken;
+ int lastWordStart = 0;
+ int i;
+ for (i = 0; i < nextToken.length(); ++i) {
+ if (!Character.isJavaIdentifierStart(nextToken.charAt(i))) {
+ if (i != lastWordStart) {
+ myWords.add(nextToken.substring(lastWordStart, i));
+ }
+ lastWordStart = i + 1;
+ }
+ }
+
+ if (i != lastWordStart) {
+ myWords.add(nextToken.substring(lastWordStart, i));
+ }
+ }
+ }
+
+ Iterator<String> iterator() {
+ return myWords.iterator();
+ }
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/OptimizingSearchHelper.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/OptimizingSearchHelper.java
new file mode 100644
index 000000000000..8d6a08b6ef95
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/OptimizingSearchHelper.java
@@ -0,0 +1,27 @@
+package com.intellij.structuralsearch.impl.matcher.compiler;
+
+import com.intellij.psi.PsiFile;
+
+import java.util.Set;
+
+/**
+ * @author Maxim.Mossienko
+*/
+public interface OptimizingSearchHelper {
+ boolean doOptimizing();
+ void clear();
+
+ boolean addWordToSearchInCode(final String refname);
+
+ boolean addWordToSearchInText(final String refname);
+
+ boolean addWordToSearchInComments(final String refname);
+
+ boolean addWordToSearchInLiterals(final String refname);
+
+ void endTransaction();
+
+ boolean isScannedSomething();
+
+ Set<PsiFile> getFilesSetToScan();
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/OptimizingSearchHelperBase.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/OptimizingSearchHelperBase.java
new file mode 100644
index 000000000000..2d6f2bc41b37
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/OptimizingSearchHelperBase.java
@@ -0,0 +1,85 @@
+package com.intellij.structuralsearch.impl.matcher.compiler;
+
+import gnu.trove.THashSet;
+
+/**
+ * @author Maxim.Mossienko
+*/
+abstract class OptimizingSearchHelperBase implements OptimizingSearchHelper {
+ private final THashSet<String> scanned;
+ private final THashSet<String> scannedText;
+ private final THashSet<String> scannedComments;
+ private final THashSet<String> scannedLiterals;
+ protected int scanRequest;
+
+
+ protected final CompileContext context;
+
+ OptimizingSearchHelperBase(CompileContext _context) {
+ context = _context;
+
+ scanRequest = 0;
+ scanned = new THashSet<String>();
+ scannedText = new THashSet<String>();
+ scannedComments = new THashSet<String>();
+ scannedLiterals = new THashSet<String>();
+ }
+
+ public void clear() {
+ scanned.clear();
+ scannedComments.clear();
+ scannedLiterals.clear();
+ }
+
+ public boolean addWordToSearchInCode(final String refname) {
+ if (!scanned.contains(refname)) {
+ doAddSearchWordInCode(refname);
+ scanned.add( refname );
+ return true;
+ }
+
+ return false;
+ }
+
+ public boolean addWordToSearchInText(final String refname) {
+ if (!scannedText.contains(refname)) {
+ doAddSearchWordInText(refname);
+ scannedText.add(refname);
+ return true;
+ }
+ return false;
+ }
+
+ protected abstract void doAddSearchWordInCode(final String refname);
+ protected abstract void doAddSearchWordInText(final String refname);
+
+ protected abstract void doAddSearchWordInComments(final String refname);
+ protected abstract void doAddSearchWordInLiterals(final String refname);
+
+ public void endTransaction() {
+ scanRequest++;
+ }
+
+ public boolean addWordToSearchInComments(final String refname) {
+ if (!scannedComments.contains(refname)) {
+ doAddSearchWordInComments(refname);
+
+ scannedComments.add( refname );
+ return true;
+ }
+ return false;
+ }
+
+ public boolean addWordToSearchInLiterals(final String refname) {
+ if (!scannedLiterals.contains(refname)) {
+ doAddSearchWordInLiterals(refname);
+ scannedLiterals.add( refname );
+ return true;
+ }
+ return false;
+ }
+
+ public boolean isScannedSomething() {
+ return scanned.size() > 0 || scannedComments.size() > 0 || scannedLiterals.size() > 0;
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/PatternCompiler.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/PatternCompiler.java
new file mode 100644
index 000000000000..e290ebb70793
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/PatternCompiler.java
@@ -0,0 +1,512 @@
+package com.intellij.structuralsearch.impl.matcher.compiler;
+
+import com.intellij.codeInsight.template.Template;
+import com.intellij.codeInsight.template.TemplateManager;
+import com.intellij.dupLocator.util.NodeFilter;
+import com.intellij.lang.Language;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.extensions.Extensions;
+import com.intellij.openapi.fileTypes.FileType;
+import com.intellij.openapi.fileTypes.LanguageFileType;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiErrorElement;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.PsiRecursiveElementWalkingVisitor;
+import com.intellij.psi.impl.source.PsiFileImpl;
+import com.intellij.psi.impl.source.tree.LeafElement;
+import com.intellij.psi.search.GlobalSearchScope;
+import com.intellij.psi.search.LocalSearchScope;
+import com.intellij.psi.util.PsiUtilCore;
+import com.intellij.structuralsearch.*;
+import com.intellij.structuralsearch.impl.matcher.CompiledPattern;
+import com.intellij.structuralsearch.impl.matcher.MatchPredicateProvider;
+import com.intellij.structuralsearch.impl.matcher.MatcherImplUtil;
+import com.intellij.structuralsearch.impl.matcher.PatternTreeContext;
+import com.intellij.structuralsearch.impl.matcher.filters.LexicalNodesFilter;
+import com.intellij.structuralsearch.impl.matcher.handlers.MatchPredicate;
+import com.intellij.structuralsearch.impl.matcher.handlers.SubstitutionHandler;
+import com.intellij.structuralsearch.impl.matcher.predicates.*;
+import com.intellij.structuralsearch.plugin.ui.Configuration;
+import com.intellij.util.IncorrectOperationException;
+import gnu.trove.TIntArrayList;
+import gnu.trove.TIntHashSet;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Compiles the handlers for usability
+ */
+public class PatternCompiler {
+ private static CompileContext lastTestingContext;
+
+ public static void transformOldPattern(MatchOptions options) {
+ StringToConstraintsTransformer.transformOldPattern(options);
+ }
+
+ public static CompiledPattern compilePattern(final Project project, final MatchOptions options) throws MalformedPatternException,
+ UnsupportedOperationException {
+ FileType fileType = options.getFileType();
+ assert fileType instanceof LanguageFileType;
+ Language language = ((LanguageFileType)fileType).getLanguage();
+ StructuralSearchProfile profile = StructuralSearchUtil.getProfileByLanguage(language);
+ assert profile != null;
+ CompiledPattern result = profile.createCompiledPattern();
+
+ final String[] prefixes = result.getTypedVarPrefixes();
+ assert prefixes.length > 0;
+
+ final CompileContext context = new CompileContext();
+ if (ApplicationManager.getApplication().isUnitTestMode()) lastTestingContext = context;
+
+ /*CompiledPattern result = options.getFileType() == StdFileTypes.JAVA ?
+ new JavaCompiledPattern() :
+ new XmlCompiledPattern();*/
+
+ try {
+ context.init(result, options, project, options.getScope() instanceof GlobalSearchScope);
+
+ List<PsiElement> elements = compileByAllPrefixes(project, options, result, context, prefixes);
+
+ context.getPattern().setNodes(elements);
+
+ if (context.getSearchHelper().doOptimizing() && context.getSearchHelper().isScannedSomething()) {
+ final Set<PsiFile> set = context.getSearchHelper().getFilesSetToScan();
+ final List<PsiFile> filesToScan = new ArrayList<PsiFile>(set.size());
+ final GlobalSearchScope scope = (GlobalSearchScope)options.getScope();
+
+ for (final PsiFile file : set) {
+ if (!scope.contains(file.getVirtualFile())) {
+ continue;
+ }
+
+ if (file instanceof PsiFileImpl) {
+ ((PsiFileImpl)file).clearCaches();
+ }
+ filesToScan.add(file);
+ }
+
+ if (filesToScan.size() == 0) {
+ throw new MalformedPatternException(SSRBundle.message("ssr.will.not.find.anything"));
+ }
+ result.setScope(
+ new LocalSearchScope(PsiUtilCore.toPsiElementArray(filesToScan))
+ );
+ }
+ } finally {
+ context.clear();
+ }
+
+ return result;
+ }
+
+ public static String getLastFindPlan() {
+ return ((TestModeOptimizingSearchHelper)lastTestingContext.getSearchHelper()).getSearchPlan();
+ }
+
+ @NotNull
+ private static List<PsiElement> compileByAllPrefixes(Project project,
+ MatchOptions options,
+ CompiledPattern pattern,
+ CompileContext context,
+ String[] applicablePrefixes) {
+ if (applicablePrefixes.length == 0) {
+ return Collections.emptyList();
+ }
+
+ List<PsiElement> elements = doCompile(project, options, pattern, new ConstantPrefixProvider(applicablePrefixes[0]), context);
+ if (elements.isEmpty()) {
+ return elements;
+ }
+
+ final PsiFile file = elements.get(0).getContainingFile();
+ if (file == null) {
+ return elements;
+ }
+
+ final PsiElement last = elements.get(elements.size() - 1);
+ final Pattern[] patterns = new Pattern[applicablePrefixes.length];
+
+ for (int i = 0; i < applicablePrefixes.length; i++) {
+ String s = StructuralSearchUtil.shieldSpecialChars(applicablePrefixes[i]);
+ patterns[i] = Pattern.compile(s + "\\w+\\b");
+ }
+
+ final int[] varEndOffsets = findAllTypedVarOffsets(file, patterns);
+
+ final int patternEndOffset = last.getTextRange().getEndOffset();
+ if (elements.size() == 0 ||
+ checkErrorElements(file, patternEndOffset, patternEndOffset, varEndOffsets, true) != Boolean.TRUE) {
+ return elements;
+ }
+
+ final int varCount = varEndOffsets.length;
+ final String[] prefixSequence = new String[varCount];
+
+ for (int i = 0; i < varCount; i++) {
+ prefixSequence[i] = applicablePrefixes[0];
+ }
+
+ final List<PsiElement> finalElements =
+ compileByPrefixes(project, options, pattern, context, applicablePrefixes, patterns, prefixSequence, 0);
+ return finalElements != null
+ ? finalElements
+ : doCompile(project, options, pattern, new ConstantPrefixProvider(applicablePrefixes[0]), context);
+ }
+
+ @Nullable
+ private static List<PsiElement> compileByPrefixes(Project project,
+ MatchOptions options,
+ CompiledPattern pattern,
+ CompileContext context,
+ String[] applicablePrefixes,
+ Pattern[] substitutionPatterns,
+ String[] prefixSequence,
+ int index) {
+ if (index >= prefixSequence.length) {
+ final List<PsiElement> elements = doCompile(project, options, pattern, new ArrayPrefixProvider(prefixSequence), context);
+ if (elements.isEmpty()) {
+ return elements;
+ }
+
+ final PsiElement parent = elements.get(0).getParent();
+ final PsiElement last = elements.get(elements.size() - 1);
+ final int[] varEndOffsets = findAllTypedVarOffsets(parent.getContainingFile(), substitutionPatterns);
+ final int patternEndOffset = last.getTextRange().getEndOffset();
+ return checkErrorElements(parent, patternEndOffset, patternEndOffset, varEndOffsets, false) != Boolean.TRUE
+ ? elements
+ : null;
+ }
+
+ String[] alternativeVariant = null;
+
+ for (String applicablePrefix : applicablePrefixes) {
+ prefixSequence[index] = applicablePrefix;
+
+ List<PsiElement> elements = doCompile(project, options, pattern, new ArrayPrefixProvider(prefixSequence), context);
+ if (elements.isEmpty()) {
+ return elements;
+ }
+
+ final PsiFile file = elements.get(0).getContainingFile();
+ if (file == null) {
+ return elements;
+ }
+
+ final int[] varEndOffsets = findAllTypedVarOffsets(file, substitutionPatterns);
+ final int offset = varEndOffsets[index];
+
+ final int patternEndOffset = elements.get(elements.size() - 1).getTextRange().getEndOffset();
+ final Boolean result = checkErrorElements(file, offset, patternEndOffset, varEndOffsets, false);
+
+ if (result == Boolean.TRUE) {
+ continue;
+ }
+
+ if (result == Boolean.FALSE || (result == null && alternativeVariant == null)) {
+ final List<PsiElement> finalElements =
+ compileByPrefixes(project, options, pattern, context, applicablePrefixes, substitutionPatterns, prefixSequence, index + 1);
+ if (finalElements != null) {
+ if (result == Boolean.FALSE) {
+ return finalElements;
+ }
+ alternativeVariant = new String[prefixSequence.length];
+ System.arraycopy(prefixSequence, 0, alternativeVariant, 0, prefixSequence.length);
+ }
+ }
+ }
+
+ return alternativeVariant != null ?
+ compileByPrefixes(project, options, pattern, context, applicablePrefixes, substitutionPatterns, alternativeVariant, index + 1) :
+ null;
+ }
+
+ @NotNull
+ private static int[] findAllTypedVarOffsets(final PsiFile file, final Pattern[] substitutionPatterns) {
+ final TIntHashSet result = new TIntHashSet();
+
+ file.accept(new PsiRecursiveElementWalkingVisitor() {
+ @Override
+ public void visitElement(PsiElement element) {
+ super.visitElement(element);
+
+ if (element instanceof LeafElement) {
+ final String text = element.getText();
+
+ for (Pattern pattern : substitutionPatterns) {
+ final Matcher matcher = pattern.matcher(text);
+
+ while (matcher.find()) {
+ result.add(element.getTextRange().getStartOffset() + matcher.end());
+ }
+ }
+ }
+ }
+ });
+
+ final int[] resultArray = result.toArray();
+ Arrays.sort(resultArray);
+ return resultArray;
+ }
+
+
+ /**
+ * False: there are no error elements before offset, except patternEndOffset
+ * Null: there are only error elements located exactly after template variables or at the end of the pattern
+ * True: otherwise
+ */
+ @Nullable
+ private static Boolean checkErrorElements(PsiElement element,
+ final int offset,
+ final int patternEndOffset,
+ final int[] varEndOffsets,
+ final boolean strict) {
+ final TIntArrayList errorOffsets = new TIntArrayList();
+ final boolean[] containsErrorTail = {false};
+ final TIntHashSet varEndOffsetsSet = new TIntHashSet(varEndOffsets);
+
+ element.accept(new PsiRecursiveElementWalkingVisitor() {
+ @Override
+ public void visitElement(PsiElement element) {
+ super.visitElement(element);
+
+ if (!(element instanceof PsiErrorElement)) {
+ return;
+ }
+
+ final int startOffset = element.getTextRange().getStartOffset();
+
+ if ((strict || !varEndOffsetsSet.contains(startOffset)) && startOffset != patternEndOffset) {
+ errorOffsets.add(startOffset);
+ }
+
+ if (startOffset == offset) {
+ containsErrorTail[0] = true;
+ }
+ }
+ });
+
+ for (int i = 0; i < errorOffsets.size(); i++) {
+ final int errorOffset = errorOffsets.get(i);
+ if (errorOffset <= offset) {
+ return true;
+ }
+ }
+ return containsErrorTail[0] ? null : false;
+ }
+
+ private interface PrefixProvider {
+ String getPrefix(int varIndex);
+ }
+
+ private static class ConstantPrefixProvider implements PrefixProvider {
+ private final String myPrefix;
+
+ private ConstantPrefixProvider(String prefix) {
+ myPrefix = prefix;
+ }
+
+ @Override
+ public String getPrefix(int varIndex) {
+ return myPrefix;
+ }
+ }
+
+ private static class ArrayPrefixProvider implements PrefixProvider {
+ private final String[] myPrefixes;
+
+ private ArrayPrefixProvider(String[] prefixes) {
+ myPrefixes = prefixes;
+ }
+
+ @Override
+ public String getPrefix(int varIndex) {
+ try {
+ return myPrefixes[varIndex];
+ } catch (ArrayIndexOutOfBoundsException e) {
+ return null;
+ }
+ }
+ }
+
+ private static List<PsiElement> doCompile(Project project,
+ MatchOptions options,
+ CompiledPattern result,
+ PrefixProvider prefixProvider,
+ CompileContext context) {
+ result.clearHandlers();
+ context.init(result, options, project, options.getScope() instanceof GlobalSearchScope);
+
+ final StringBuilder buf = new StringBuilder();
+
+ Template template = TemplateManager.getInstance(project).createTemplate("","",options.getSearchPattern());
+
+ int segmentsCount = template.getSegmentsCount();
+ String text = template.getTemplateText();
+ buf.setLength(0);
+ int prevOffset = 0;
+
+ for(int i=0;i<segmentsCount;++i) {
+ final int offset = template.getSegmentOffset(i);
+ final String name = template.getSegmentName(i);
+
+ final String prefix = prefixProvider.getPrefix(i);
+ if (prefix == null) {
+ throw new MalformedPatternException();
+ }
+
+ buf.append(text.substring(prevOffset,offset));
+ buf.append(prefix);
+ buf.append(name);
+
+ MatchVariableConstraint constraint = options.getVariableConstraint(name);
+ if (constraint==null) {
+ // we do not edited the constraints
+ constraint = new MatchVariableConstraint();
+ constraint.setName( name );
+ options.addVariableConstraint(constraint);
+ }
+
+ SubstitutionHandler handler = result.createSubstitutionHandler(
+ name,
+ prefix + name,
+ constraint.isPartOfSearchResults(),
+ constraint.getMinCount(),
+ constraint.getMaxCount(),
+ constraint.isGreedy()
+ );
+
+ if(constraint.isWithinHierarchy()) {
+ handler.setSubtype(true);
+ }
+
+ if(constraint.isStrictlyWithinHierarchy()) {
+ handler.setStrictSubtype(true);
+ }
+
+ MatchPredicate predicate;
+
+ if (!StringUtil.isEmptyOrSpaces(constraint.getRegExp())) {
+ predicate = new RegExpPredicate(
+ constraint.getRegExp(),
+ options.isCaseSensitiveMatch(),
+ name,
+ constraint.isWholeWordsOnly(),
+ constraint.isPartOfSearchResults()
+ );
+ if (constraint.isInvertRegExp()) {
+ predicate = new NotPredicate(predicate);
+ }
+ addPredicate(handler,predicate);
+ }
+
+ if (constraint.isReference()) {
+ predicate = new ReferencePredicate( constraint.getNameOfReferenceVar() );
+
+ if (constraint.isInvertReference()) {
+ predicate = new NotPredicate(predicate);
+ }
+ addPredicate(handler,predicate);
+ }
+
+ Set<MatchPredicate> predicates = new LinkedHashSet<MatchPredicate>();
+ for (MatchPredicateProvider matchPredicateProvider : Extensions.getExtensions(MatchPredicateProvider.EP_NAME)) {
+ matchPredicateProvider.collectPredicates(constraint, name, options, predicates);
+ }
+ for (MatchPredicate matchPredicate : predicates) {
+ addPredicate(handler, matchPredicate);
+ }
+
+ addScriptConstraint(name, constraint, handler);
+
+ if (!StringUtil.isEmptyOrSpaces(constraint.getContainsConstraint())) {
+ predicate = new ContainsPredicate(name, constraint.getContainsConstraint());
+ if (constraint.isInvertContainsConstraint()) {
+ predicate = new NotPredicate(predicate);
+ }
+ addPredicate(handler,predicate);
+ }
+
+ if (!StringUtil.isEmptyOrSpaces(constraint.getWithinConstraint())) {
+ assert false;
+ }
+
+ prevOffset = offset;
+ }
+
+ MatchVariableConstraint constraint = options.getVariableConstraint(Configuration.CONTEXT_VAR_NAME);
+ if (constraint != null) {
+ SubstitutionHandler handler = result.createSubstitutionHandler(
+ Configuration.CONTEXT_VAR_NAME,
+ Configuration.CONTEXT_VAR_NAME,
+ constraint.isPartOfSearchResults(),
+ constraint.getMinCount(),
+ constraint.getMaxCount(),
+ constraint.isGreedy()
+ );
+
+ if (!StringUtil.isEmptyOrSpaces(constraint.getWithinConstraint())) {
+ MatchPredicate predicate = new WithinPredicate(Configuration.CONTEXT_VAR_NAME, constraint.getWithinConstraint(), project);
+ if (constraint.isInvertWithinConstraint()) {
+ predicate = new NotPredicate(predicate);
+ }
+ addPredicate(handler,predicate);
+ }
+
+ addScriptConstraint(Configuration.CONTEXT_VAR_NAME, constraint, handler);
+ }
+
+ buf.append(text.substring(prevOffset,text.length()));
+
+ PsiElement[] matchStatements;
+
+ try {
+ final String pattern = buf.toString();
+ matchStatements = MatcherImplUtil.createTreeFromText(pattern, PatternTreeContext.Block, options.getFileType(),
+ options.getDialect(), options.getPatternContext(), project, false);
+ if (matchStatements.length==0) throw new MalformedPatternException(pattern);
+ } catch (IncorrectOperationException e) {
+ throw new MalformedPatternException(e.getMessage());
+ }
+
+ NodeFilter filter = LexicalNodesFilter.getInstance();
+
+ GlobalCompilingVisitor compilingVisitor = new GlobalCompilingVisitor();
+ compilingVisitor.compile(matchStatements,context);
+ ArrayList<PsiElement> elements = new ArrayList<PsiElement>();
+
+ for (PsiElement matchStatement : matchStatements) {
+ if (!filter.accepts(matchStatement)) {
+ elements.add(matchStatement);
+ }
+ }
+
+ new DeleteNodesAction(compilingVisitor.getLexicalNodes()).run();
+ return elements;
+ }
+
+ private static void addScriptConstraint(String name, MatchVariableConstraint constraint, SubstitutionHandler handler) {
+ MatchPredicate predicate;
+ if (constraint.getScriptCodeConstraint()!= null && constraint.getScriptCodeConstraint().length() > 2) {
+ final String script = StringUtil.stripQuotesAroundValue(constraint.getScriptCodeConstraint());
+ final String s = ScriptSupport.checkValidScript(script);
+ if (s != null) throw new MalformedPatternException("Script constraint for " + constraint.getName() + " has problem "+s);
+ predicate = new ScriptPredicate(name, script);
+ addPredicate(handler,predicate);
+ }
+ }
+
+ static void addPredicate(SubstitutionHandler handler, MatchPredicate predicate) {
+ if (handler.getPredicate()==null) {
+ handler.setPredicate(predicate);
+ } else {
+ handler.setPredicate(new BinaryPredicate(handler.getPredicate(), predicate, false));
+ }
+ }
+
+} \ No newline at end of file
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/StringToConstraintsTransformer.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/StringToConstraintsTransformer.java
new file mode 100644
index 000000000000..0d684503a14d
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/StringToConstraintsTransformer.java
@@ -0,0 +1,436 @@
+package com.intellij.structuralsearch.impl.matcher.compiler;
+
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.structuralsearch.*;
+import com.intellij.structuralsearch.plugin.ui.Configuration;
+import org.jetbrains.annotations.NonNls;
+
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+
+/**
+ * Created by IntelliJ IDEA.
+ * User: maxim
+ * Date: 17.11.2004
+ * Time: 19:29:05
+ * To change this template use File | Settings | File Templates.
+ */
+class StringToConstraintsTransformer {
+ @NonNls private static final String P_STR = "(\\w+)\\('(\\w+)\\)";
+ private static final Pattern p = Pattern.compile(P_STR);
+ @NonNls private static final String P2_STR = "(\\w+)";
+ private static final Pattern p2 = Pattern.compile(P2_STR);
+ @NonNls private static final String P3_STR = "(\\w+)\\(( ?(?:[\\\"\\*<>!\\.\\?\\:\\$\\\\\\(\\)\\[\\]\\w\\|\\+ =]*|(?:\\\"[^\\\"]*\\\")) ?)\\)";
+ private static final Pattern p3 = Pattern.compile(P3_STR);
+ @NonNls private static final String REF = "ref";
+ @NonNls private static final String READ = "read";
+ @NonNls private static final String WRITE = "write";
+ @NonNls private static final String REGEX = "regex";
+ @NonNls private static final String REGEXW = "regexw";
+ @NonNls private static final String EXPRTYPE = "exprtype";
+ @NonNls private static final String FORMAL = "formal";
+ @NonNls private static final String SCRIPT = "script";
+ @NonNls private static final String CONTAINS = "contains";
+ @NonNls private static final String WITHIN = "within";
+
+ static void transformOldPattern(MatchOptions options) {
+ final String pattern = options.getSearchPattern();
+
+ final StringBuilder buf = new StringBuilder();
+
+ StringBuilder miscBuffer = null;
+ int anonymousTypedVarsCount = 0;
+
+ for(int index=0;index < pattern.length();++index) {
+ char ch = pattern.charAt(index);
+
+ if (ch=='\'') {
+ // doubling '
+ final int length = pattern.length();
+ if (index + 1 < length &&
+ pattern.charAt(index + 1)=='\''
+ ) {
+ // ignore next '
+ index++;
+ } else if (index + 2 < length &&
+ pattern.charAt(index + 2)=='\''
+ ) {
+ // eat simple character
+ buf.append(ch);
+ buf.append(pattern.charAt(++index));
+ ch = pattern.charAt(++index);
+ } else if (index + 3 < length &&
+ pattern.charAt(index + 1)=='\\' &&
+ pattern.charAt(index + 3)=='\''
+ ) {
+ // eat simple escape character
+ buf.append(ch);
+ buf.append(pattern.charAt(++index));
+ buf.append(pattern.charAt(++index));
+ ch = pattern.charAt(++index);
+ } else if (index + 7 < length &&
+ pattern.charAt(index + 1)=='\\' &&
+ pattern.charAt(index + 2)=='u' &&
+ pattern.charAt(index + 7)=='\'') {
+ // eat simple escape character
+ buf.append(ch);
+ buf.append(pattern.charAt(++index));
+ buf.append(pattern.charAt(++index));
+ buf.append(pattern.charAt(++index));
+ buf.append(pattern.charAt(++index));
+ buf.append(pattern.charAt(++index));
+ buf.append(pattern.charAt(++index));
+ ch = pattern.charAt(++index);
+ } else {
+ // typed variable
+
+ buf.append("$");
+ if (miscBuffer == null) miscBuffer = new StringBuilder();
+ else miscBuffer.setLength(0);
+
+ // eat the name of typed var
+ for(++index; index< length && Character.isJavaIdentifierPart(ch = pattern.charAt(index)); ++index) {
+ miscBuffer.append(ch);
+ buf.append(ch);
+ }
+
+ boolean anonymous = false;
+
+ if (miscBuffer.length() == 0) throw new MalformedPatternException(SSRBundle.message("error.expected.character"));
+ if (miscBuffer.charAt(0)=='_') {
+ anonymous = true;
+
+ if(miscBuffer.length() == 1) {
+ // anonymous var, make it unique for the case of constraints
+ anonymousTypedVarsCount++;
+ miscBuffer.append(anonymousTypedVarsCount);
+ buf.append(anonymousTypedVarsCount);
+ } else {
+ buf.deleteCharAt(buf.length()-miscBuffer.length());
+ miscBuffer.deleteCharAt(0);
+ }
+ }
+
+
+ buf.append("$");
+ String typedVar = miscBuffer.toString();
+ int minOccurs = 1;
+ int maxOccurs = 1;
+ boolean greedy = true;
+ MatchVariableConstraint constraint = options.getVariableConstraint(typedVar);
+ boolean constraintCreated = false;
+
+ if (constraint==null) {
+ constraint = new MatchVariableConstraint();
+ constraint.setName( typedVar );
+ constraintCreated = true;
+ }
+
+ // Check the number of occurrences for typed variable
+ if (index < length) {
+ char possibleQuantifier = pattern.charAt(index);
+
+ if (possibleQuantifier=='+') {
+ maxOccurs = Integer.MAX_VALUE;
+ ++index;
+ } else if (possibleQuantifier=='?') {
+ minOccurs = 0;
+ ++index;
+ } else if (possibleQuantifier=='*') {
+ minOccurs = 0;
+ maxOccurs = Integer.MAX_VALUE;
+ ++index;
+ } else if (possibleQuantifier=='{') {
+ ++index;
+ minOccurs = 0;
+ while (index < length && (ch = pattern.charAt(index)) >= '0' && ch <= '9') {
+ minOccurs *= 10;
+ minOccurs += (ch - '0');
+ if (minOccurs < 0) throw new MalformedPatternException(SSRBundle.message("error.overflow"));
+ ++index;
+ }
+
+ if (ch==',') {
+ ++index;
+ maxOccurs = 0;
+
+ while (index < length && (ch = pattern.charAt(index)) >= '0' && ch <= '9') {
+ maxOccurs *= 10;
+ maxOccurs += (ch - '0');
+ if (maxOccurs < 0) throw new MalformedPatternException(SSRBundle.message("error.overflow"));
+ ++index;
+ }
+ } else {
+ maxOccurs = Integer.MAX_VALUE;
+ }
+
+ if (ch != '}') {
+ if (maxOccurs == Integer.MAX_VALUE) throw new MalformedPatternException(SSRBundle.message("error.expected.brace1"));
+ else throw new MalformedPatternException(SSRBundle.message("error.expected.brace2"));
+ }
+ ++index;
+ }
+
+ if (index < length) {
+ ch = pattern.charAt(index);
+ if (ch=='?') {
+ greedy = false;
+ ++index;
+ }
+ }
+ }
+
+ if (constraintCreated) {
+ constraint.setMinCount(minOccurs);
+ constraint.setMaxCount(maxOccurs);
+ constraint.setGreedy(greedy);
+ constraint.setPartOfSearchResults(!anonymous);
+ }
+
+ if (index < length && pattern.charAt(index) == ':') {
+ ++index;
+ if (index >= length) throw new MalformedPatternException(SSRBundle.message("error.expected.condition", ':'));
+ ch = pattern.charAt(index);
+ if (ch == ':') {
+ // double colon instead of condition
+ buf.append(ch);
+ }
+ else {
+ index = eatTypedVarCondition(index, pattern, miscBuffer, constraint);
+ }
+ }
+
+ if (constraintCreated) {
+ if (constraint.getWithinConstraint().length() > 0) {
+ constraint.setName(Configuration.CONTEXT_VAR_NAME);
+ }
+ options.addVariableConstraint(constraint);
+ }
+
+ if (index == length) break;
+ // fall through to append white space
+ ch = pattern.charAt(index);
+ }
+ }
+
+ buf.append(ch);
+ }
+
+ options.setSearchPattern( buf.toString() );
+ }
+
+ private static int eatTypedVarCondition(int index,
+ String pattern,
+ StringBuilder miscBuffer,
+ MatchVariableConstraint constraint) {
+ final int length = pattern.length();
+
+ char ch = pattern.charAt(index);
+ if (ch == '+' || ch == '*') {
+ // this is type axis navigation relation
+ switch(ch) {
+ case '+':
+ constraint.setStrictlyWithinHierarchy(true);
+ break;
+ case '*':
+ constraint.setWithinHierarchy(true);
+ break;
+ }
+
+ ++index;
+ if (index >= length) throw new MalformedPatternException(SSRBundle.message("error.expected.condition", ch));
+ ch = pattern.charAt(index);
+ }
+
+ if (ch == '[') {
+ // eat complete condition
+
+ miscBuffer.setLength(0);
+ for(++index; index < length && ((ch = pattern.charAt(index))!=']' || pattern.charAt(index-1)=='\\'); ++index) {
+ miscBuffer.append(ch);
+ }
+ if (ch != ']') throw new MalformedPatternException(SSRBundle.message("error.expected.condition.or.bracket"));
+ ++index;
+ parseCondition(constraint, miscBuffer.toString());
+ } else {
+ // eat reg exp constraint
+ miscBuffer.setLength(0);
+ index = handleRegExp(index, pattern, miscBuffer, constraint);
+ }
+ return index;
+ }
+
+ private static int handleRegExp(int index,
+ String pattern,
+ StringBuilder miscBuffer,
+ MatchVariableConstraint constraint) {
+ char c = pattern.charAt(index - 1);
+ final int length = pattern.length();
+ for(char ch; index < length && !Character.isWhitespace(ch = pattern.charAt(index)); ++index) {
+ miscBuffer.append(ch);
+ }
+
+ if (miscBuffer.length() == 0)
+ if (c == ':') throw new MalformedPatternException(SSRBundle.message("error.expected.condition", c));
+ else return index;
+ String regexp = miscBuffer.toString();
+
+ if (constraint.getRegExp()!=null &&
+ constraint.getRegExp().length() > 0 &&
+ !constraint.getRegExp().equals(regexp)) {
+ throw new MalformedPatternException(SSRBundle.message("error.two.different.type.constraints"));
+ } else {
+ try {
+ Pattern.compile(regexp);
+ } catch (PatternSyntaxException e) {
+ throw new MalformedPatternException(SSRBundle.message("invalid.regular.expression"));
+ }
+ constraint.setRegExp(regexp);
+ }
+
+ return index;
+ }
+
+ private static void parseCondition(MatchVariableConstraint constraint, String condition) {
+ if (condition.isEmpty()) throw new MalformedPatternException(SSRBundle.message("error.expected.condition", "["));
+ final List<String> tokens = StringUtil.split(condition, "&&", true, false);
+
+ for (String token : tokens) {
+ token = token.trim();
+ if (token.isEmpty()) throw new MalformedPatternException(SSRBundle.message("error.expected.condition", "&&"));
+ boolean hasNot = false;
+ boolean consumed = false;
+
+ if (StringUtil.startsWithChar(token, '!')) {
+ token = token.substring(1);
+ if (token.isEmpty()) throw new MalformedPatternException(SSRBundle.message("error.expected.condition", "!"));
+ hasNot = true;
+ }
+
+ Matcher m = p.matcher(token);
+
+ if (m.matches()) {
+ String option = m.group(1);
+
+ if (option.equalsIgnoreCase(REF)) {
+ String name = m.group(2);
+
+ constraint.setReference(true);
+ constraint.setInvertReference(hasNot);
+ constraint.setNameOfReferenceVar(name);
+ consumed = true;
+ }
+ } else {
+ m = p2.matcher(token);
+
+ if (m.matches()) {
+ String option = m.group(1);
+
+ if (option.equalsIgnoreCase(READ)) {
+ constraint.setReadAccess(true);
+ constraint.setInvertReadAccess(hasNot);
+ consumed = true;
+ } else if (option.equalsIgnoreCase(WRITE)) {
+ constraint.setWriteAccess(true);
+ constraint.setInvertWriteAccess(hasNot);
+ consumed = true;
+ }
+ } else {
+ m = p3.matcher(token);
+
+ if (m.matches()) {
+ String option = m.group(1);
+
+ if (option.equalsIgnoreCase(REGEX) || option.equalsIgnoreCase(REGEXW)) {
+ String typePattern = getSingleParameter(m, SSRBundle.message("reg.exp.should.be.delimited.with.spaces.error.message"));
+ if (typePattern.isEmpty()) throw new MalformedPatternException(SSRBundle.message("no.reg.exp.specified.error.message"));
+
+ if (StringUtil.startsWithChar(typePattern, '*')) {
+ typePattern = typePattern.substring(1);
+ constraint.setWithinHierarchy(true);
+ }
+ try {
+ Pattern.compile(typePattern);
+ } catch (PatternSyntaxException e) {
+ throw new MalformedPatternException(SSRBundle.message("invalid.regular.expression"));
+ }
+ constraint.setRegExp( typePattern );
+ constraint.setInvertRegExp( hasNot );
+ consumed = true;
+ if (option.equalsIgnoreCase(REGEXW)) {
+ constraint.setWholeWordsOnly(true);
+ }
+ } else if (option.equalsIgnoreCase(EXPRTYPE)) {
+ String exprTypePattern = getSingleParameter(m, SSRBundle.message(
+ "reg.exp.in.expr.type.should.be.delimited.with.spaces.error.message"));
+
+ if (StringUtil.startsWithChar(exprTypePattern, '*')) {
+ exprTypePattern = exprTypePattern.substring(1);
+ constraint.setExprTypeWithinHierarchy(true);
+ }
+ try {
+ Pattern.compile(exprTypePattern);
+ } catch (PatternSyntaxException e) {
+ throw new MalformedPatternException(SSRBundle.message("invalid.regular.expression"));
+ }
+ constraint.setNameOfExprType( exprTypePattern );
+ constraint.setInvertExprType( hasNot );
+ consumed = true;
+ } else if (option.equalsIgnoreCase(FORMAL)) {
+ String exprTypePattern = getSingleParameter(m, SSRBundle.message(
+ "reg.exp.in.formal.arg.type.should.be.delimited.with.spaces.error.message"));
+
+ if (StringUtil.startsWithChar(exprTypePattern, '*')) {
+ exprTypePattern = exprTypePattern.substring(1);
+ constraint.setFormalArgTypeWithinHierarchy(true);
+ }
+ try {
+ Pattern.compile(exprTypePattern);
+ } catch (PatternSyntaxException e) {
+ throw new MalformedPatternException(SSRBundle.message("invalid.regular.expression"));
+ }
+ constraint.setNameOfFormalArgType( exprTypePattern );
+ constraint.setInvertFormalType( hasNot );
+ consumed = true;
+ } else if (option.equalsIgnoreCase(SCRIPT)) {
+ String script = getSingleParameter(m, SSRBundle.message("script.should.be.delimited.with.spaces.error.message"));
+
+ constraint.setScriptCodeConstraint( script );
+ consumed = true;
+ } else if (option.equalsIgnoreCase(CONTAINS)) {
+ if (hasNot) constraint.setInvertContainsConstraint(true);
+ String script = getSingleParameter(m, SSRBundle.message("script.should.be.delimited.with.spaces.error.message"));
+
+ constraint.setContainsConstraint(script );
+ consumed = true;
+ } else if (option.equalsIgnoreCase(WITHIN)) {
+ if (hasNot) constraint.setInvertWithinConstraint(true);
+ String script = getSingleParameter(m, SSRBundle.message("script.should.be.delimited.with.spaces.error.message"));
+
+ constraint.setWithinConstraint(script);
+ consumed = true;
+ }
+ }
+ }
+ }
+
+ if (!consumed) {
+ throw new UnsupportedPatternException(
+ SSRBundle.message("option.is.not.recognized.error.message", token)
+ );
+ }
+ }
+ }
+
+ private static String getSingleParameter(Matcher m, String errorMessage) {
+ final String value = m.group(2);
+ if (value.isEmpty()) return value;
+
+ if (value.charAt(0)!=' ' || value.charAt(value.length()-1)!=' ') {
+ throw new MalformedPatternException(errorMessage);
+ }
+ return value.trim();
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/TestModeOptimizingSearchHelper.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/TestModeOptimizingSearchHelper.java
new file mode 100644
index 000000000000..e341b57da8ce
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/TestModeOptimizingSearchHelper.java
@@ -0,0 +1,70 @@
+package com.intellij.structuralsearch.impl.matcher.compiler;
+
+import com.intellij.psi.PsiFile;
+
+import java.util.Collections;
+import java.util.Set;
+
+/**
+ * @author Maxim.Mossienko
+ */
+public class TestModeOptimizingSearchHelper extends OptimizingSearchHelperBase {
+ private static String lastString;
+ private final StringBuilder builder = new StringBuilder();
+ private int lastLength;
+
+ TestModeOptimizingSearchHelper(CompileContext _context) {
+ super(_context);
+ }
+
+ public boolean doOptimizing() {
+ return true;
+ }
+
+ public void clear() {
+ lastString = builder.toString();
+ builder.setLength(0);
+ lastLength = 0;
+ }
+
+ protected void doAddSearchWordInCode(final String refname) {
+ append(refname, "reserved in code:");
+ }
+
+ @Override
+ protected void doAddSearchWordInText(String refname) {
+ append(refname, "in text:");
+ }
+
+ private void append(final String refname, final String str) {
+ if (builder.length() == lastLength) builder.append("[");
+ else builder.append("|");
+ builder.append(str).append(refname);
+ }
+
+ protected void doAddSearchWordInComments(final String refname) {
+ append(refname, "in comments:");
+ }
+
+ protected void doAddSearchWordInLiterals(final String refname) {
+ append(refname, "in literals:");
+ }
+
+ public void endTransaction() {
+ super.endTransaction();
+ builder.append("]");
+ lastLength = builder.length();
+ }
+
+ public boolean isScannedSomething() {
+ return false;
+ }
+
+ public Set<PsiFile> getFilesSetToScan() {
+ return Collections.emptySet();
+ }
+
+ public String getSearchPlan() {
+ return lastString;
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/XmlCompilingVisitor.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/XmlCompilingVisitor.java
new file mode 100644
index 000000000000..99917ad23240
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/XmlCompilingVisitor.java
@@ -0,0 +1,53 @@
+package com.intellij.structuralsearch.impl.matcher.compiler;
+
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.XmlRecursiveElementVisitor;
+import com.intellij.psi.xml.XmlTag;
+import com.intellij.psi.xml.XmlText;
+import com.intellij.psi.xml.XmlToken;
+import com.intellij.structuralsearch.impl.matcher.filters.TagValueFilter;
+import com.intellij.structuralsearch.impl.matcher.handlers.MatchingHandler;
+import com.intellij.structuralsearch.impl.matcher.handlers.TopLevelMatchingHandler;
+import com.intellij.structuralsearch.impl.matcher.handlers.XmlTextHandler;
+import com.intellij.structuralsearch.impl.matcher.strategies.XmlMatchingStrategy;
+
+/**
+* @author Eugene.Kudelevsky
+*/
+public class XmlCompilingVisitor extends XmlRecursiveElementVisitor {
+ private final GlobalCompilingVisitor myCompilingVisitor;
+
+ public XmlCompilingVisitor(GlobalCompilingVisitor compilingVisitor) {
+ this.myCompilingVisitor = compilingVisitor;
+ }
+
+ @Override public void visitElement(PsiElement element) {
+ myCompilingVisitor.handle(element);
+ super.visitElement(element);
+ }
+
+ @Override public void visitXmlToken(XmlToken token) {
+ super.visitXmlToken(token);
+
+ if (token.getParent() instanceof XmlText && myCompilingVisitor.getContext().getPattern().isRealTypedVar(token)) {
+ final MatchingHandler handler = myCompilingVisitor.getContext().getPattern().getHandler(token);
+ handler.setFilter(TagValueFilter.getInstance());
+
+ final XmlTextHandler parentHandler = new XmlTextHandler();
+ myCompilingVisitor.getContext().getPattern().setHandler(token.getParent(), parentHandler);
+ parentHandler.setFilter(TagValueFilter.getInstance());
+ }
+ }
+
+ @Override public void visitXmlTag(XmlTag xmlTag) {
+ myCompilingVisitor.setCodeBlockLevel(myCompilingVisitor.getCodeBlockLevel() + 1);
+ super.visitXmlTag(xmlTag);
+ myCompilingVisitor.setCodeBlockLevel(myCompilingVisitor.getCodeBlockLevel() - 1);
+
+ if (myCompilingVisitor.getCodeBlockLevel() == 1) {
+ myCompilingVisitor.getContext().getPattern().setStrategy(XmlMatchingStrategy.getInstance());
+ myCompilingVisitor.getContext().getPattern()
+ .setHandler(xmlTag, new TopLevelMatchingHandler(myCompilingVisitor.getContext().getPattern().getHandler(xmlTag)));
+ }
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/filters/CompositeFilter.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/filters/CompositeFilter.java
new file mode 100644
index 000000000000..db962ed8edf1
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/filters/CompositeFilter.java
@@ -0,0 +1,27 @@
+package com.intellij.structuralsearch.impl.matcher.filters;
+
+import com.intellij.dupLocator.util.NodeFilter;
+import com.intellij.psi.PsiElement;
+
+/**
+ * Created by IntelliJ IDEA.
+ * User: maxim
+ * Date: 28.12.2003
+ * Time: 0:13:19
+ * To change this template use Options | File Templates.
+ */
+public class CompositeFilter implements NodeFilter {
+ private final NodeFilter first;
+ private final NodeFilter second;
+ protected boolean result;
+
+ public boolean accepts(PsiElement element) {
+ return first.accepts(element) ||
+ second.accepts(element);
+ }
+
+ public CompositeFilter(NodeFilter _first, NodeFilter _second) {
+ first = _first;
+ second = _second;
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/filters/DefaultFilter.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/filters/DefaultFilter.java
new file mode 100644
index 000000000000..267f37699945
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/filters/DefaultFilter.java
@@ -0,0 +1,16 @@
+package com.intellij.structuralsearch.impl.matcher.filters;
+
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.impl.source.tree.LeafElement;
+
+/**
+ * Default searching filter
+ */
+public class DefaultFilter {
+ public static boolean accepts(PsiElement element, PsiElement element2) {
+ if (element instanceof LeafElement && element2 instanceof LeafElement) {
+ return ((LeafElement)element).getElementType() == ((LeafElement)element2).getElementType();
+ }
+ return element.getClass()==element2.getClass();
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/filters/LexicalNodesFilter.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/filters/LexicalNodesFilter.java
new file mode 100644
index 000000000000..3cef402b09d3
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/filters/LexicalNodesFilter.java
@@ -0,0 +1,51 @@
+package com.intellij.structuralsearch.impl.matcher.filters;
+
+import com.intellij.dupLocator.util.NodeFilter;
+import com.intellij.psi.PsiElement;
+import com.intellij.structuralsearch.StructuralSearchProfile;
+import com.intellij.structuralsearch.StructuralSearchUtil;
+
+/**
+ * Filter for lexical nodes
+ */
+public final class LexicalNodesFilter implements NodeFilter {
+ private boolean careKeyWords;
+ private boolean result;
+
+ private LexicalNodesFilter() {}
+
+ public static NodeFilter getInstance() {
+ return NodeFilterHolder.instance;
+ }
+
+ public boolean getResult() {
+ return result;
+ }
+
+ public void setResult(boolean result) {
+ this.result = result;
+ }
+
+ private static class NodeFilterHolder {
+ private static final NodeFilter instance = new LexicalNodesFilter();
+ }
+
+ public boolean isCareKeyWords() {
+ return careKeyWords;
+ }
+
+ public void setCareKeyWords(boolean careKeyWords) {
+ this.careKeyWords = careKeyWords;
+ }
+
+ public boolean accepts(PsiElement element) {
+ result = false;
+ if (element!=null) {
+ final StructuralSearchProfile profile = StructuralSearchUtil.getProfileByPsiElement(element);
+ if (profile != null) {
+ element.accept(profile.getLexicalNodesFilter(this));
+ }
+ }
+ return result;
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/filters/TagValueFilter.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/filters/TagValueFilter.java
new file mode 100644
index 000000000000..97ed9627e3fc
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/filters/TagValueFilter.java
@@ -0,0 +1,43 @@
+package com.intellij.structuralsearch.impl.matcher.filters;
+
+import com.intellij.dupLocator.util.NodeFilter;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.XmlElementVisitor;
+import com.intellij.psi.xml.XmlTag;
+import com.intellij.psi.xml.XmlText;
+
+/**
+ * Created by IntelliJ IDEA.
+ * User: maxim.mossienko
+ * Date: Oct 12, 2005
+ * Time: 4:44:19 PM
+ * To change this template use File | Settings | File Templates.
+ */
+public class TagValueFilter extends XmlElementVisitor implements NodeFilter {
+ private boolean result;
+
+ @Override public void visitXmlText(XmlText text) {
+ result = true;
+ }
+
+ @Override public void visitXmlTag(XmlTag tag) {
+ result = true;
+ }
+
+ private static class NodeFilterHolder {
+ private static final NodeFilter instance = new TagValueFilter();
+ }
+
+ public static NodeFilter getInstance() {
+ return NodeFilterHolder.instance;
+ }
+
+ private TagValueFilter() {
+ }
+
+ public boolean accepts(PsiElement element) {
+ result = false;
+ if (element!=null) element.accept(this);
+ return result;
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/filters/XmlLexicalNodesFilter.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/filters/XmlLexicalNodesFilter.java
new file mode 100644
index 000000000000..89c295ce1d80
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/filters/XmlLexicalNodesFilter.java
@@ -0,0 +1,19 @@
+package com.intellij.structuralsearch.impl.matcher.filters;
+
+import com.intellij.psi.XmlElementVisitor;
+import com.intellij.psi.xml.XmlText;
+
+/**
+* @author Eugene.Kudelevsky
+*/
+public class XmlLexicalNodesFilter extends XmlElementVisitor {
+ private final LexicalNodesFilter myLexicalNodesFilter;
+
+ public XmlLexicalNodesFilter(LexicalNodesFilter lexicalNodesFilter) {
+ this.myLexicalNodesFilter = lexicalNodesFilter;
+ }
+
+ @Override public void visitXmlText(XmlText text) {
+ myLexicalNodesFilter.setResult(true);
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/DelegatingHandler.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/DelegatingHandler.java
new file mode 100644
index 000000000000..c95a85623753
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/DelegatingHandler.java
@@ -0,0 +1,8 @@
+package com.intellij.structuralsearch.impl.matcher.handlers;
+
+/**
+ * @author Eugene.Kudelevsky
+ */
+public interface DelegatingHandler {
+ MatchingHandler getDelegate();
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/LightTopLevelMatchingHandler.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/LightTopLevelMatchingHandler.java
new file mode 100644
index 000000000000..34941cfa0257
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/LightTopLevelMatchingHandler.java
@@ -0,0 +1,46 @@
+package com.intellij.structuralsearch.impl.matcher.handlers;
+
+import com.intellij.psi.PsiElement;
+import com.intellij.structuralsearch.impl.matcher.MatchContext;
+import com.intellij.dupLocator.iterators.NodeIterator;
+import org.jetbrains.annotations.NotNull;
+
+public final class LightTopLevelMatchingHandler extends MatchingHandler implements DelegatingHandler {
+ private final MatchingHandler myDelegate;
+
+ public LightTopLevelMatchingHandler(@NotNull MatchingHandler delegate) {
+ myDelegate = delegate;
+ }
+
+ public boolean match(final PsiElement patternNode, final PsiElement matchedNode, final MatchContext matchContext) {
+ return myDelegate.match(patternNode, matchedNode, matchContext);
+ }
+
+ @Override
+ public boolean canMatch(PsiElement patternNode, PsiElement matchedNode) {
+ return myDelegate.canMatch(patternNode, matchedNode);
+ }
+
+ @Override
+ public boolean matchSequentially(final NodeIterator nodes, final NodeIterator nodes2, final MatchContext context) {
+ return myDelegate.matchSequentially(nodes, nodes2, context);
+ }
+
+ public boolean match(final PsiElement patternNode,
+ final PsiElement matchedNode, final int start, final int end, final MatchContext context) {
+ return myDelegate.match(patternNode, matchedNode, start, end, context);
+ }
+
+ public boolean isMatchSequentiallySucceeded(final NodeIterator nodes2) {
+ return true;
+ }
+
+ @Override
+ public boolean shouldAdvanceTheMatchFor(final PsiElement patternElement, final PsiElement matchedElement) {
+ return myDelegate.shouldAdvanceTheMatchFor(patternElement, matchedElement);
+ }
+
+ public MatchingHandler getDelegate() {
+ return myDelegate;
+ }
+} \ No newline at end of file
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/LiteralWithSubstitutionHandler.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/LiteralWithSubstitutionHandler.java
new file mode 100644
index 000000000000..78967106755f
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/LiteralWithSubstitutionHandler.java
@@ -0,0 +1,49 @@
+package com.intellij.structuralsearch.impl.matcher.handlers;
+
+import com.intellij.psi.PsiElement;
+import com.intellij.structuralsearch.impl.matcher.MatchContext;
+import com.intellij.structuralsearch.impl.matcher.predicates.RegExpPredicate;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.List;
+
+/**
+ * Created by IntelliJ IDEA.
+ * User: Maxim.Mossienko
+ * Date: Jun 30, 2004
+ * Time: 5:07:33 PM
+ * To change this template use File | Settings | File Templates.
+ */
+public class LiteralWithSubstitutionHandler extends MatchingHandler {
+ private final String matchExpression;
+ private Matcher matcher;
+ private final List<SubstitutionHandler> handlers;
+
+ public LiteralWithSubstitutionHandler(String _matchedExpression,List<SubstitutionHandler> _handlers) {
+ matchExpression = _matchedExpression;
+ handlers = _handlers;
+ }
+
+ public boolean match(PsiElement patternNode, PsiElement matchedNode, MatchContext context) {
+ final String text = RegExpPredicate.getMeaningfulText(matchedNode);
+ int offset = matchedNode.getText().indexOf(text);
+ if (matcher==null) {
+ matcher = Pattern.compile(matchExpression).matcher(text);
+ } else {
+ matcher.reset(text);
+ }
+
+ while (matcher.find()) {
+ for (int i = 0; i < handlers.size(); ++i) {
+ SubstitutionHandler handler = handlers.get(i);
+
+ if (!handler.handle(matchedNode,offset + matcher.start(i+1), offset + matcher.end(i+1),context)) {
+ return false;
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/MatchPredicate.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/MatchPredicate.java
new file mode 100644
index 000000000000..cba96681ab71
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/MatchPredicate.java
@@ -0,0 +1,27 @@
+package com.intellij.structuralsearch.impl.matcher.handlers;
+
+import com.intellij.psi.PsiElement;
+import com.intellij.structuralsearch.impl.matcher.MatchContext;
+
+/**
+ * Root of handlers for pattern node matching. Handles simpliest type of the match.
+ */
+public abstract class MatchPredicate {
+ /**
+ * Matches given handler node against given value.
+ * @param matchedNode for matching
+ * @param context of the matching
+ * @return true if matching was successfull and false otherwise
+ */
+ public boolean match(PsiElement patternNode,PsiElement matchedNode, int start, int end, MatchContext context) {
+ return match(patternNode,matchedNode,context);
+ }
+
+ /**
+ * Matches given handler node against given value.
+ * @param matchedNode for matching
+ * @param context of the matching
+ * @return true if matching was successfull and false otherwise
+ */
+ public abstract boolean match(PsiElement patternNode,PsiElement matchedNode, MatchContext context);
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/MatchingHandler.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/MatchingHandler.java
new file mode 100644
index 000000000000..c53b08412b6b
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/MatchingHandler.java
@@ -0,0 +1,271 @@
+package com.intellij.structuralsearch.impl.matcher.handlers;
+
+import com.intellij.dupLocator.iterators.NodeIterator;
+import com.intellij.dupLocator.util.NodeFilter;
+import com.intellij.psi.*;
+import com.intellij.structuralsearch.StructuralSearchUtil;
+import com.intellij.structuralsearch.impl.matcher.CompiledPattern;
+import com.intellij.structuralsearch.impl.matcher.MatchContext;
+import com.intellij.structuralsearch.impl.matcher.MatchResultImpl;
+import com.intellij.structuralsearch.impl.matcher.filters.DefaultFilter;
+import com.intellij.structuralsearch.impl.matcher.predicates.BinaryPredicate;
+import com.intellij.structuralsearch.impl.matcher.predicates.NotPredicate;
+import com.intellij.structuralsearch.impl.matcher.predicates.RegExpPredicate;
+import com.intellij.structuralsearch.impl.matcher.strategies.MatchingStrategy;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Root of handlers for pattern node matching. Handles simplest type of the match.
+ */
+public abstract class MatchingHandler extends MatchPredicate {
+ protected NodeFilter filter;
+ private PsiElement pinnedElement;
+
+ public void setFilter(NodeFilter filter) {
+ this.filter = filter;
+ }
+
+ /**
+ * Matches given handler node against given value.
+ * @param matchedNode for matching
+ * @param context of the matching
+ * @return true if matching was successful and false otherwise
+ */
+ public boolean match(PsiElement patternNode,PsiElement matchedNode, int start, int end, MatchContext context) {
+ return match(patternNode,matchedNode,context);
+ }
+
+ /**
+ * Matches given handler node against given value.
+ * @param matchedNode for matching
+ * @param context of the matching
+ * @return true if matching was successful and false otherwise
+ */
+ public boolean match(PsiElement patternNode,PsiElement matchedNode, MatchContext context) {
+ if (patternNode == null) {
+ return matchedNode == null;
+ }
+
+ return canMatch(patternNode, matchedNode);
+ }
+
+ public boolean canMatch(final PsiElement patternNode, final PsiElement matchedNode) {
+ if (filter!=null) {
+ return filter.accepts(matchedNode);
+ } else {
+ return DefaultFilter.accepts(patternNode, matchedNode);
+ }
+ }
+
+ public boolean matchSequentially(NodeIterator nodes, NodeIterator nodes2, MatchContext context) {
+ PsiElement patternElement;
+ MatchingHandler handler;
+ MatchingStrategy strategy = context.getPattern().getStrategy();
+
+ skipIfNecessary(nodes, nodes2, strategy);
+ skipIfNecessary(nodes2, nodes, strategy);
+
+ if (nodes2.hasNext() &&
+ (handler = context.getPattern().getHandler(nodes.current())).match(patternElement = nodes.current(),nodes2.current(),context)) {
+
+ nodes.advance();
+
+ if (shouldAdvanceTheMatchFor(patternElement, nodes2.current())) {
+ nodes2.advance();
+ skipIfNecessary(nodes, nodes2, strategy);
+ }
+ skipIfNecessary(nodes2, nodes, strategy);
+
+ if (nodes.hasNext()) {
+ final MatchingHandler nextHandler = context.getPattern().getHandler(nodes.current());
+
+ if (nextHandler.matchSequentially(nodes,nodes2,context)) {
+ // match was found!
+ return true;
+ } else {
+ // rewind, we was not able to match descendants
+ nodes.rewind();
+ nodes2.rewind();
+ }
+ } else {
+ // match was found
+ return handler.isMatchSequentiallySucceeded(nodes2);
+ }
+ }
+ return false;
+ }
+
+ private static void skipIfNecessary(NodeIterator nodes, NodeIterator nodes2, MatchingStrategy strategy) {
+ while (strategy.shouldSkip(nodes2.current(), nodes.current())) {
+ nodes2.advance();
+ }
+ }
+
+ protected boolean isMatchSequentiallySucceeded(final NodeIterator nodes2) {
+ return !nodes2.hasNext();
+ }
+
+ private static MatchPredicate findRegExpPredicate(MatchPredicate start) {
+ if (start==null) return null;
+ if (start instanceof RegExpPredicate) return start;
+
+ if(start instanceof BinaryPredicate) {
+ BinaryPredicate binary = (BinaryPredicate)start;
+ final MatchPredicate result = findRegExpPredicate(binary.getFirst());
+ if (result!=null) return result;
+
+ return findRegExpPredicate(binary.getSecond());
+ } else if (start instanceof NotPredicate) {
+ return null;
+ }
+ return null;
+ }
+
+ public static RegExpPredicate getSimpleRegExpPredicate(SubstitutionHandler handler) {
+ if (handler == null) return null;
+ return (RegExpPredicate)findRegExpPredicate(handler.getPredicate());
+ }
+
+ static class ClearStateVisitor extends PsiRecursiveElementWalkingVisitor {
+ private CompiledPattern pattern;
+
+ ClearStateVisitor() {
+ super(true);
+ }
+
+ @Override public void visitElement(PsiElement element) {
+ // We do not reset certain handlers because they are also bound to higher level nodes
+ // e.g. Identifier handler in name is also bound to PsiMethod
+ if (pattern.isToResetHandler(element)) {
+ MatchingHandler handler = pattern.getHandlerSimple(element);
+ if (handler instanceof SubstitutionHandler) {
+ handler.reset();
+ }
+ }
+ super.visitElement(element);
+ }
+
+ synchronized void clearState(CompiledPattern _pattern, PsiElement el) {
+ pattern = _pattern;
+ el.acceptChildren(this);
+ pattern = null;
+ }
+ }
+
+ protected static ClearStateVisitor clearingVisitor = new ClearStateVisitor();
+
+ public boolean matchInAnyOrder(NodeIterator patternNodes, NodeIterator matchedNodes, final MatchContext context) {
+ final MatchResultImpl saveResult = context.hasResult() ? context.getResult() : null;
+ context.setResult(null);
+
+ try {
+
+ if (patternNodes.hasNext() && !matchedNodes.hasNext()) {
+ return validateSatisfactionOfHandlers(patternNodes, context);
+ }
+
+ Set<PsiElement> matchedElements = null;
+
+ for(; patternNodes.hasNext(); patternNodes.advance()) {
+ final PsiElement patternNode = patternNodes.current();
+ final CompiledPattern pattern = context.getPattern();
+ final MatchingHandler handler = pattern.getHandler(patternNode);
+
+ final PsiElement startMatching = matchedNodes.current();
+ do {
+ final PsiElement element = handler.getPinnedNode(null);
+ final PsiElement matchedNode = element != null ? element : matchedNodes.current();
+
+ if (element == null) matchedNodes.advance();
+ if (!matchedNodes.hasNext()) matchedNodes.reset();
+
+ if (matchedElements == null || !matchedElements.contains(matchedNode)) {
+
+ if (handler.match(patternNode, matchedNode, context)) {
+ if (matchedElements == null) matchedElements = new HashSet<PsiElement>();
+ matchedElements.add(matchedNode);
+ if (handler.shouldAdvanceThePatternFor(patternNode, matchedNode)) {
+ break;
+ }
+ } else if (element != null) {
+ return false;
+ }
+
+ // clear state of dependent objects
+ clearingVisitor.clearState(pattern, patternNode);
+ }
+
+ // passed of elements and does not found the match
+ if (startMatching == matchedNodes.current()) {
+ final boolean result = validateSatisfactionOfHandlers(patternNodes,context);
+ if (result && context.getMatchedElementsListener() != null) {
+ context.getMatchedElementsListener().matchedElements(matchedElements);
+ }
+ return result;
+ }
+ } while(true);
+
+ if (!handler.shouldAdvanceThePatternFor(patternNode, null)) {
+ patternNodes.rewind();
+ }
+ }
+
+ final boolean result = validateSatisfactionOfHandlers(patternNodes, context);
+ if (result && context.getMatchedElementsListener() != null) {
+ context.getMatchedElementsListener().matchedElements(matchedElements);
+ }
+ return result;
+ } finally {
+ if (saveResult!=null) {
+ if (context.hasResult()) {
+ saveResult.getMatches().addAll(context.getResult().getMatches());
+ }
+ context.setResult(saveResult);
+ }
+ }
+ }
+
+ protected static boolean validateSatisfactionOfHandlers(NodeIterator nodes, MatchContext context) {
+ while(nodes.hasNext()) {
+ final PsiElement element = nodes.current();
+ final MatchingHandler handler = context.getPattern().getHandler( element );
+
+ if (handler instanceof SubstitutionHandler) {
+ if (!((SubstitutionHandler)handler).validate(
+ context, StructuralSearchUtil.getProfileByPsiElement(element).getElementContextByPsi(element))) {
+ return false;
+ }
+ } else {
+ return false;
+ }
+ nodes.advance();
+ }
+ return true;
+ }
+
+ public NodeFilter getFilter() {
+ return filter;
+ }
+
+ public boolean shouldAdvanceThePatternFor(PsiElement patternElement, PsiElement matchedElement) {
+ return true;
+ }
+
+ public boolean shouldAdvanceTheMatchFor(PsiElement patternElement, PsiElement matchedElement) {
+ return true;
+ }
+
+ public void reset() {
+ //pinnedElement = null;
+ }
+
+ public PsiElement getPinnedNode(PsiElement context) {
+ return pinnedElement;
+ }
+
+ public void setPinnedElement(final PsiElement pinnedElement) {
+ this.pinnedElement = pinnedElement;
+ }
+} \ No newline at end of file
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/SimpleHandler.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/SimpleHandler.java
new file mode 100644
index 000000000000..3dffe792e308
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/SimpleHandler.java
@@ -0,0 +1,20 @@
+package com.intellij.structuralsearch.impl.matcher.handlers;
+
+import com.intellij.psi.PsiElement;
+import com.intellij.structuralsearch.impl.matcher.MatchContext;
+
+/**
+ * Root of handlers for pattern node matching. Handles simpliest type of the match.
+ */
+public final class SimpleHandler extends MatchingHandler {
+ /**
+ * Matches given handler node against given value.
+ * @param matchedNode for matching
+ * @param context of the matching
+ * @return true if matching was successfull and false otherwise
+ */
+ public boolean match(PsiElement patternNode,PsiElement matchedNode, MatchContext context) {
+ if (!super.match(patternNode,matchedNode,context)) return false;
+ return context.getMatcher().match(patternNode,matchedNode);
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/SkippingHandler.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/SkippingHandler.java
new file mode 100644
index 000000000000..389c4a7916c2
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/SkippingHandler.java
@@ -0,0 +1,108 @@
+package com.intellij.structuralsearch.impl.matcher.handlers;
+
+import com.intellij.dupLocator.equivalence.EquivalenceDescriptor;
+import com.intellij.dupLocator.iterators.NodeIterator;
+import com.intellij.dupLocator.util.DuplocatorUtil;
+import com.intellij.dupLocator.util.NodeFilter;
+import com.intellij.psi.PsiElement;
+import com.intellij.structuralsearch.impl.matcher.MatchContext;
+import com.intellij.structuralsearch.impl.matcher.filters.LexicalNodesFilter;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * @author Eugene.Kudelevsky
+ */
+public class SkippingHandler extends MatchingHandler implements DelegatingHandler {
+
+ private final MatchingHandler myDelegate;
+
+ public SkippingHandler(@NotNull MatchingHandler delegate) {
+ myDelegate = delegate;
+ }
+
+ public boolean match(PsiElement patternNode, PsiElement matchedNode, final MatchContext matchContext) {
+ if (patternNode == null || matchedNode == null || matchedNode.getClass() == patternNode.getClass()) {
+ return myDelegate.match(patternNode, matchedNode, matchContext);
+ }
+
+ /*if (patternNode != null && matchedNode != null && patternNode.getClass() == matchedNode.getClass()) {
+ //return myDelegate.match(patternNode, matchedNode, matchContext);
+ }*/
+ PsiElement newPatternNode = skipNodeIfNeccessary(patternNode);
+ matchedNode = skipNodeIfNeccessary(matchedNode);
+
+ if (newPatternNode != patternNode) {
+ return matchContext.getPattern().getHandler(newPatternNode).match(newPatternNode, matchedNode, matchContext);
+ }
+
+ return myDelegate.match(patternNode, matchedNode, matchContext);
+ }
+
+ @Override
+ public boolean canMatch(PsiElement patternNode, PsiElement matchedNode) {
+ return myDelegate.canMatch(patternNode, matchedNode);
+ }
+
+ @Override
+ public boolean matchSequentially(final NodeIterator nodes, final NodeIterator nodes2, final MatchContext context) {
+ return myDelegate.matchSequentially(nodes, nodes2, context);
+ }
+
+ public boolean match(PsiElement patternNode,
+ PsiElement matchedNode,
+ final int start,
+ final int end,
+ final MatchContext context) {
+ if (patternNode == null || matchedNode == null || patternNode.getClass() == matchedNode.getClass()) {
+ return myDelegate.match(patternNode, matchedNode, start, end, context);
+ }
+
+ PsiElement newPatternNode = skipNodeIfNeccessary(patternNode);
+ matchedNode = skipNodeIfNeccessary(matchedNode);
+
+ if (newPatternNode != patternNode) {
+ return context.getPattern().getHandler(newPatternNode).match(newPatternNode, matchedNode, start, end, context);
+ }
+
+ return myDelegate.match(patternNode, matchedNode, start, end, context);
+ }
+
+ protected boolean isMatchSequentiallySucceeded(final NodeIterator nodes2) {
+ return myDelegate.isMatchSequentiallySucceeded(nodes2);
+ }
+
+ @Override
+ public boolean shouldAdvanceTheMatchFor(PsiElement patternElement, PsiElement matchedElement) {
+ return true;
+ }
+
+ public MatchingHandler getDelegate() {
+ return myDelegate;
+ }
+
+ @Nullable
+ public static PsiElement getOnlyNonWhitespaceChild(PsiElement element) {
+ PsiElement onlyChild = null;
+ for (PsiElement child = element.getFirstChild(); child != null; child = child.getNextSibling()) {
+ if (DuplocatorUtil.isIgnoredNode(element) || child.getTextLength() == 0) {
+ continue;
+ }
+ if (onlyChild != null) {
+ return null;
+ }
+ onlyChild = child;
+ }
+ return onlyChild;
+ }
+
+ @Nullable
+ public static PsiElement skipNodeIfNeccessary(PsiElement element) {
+ return skipNodeIfNeccessary(element, null, null);
+ }
+
+ @Nullable
+ public static PsiElement skipNodeIfNeccessary(PsiElement element, EquivalenceDescriptor descriptor, NodeFilter filter) {
+ return DuplocatorUtil.skipNodeIfNeccessary(element, descriptor, filter != null ? filter : LexicalNodesFilter.getInstance());
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/SubstitutionHandler.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/SubstitutionHandler.java
new file mode 100644
index 000000000000..0c84e3bb2436
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/SubstitutionHandler.java
@@ -0,0 +1,557 @@
+package com.intellij.structuralsearch.impl.matcher.handlers;
+
+import com.intellij.dupLocator.iterators.FilteringNodeIterator;
+import com.intellij.dupLocator.iterators.NodeIterator;
+import com.intellij.dupLocator.util.NodeFilter;
+import com.intellij.psi.PsiElement;
+import com.intellij.structuralsearch.MatchResult;
+import com.intellij.structuralsearch.StructuralSearchProfile;
+import com.intellij.structuralsearch.StructuralSearchUtil;
+import com.intellij.structuralsearch.impl.matcher.CompiledPattern;
+import com.intellij.structuralsearch.impl.matcher.MatchContext;
+import com.intellij.structuralsearch.impl.matcher.MatchResultImpl;
+import com.intellij.structuralsearch.plugin.ui.Configuration;
+import com.intellij.structuralsearch.plugin.util.SmartPsiPointer;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Matching handler that manages substitutions matching
+ */
+public class SubstitutionHandler extends MatchingHandler {
+ private final String name;
+ private final int maxOccurs;
+ private final int minOccurs;
+ private final boolean greedy;
+ private boolean target;
+ private MatchPredicate predicate;
+ private MatchingHandler matchHandler;
+ private boolean subtype;
+ private boolean strictSubtype;
+ // matchedOccurs + 1 = number of item being matched
+ private int matchedOccurs;
+ private int totalMatchedOccurs = -1;
+ private MatchResultImpl myNestedResult;
+
+ private static final NodeFilter VARS_DELIM_FILTER = new NodeFilter() {
+ @Override
+ public boolean accepts(PsiElement element) {
+ if (element == null) {
+ return false;
+ }
+
+ final StructuralSearchProfile profile = StructuralSearchUtil.getProfileByPsiElement(element);
+ if (profile == null) {
+ return false;
+ }
+
+ return profile.canBeVarDelimeter(element);
+ }
+ };
+
+ public SubstitutionHandler(final String name, final boolean target, int minOccurs,
+ int maxOccurs, boolean greedy) {
+ this.name = name;
+ this.maxOccurs = maxOccurs;
+ this.minOccurs = minOccurs;
+ this.target = target;
+ this.greedy = greedy;
+ }
+
+ public SubstitutionHandler(final SubstitutionHandler substitutionHandler) {
+ this(substitutionHandler.getName(),substitutionHandler.isTarget(), substitutionHandler.getMinOccurs(),
+ substitutionHandler.getMaxOccurs(), substitutionHandler.greedy);
+ }
+
+ public boolean isSubtype() {
+ return subtype;
+ }
+
+ public boolean isStrictSubtype() {
+ return strictSubtype;
+ }
+
+ public void setStrictSubtype(boolean strictSubtype) {
+ this.strictSubtype = strictSubtype;
+ }
+
+ public void setSubtype(boolean subtype) {
+ this.subtype = subtype;
+ }
+
+ public void setPredicate(MatchPredicate handler) {
+ predicate = handler;
+ }
+
+ // Matcher
+
+ public MatchPredicate getPredicate() {
+ return predicate;
+ }
+
+ private static boolean validateOneMatch(final PsiElement match, int start, int end, final MatchResultImpl result, final MatchContext matchContext) {
+ final boolean matchresult;
+
+ if (match!=null) {
+ if (start==0 && end==-1 && result.getStart()==0 && result.getEnd()==-1) {
+ matchresult = matchContext.getMatcher().match(match,result.getMatchRef().getElement());
+ } else {
+ matchresult = StructuralSearchUtil.getProfileByPsiElement(match).getText(match, start, end).equals(
+ result.getMatchImage()
+ );
+ }
+ } else {
+ matchresult = result.isMatchImageNull();
+ }
+
+ return matchresult;
+ }
+
+ public boolean validate(final PsiElement match, int start, int end, MatchContext context) {
+ if (predicate!=null) {
+ if(!predicate.match(null,match,start,end,context)) return false;
+ }
+
+ if (maxOccurs==0) {
+ totalMatchedOccurs++;
+ return false;
+ }
+
+ MatchResultImpl result = context.getResult().findSon(name);
+
+ if (result == null && context.getPreviousResult() != null) {
+ result = context.getPreviousResult().findSon(name);
+ }
+
+ if (result!=null) {
+ if (minOccurs == 1 && maxOccurs == 1) {
+ // check if they are the same
+ return validateOneMatch(match, start, end, result,context);
+ } else if (maxOccurs > 1 && totalMatchedOccurs!=-1) {
+ final int size = result.getAllSons().size();
+ if (matchedOccurs >= size) {
+ return false;
+ }
+ result = size == 0 ?result:(MatchResultImpl)result.getAllSons().get(matchedOccurs);
+ // check if they are the same
+ return validateOneMatch(match, start, end, result, context);
+ }
+ }
+
+ return true;
+ }
+
+ public boolean match(final PsiElement node, final PsiElement match, MatchContext context) {
+ if (!super.match(node,match,context)) return false;
+
+ return matchHandler == null ?
+ context.getMatcher().match(node, match):
+ matchHandler.match(node,match,context);
+ }
+
+ public boolean handle(final PsiElement match, MatchContext context) {
+ return handle(match,0,-1,context);
+ }
+
+ public void addResult(PsiElement match,int start, int end,MatchContext context) {
+ if (totalMatchedOccurs == -1) {
+ final MatchResultImpl matchResult = context.getResult();
+ final MatchResultImpl substitution = matchResult.findSon(name);
+
+ if (substitution == null) {
+ matchResult.addSon( createMatch(match,start,end) );
+ } else if (maxOccurs > 1) {
+ final MatchResultImpl result = createMatch(match,start,end);
+
+ if (!substitution.isMultipleMatch()) {
+ // adding intermediate node to contain all multiple matches
+ MatchResultImpl sonresult = new MatchResultImpl(
+ substitution.getName(),
+ substitution.getMatchImage(),
+ substitution.getMatchRef(),
+ substitution.getStart(),
+ substitution.getEnd(),
+ target
+ );
+
+ sonresult.setParent(substitution);
+ substitution.setMatchRef(
+ new SmartPsiPointer(match == null ? null : match)
+ );
+
+ substitution.setMultipleMatch(true);
+
+ if (substitution.isScopeMatch()) {
+ substitution.setScopeMatch(false);
+ sonresult.setScopeMatch(true);
+ for(MatchResult r:substitution.getAllSons()) sonresult.addSon((MatchResultImpl)r);
+ substitution.clearMatches();
+ }
+
+ substitution.addSon( sonresult);
+ }
+
+ result.setParent(substitution);
+ substitution.addSon( result );
+ }
+ }
+ }
+
+ public boolean handle(final PsiElement match, int start, int end, MatchContext context) {
+ if (!validate(match,start,end,context)) {
+ myNestedResult = null;
+
+ //if (maxOccurs==1 && minOccurs==1) {
+ // if (context.hasResult()) context.getResult().removeSon(name);
+ //}
+ // @todo we may fail fast the match by throwing an exception
+
+ return false;
+ }
+
+ if (!Configuration.CONTEXT_VAR_NAME.equals(name)) addResult(match, start, end, context);
+
+ return true;
+ }
+
+ private MatchResultImpl createMatch(final PsiElement match, int start, int end) {
+ final String image = match == null ? null : StructuralSearchUtil.getProfileByPsiElement(match).getText(match, start, end);
+ final SmartPsiPointer ref = new SmartPsiPointer(match);
+
+ final MatchResultImpl result = myNestedResult == null ? new MatchResultImpl(
+ name,
+ image,
+ ref,
+ start,
+ end,
+ target
+ ) : myNestedResult;
+
+ if (myNestedResult != null) {
+ myNestedResult.setName( name );
+ myNestedResult.setMatchImage( image );
+ myNestedResult.setMatchRef( ref );
+ myNestedResult.setStart( start );
+ myNestedResult.setEnd( end );
+ myNestedResult.setTarget( target );
+ myNestedResult = null;
+ }
+
+ return result;
+ }
+
+ boolean validate(MatchContext context, Class elementContext) {
+ MatchResult substitution = context.getResult().findSon(name);
+
+ if (minOccurs >= 1 &&
+ ( substitution == null ||
+ StructuralSearchUtil.getProfileByFileType(context.getOptions().getFileType()).getElementContextByPsi(substitution.getMatchRef().getElement()) != elementContext
+ )
+ ) {
+ return false;
+ } else if (maxOccurs <= 1 &&
+ substitution!=null && substitution.hasSons()
+ ) {
+ return false;
+ } else if (maxOccurs==0 && totalMatchedOccurs!=-1) {
+ return false;
+ }
+ return true;
+ }
+
+ public int getMinOccurs() {
+ return minOccurs;
+ }
+
+ public int getMaxOccurs() {
+ return maxOccurs;
+ }
+
+ private void removeLastResults(int numberOfResults, MatchContext context) {
+ if (numberOfResults == 0) return;
+ final MatchResultImpl substitution = context.getResult().findSon(name);
+
+ if (substitution!=null) {
+ final List<PsiElement> matchedNodes = context.getMatchedNodes();
+
+ if (substitution.hasSons()) {
+ final List<MatchResult> sons = substitution.getMatches();
+
+ while(numberOfResults > 0) {
+ --numberOfResults;
+ final MatchResult matchResult = sons.remove(sons.size() - 1);
+ if (matchedNodes != null) matchedNodes.remove(matchResult.getMatch());
+ }
+
+ if (sons.isEmpty()) {
+ context.getResult().removeSon(name);
+ }
+ } else {
+ final MatchResultImpl matchResult = context.getResult().removeSon(name);
+ if (matchedNodes != null) matchedNodes.remove(matchResult.getMatch());
+ }
+ }
+ }
+
+ public boolean matchInAnyOrder(NodeIterator patternNodes, NodeIterator matchedNodes, final MatchContext context) {
+ final MatchResultImpl saveResult = context.hasResult() ? context.getResult() : null;
+ context.setResult(null);
+
+ try {
+
+ if (patternNodes.hasNext() && !matchedNodes.hasNext()) {
+ return validateSatisfactionOfHandlers(patternNodes, context);
+ }
+
+ Set<PsiElement> matchedElements = null;
+
+ for(; patternNodes.hasNext(); patternNodes.advance()) {
+ int matchedOccurs = 0;
+ final PsiElement patternNode = patternNodes.current();
+ final CompiledPattern pattern = context.getPattern();
+ final MatchingHandler handler = pattern.getHandler(patternNode);
+
+ final PsiElement startMatching = matchedNodes.current();
+ do {
+ final PsiElement element = handler.getPinnedNode(null);
+ final PsiElement matchedNode = (element != null) ? element : matchedNodes.current();
+
+ if (element == null) matchedNodes.advance();
+ if (!matchedNodes.hasNext()) matchedNodes.reset();
+
+ if (matchedOccurs <= maxOccurs &&
+ (matchedElements == null || !matchedElements.contains(matchedNode))) {
+
+ if (handler.match(patternNode, matchedNode, context)) {
+ ++matchedOccurs;
+ if (matchedElements == null) matchedElements = new HashSet<PsiElement>();
+ matchedElements.add(matchedNode);
+ if (handler.shouldAdvanceThePatternFor(patternNode, matchedNode)) {
+ break;
+ }
+ } else if (element != null) {
+ return false;
+ }
+
+ // clear state of dependent objects
+ clearingVisitor.clearState(pattern, patternNode);
+ }
+
+ // passed of elements and does not found the match
+ if (startMatching == matchedNodes.current()) {
+ final boolean result = validateSatisfactionOfHandlers(patternNodes, context) &&
+ matchedOccurs >= minOccurs && matchedOccurs <= maxOccurs;
+ if (result && context.getMatchedElementsListener() != null) {
+ context.getMatchedElementsListener().matchedElements(matchedElements);
+ }
+ return result;
+ }
+ } while(true);
+
+ if (!handler.shouldAdvanceThePatternFor(patternNode, null)) {
+ patternNodes.rewind();
+ }
+ }
+
+ final boolean result = validateSatisfactionOfHandlers(patternNodes, context);
+ if (result && context.getMatchedElementsListener() != null) {
+ context.getMatchedElementsListener().matchedElements(matchedElements);
+ }
+ return result;
+ } finally {
+ if (saveResult!=null) {
+ if (context.hasResult()) {
+ saveResult.getMatches().addAll(context.getResult().getMatches());
+ }
+ context.setResult(saveResult);
+ }
+ }
+ }
+
+ public boolean matchSequentially(NodeIterator nodes, NodeIterator nodes2, MatchContext context) {
+ return doMatchSequentially(nodes, nodes2, context);
+ }
+
+ protected boolean doMatchSequentiallyBySimpleHandler(NodeIterator nodes, NodeIterator nodes2, MatchContext context) {
+ final boolean oldValue = context.shouldRecursivelyMatch();
+ context.setShouldRecursivelyMatch(false);
+ final boolean result = super.matchSequentially(nodes, nodes2, context);
+ context.setShouldRecursivelyMatch(oldValue);
+ return result;
+ }
+
+ protected boolean doMatchSequentially(NodeIterator nodes, NodeIterator nodes2, MatchContext context) {
+ final int previousMatchedOccurs = matchedOccurs;
+
+ FilteringNodeIterator fNodes2 = new FilteringNodeIterator(nodes2, VARS_DELIM_FILTER);
+
+ try {
+ MatchingHandler handler = context.getPattern().getHandler(nodes.current());
+ matchedOccurs = 0;
+
+ boolean flag = false;
+
+ while(fNodes2.hasNext() && matchedOccurs < minOccurs) {
+ if (handler.match(nodes.current(), nodes2.current(), context)) {
+ ++matchedOccurs;
+ } else {
+ break;
+ }
+ fNodes2.advance();
+ flag = true;
+ }
+
+ if (matchedOccurs!=minOccurs) {
+ // failed even for min occurs
+ removeLastResults(matchedOccurs, context);
+ fNodes2.rewind(matchedOccurs);
+ return false;
+ }
+
+ if (greedy) {
+ // go greedily to maxOccurs
+
+ while(fNodes2.hasNext() && matchedOccurs < maxOccurs) {
+ if (handler.match(nodes.current(), nodes2.current(), context)) {
+ ++matchedOccurs;
+ } else {
+ // no more matches could take!
+ break;
+ }
+ fNodes2.advance();
+ flag = true;
+ }
+
+ if (flag) {
+ fNodes2.rewind();
+ nodes2.advance();
+ }
+
+ nodes.advance();
+
+ if (nodes.hasNext()) {
+ final MatchingHandler nextHandler = context.getPattern().getHandler(nodes.current());
+
+ while(matchedOccurs >= minOccurs) {
+ if (nextHandler.matchSequentially(nodes, nodes2, context)) {
+ totalMatchedOccurs = matchedOccurs;
+ // match found
+ return true;
+ }
+
+ if (matchedOccurs > 0) {
+ nodes2.rewind();
+ removeLastResults(1,context);
+ }
+ --matchedOccurs;
+ }
+
+ if (matchedOccurs > 0) {
+ removeLastResults(matchedOccurs, context);
+ }
+ nodes.rewind();
+ return false;
+ } else {
+ // match found
+ if (handler.isMatchSequentiallySucceeded(nodes2)) {
+ return checkSameOccurrencesConstraint();
+ }
+ removeLastResults(matchedOccurs, context);
+ return false;
+ }
+ } else {
+ nodes.advance();
+
+ if (flag) {
+ fNodes2.rewind();
+ nodes2.advance();
+ }
+
+ if (nodes.hasNext()) {
+ final MatchingHandler nextHandler = context.getPattern().getHandler(nodes.current());
+
+ flag = false;
+
+ while(nodes2.hasNext() && matchedOccurs <= maxOccurs) {
+ if (nextHandler.matchSequentially(nodes, nodes2, context)) {
+ return checkSameOccurrencesConstraint();
+ }
+
+ if (flag) {
+ nodes2.rewind();
+ fNodes2.advance();
+ }
+
+ if (handler.match(nodes.current(), nodes2.current(), context)) {
+ matchedOccurs++;
+ } else {
+ nodes.rewind();
+ removeLastResults(matchedOccurs,context);
+ return false;
+ }
+ nodes2.advance();
+ flag = true;
+ }
+
+ nodes.rewind();
+ removeLastResults(matchedOccurs,context);
+ return false;
+ } else {
+ return checkSameOccurrencesConstraint();
+ }
+ }
+ } finally {
+ matchedOccurs = previousMatchedOccurs;
+ }
+ }
+
+ private boolean checkSameOccurrencesConstraint() {
+ if (totalMatchedOccurs == -1) {
+ totalMatchedOccurs = matchedOccurs;
+ return true;
+ }
+ else {
+ return totalMatchedOccurs == matchedOccurs;
+ }
+ }
+
+ public void setTarget(boolean target) {
+ this.target = target;
+ }
+
+ public MatchingHandler getMatchHandler() {
+ return matchHandler;
+ }
+
+ public void setMatchHandler(MatchingHandler matchHandler) {
+ this.matchHandler = matchHandler;
+ }
+
+ public boolean isTarget() {
+ return target;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void reset() {
+ super.reset();
+ totalMatchedOccurs = -1;
+ }
+
+ public boolean shouldAdvanceThePatternFor(PsiElement patternElement, PsiElement matchedElement) {
+ if(maxOccurs > 1) return false;
+ return super.shouldAdvanceThePatternFor(patternElement,matchedElement);
+ }
+
+ public void setNestedResult(final MatchResultImpl nestedResult) {
+ myNestedResult = nestedResult;
+ }
+
+ public MatchResultImpl getNestedResult() {
+ return myNestedResult;
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/SymbolHandler.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/SymbolHandler.java
new file mode 100644
index 000000000000..4d1f9a852940
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/SymbolHandler.java
@@ -0,0 +1,21 @@
+package com.intellij.structuralsearch.impl.matcher.handlers;
+
+import com.intellij.psi.PsiElement;
+import com.intellij.structuralsearch.impl.matcher.MatchContext;
+
+/**
+ * Search handler for symbol search
+ */
+public class SymbolHandler extends MatchingHandler {
+ private final SubstitutionHandler handler;
+
+ public SymbolHandler(SubstitutionHandler handler) {
+ this.handler = handler;
+ }
+
+ public boolean match(PsiElement patternNode, PsiElement matchedNode, MatchContext context) {
+ // there is no need to do filtering since this is delegate of Substituion handler
+
+ return handler.handle(matchedNode,context);
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/TopLevelMatchingHandler.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/TopLevelMatchingHandler.java
new file mode 100644
index 000000000000..6711bcb1fc7f
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/TopLevelMatchingHandler.java
@@ -0,0 +1,85 @@
+package com.intellij.structuralsearch.impl.matcher.handlers;
+
+import com.intellij.dupLocator.iterators.NodeIterator;
+import com.intellij.dupLocator.iterators.SiblingNodeIterator;
+import com.intellij.psi.PsiComment;
+import com.intellij.psi.PsiElement;
+import com.intellij.structuralsearch.StructuralSearchUtil;
+import com.intellij.structuralsearch.impl.matcher.MatchContext;
+import com.intellij.structuralsearch.impl.matcher.iterators.SsrFilteringNodeIterator;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public final class TopLevelMatchingHandler extends MatchingHandler implements DelegatingHandler {
+ private final MatchingHandler delegate;
+
+ public TopLevelMatchingHandler(@NotNull MatchingHandler _delegate) {
+ delegate = _delegate;
+ setFilter(_delegate.getFilter());
+ }
+
+ public boolean match(final PsiElement patternNode, final PsiElement matchedNode, final MatchContext matchContext) {
+ final boolean matched = delegate.match(patternNode, matchedNode, matchContext);
+
+ if (matched) {
+ List<PsiElement> matchedNodes = matchContext.getMatchedNodes();
+ if (matchedNodes == null) {
+ matchedNodes = new ArrayList<PsiElement>();
+ matchContext.setMatchedNodes(matchedNodes);
+ }
+
+ PsiElement elementToAdd = matchedNode;
+
+ if (patternNode instanceof PsiComment && StructuralSearchUtil.isDocCommentOwner(matchedNode)) {
+ // psicomment and psidoccomment are placed inside the psimember next to them so
+ // simple topdown matching should do additional "dances" to cover this case.
+ elementToAdd = matchedNode.getFirstChild();
+ assert elementToAdd instanceof PsiComment;
+ }
+
+ matchedNodes.add(elementToAdd);
+ }
+
+ if ((!matched || matchContext.getOptions().isRecursiveSearch()) &&
+ matchContext.getPattern().getStrategy().continueMatching(matchedNode) &&
+ matchContext.shouldRecursivelyMatch()
+ ) {
+ matchContext.getMatcher().matchContext(
+ new SsrFilteringNodeIterator(
+ new SiblingNodeIterator(matchedNode.getFirstChild())
+ )
+ );
+ }
+ return matched;
+ }
+
+ @Override
+ public boolean canMatch(PsiElement patternNode, PsiElement matchedNode) {
+ return delegate.canMatch(patternNode, matchedNode);
+ }
+
+ @Override
+ public boolean matchSequentially(final NodeIterator nodes, final NodeIterator nodes2, final MatchContext context) {
+ return delegate.matchSequentially(nodes, nodes2, context);
+ }
+
+ public boolean match(final PsiElement patternNode,
+ final PsiElement matchedNode, final int start, final int end, final MatchContext context) {
+ return match(patternNode, matchedNode, context);
+ }
+
+ public boolean isMatchSequentiallySucceeded(final NodeIterator nodes2) {
+ return true;
+ }
+
+ @Override
+ public boolean shouldAdvanceTheMatchFor(final PsiElement patternElement, final PsiElement matchedElement) {
+ return delegate.shouldAdvanceTheMatchFor(patternElement, matchedElement);
+ }
+
+ public MatchingHandler getDelegate() {
+ return delegate;
+ }
+} \ No newline at end of file
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/TypedSymbolHandler.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/TypedSymbolHandler.java
new file mode 100644
index 000000000000..08e335614d5a
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/TypedSymbolHandler.java
@@ -0,0 +1,21 @@
+package com.intellij.structuralsearch.impl.matcher.handlers;
+
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiDeclarationStatement;
+import com.intellij.structuralsearch.impl.matcher.MatchContext;
+
+/**
+ * Search handler for typed symbol ('T<a*>)
+ */
+public class TypedSymbolHandler extends MatchingHandler {
+ public boolean match(PsiElement patternNode, PsiElement matchedNode, MatchContext context) {
+ if (!super.match(patternNode,matchedNode,context)) {
+ return false;
+ }
+
+ return context.getMatcher().match(
+ patternNode.getFirstChild(),
+ matchedNode
+ );
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/XmlTextHandler.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/XmlTextHandler.java
new file mode 100644
index 000000000000..cc71c02ee403
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/XmlTextHandler.java
@@ -0,0 +1,23 @@
+package com.intellij.structuralsearch.impl.matcher.handlers;
+
+import com.intellij.dupLocator.iterators.ArrayBackedNodeIterator;
+import com.intellij.dupLocator.iterators.NodeIterator;
+import com.intellij.psi.PsiElement;
+import com.intellij.structuralsearch.impl.matcher.GlobalMatchingVisitor;
+import com.intellij.structuralsearch.impl.matcher.MatchContext;
+import com.intellij.structuralsearch.impl.matcher.iterators.SsrFilteringNodeIterator;
+
+/**
+ * Root of handlers for pattern node matching. Handles simpliest type of the match.
+ */
+public final class XmlTextHandler extends MatchingHandler {
+ public boolean matchSequentially(NodeIterator nodes, NodeIterator nodes2, MatchContext context) {
+ final PsiElement psiElement = nodes.current();
+
+ return GlobalMatchingVisitor.continueMatchingSequentially(
+ new SsrFilteringNodeIterator( new ArrayBackedNodeIterator(psiElement.getChildren()) ),
+ nodes2,
+ context
+ );
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/iterators/SsrFilteringNodeIterator.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/iterators/SsrFilteringNodeIterator.java
new file mode 100644
index 000000000000..a2b14ae83e17
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/iterators/SsrFilteringNodeIterator.java
@@ -0,0 +1,20 @@
+package com.intellij.structuralsearch.impl.matcher.iterators;
+
+import com.intellij.dupLocator.iterators.FilteringNodeIterator;
+import com.intellij.dupLocator.iterators.NodeIterator;
+import com.intellij.dupLocator.iterators.SiblingNodeIterator;
+import com.intellij.psi.PsiElement;
+import com.intellij.structuralsearch.impl.matcher.filters.LexicalNodesFilter;
+
+/**
+ * @author Eugene.Kudelevsky
+ */
+public class SsrFilteringNodeIterator extends FilteringNodeIterator {
+ public SsrFilteringNodeIterator(final NodeIterator iterator) {
+ super(iterator, LexicalNodesFilter.getInstance());
+ }
+
+ public SsrFilteringNodeIterator(final PsiElement element) {
+ this(new SiblingNodeIterator(element));
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/AbstractStringBasedPredicate.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/AbstractStringBasedPredicate.java
new file mode 100644
index 000000000000..916fc1e15639
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/AbstractStringBasedPredicate.java
@@ -0,0 +1,22 @@
+package com.intellij.structuralsearch.impl.matcher.predicates;
+
+import com.intellij.psi.PsiElement;
+import com.intellij.structuralsearch.impl.matcher.MatchContext;
+import com.intellij.structuralsearch.impl.matcher.handlers.MatchPredicate;
+
+/**
+ * @author Maxim.Mossienko
+ */
+public class AbstractStringBasedPredicate extends MatchPredicate {
+ protected final String myName;
+ protected final String myWithin;
+
+ public AbstractStringBasedPredicate(String name, String within) {
+ myName = name;
+ myWithin = within;
+ }
+
+ public boolean match(PsiElement patternNode, PsiElement matchedNode, MatchContext context) {
+ return match(patternNode, matchedNode, 0, -1, context);
+ }
+} \ No newline at end of file
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/BinaryPredicate.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/BinaryPredicate.java
new file mode 100644
index 000000000000..b7d5432964d4
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/BinaryPredicate.java
@@ -0,0 +1,38 @@
+package com.intellij.structuralsearch.impl.matcher.predicates;
+
+import com.intellij.psi.PsiElement;
+import com.intellij.structuralsearch.impl.matcher.handlers.MatchPredicate;
+import com.intellij.structuralsearch.impl.matcher.MatchContext;
+
+/**
+ * Binary predicate
+ */
+public final class BinaryPredicate extends MatchPredicate {
+ private final MatchPredicate first;
+ private final MatchPredicate second;
+ private final boolean or;
+
+ public BinaryPredicate(MatchPredicate first, MatchPredicate second, boolean or) {
+ this.first = first;
+ this.second = second;
+ this.or = or;
+ }
+
+ public boolean match(PsiElement patternNode, PsiElement matchedNode, MatchContext context) {
+ if (or) {
+ return first.match(patternNode,matchedNode,context) ||
+ second.match(patternNode,matchedNode,context);
+ } else {
+ return first.match(patternNode,matchedNode,context) &&
+ second.match(patternNode,matchedNode,context);
+ }
+ }
+
+ public MatchPredicate getFirst() {
+ return first;
+ }
+
+ public MatchPredicate getSecond() {
+ return second;
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/ContainsPredicate.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/ContainsPredicate.java
new file mode 100644
index 000000000000..2ed894fe6b64
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/ContainsPredicate.java
@@ -0,0 +1,18 @@
+package com.intellij.structuralsearch.impl.matcher.predicates;
+
+import com.intellij.psi.PsiElement;
+import com.intellij.structuralsearch.impl.matcher.MatchContext;
+
+/**
+ * @author Maxim.Mossienko
+ */
+public class ContainsPredicate extends AbstractStringBasedPredicate {
+
+ public ContainsPredicate(String name, String within) {
+ super(name, within);
+ }
+
+ public boolean match(PsiElement node, PsiElement match, int start, int end, MatchContext context) {
+ return false;
+ }
+} \ No newline at end of file
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/NotPredicate.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/NotPredicate.java
new file mode 100644
index 000000000000..3d28a635e3af
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/NotPredicate.java
@@ -0,0 +1,24 @@
+package com.intellij.structuralsearch.impl.matcher.predicates;
+
+import com.intellij.psi.PsiElement;
+import com.intellij.structuralsearch.impl.matcher.MatchContext;
+import com.intellij.structuralsearch.impl.matcher.handlers.MatchPredicate;
+
+/**
+ * Negates predicate
+ */
+public final class NotPredicate extends MatchPredicate {
+ private final MatchPredicate handler;
+
+ public NotPredicate(final MatchPredicate _handler) {
+ handler = _handler;
+ }
+
+ public boolean match(PsiElement patternNode, PsiElement matchedNode, MatchContext context) {
+ return !handler.match(patternNode,matchedNode,context);
+ }
+
+ public MatchPredicate getHandler() {
+ return handler;
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/ReferencePredicate.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/ReferencePredicate.java
new file mode 100644
index 000000000000..17570ed8f5df
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/ReferencePredicate.java
@@ -0,0 +1,31 @@
+package com.intellij.structuralsearch.impl.matcher.predicates;
+
+import com.intellij.psi.*;
+import com.intellij.structuralsearch.StructuralSearchUtil;
+import com.intellij.structuralsearch.impl.matcher.handlers.SubstitutionHandler;
+import com.intellij.structuralsearch.impl.matcher.MatchContext;
+import com.intellij.structuralsearch.impl.matcher.MatchUtils;
+
+/**
+ * Handles finding method
+ */
+public final class ReferencePredicate extends SubstitutionHandler {
+ public ReferencePredicate(String _name) {
+ super(_name, true, 1, 1, true);
+ }
+
+ public boolean match(PsiElement node, PsiElement match, MatchContext context) {
+ if (StructuralSearchUtil.isIdentifier(match)) {
+ // since we pickup tokens
+ match = match.getParent();
+ }
+
+ PsiElement result = MatchUtils.getReferencedElement(match);
+ if (result == null) {
+ result = match;
+ //return false;
+ }
+
+ return handle(result,context);
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/RegExpPredicate.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/RegExpPredicate.java
new file mode 100644
index 000000000000..f7b8ecedcf3d
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/RegExpPredicate.java
@@ -0,0 +1,175 @@
+package com.intellij.structuralsearch.impl.matcher.predicates;
+
+import com.intellij.psi.*;
+import com.intellij.structuralsearch.MalformedPatternException;
+import com.intellij.structuralsearch.SSRBundle;
+import com.intellij.structuralsearch.StructuralSearchProfile;
+import com.intellij.structuralsearch.StructuralSearchUtil;
+import com.intellij.structuralsearch.impl.matcher.MatchContext;
+import com.intellij.structuralsearch.impl.matcher.MatchResultImpl;
+import com.intellij.structuralsearch.impl.matcher.MatchUtils;
+import com.intellij.structuralsearch.impl.matcher.handlers.MatchPredicate;
+import com.intellij.structuralsearch.plugin.util.SmartPsiPointer;
+import org.jetbrains.annotations.NonNls;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+
+/**
+ * Root of handlers for pattern node matching. Handles simpliest type of the match.
+ */
+public final class RegExpPredicate extends MatchPredicate {
+ private Pattern pattern;
+ private final String baseHandlerName;
+ private boolean simpleString;
+ private final boolean couldBeOptimized;
+ private final String regexp;
+ private final boolean caseSensitive;
+ private boolean multiline;
+ private final boolean wholeWords;
+ private final boolean target;
+ private NodeTextGenerator myNodeTextGenerator;
+
+ public interface NodeTextGenerator {
+ String getText(PsiElement element);
+ }
+
+ public RegExpPredicate(final String regexp, final boolean caseSensitive, final String _baseHandlerName, boolean _wholeWords, boolean _target) {
+ couldBeOptimized = containsRegExp(regexp);
+ if (!_wholeWords) {
+ simpleString = couldBeOptimized;
+ }
+
+ this.regexp = regexp;
+ this.caseSensitive = caseSensitive;
+ this.wholeWords = _wholeWords;
+ baseHandlerName = _baseHandlerName;
+
+ if (!simpleString) {
+ compilePattern();
+ }
+ target = _target;
+ }
+
+ private static boolean containsRegExp(final String regexp) {
+ for(int i=0;i<regexp.length();++i) {
+ if(MatchUtils.SPECIAL_CHARS.indexOf(regexp.charAt(i))!=-1) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private void compilePattern() {
+ try {
+ @NonNls String realRegexp = regexp;
+ if (wholeWords) {
+ realRegexp = ".*?\\b(?:" + realRegexp + ")\\b.*?";
+ }
+
+ pattern = Pattern.compile(
+ realRegexp,
+ (caseSensitive ? 0: Pattern.CASE_INSENSITIVE) | (multiline ? Pattern.DOTALL:0)
+ );
+ } catch(PatternSyntaxException ex) {
+ throw new MalformedPatternException(SSRBundle.message("error.incorrect.regexp.constraint", regexp, baseHandlerName));
+ }
+ }
+
+ public boolean couldBeOptimized() {
+ return couldBeOptimized;
+ }
+
+ public String getRegExp() {
+ return regexp;
+ }
+
+ /**
+ * Attempts to match given handler node against given node.
+ * @param matchedNode for matching
+ * @param context of the matching
+ * @return true if matching was successfull and false otherwise
+ */
+ public boolean match(PsiElement node,PsiElement matchedNode, int start, int end, MatchContext context) {
+ if (matchedNode==null) return false;
+ String text;
+
+ text = myNodeTextGenerator != null ? myNodeTextGenerator.getText(matchedNode) : getMeaningfulText(matchedNode);
+
+ boolean result = doMatch(text, start, end, context, matchedNode);
+
+ if (!result) {
+
+ if(StructuralSearchUtil.isIdentifier(matchedNode)) {
+ matchedNode = matchedNode.getParent();
+ }
+
+ String alternativeText = context.getPattern().getAlternativeTextToMatch(matchedNode, text);
+ if (alternativeText != null) {
+ result = doMatch(alternativeText, start, end, context, matchedNode);
+ }
+ }
+
+ return result;
+ }
+
+ public static String getMeaningfulText(PsiElement matchedNode) {
+ final StructuralSearchProfile profile = StructuralSearchUtil.getProfileByPsiElement(matchedNode);
+ return profile != null ? profile.getMeaningfulText(matchedNode) : matchedNode.getText();
+ }
+
+ public boolean match(PsiElement patternNode, PsiElement matchedNode, MatchContext context) {
+ return match(patternNode,matchedNode,0,-1,context);
+ }
+
+ boolean doMatch(String text, MatchContext context, PsiElement matchedElement) {
+ return doMatch(text,0,-1,context, matchedElement);
+ }
+
+ boolean doMatch(String text, int from, int end, MatchContext context,PsiElement matchedElement) {
+ if (from > 0 || end != -1) {
+ text = text.substring(from, end == -1 || end >= text.length() ? text.length():end);
+ }
+
+ if (simpleString) {
+ return (caseSensitive)?text.equals(regexp):text.equalsIgnoreCase(regexp);
+ }
+
+ if(!multiline && text.contains("\n")) setMultiline(true);
+ final Matcher matcher = pattern.matcher(text);
+
+ if (matcher.matches()) {
+ for (int i=1;i<=matcher.groupCount();++i) {
+ context.getResult().addSon(
+ new MatchResultImpl(
+ baseHandlerName + "_" + i,
+ matcher.group(i),
+ new SmartPsiPointer(matchedElement),
+ matcher.start(i),
+ matcher.end(i),
+ target
+ )
+ );
+ }
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+
+ public void setNodeTextGenerator(final NodeTextGenerator nodeTextGenerator) {
+ myNodeTextGenerator = nodeTextGenerator;
+ }
+
+ public void setMultiline(boolean b) {
+ multiline = b;
+ compilePattern();
+ }
+
+ public boolean isWholeWords() {
+ return wholeWords;
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/ScriptPredicate.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/ScriptPredicate.java
new file mode 100644
index 000000000000..9310eb9e70a0
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/ScriptPredicate.java
@@ -0,0 +1,28 @@
+package com.intellij.structuralsearch.impl.matcher.predicates;
+
+import com.intellij.psi.PsiElement;
+import com.intellij.structuralsearch.impl.matcher.MatchContext;
+
+/**
+ * @author Maxim.Mossienko
+ */
+public class ScriptPredicate extends AbstractStringBasedPredicate {
+ private final ScriptSupport scriptSupport;
+
+ public ScriptPredicate(String name, String within) {
+ super(name, within);
+ scriptSupport = new ScriptSupport(within, name);
+ }
+
+ public boolean match(PsiElement node, PsiElement match, int start, int end, MatchContext context) {
+ if (match == null) return false;
+
+ return Boolean.TRUE.equals(
+ Boolean.valueOf(scriptSupport.evaluate(
+ context.hasResult() ? context.getResult() : null,
+ match
+ ))
+ );
+ }
+
+} \ No newline at end of file
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/ScriptSupport.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/ScriptSupport.java
new file mode 100644
index 000000000000..e4136ede2831
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/ScriptSupport.java
@@ -0,0 +1,91 @@
+package com.intellij.structuralsearch.impl.matcher.predicates;
+
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.psi.PsiElement;
+import com.intellij.structuralsearch.MatchResult;
+import com.intellij.structuralsearch.SSRBundle;
+import com.intellij.structuralsearch.StructuralSearchException;
+import com.intellij.structuralsearch.StructuralSearchUtil;
+import com.intellij.structuralsearch.impl.matcher.MatchResultImpl;
+import groovy.lang.Binding;
+import groovy.lang.GroovyRuntimeException;
+import groovy.lang.GroovyShell;
+import groovy.lang.Script;
+import org.codehaus.groovy.control.CompilationFailedException;
+import org.codehaus.groovy.control.ErrorCollector;
+import org.codehaus.groovy.control.MultipleCompilationErrorsException;
+import org.codehaus.groovy.control.messages.Message;
+import org.codehaus.groovy.control.messages.SyntaxErrorMessage;
+import org.codehaus.groovy.syntax.SyntaxException;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * @author Maxim.Mossienko
+ * Date: 11.06.2009
+ * Time: 16:25:12
+ */
+public class ScriptSupport {
+ private final Script script;
+
+ public ScriptSupport(String text, String name) {
+ File scriptFile = new File(text);
+ GroovyShell shell = new GroovyShell();
+ try {
+ script = scriptFile.exists() ? shell.parse(scriptFile):shell.parse(text, name);
+ } catch (Exception ex) {
+ Logger.getInstance(getClass().getName()).error(ex);
+ throw new RuntimeException(ex);
+ }
+ }
+
+ public String evaluate(MatchResultImpl result, PsiElement context) {
+ try {
+ Binding binding = new Binding();
+
+ if (result != null) {
+ for(MatchResult r:result.getMatches()) {
+ binding.setVariable(r.getName(),r.getMatchRef().getElement());
+ }
+ }
+
+ if (context == null) {
+ context = result.getMatchRef().getElement();
+ }
+ if (StructuralSearchUtil.isIdentifier(context)) context = context.getParent();
+ binding.setVariable("__context__", context);
+ script.setBinding(binding);
+
+ Object o = script.run();
+ return String.valueOf(o);
+ } catch (GroovyRuntimeException ex) {
+ throw new StructuralSearchException(SSRBundle.message("groovy.script.error", ex.getMessage()));
+ }
+ }
+
+ public static String checkValidScript(String scriptText) {
+ try {
+ final File scriptFile = new File(scriptText);
+ final GroovyShell shell = new GroovyShell();
+ final Script script = scriptFile.exists() ? shell.parse(scriptFile) : shell.parse(scriptText);
+ return null;
+ } catch (IOException e) {
+ return e.getMessage();
+ } catch (MultipleCompilationErrorsException e) {
+ final ErrorCollector errorCollector = e.getErrorCollector();
+ final List<Message> errors = errorCollector.getErrors();
+ for (Message error : errors) {
+ if (error instanceof SyntaxErrorMessage) {
+ final SyntaxErrorMessage errorMessage = (SyntaxErrorMessage)error;
+ final SyntaxException cause = errorMessage.getCause();
+ return cause.getMessage();
+ }
+ }
+ return e.getMessage();
+ } catch (CompilationFailedException ex) {
+ return ex.getLocalizedMessage();
+ }
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/WithinPredicate.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/WithinPredicate.java
new file mode 100644
index 000000000000..3904eb397bce
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/WithinPredicate.java
@@ -0,0 +1,38 @@
+package com.intellij.structuralsearch.impl.matcher.predicates;
+
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.psi.PsiElement;
+import com.intellij.structuralsearch.MatchOptions;
+import com.intellij.structuralsearch.MatchResult;
+import com.intellij.structuralsearch.Matcher;
+import com.intellij.structuralsearch.impl.matcher.MatchContext;
+import com.intellij.structuralsearch.impl.matcher.compiler.PatternCompiler;
+
+/**
+ * @author Maxim.Mossienko
+ */
+public class WithinPredicate extends AbstractStringBasedPredicate {
+ private final MatchOptions myMatchOptions;
+ private Matcher matcher;
+
+ public WithinPredicate(String name, String within, Project project) {
+ super(name, within);
+ myMatchOptions = new MatchOptions();
+
+ myMatchOptions.setLooseMatching(true);
+ final String unquoted = StringUtil.stripQuotesAroundValue(within);
+ if (!unquoted.equals(within)) {
+ myMatchOptions.setSearchPattern(unquoted);
+ PatternCompiler.transformOldPattern(myMatchOptions);
+ matcher = new Matcher(project, myMatchOptions);
+ } else {
+ assert false;
+ }
+ }
+
+ public boolean match(PsiElement node, PsiElement match, int start, int end, MatchContext context) {
+ final MatchResult result = matcher.isMatchedByDownUp(match, myMatchOptions);
+ return result != null;
+ }
+} \ No newline at end of file
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/strategies/MatchingStrategy.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/strategies/MatchingStrategy.java
new file mode 100644
index 000000000000..8f5b5bc2fc20
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/strategies/MatchingStrategy.java
@@ -0,0 +1,13 @@
+package com.intellij.structuralsearch.impl.matcher.strategies;
+
+import com.intellij.psi.PsiElement;
+
+
+/**
+ * CommonStrategy of metching process
+ */
+public interface MatchingStrategy {
+ boolean continueMatching(PsiElement start);
+
+ boolean shouldSkip(PsiElement element, PsiElement elementToMatchWith);
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/strategies/XmlMatchingStrategy.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/strategies/XmlMatchingStrategy.java
new file mode 100644
index 000000000000..b2ca87a176e8
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/strategies/XmlMatchingStrategy.java
@@ -0,0 +1,42 @@
+package com.intellij.structuralsearch.impl.matcher.strategies;
+
+import com.intellij.dupLocator.util.NodeFilter;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.XmlElementVisitor;
+import com.intellij.psi.xml.XmlTag;
+
+/**
+ * Base filtering strategy to find statements
+ */
+public class XmlMatchingStrategy extends XmlElementVisitor implements MatchingStrategy,NodeFilter {
+ protected boolean result;
+
+ @Override public void visitXmlTag(final XmlTag element) {
+ result = true;
+ }
+
+ public boolean continueMatching(final PsiElement start) {
+ return accepts(start);
+ }
+
+ @Override
+ public boolean shouldSkip(PsiElement element, PsiElement elementToMatchWith) {
+ return false;
+ }
+
+ protected XmlMatchingStrategy() {}
+
+ private static class XmlMatchingStrategyHolder {
+ private static final XmlMatchingStrategy instance = new XmlMatchingStrategy();
+ }
+
+ public static MatchingStrategy getInstance() {
+ return XmlMatchingStrategyHolder.instance;
+ }
+
+ public boolean accepts(PsiElement element) {
+ result = false;
+ if (element!=null) element.accept(this);
+ return result;
+ }
+}
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);
+ }
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/StructuralReplaceAction.java b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/StructuralReplaceAction.java
new file mode 100644
index 000000000000..974d04a2b621
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/StructuralReplaceAction.java
@@ -0,0 +1,50 @@
+package com.intellij.structuralsearch.plugin;
+
+import com.intellij.openapi.actionSystem.*;
+import com.intellij.openapi.project.Project;
+import com.intellij.structuralsearch.plugin.replace.ui.ReplaceDialog;
+import com.intellij.structuralsearch.plugin.ui.Configuration;
+import com.intellij.structuralsearch.plugin.ui.SearchContext;
+
+/**
+ * Search and replace structural java code patterns action.
+ */
+public class StructuralReplaceAction extends AnAction {
+
+ /** Handles IDEA action event
+ * @param event the event of action
+ */
+ public void actionPerformed(AnActionEvent event) {
+ triggerAction(null, SearchContext.buildFromDataContext(event.getDataContext()));
+ }
+
+ public static void triggerAction(Configuration config, SearchContext searchContext) {
+ ReplaceDialog replaceDialog = new ReplaceDialog(searchContext);
+
+ if (config!=null) {
+ replaceDialog.setUseLastConfiguration(true);
+ replaceDialog.setValuesFromConfig(config);
+ }
+
+ replaceDialog.show();
+ }
+
+ /** Updates the state of the action
+ * @param event the action event
+ */
+ public void update(AnActionEvent event) {
+ final Presentation presentation = event.getPresentation();
+ final DataContext context = event.getDataContext();
+ final Project project = CommonDataKeys.PROJECT.getData(context);
+ final StructuralSearchPlugin plugin = (project == null)? null:StructuralSearchPlugin.getInstance( project );
+
+ if (plugin== null || plugin.isSearchInProgress() || plugin.isReplaceInProgress() || plugin.isDialogVisible()) {
+ presentation.setEnabled( false );
+ } else {
+ presentation.setEnabled( true );
+ }
+
+ super.update(event);
+ }
+}
+
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/StructuralSearchAction.java b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/StructuralSearchAction.java
new file mode 100644
index 000000000000..fdfccb951361
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/StructuralSearchAction.java
@@ -0,0 +1,49 @@
+package com.intellij.structuralsearch.plugin;
+
+import com.intellij.openapi.actionSystem.*;
+import com.intellij.openapi.project.Project;
+import com.intellij.structuralsearch.plugin.ui.Configuration;
+import com.intellij.structuralsearch.plugin.ui.SearchContext;
+import com.intellij.structuralsearch.plugin.ui.SearchDialog;
+
+public class StructuralSearchAction extends AnAction {
+
+ /** Handles IDEA action event
+ * @param event the event of action
+ */
+ public void actionPerformed(AnActionEvent event) {
+ triggerAction(null, SearchContext.buildFromDataContext(event.getDataContext()));
+ }
+
+ public static void triggerAction(Configuration config, SearchContext searchContext) {
+ //StructuralSearchPlugin.getInstance(searchContext.getProject());
+ final SearchDialog searchDialog = new SearchDialog(searchContext);
+
+ if (config!=null) {
+ searchDialog.setUseLastConfiguration(true);
+ searchDialog.setValuesFromConfig(config);
+ }
+
+ searchDialog.show();
+ }
+
+ /** Updates the state of the action
+ * @param event the action event
+ */
+ public void update(AnActionEvent event) {
+ final Presentation presentation = event.getPresentation();
+ final DataContext context = event.getDataContext();
+ final Project project = CommonDataKeys.PROJECT.getData(context);
+ final StructuralSearchPlugin plugin = project==null ? null:StructuralSearchPlugin.getInstance( project );
+
+ if (plugin == null || plugin.isSearchInProgress() || plugin.isDialogVisible()) {
+ presentation.setEnabled( false );
+ } else {
+ presentation.setEnabled( true );
+ }
+
+ super.update(event);
+ }
+
+}
+
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/StructuralSearchPlugin.java b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/StructuralSearchPlugin.java
new file mode 100644
index 000000000000..9acc1dba66d4
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/StructuralSearchPlugin.java
@@ -0,0 +1,111 @@
+package com.intellij.structuralsearch.plugin;
+
+import com.intellij.openapi.components.ProjectComponent;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.JDOMExternalizable;
+import com.intellij.structuralsearch.plugin.ui.ConfigurationManager;
+import com.intellij.structuralsearch.plugin.ui.ExistingTemplatesComponent;
+import org.jdom.Element;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * Structural search plugin main class.
+ */
+public final class StructuralSearchPlugin implements ProjectComponent, JDOMExternalizable {
+ private boolean searchInProgress;
+ private boolean replaceInProgress;
+ private boolean myDialogVisible;
+ private final ConfigurationManager myConfigurationManager = new ConfigurationManager();
+ private ExistingTemplatesComponent myExistingTemplatesComponent;
+
+ public boolean isSearchInProgress() {
+ return searchInProgress;
+ }
+
+ public void setSearchInProgress(boolean searchInProgress) {
+ this.searchInProgress = searchInProgress;
+ }
+
+ public boolean isReplaceInProgress() {
+ return replaceInProgress;
+ }
+
+ public void setReplaceInProgress(boolean replaceInProgress) {
+ this.replaceInProgress = replaceInProgress;
+ }
+
+ public boolean isDialogVisible() {
+ return myDialogVisible;
+ }
+
+ public void setDialogVisible(boolean dialogVisible) {
+ myDialogVisible = dialogVisible;
+ }
+
+ /**
+ * Method is called after plugin is already created and configured. Plugin can start to communicate with
+ * other plugins only in this method.
+ */
+ public void initComponent() {
+ }
+
+ /**
+ * This method is called on plugin disposal.
+ */
+ public void disposeComponent() {
+ }
+
+ /**
+ * Returns the name of component
+ *
+ * @return String representing component name. Use PluginName.ComponentName notation
+ * to avoid conflicts.
+ */
+ @NotNull
+ public String getComponentName() {
+ return "StructuralSearchPlugin";
+ }
+
+ // Simple logging facility
+
+ // Logs given string to IDEA logger
+
+ private static class LoggerHolder {
+ private static final Logger logger = Logger.getInstance("Structural search");
+ }
+
+ public static void debug(String str) {
+ LoggerHolder.logger.info(str);
+ }
+
+ public void readExternal(Element element) {
+ myConfigurationManager.loadConfigurations(element);
+ }
+
+ public void writeExternal(Element element) {
+ myConfigurationManager.saveConfigurations(element);
+ }
+
+ public void projectOpened() {
+ }
+
+ public void projectClosed() {
+ }
+
+ public static StructuralSearchPlugin getInstance(Project project) {
+ return project.getComponent(StructuralSearchPlugin.class);
+ }
+
+ public ConfigurationManager getConfigurationManager() {
+ return myConfigurationManager;
+ }
+
+ public ExistingTemplatesComponent getExistingTemplatesComponent() {
+ return myExistingTemplatesComponent;
+ }
+
+ public void setExistingTemplatesComponent(ExistingTemplatesComponent existingTemplatesComponent) {
+ myExistingTemplatesComponent = existingTemplatesComponent;
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/ReplaceOptions.java b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/ReplaceOptions.java
new file mode 100644
index 000000000000..31642aa80e22
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/ReplaceOptions.java
@@ -0,0 +1,185 @@
+package com.intellij.structuralsearch.plugin.replace;
+
+import com.intellij.openapi.util.JDOMExternalizable;
+import com.intellij.structuralsearch.MatchOptions;
+import com.intellij.structuralsearch.ReplacementVariableDefinition;
+import org.jdom.Attribute;
+import org.jdom.DataConversionException;
+import org.jdom.Element;
+import org.jetbrains.annotations.NonNls;
+
+import java.util.*;
+
+/**
+ * @author Maxim.Mossienko
+ * Date: Mar 5, 2004
+ * Time: 7:51:38 PM
+ */
+public class ReplaceOptions implements JDOMExternalizable, Cloneable {
+ private Map<String, ReplacementVariableDefinition> variableDefs;
+ private String replacement = "";
+ private boolean toShortenFQN;
+ private boolean myToReformatAccordingToStyle;
+ private boolean myToUseStaticImport = false;
+ private MatchOptions matchOptions = new MatchOptions();
+
+ @NonNls private static final String REFORMAT_ATTR_NAME = "reformatAccordingToStyle";
+ @NonNls private static final String REPLACEMENT_ATTR_NAME = "replacement";
+ @NonNls private static final String SHORTEN_FQN_ATTR_NAME = "shortenFQN";
+ @NonNls private static final String USE_STATIC_IMPORT_ATTR_NAME = "useStaticImport";
+
+ @NonNls private static final String VARIABLE_DEFINITION_TAG_NAME = "variableDefinition";
+
+ public String getReplacement() {
+ return replacement;
+ }
+
+ public void setReplacement(String replacement) {
+ this.replacement = replacement;
+ }
+
+ public boolean isToShortenFQN() {
+ return toShortenFQN;
+ }
+
+ public void setToShortenFQN(boolean shortedFQN) {
+ this.toShortenFQN = shortedFQN;
+ }
+
+ public boolean isToReformatAccordingToStyle() {
+ return myToReformatAccordingToStyle;
+ }
+
+ public MatchOptions getMatchOptions() {
+ return matchOptions;
+ }
+
+ public void setMatchOptions(MatchOptions matchOptions) {
+ this.matchOptions = matchOptions;
+ }
+
+ public void setToReformatAccordingToStyle(boolean reformatAccordingToStyle) {
+ myToReformatAccordingToStyle = reformatAccordingToStyle;
+ }
+
+ public boolean isToUseStaticImport() {
+ return myToUseStaticImport;
+ }
+
+ public void setToUseStaticImport(boolean useStaticImport) {
+ myToUseStaticImport = useStaticImport;
+ }
+
+ public void readExternal(Element element) {
+ matchOptions.readExternal(element);
+
+ Attribute attribute = element.getAttribute(REFORMAT_ATTR_NAME);
+ try {
+ myToReformatAccordingToStyle = attribute.getBooleanValue();
+ } catch(DataConversionException ex) {
+ }
+
+ attribute = element.getAttribute(SHORTEN_FQN_ATTR_NAME);
+ try {
+ toShortenFQN = attribute.getBooleanValue();
+ } catch(DataConversionException ex) {}
+
+ attribute = element.getAttribute(USE_STATIC_IMPORT_ATTR_NAME);
+ if (attribute != null) { // old saved configurations without this attribute present
+ try {
+ myToUseStaticImport = attribute.getBooleanValue();
+ }
+ catch (DataConversionException ignore) {}
+ }
+
+ replacement = element.getAttributeValue(REPLACEMENT_ATTR_NAME);
+
+ List<Element> elements = element.getChildren(VARIABLE_DEFINITION_TAG_NAME);
+
+ if (elements!=null && elements.size() > 0) {
+ for (final Element element1 : elements) {
+ final ReplacementVariableDefinition variableDefinition = new ReplacementVariableDefinition();
+ variableDefinition.readExternal(element1);
+ addVariableDefinition(variableDefinition);
+ }
+ }
+ }
+
+ public void writeExternal(Element element) {
+ matchOptions.writeExternal(element);
+
+ element.setAttribute(REFORMAT_ATTR_NAME,String.valueOf(myToReformatAccordingToStyle));
+ element.setAttribute(SHORTEN_FQN_ATTR_NAME,String.valueOf(toShortenFQN));
+ if (isToUseStaticImport()) {
+ element.setAttribute(USE_STATIC_IMPORT_ATTR_NAME, String.valueOf(isToUseStaticImport()));
+ }
+ element.setAttribute(REPLACEMENT_ATTR_NAME,replacement);
+
+ if (variableDefs!=null) {
+ for (final ReplacementVariableDefinition variableDefinition : variableDefs.values()) {
+ final Element infoElement = new Element(VARIABLE_DEFINITION_TAG_NAME);
+ element.addContent(infoElement);
+ variableDefinition.writeExternal(infoElement);
+ }
+ }
+ }
+
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof ReplaceOptions)) return false;
+
+ final ReplaceOptions replaceOptions = (ReplaceOptions)o;
+
+ if (myToReformatAccordingToStyle != replaceOptions.myToReformatAccordingToStyle) return false;
+ if (toShortenFQN != replaceOptions.toShortenFQN) return false;
+ if (myToUseStaticImport != replaceOptions.myToUseStaticImport) return false;
+ if (matchOptions != null ? !matchOptions.equals(replaceOptions.matchOptions) : replaceOptions.matchOptions != null) return false;
+ if (replacement != null ? !replacement.equals(replaceOptions.replacement) : replaceOptions.replacement != null) return false;
+ if (variableDefs != null ? !variableDefs.equals(replaceOptions.variableDefs) : replaceOptions.variableDefs != null) {
+ return false;
+ }
+
+ return true;
+ }
+
+ public int hashCode() {
+ int result;
+ result = (replacement != null ? replacement.hashCode() : 0);
+ result = 29 * result + (toShortenFQN ? 1 : 0);
+ result = 29 * result + (myToReformatAccordingToStyle ? 1 : 0);
+ result = 29 * result + (myToUseStaticImport ? 1 : 0);
+ result = 29 * result + (matchOptions != null ? matchOptions.hashCode() : 0);
+ result = 29 * result + (variableDefs != null ? variableDefs.hashCode() : 0);
+ return result;
+ }
+
+ public ReplaceOptions clone() {
+ try {
+ ReplaceOptions replaceOptions = (ReplaceOptions) super.clone();
+ replaceOptions.matchOptions = matchOptions.clone();
+ return replaceOptions;
+ } catch (CloneNotSupportedException e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ public ReplacementVariableDefinition getVariableDefinition(String name) {
+ return variableDefs != null ? variableDefs.get(name): null;
+ }
+
+ public void addVariableDefinition(ReplacementVariableDefinition definition) {
+ if (variableDefs==null) {
+ variableDefs = new LinkedHashMap<String, ReplacementVariableDefinition>();
+ }
+ variableDefs.put( definition.getName(), definition );
+ }
+
+ public Collection<ReplacementVariableDefinition> getReplacementVariableDefinitions() {
+ return variableDefs != null ? variableDefs.values() : Collections.<ReplacementVariableDefinition>emptyList();
+ }
+
+ public void clearVariableDefinitions() {
+ variableDefs = null;
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/ReplacementInfo.java b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/ReplacementInfo.java
new file mode 100644
index 000000000000..6b74197c19dd
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/ReplacementInfo.java
@@ -0,0 +1,22 @@
+package com.intellij.structuralsearch.plugin.replace;
+
+import com.intellij.psi.PsiElement;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Created by IntelliJ IDEA.
+ * User: maxim
+ * Date: 03.12.2004
+ * Time: 21:33:53
+ * To change this template use File | Settings | File Templates.
+ */
+public abstract class ReplacementInfo {
+ public abstract String getReplacement();
+
+ public abstract void setReplacement(String replacement);
+
+ @Nullable
+ public abstract PsiElement getMatch(int index);
+
+ public abstract int getMatchesCount();
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/impl/ParameterInfo.java b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/impl/ParameterInfo.java
new file mode 100644
index 000000000000..3e232731bd88
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/impl/ParameterInfo.java
@@ -0,0 +1,114 @@
+package com.intellij.structuralsearch.plugin.replace.impl;
+
+import com.intellij.psi.PsiElement;
+
+public final class ParameterInfo {
+ private String name;
+ private int startIndex;
+ private boolean argumentContext;
+ private boolean methodParameterContext;
+ private boolean statementContext;
+ private boolean variableInitializerContext;
+ private int afterDelimiterPos;
+ private boolean hasCommaBefore;
+ private int beforeDelimiterPos;
+ private boolean hasCommaAfter;
+ private boolean replacementVariable;
+ private PsiElement myElement;
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public int getStartIndex() {
+ return startIndex;
+ }
+
+ public void setStartIndex(int startIndex) {
+ this.startIndex = startIndex;
+ }
+
+ public boolean isArgumentContext() {
+ return argumentContext;
+ }
+
+ public void setArgumentContext(boolean argumentContext) {
+ this.argumentContext = argumentContext;
+ }
+
+ public boolean isMethodParameterContext() {
+ return methodParameterContext;
+ }
+
+ public void setMethodParameterContext(boolean methodParameterContext) {
+ this.methodParameterContext = methodParameterContext;
+ }
+
+ public boolean isStatementContext() {
+ return statementContext;
+ }
+
+ public void setStatementContext(boolean statementContext) {
+ this.statementContext = statementContext;
+ }
+
+ public boolean isVariableInitializerContext() {
+ return variableInitializerContext;
+ }
+
+ public void setVariableInitializerContext(boolean variableInitializerContext) {
+ this.variableInitializerContext = variableInitializerContext;
+ }
+
+ public int getAfterDelimiterPos() {
+ return afterDelimiterPos;
+ }
+
+ public void setAfterDelimiterPos(int afterDelimiterPos) {
+ this.afterDelimiterPos = afterDelimiterPos;
+ }
+
+ public boolean isHasCommaBefore() {
+ return hasCommaBefore;
+ }
+
+ public void setHasCommaBefore(boolean hasCommaBefore) {
+ this.hasCommaBefore = hasCommaBefore;
+ }
+
+ public int getBeforeDelimiterPos() {
+ return beforeDelimiterPos;
+ }
+
+ public void setBeforeDelimiterPos(int beforeDelimiterPos) {
+ this.beforeDelimiterPos = beforeDelimiterPos;
+ }
+
+ public boolean isHasCommaAfter() {
+ return hasCommaAfter;
+ }
+
+ public void setHasCommaAfter(boolean hasCommaAfter) {
+ this.hasCommaAfter = hasCommaAfter;
+ }
+
+ public boolean isReplacementVariable() {
+ return replacementVariable;
+ }
+
+ public void setReplacementVariable(boolean replacementVariable) {
+ this.replacementVariable = replacementVariable;
+ }
+
+ public PsiElement getElement() {
+ return myElement;
+ }
+
+ public void setElement(PsiElement element) {
+ myElement = element;
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/impl/ReplacementBuilder.java b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/impl/ReplacementBuilder.java
new file mode 100644
index 000000000000..e72fd5555531
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/impl/ReplacementBuilder.java
@@ -0,0 +1,218 @@
+package com.intellij.structuralsearch.plugin.replace.impl;
+
+import com.intellij.codeInsight.template.Template;
+import com.intellij.codeInsight.template.TemplateManager;
+import com.intellij.openapi.fileTypes.FileType;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.psi.*;
+import com.intellij.structuralsearch.MalformedPatternException;
+import com.intellij.structuralsearch.MatchResult;
+import com.intellij.structuralsearch.StructuralSearchProfile;
+import com.intellij.structuralsearch.StructuralSearchUtil;
+import com.intellij.structuralsearch.impl.matcher.MatchResultImpl;
+import com.intellij.structuralsearch.impl.matcher.MatcherImplUtil;
+import com.intellij.structuralsearch.impl.matcher.PatternTreeContext;
+import com.intellij.structuralsearch.impl.matcher.predicates.ScriptSupport;
+import com.intellij.structuralsearch.plugin.replace.ReplaceOptions;
+import com.intellij.util.IncorrectOperationException;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.*;
+
+/**
+ * @author maxim
+ * Date: 24.02.2004
+ * Time: 15:34:57
+ */
+public final class ReplacementBuilder {
+ private String replacement;
+ private List<ParameterInfo> parameterizations;
+ private final Map<String, ScriptSupport> replacementVarsMap;
+ private final ReplaceOptions options;
+
+ ReplacementBuilder(final Project project,final ReplaceOptions options) {
+ replacementVarsMap = new HashMap<String, ScriptSupport>();
+ this.options = options;
+ String _replacement = options.getReplacement();
+ FileType fileType = options.getMatchOptions().getFileType();
+
+ final Template template = TemplateManager.getInstance(project).createTemplate("","",_replacement);
+
+ final int segmentsCount = template.getSegmentsCount();
+ replacement = template.getTemplateText();
+
+ for(int i=0;i<segmentsCount;++i) {
+ final int offset = template.getSegmentOffset(i);
+ final String name = template.getSegmentName(i);
+
+ final ParameterInfo info = new ParameterInfo();
+ info.setStartIndex(offset);
+ info.setName(name);
+ info.setReplacementVariable(options.getVariableDefinition(name) != null);
+
+ // find delimiter
+ int pos;
+ for(pos = offset-1; pos >=0 && pos < replacement.length() && Character.isWhitespace(replacement.charAt(pos));) {
+ --pos;
+ }
+
+ if (pos >= 0) {
+ if (replacement.charAt(pos) == ',') {
+ info.setHasCommaBefore(true);
+ }
+ info.setBeforeDelimiterPos(pos);
+ }
+
+ for(pos = offset; pos < replacement.length() && Character.isWhitespace(replacement.charAt(pos));) {
+ ++pos;
+ }
+
+ if (pos < replacement.length()) {
+ final char ch = replacement.charAt(pos);
+
+ if (ch == ';') {
+ info.setStatementContext(true);
+ }
+ else if (ch == ',' || ch == ')') {
+ info.setArgumentContext(true);
+ info.setHasCommaAfter(ch == ',');
+ }
+ info.setAfterDelimiterPos(pos);
+ }
+
+ if (parameterizations==null) {
+ parameterizations = new ArrayList<ParameterInfo>();
+ }
+
+ parameterizations.add(info);
+ }
+
+ final StructuralSearchProfile profile = parameterizations != null ? StructuralSearchUtil.getProfileByFileType(fileType) : null;
+ if (profile != null) {
+ try {
+ final PsiElement[] elements = MatcherImplUtil.createTreeFromText(
+ _replacement,
+ PatternTreeContext.Block,
+ fileType,
+ options.getMatchOptions().getDialect(),
+ options.getMatchOptions().getPatternContext(),
+ project,
+ false
+ );
+ if (elements.length > 0) {
+ final PsiElement patternNode = elements[0].getParent();
+ profile.provideAdditionalReplaceOptions(patternNode, options, this);
+ }
+ } catch (IncorrectOperationException e) {
+ throw new MalformedPatternException();
+ }
+ }
+ }
+
+ private static void fill(MatchResult r,Map<String,MatchResult> m) {
+ if (r.getName()!=null) {
+ if (m.get(r.getName()) == null) {
+ m.put(r.getName(), r);
+ }
+ }
+
+ if (!r.isScopeMatch() || !r.isMultipleMatch()) {
+ for (final MatchResult matchResult : r.getAllSons()) {
+ fill(matchResult, m);
+ }
+ } else if (r.hasSons()) {
+ final List<MatchResult> allSons = r.getAllSons();
+ if (allSons.size() > 0) {
+ fill(allSons.get(0),m);
+ }
+ }
+ }
+
+ String process(MatchResult match, ReplacementInfoImpl replacementInfo, FileType type) {
+ if (parameterizations==null) {
+ return replacement;
+ }
+
+ final StringBuilder result = new StringBuilder(replacement);
+ HashMap<String, MatchResult> matchMap = new HashMap<String, MatchResult>();
+ fill(match, matchMap);
+
+ int offset = 0;
+
+ final StructuralSearchProfile profile = StructuralSearchUtil.getProfileByFileType(type);
+
+ for (final ParameterInfo info : parameterizations) {
+ MatchResult r = matchMap.get(info.getName());
+ if (info.isReplacementVariable()) {
+ offset = Replacer.insertSubstitution(result, offset, info, generateReplacement(info, match));
+ }
+ else if (r != null) {
+ offset = profile != null ? profile.handleSubstitution(info, r, result, offset, matchMap) : StructuralSearchProfile.defaultHandleSubstitution(info, r, result, offset);
+ }
+ else {
+ if (info.isHasCommaBefore()) {
+ result.delete(info.getBeforeDelimiterPos() + offset, info.getBeforeDelimiterPos() + 1 + offset);
+ --offset;
+ }
+ else if (info.isHasCommaAfter()) {
+ result.delete(info.getAfterDelimiterPos() + offset, info.getAfterDelimiterPos() + 1 + offset);
+ --offset;
+ }
+ else if (info.isVariableInitializerContext()) {
+ //if (info.afterDelimiterPos > 0) {
+ result.delete(info.getBeforeDelimiterPos() + offset, info.getAfterDelimiterPos() + offset - 1);
+ offset -= (info.getAfterDelimiterPos() - info.getBeforeDelimiterPos() - 1);
+ //}
+ } else if (profile != null) {
+ offset = profile.processAdditionalOptions(info, offset, result, r);
+ }
+ offset = Replacer.insertSubstitution(result, offset, info, "");
+ }
+ }
+
+ replacementInfo.variableMap = (HashMap<String, MatchResult>)matchMap.clone();
+ matchMap.clear();
+ return result.toString();
+ }
+
+ private String generateReplacement(ParameterInfo info, MatchResult match) {
+ ScriptSupport scriptSupport = replacementVarsMap.get(info.getName());
+
+ if (scriptSupport == null) {
+ String constraint = options.getVariableDefinition(info.getName()).getScriptCodeConstraint();
+ scriptSupport = new ScriptSupport(StringUtil.stripQuotesAroundValue(constraint), info.getName());
+ replacementVarsMap.put(info.getName(), scriptSupport);
+ }
+ return scriptSupport.evaluate((MatchResultImpl)match, null);
+ }
+
+ @Nullable
+ public ParameterInfo findParameterization(String name) {
+ if (parameterizations==null) return null;
+
+ for (final ParameterInfo info : parameterizations) {
+
+ if (info.getName().equals(name)) {
+ return info;
+ }
+ }
+
+ return null;
+ }
+
+ public void clear() {
+ replacement = null;
+
+ if (parameterizations!=null) {
+ parameterizations.clear();
+ parameterizations = null;
+ }
+ }
+
+ public void addParametrization(@NotNull ParameterInfo e) {
+ assert parameterizations != null;
+ parameterizations.add(e);
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/impl/ReplacementContext.java b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/impl/ReplacementContext.java
new file mode 100644
index 000000000000..c77d0b7ce1f1
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/impl/ReplacementContext.java
@@ -0,0 +1,57 @@
+package com.intellij.structuralsearch.plugin.replace.impl;
+
+import com.intellij.openapi.project.Project;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiNamedElement;
+import com.intellij.structuralsearch.MatchResult;
+import com.intellij.structuralsearch.StructuralSearchUtil;
+import com.intellij.structuralsearch.plugin.replace.ReplaceOptions;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Created by IntelliJ IDEA.
+ * User: Maxim.Mossienko
+ * Date: 27.09.2005
+ * Time: 14:27:20
+ * To change this template use File | Settings | File Templates.
+ */
+public class ReplacementContext {
+ ReplacementInfoImpl replacementInfo;
+ ReplaceOptions options;
+ Project project;
+
+ public ReplaceOptions getOptions() {
+ return options;
+ }
+
+ public Project getProject() {
+ return project;
+ }
+
+ ReplacementContext(ReplaceOptions _options, Project _project) {
+ options = _options;
+ project = _project;
+ }
+
+ public Map<String, String> getNewName2PatternNameMap() {
+ Map<String, String> newNameToSearchPatternNameMap = new HashMap<String, String>(1);
+ final Map<String, MatchResult> variableMap = replacementInfo.getVariableMap();
+
+ if (variableMap != null) {
+ for (String s : variableMap.keySet()) {
+ final MatchResult matchResult = replacementInfo.getVariableMap().get(s);
+ PsiElement match = matchResult.getMatchRef() != null ? matchResult.getMatch() : null;
+ if (StructuralSearchUtil.isIdentifier(match)) match = match.getParent();
+
+ if (match instanceof PsiNamedElement) {
+ final String name = ((PsiNamedElement)match).getName();
+
+ newNameToSearchPatternNameMap.put(name, s);
+ }
+ }
+ }
+ return newNameToSearchPatternNameMap;
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/impl/ReplacementInfoImpl.java b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/impl/ReplacementInfoImpl.java
new file mode 100644
index 000000000000..318e88f27c24
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/impl/ReplacementInfoImpl.java
@@ -0,0 +1,52 @@
+package com.intellij.structuralsearch.plugin.replace.impl;
+
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.SmartPsiElementPointer;
+import com.intellij.structuralsearch.MatchResult;
+import com.intellij.structuralsearch.plugin.replace.ReplacementInfo;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Created by IntelliJ IDEA.
+ * User: maxim
+ * Date: 03.12.2004
+ * Time: 21:33:53
+ * To change this template use File | Settings | File Templates.
+ */
+public class ReplacementInfoImpl extends ReplacementInfo {
+ List<SmartPsiElementPointer> matchesPtrList;
+ String result;
+ MatchResult matchResult;
+ Map<String,MatchResult> variableMap;
+ Map<PsiElement,String> elementToVariableNameMap;
+
+ public String getReplacement() {
+ return result;
+ }
+
+ public void setReplacement(String replacement) {
+ result = replacement;
+ }
+
+ @Nullable
+ @Override
+ public PsiElement getMatch(int index) {
+ return matchesPtrList.get(index).getElement();
+ }
+
+ @Override
+ public int getMatchesCount() {
+ return matchesPtrList.size();
+ }
+
+ public Map<String, MatchResult> getVariableMap() {
+ return variableMap;
+ }
+
+ public MatchResult getMatchResult() {
+ return matchResult;
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/impl/Replacer.java b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/impl/Replacer.java
new file mode 100644
index 000000000000..32cb2f83c36f
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/impl/Replacer.java
@@ -0,0 +1,426 @@
+package com.intellij.structuralsearch.plugin.replace.impl;
+
+import com.intellij.codeInsight.template.Template;
+import com.intellij.codeInsight.template.TemplateManager;
+import com.intellij.lang.Language;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.command.CommandProcessor;
+import com.intellij.openapi.fileEditor.FileDocumentManager;
+import com.intellij.openapi.fileTypes.FileType;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.psi.*;
+import com.intellij.psi.codeStyle.CodeStyleManager;
+import com.intellij.psi.search.LocalSearchScope;
+import com.intellij.structuralsearch.*;
+import com.intellij.structuralsearch.impl.matcher.MatcherImplUtil;
+import com.intellij.structuralsearch.impl.matcher.PatternTreeContext;
+import com.intellij.structuralsearch.impl.matcher.predicates.ScriptSupport;
+import com.intellij.structuralsearch.plugin.replace.ReplaceOptions;
+import com.intellij.structuralsearch.plugin.replace.ReplacementInfo;
+import com.intellij.structuralsearch.plugin.util.CollectingMatchResultSink;
+import com.intellij.util.IncorrectOperationException;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.*;
+
+/**
+ * @author Maxim.Mossienko
+ * Date: Mar 4, 2004
+ * Time: 9:19:34 PM
+ */
+public class Replacer {
+ private final Project project;
+ private ReplacementBuilder replacementBuilder;
+ private ReplaceOptions options;
+ private ReplacementContext context;
+ private StructuralReplaceHandler replaceHandler;
+
+ public Replacer(Project project, ReplaceOptions options) {
+ this.project = project;
+ this.options = options;
+ }
+
+ public static String stripTypedVariableDecoration(final String type) {
+ return type.substring(1,type.length()-1);
+ }
+
+ public static int insertSubstitution(StringBuilder result, int offset, final ParameterInfo info, String image) {
+ if (image.length() > 0) result.insert(offset+ info.getStartIndex(),image);
+ offset += image.length();
+ return offset;
+ }
+
+ public String testReplace(String in, String what, String by, ReplaceOptions options) throws IncorrectOperationException {
+ return testReplace(in, what, by, options,false);
+ }
+
+ public String testReplace(String in, String what, String by, ReplaceOptions options, boolean filePattern) {
+ FileType type = options.getMatchOptions().getFileType();
+ return testReplace(in, what, by, options, filePattern, false, type, null);
+ }
+
+ public String testReplace(String in, String what, String by, ReplaceOptions options, boolean filePattern, boolean createPhysicalFile,
+ FileType sourceFileType, Language sourceDialect) {
+ this.options = options;
+ this.options.getMatchOptions().setSearchPattern(what);
+ this.options.setReplacement(by);
+ replacementBuilder=null;
+ context = null;
+ replaceHandler = null;
+
+ this.options.getMatchOptions().clearVariableConstraints();
+ MatcherImplUtil.transform(this.options.getMatchOptions());
+
+ checkSupportedReplacementPattern(project, options);
+
+ Matcher matcher = new Matcher(project);
+ try {
+ PsiElement firstElement, lastElement, parent;
+
+ if (options.getMatchOptions().getScope() == null) {
+ PsiElement[] elements = MatcherImplUtil.createTreeFromText(
+ in,
+ filePattern ? PatternTreeContext.File : PatternTreeContext.Block,
+ sourceFileType, sourceDialect, null,
+ project,
+ createPhysicalFile
+ );
+
+ firstElement = elements[0];
+ lastElement = elements[elements.length-1];
+ parent = firstElement.getParent();
+
+ this.options.getMatchOptions().setScope(
+ new LocalSearchScope(parent)
+ );
+ } else {
+ parent = ((LocalSearchScope)options.getMatchOptions().getScope()).getScope()[0];
+ firstElement = parent.getFirstChild();
+ lastElement = parent.getLastChild();
+ }
+
+ this.options.getMatchOptions().setResultIsContextMatch(true);
+ CollectingMatchResultSink sink = new CollectingMatchResultSink();
+ matcher.testFindMatches(sink, this.options.getMatchOptions());
+
+ final List<ReplacementInfo> resultPtrList = new ArrayList<ReplacementInfo>();
+
+ for (final MatchResult result : sink.getMatches()) {
+ resultPtrList.add(buildReplacement(result));
+ }
+
+ sink.getMatches().clear();
+
+ int startOffset = firstElement.getTextRange().getStartOffset();
+ int endOffset = filePattern ?0: parent.getTextLength() - (lastElement.getTextRange().getEndOffset());
+
+ // get nodes from text may contain
+ PsiElement prevSibling = firstElement.getPrevSibling();
+ if (prevSibling instanceof PsiWhiteSpace) {
+ startOffset -= prevSibling.getTextLength() - 1;
+ }
+
+ PsiElement nextSibling = lastElement.getNextSibling();
+ if (nextSibling instanceof PsiWhiteSpace) {
+ endOffset -= nextSibling.getTextLength() - 1;
+ }
+
+ replaceAll(resultPtrList);
+
+ String result = parent.getText();
+ result = result.substring(startOffset);
+ result = result.substring(0,result.length() - endOffset);
+
+ return result;
+ }
+ catch (Exception e) {
+ throw new IncorrectOperationException(e);
+ }
+ finally {
+ options.getMatchOptions().setScope(null);
+ }
+ }
+
+ public void replaceAll(final List<ReplacementInfo> resultPtrList) {
+ PsiElement lastAffectedElement = null;
+ PsiElement currentAffectedElement;
+
+ for (ReplacementInfo info : resultPtrList) {
+ PsiElement element = info.getMatch(0);
+ initContextAndHandler(element);
+ if (replaceHandler != null) {
+ replaceHandler.prepare(info);
+ }
+ }
+
+ for (final ReplacementInfo aResultPtrList : resultPtrList) {
+ currentAffectedElement = doReplace(aResultPtrList);
+
+ if (currentAffectedElement != lastAffectedElement) {
+ if (lastAffectedElement != null) reformatAndPostProcess(lastAffectedElement);
+ lastAffectedElement = currentAffectedElement;
+ }
+ }
+
+ reformatAndPostProcess(lastAffectedElement);
+ }
+
+ public void replace(ReplacementInfo info) {
+ PsiElement element = info.getMatch(0);
+ initContextAndHandler(element);
+
+ if (replaceHandler != null) {
+ replaceHandler.prepare(info);
+ }
+ reformatAndPostProcess(doReplace(info));
+ }
+
+ @Nullable
+ private PsiElement doReplace(final ReplacementInfo info) {
+ final ReplacementInfoImpl replacementInfo = (ReplacementInfoImpl)info;
+ final PsiElement element = replacementInfo.matchesPtrList.get(0).getElement();
+
+ if (element==null || !element.isWritable() || !element.isValid()) return null;
+
+ final PsiElement elementParent = element.getParent();
+
+ //noinspection HardCodedStringLiteral
+ CommandProcessor.getInstance().executeCommand(
+ project,
+ new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(
+ new Runnable() {
+ public void run() {
+ doReplace(element, replacementInfo);
+ }
+ }
+ );
+ PsiDocumentManager.getInstance(project).commitAllDocuments();
+ }
+ },
+ "ssreplace",
+ "test"
+ );
+
+ if (!elementParent.isValid() || !elementParent.isWritable()) {
+ return null;
+ }
+
+ return elementParent;
+ }
+
+ private void reformatAndPostProcess(final PsiElement elementParent) {
+ if (elementParent == null) return;
+ final Runnable action = new Runnable() {
+ public void run() {
+ final PsiFile containingFile = elementParent.getContainingFile();
+
+ if (containingFile != null && options.isToReformatAccordingToStyle()) {
+ if (containingFile.getVirtualFile() != null) {
+ PsiDocumentManager.getInstance(project)
+ .commitDocument(FileDocumentManager.getInstance().getDocument(containingFile.getVirtualFile()));
+ }
+
+ final int parentOffset = elementParent.getTextRange().getStartOffset();
+ CodeStyleManager.getInstance(project)
+ .reformatRange(containingFile, parentOffset, parentOffset + elementParent.getTextLength(), true);
+ }
+ if (replaceHandler != null) {
+ replaceHandler.postProcess(elementParent, options);
+ }
+ }
+ };
+
+ CommandProcessor.getInstance().executeCommand(
+ project,
+ new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(action);
+ }
+ },
+ "reformat and shorten refs after ssr",
+ "test"
+ );
+ }
+
+ private void doReplace(final PsiElement elementToReplace,
+ final ReplacementInfoImpl info) {
+ CodeStyleManager.getInstance(project).performActionWithFormatterDisabled(new Runnable() {
+ public void run() {
+ initContextAndHandler(elementToReplace);
+
+ context.replacementInfo = info;
+
+ if (replaceHandler != null) {
+ replaceHandler.replace(info, options);
+ }
+ }
+ }
+ );
+ }
+
+ private void initContextAndHandler(PsiElement psiContext) {
+ if (context == null) {
+ context = new ReplacementContext(options, project);
+ }
+ if (replaceHandler == null) {
+ StructuralSearchProfile profile = StructuralSearchUtil.getProfileByPsiElement(psiContext);
+ if (profile != null) {
+ replaceHandler = profile.getReplaceHandler(this.context);
+ }
+ }
+ }
+
+ public static void handleComments(final PsiElement el, final PsiElement replacement, ReplacementContext context) throws IncorrectOperationException {
+ ReplacementInfoImpl replacementInfo = context.replacementInfo;
+ if (replacementInfo.elementToVariableNameMap == null) {
+ replacementInfo.elementToVariableNameMap = new HashMap<PsiElement, String>(1);
+ Map<String, MatchResult> variableMap = replacementInfo.variableMap;
+ if (variableMap != null) {
+ for(String name:variableMap.keySet()) {
+ fill(name,replacementInfo.variableMap.get(name),replacementInfo.elementToVariableNameMap);
+ }
+ }
+ }
+
+ PsiElement lastChild = el.getLastChild();
+ if (lastChild instanceof PsiComment &&
+ replacementInfo.elementToVariableNameMap.get(lastChild) == null &&
+ !(replacement.getLastChild() instanceof PsiComment)
+ ) {
+ PsiElement firstElementAfterStatementEnd = lastChild;
+ for(PsiElement curElement=firstElementAfterStatementEnd.getPrevSibling();curElement!=null;curElement = curElement.getPrevSibling()) {
+ if (!(curElement instanceof PsiWhiteSpace) && !(curElement instanceof PsiComment)) break;
+ firstElementAfterStatementEnd = curElement;
+ }
+ replacement.addRangeAfter(firstElementAfterStatementEnd,lastChild,replacement.getLastChild());
+ }
+
+ final PsiElement firstChild = el.getFirstChild();
+ if (firstChild instanceof PsiComment &&
+ !(firstChild instanceof PsiDocCommentBase) &&
+ replacementInfo.elementToVariableNameMap.get(firstChild) == null
+ ) {
+ PsiElement lastElementBeforeStatementStart = firstChild;
+
+ for(PsiElement curElement=lastElementBeforeStatementStart.getNextSibling();curElement!=null;curElement = curElement.getNextSibling()) {
+ if (!(curElement instanceof PsiWhiteSpace) && !(curElement instanceof PsiComment)) break;
+ lastElementBeforeStatementStart = curElement;
+ }
+ replacement.addRangeBefore(firstChild,lastElementBeforeStatementStart,replacement.getFirstChild());
+ }
+ }
+
+ private static void fill(final String name, final MatchResult matchResult, final Map<PsiElement, String> elementToVariableNameMap) {
+ boolean b = matchResult.isMultipleMatch() || matchResult.isScopeMatch();
+ if(matchResult.hasSons() && b) {
+ for(MatchResult r:matchResult.getAllSons()) {
+ fill(name, r, elementToVariableNameMap);
+ }
+ } else if (!b && matchResult.getMatchRef() != null) {
+ elementToVariableNameMap.put(matchResult.getMatch(),name);
+ }
+ }
+
+ public static void checkSupportedReplacementPattern(Project project, ReplaceOptions options) throws UnsupportedPatternException {
+ try {
+ String search = options.getMatchOptions().getSearchPattern();
+ String replacement = options.getReplacement();
+ FileType fileType = options.getMatchOptions().getFileType();
+ Template template = TemplateManager.getInstance(project).createTemplate("","",search);
+ Template template2 = TemplateManager.getInstance(project).createTemplate("","",replacement);
+
+ int segmentCount = template2.getSegmentsCount();
+ for(int i=0;i<segmentCount;++i) {
+ final String replacementSegmentName = template2.getSegmentName(i);
+ final int segmentCount2 = template.getSegmentsCount();
+ int j;
+
+ for(j=0;j<segmentCount2;++j) {
+ final String searchSegmentName = template.getSegmentName(j);
+
+ if (replacementSegmentName.equals(searchSegmentName)) break;
+
+ // Reference to
+ if (replacementSegmentName.startsWith(searchSegmentName) &&
+ replacementSegmentName.charAt(searchSegmentName.length())=='_'
+ ) {
+ try {
+ Integer.parseInt(replacementSegmentName.substring(searchSegmentName.length()+1));
+ break;
+ } catch(NumberFormatException ex) {}
+ }
+ }
+
+ if (j==segmentCount2) {
+ ReplacementVariableDefinition definition = options.getVariableDefinition(replacementSegmentName);
+
+ if (definition == null || definition.getScriptCodeConstraint().length() <= 2 /*empty quotes*/) {
+ throw new UnsupportedPatternException(
+ SSRBundle.message("replacement.variable.is.not.defined.message", replacementSegmentName)
+ );
+ } else {
+ String message = ScriptSupport.checkValidScript(StringUtil.stripQuotesAroundValue(definition.getScriptCodeConstraint()));
+ if (message != null) {
+ throw new UnsupportedPatternException(
+ SSRBundle.message("replacement.variable.is.not.valid", replacementSegmentName, message)
+ );
+ }
+ }
+ }
+ }
+
+ StructuralSearchProfile profile = StructuralSearchUtil.getProfileByFileType(fileType);
+
+ profile.checkReplacementPattern(project, options);
+
+ } catch(IncorrectOperationException ex) {
+ throw new UnsupportedPatternException(SSRBundle.message("incorrect.pattern.message"));
+ }
+ }
+
+ public ReplacementInfo buildReplacement(MatchResult result) {
+ List<SmartPsiElementPointer> l = new ArrayList<SmartPsiElementPointer>();
+ SmartPointerManager manager = SmartPointerManager.getInstance(project);
+
+ if (MatchResult.MULTI_LINE_MATCH.equals(result.getName())) {
+ for(Iterator<MatchResult> i=result.getAllSons().iterator();i.hasNext();) {
+ final MatchResult r = i.next();
+
+ if (MatchResult.LINE_MATCH.equals(r.getName())) {
+ PsiElement element = r.getMatchRef().getElement();
+
+ if (element instanceof PsiDocCommentBase) { // doc comment is not collapsed when created in block
+ if (i.hasNext()) {
+ MatchResult matchResult = i.next();
+
+ if (MatchResult.LINE_MATCH.equals(matchResult.getName()) &&
+ StructuralSearchUtil.isDocCommentOwner(matchResult.getMatch())) {
+ element = matchResult.getMatch();
+ } else {
+ l.add( manager.createSmartPsiElementPointer(element) );
+ element = matchResult.getMatch();
+ }
+ }
+ }
+ l.add( manager.createSmartPsiElementPointer(element) );
+ }
+ }
+ } else {
+ l.add( manager.createSmartPsiElementPointer(result.getMatchRef().getElement()));
+ }
+
+ ReplacementInfoImpl replacementInfo = new ReplacementInfoImpl();
+
+ replacementInfo.matchesPtrList = l;
+ if (replacementBuilder==null) {
+ replacementBuilder = new ReplacementBuilder(project,options);
+ }
+ replacementInfo.result = replacementBuilder.process(result, replacementInfo, options.getMatchOptions().getFileType());
+ replacementInfo.matchResult = result;
+
+ return replacementInfo;
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/impl/ReplacerUtil.java b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/impl/ReplacerUtil.java
new file mode 100644
index 000000000000..365ec74bc01e
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/impl/ReplacerUtil.java
@@ -0,0 +1,49 @@
+package com.intellij.structuralsearch.plugin.replace.impl;
+
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.fileTypes.FileType;
+import com.intellij.psi.PsiComment;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiWhiteSpace;
+import com.intellij.structuralsearch.StructuralSearchProfile;
+import com.intellij.structuralsearch.impl.matcher.MatcherImplUtil;
+import com.intellij.structuralsearch.impl.matcher.PatternTreeContext;
+
+/**
+ * @author Eugene.Kudelevsky
+ */
+public class ReplacerUtil {
+ private ReplacerUtil() {
+ }
+
+ public static PsiElement[] createTreeForReplacement(String replacement, PatternTreeContext treeContext, ReplacementContext context) {
+ FileType fileType = context.getOptions().getMatchOptions().getFileType();
+ return MatcherImplUtil.createTreeFromText(replacement, treeContext, fileType, context.getProject());
+ }
+
+ public static PsiElement copySpacesAndCommentsBefore(PsiElement elementToReplace,
+ PsiElement[] patternElements,
+ String replacementToMake,
+ PsiElement elementParent) {
+ int i = 0;
+ while (true) { // if it goes out of bounds then deep error happens
+ if (!(patternElements[i] instanceof PsiComment || patternElements[i] instanceof PsiWhiteSpace)) {
+ break;
+ }
+ ++i;
+ if (patternElements.length == i) {
+ break;
+ }
+ }
+
+ if (patternElements.length == i) {
+ Logger logger = Logger.getInstance(StructuralSearchProfile.class.getName());
+ logger.error("Unexpected replacement structure:" + replacementToMake);
+ }
+
+ if (i != 0) {
+ elementParent.addRangeBefore(patternElements[0], patternElements[i - 1], elementToReplace);
+ }
+ return patternElements[i];
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/ui/ReplaceCommand.java b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/ui/ReplaceCommand.java
new file mode 100644
index 000000000000..1b704f7c968e
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/ui/ReplaceCommand.java
@@ -0,0 +1,40 @@
+package com.intellij.structuralsearch.plugin.replace.ui;
+
+import com.intellij.openapi.project.Project;
+import com.intellij.structuralsearch.MatchResult;
+import com.intellij.structuralsearch.plugin.StructuralSearchPlugin;
+import com.intellij.structuralsearch.plugin.ui.SearchCommand;
+import com.intellij.usages.Usage;
+
+/**
+ * Created by IntelliJ IDEA.
+ * User: Maxim.Mossienko
+ * Date: Mar 31, 2004
+ * Time: 3:54:03 PM
+ * To change this template use File | Settings | File Templates.
+ */
+public class ReplaceCommand extends SearchCommand {
+
+ public ReplaceCommand(Project project, ReplaceUsageViewContext context) {
+ super( project, context );
+ }
+
+ protected void findStarted() {
+ super.findStarted();
+
+ StructuralSearchPlugin.getInstance(project).setReplaceInProgress(true);
+ }
+
+ protected void findEnded() {
+ StructuralSearchPlugin.getInstance(project).setReplaceInProgress( false );
+
+ super.findEnded();
+ }
+
+ protected void foundUsage(MatchResult result, Usage usage) {
+ super.foundUsage(result, usage);
+
+ final ReplaceUsageViewContext replaceUsageViewContext = ((ReplaceUsageViewContext)context);
+ replaceUsageViewContext.addReplaceUsage(usage,replaceUsageViewContext.getReplacer().buildReplacement(result));
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/ui/ReplaceConfiguration.java b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/ui/ReplaceConfiguration.java
new file mode 100644
index 000000000000..fa9beaa36a23
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/ui/ReplaceConfiguration.java
@@ -0,0 +1,46 @@
+package com.intellij.structuralsearch.plugin.replace.ui;
+
+import org.jdom.Element;
+import com.intellij.structuralsearch.plugin.ui.Configuration;
+import com.intellij.structuralsearch.plugin.replace.ReplaceOptions;
+import com.intellij.structuralsearch.MatchOptions;
+
+/**
+ * @author Maxim.Mossienko
+ * Date: Apr 14, 2004
+ * Time: 4:41:37 PM
+ */
+public class ReplaceConfiguration extends Configuration {
+ private final ReplaceOptions options = new ReplaceOptions();
+ public static final String REPLACEMENT_VARIABLE_SUFFIX = "$replacement";
+
+ public ReplaceOptions getOptions() {
+ return options;
+ }
+
+ public MatchOptions getMatchOptions() {
+ return options.getMatchOptions();
+ }
+
+ public void readExternal(Element element) {
+ super.readExternal(element);
+ options.readExternal(element);
+ }
+
+ public void writeExternal(Element element) {
+ super.writeExternal(element);
+ options.writeExternal(element);
+ }
+
+ public boolean equals(Object configuration) {
+ if (!super.equals(configuration)) return false;
+ if (configuration instanceof ReplaceConfiguration) {
+ return options.equals(((ReplaceConfiguration)configuration).options);
+ }
+ return false;
+ }
+
+ public int hashCode() {
+ return options.hashCode();
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/ui/ReplaceDialog.java b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/ui/ReplaceDialog.java
new file mode 100644
index 000000000000..b02fadab8245
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/ui/ReplaceDialog.java
@@ -0,0 +1,195 @@
+package com.intellij.structuralsearch.plugin.replace.ui;
+
+import com.intellij.codeInsight.CodeInsightBundle;
+import com.intellij.codeInsight.template.impl.Variable;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.editor.EditorFactory;
+import com.intellij.openapi.ui.Splitter;
+import com.intellij.structuralsearch.MalformedPatternException;
+import com.intellij.structuralsearch.ReplacementVariableDefinition;
+import com.intellij.structuralsearch.SSRBundle;
+import com.intellij.structuralsearch.UnsupportedPatternException;
+import com.intellij.structuralsearch.plugin.replace.ReplaceOptions;
+import com.intellij.structuralsearch.plugin.replace.impl.Replacer;
+import com.intellij.structuralsearch.plugin.ui.*;
+import com.intellij.util.containers.hash.LinkedHashMap;
+
+import javax.swing.*;
+import java.awt.*;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+// Class to show the user the request for search
+
+@SuppressWarnings({"RefusedBequest"})
+public class ReplaceDialog extends SearchDialog {
+ private Editor replaceCriteriaEdit;
+ private JCheckBox shortenFQN;
+ private JCheckBox formatAccordingToStyle;
+ private JCheckBox useStaticImport;
+
+ private String mySavedEditorText;
+
+ protected String getDefaultTitle() {
+ return SSRBundle.message("structural.replace.title");
+ }
+
+ protected JComponent createEditorContent() {
+ JPanel result = new JPanel(new BorderLayout());
+ Splitter p;
+
+ result.add(BorderLayout.CENTER, p = new Splitter(true, 0.5f));
+ p.setFirstComponent(super.createEditorContent());
+
+ replaceCriteriaEdit = createEditor(searchContext, mySavedEditorText != null ? mySavedEditorText : "");
+ JPanel replace = new JPanel(new BorderLayout());
+ replace.add(BorderLayout.NORTH, new JLabel(SSRBundle.message("replacement.template.label")));
+ replace.add(BorderLayout.CENTER, replaceCriteriaEdit.getComponent());
+ replaceCriteriaEdit.getComponent().setMinimumSize(new Dimension(150, 100));
+
+ p.setSecondComponent(replace);
+
+ return result;
+ }
+
+ protected int getRowsCount() {
+ return super.getRowsCount() + 1;
+ }
+
+ protected String getDimensionServiceKey() {
+ return "#com.intellij.structuralsearch.plugin.replace.ui.ReplaceDialog";
+ }
+
+ protected void buildOptions(JPanel searchOptions) {
+ super.buildOptions(searchOptions);
+ searchOptions.add(UIUtil.createOptionLine(shortenFQN = new JCheckBox(
+ SSRBundle.message("shorten.fully.qualified.names.checkbox"), true)));
+
+ searchOptions.add(UIUtil.createOptionLine(formatAccordingToStyle = new JCheckBox(
+ CodeInsightBundle.message("dialog.edit.template.checkbox.reformat.according.to.style"), true)));
+
+ searchOptions.add(UIUtil.createOptionLine(useStaticImport = new JCheckBox(
+ CodeInsightBundle.message("dialog.edit.template.checkbox.use.static.import"), true)));
+ }
+
+ protected UsageViewContext createUsageViewContext(Configuration configuration) {
+ return new ReplaceUsageViewContext(searchContext, configuration);
+ }
+
+ public ReplaceDialog(SearchContext searchContext) {
+ super(searchContext);
+ }
+
+ public ReplaceDialog(SearchContext searchContext, boolean showScope, boolean runFindActionOnClose) {
+ super(searchContext, showScope, runFindActionOnClose);
+ }
+
+
+ public Configuration createConfiguration() {
+ ReplaceConfiguration configuration = new ReplaceConfiguration();
+ configuration.setName(USER_DEFINED);
+ return configuration;
+ }
+
+ protected void disposeEditorContent() {
+ mySavedEditorText = replaceCriteriaEdit.getDocument().getText();
+ EditorFactory.getInstance().releaseEditor(replaceCriteriaEdit);
+ super.disposeEditorContent();
+ }
+
+ public void setValuesFromConfig(Configuration configuration) {
+ //replaceCriteriaEdit.putUserData(SubstitutionShortInfoHandler.CURRENT_CONFIGURATION_KEY, configuration);
+
+ if (configuration instanceof ReplaceConfiguration) {
+ final ReplaceConfiguration config = (ReplaceConfiguration)configuration;
+ final ReplaceOptions options = config.getOptions();
+ super.setValuesFromConfig(config);
+
+ UIUtil.setContent(replaceCriteriaEdit, config.getOptions().getReplacement(), 0, replaceCriteriaEdit.getDocument().getTextLength(),
+ searchContext.getProject());
+
+ shortenFQN.setSelected(options.isToShortenFQN());
+ formatAccordingToStyle.setSelected(options.isToReformatAccordingToStyle());
+ useStaticImport.setSelected(options.isToUseStaticImport());
+
+ ReplaceOptions newReplaceOptions = ((ReplaceConfiguration)model.getConfig()).getOptions();
+ newReplaceOptions.clearVariableDefinitions();
+
+ for (ReplacementVariableDefinition def : options.getReplacementVariableDefinitions()) {
+ newReplaceOptions.addVariableDefinition((ReplacementVariableDefinition)def.clone());
+ }
+ }
+ else {
+ super.setValuesFromConfig(configuration);
+
+ UIUtil.setContent(replaceCriteriaEdit, configuration.getMatchOptions().getSearchPattern(), 0,
+ replaceCriteriaEdit.getDocument().getTextLength(), searchContext.getProject());
+ }
+ }
+
+ protected void setValuesToConfig(Configuration config) {
+ super.setValuesToConfig(config);
+
+ final ReplaceConfiguration replaceConfiguration = (ReplaceConfiguration)config;
+ final ReplaceOptions options = replaceConfiguration.getOptions();
+
+ options.setMatchOptions(replaceConfiguration.getMatchOptions());
+ options.setReplacement(replaceCriteriaEdit.getDocument().getText());
+ options.setToShortenFQN(shortenFQN.isSelected());
+ options.setToReformatAccordingToStyle(formatAccordingToStyle.isSelected());
+ options.setToUseStaticImport(useStaticImport.isSelected());
+ }
+
+ protected boolean isRecursiveSearchEnabled() {
+ return false;
+ }
+
+ protected java.util.List<Variable> getVariablesFromListeners() {
+ ArrayList<Variable> vars = getVarsFrom(replaceCriteriaEdit);
+ List<Variable> searchVars = super.getVariablesFromListeners();
+ Map<String, Variable> varsMap = new LinkedHashMap<String, Variable>(searchVars.size());
+
+ for(Variable var:searchVars) varsMap.put(var.getName(), var);
+ for(Variable var:vars) {
+ if (!varsMap.containsKey(var.getName())) {
+ String newVarName = var.getName() + ReplaceConfiguration.REPLACEMENT_VARIABLE_SUFFIX;
+ varsMap.put(newVarName, new Variable(newVarName, null, null, false, false));
+ }
+ }
+ return new ArrayList<Variable>(varsMap.values());
+ }
+
+ protected boolean isValid() {
+ if (!super.isValid()) return false;
+
+ try {
+ Replacer.checkSupportedReplacementPattern(searchContext.getProject(), ((ReplaceConfiguration)model.getConfig()).getOptions());
+ }
+ catch (UnsupportedPatternException ex) {
+ reportMessage("unsupported.replacement.pattern.message", replaceCriteriaEdit, ex.getMessage());
+ return false;
+ }
+ catch (MalformedPatternException ex) {
+ reportMessage("malformed.replacement.pattern.message", replaceCriteriaEdit, ex.getMessage());
+ return false;
+ }
+
+ return true;
+ }
+
+ public void show() {
+ replaceCriteriaEdit.putUserData(SubstitutionShortInfoHandler.CURRENT_CONFIGURATION_KEY, model.getConfig());
+
+ super.show();
+ }
+
+ protected boolean isReplaceDialog() {
+ return true;
+ }
+
+ protected void addOrReplaceSelection(final String selection) {
+ super.addOrReplaceSelection(selection);
+ addOrReplaceSelectionForEditor(selection, replaceCriteriaEdit);
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/ui/ReplaceUsageViewContext.java b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/ui/ReplaceUsageViewContext.java
new file mode 100644
index 000000000000..6a0d352d3284
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/ui/ReplaceUsageViewContext.java
@@ -0,0 +1,180 @@
+package com.intellij.structuralsearch.plugin.replace.ui;
+
+import com.intellij.history.LocalHistory;
+import com.intellij.history.LocalHistoryAction;
+import com.intellij.openapi.vfs.ReadonlyStatusHandler;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.structuralsearch.SSRBundle;
+import com.intellij.structuralsearch.plugin.replace.ReplacementInfo;
+import com.intellij.structuralsearch.plugin.replace.impl.Replacer;
+import com.intellij.structuralsearch.plugin.ui.Configuration;
+import com.intellij.structuralsearch.plugin.ui.SearchCommand;
+import com.intellij.structuralsearch.plugin.ui.SearchContext;
+import com.intellij.structuralsearch.plugin.ui.UsageViewContext;
+import com.intellij.usageView.UsageInfo;
+import com.intellij.usages.Usage;
+import com.intellij.usages.UsageInfo2UsageAdapter;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Created by IntelliJ IDEA.
+ * User: Maxim.Mossienko
+ * Date: Mar 9, 2005
+ * Time: 4:37:08 PM
+ * To change this template use File | Settings | File Templates.
+ */
+class ReplaceUsageViewContext extends UsageViewContext {
+ private HashMap<Usage,ReplacementInfo> usage2ReplacementInfo;
+ private Replacer replacer;
+
+ ReplaceUsageViewContext(final SearchContext context, final Configuration configuration) {
+ super(context,configuration);
+ }
+
+ protected SearchCommand createCommand() {
+ ReplaceCommand command = new ReplaceCommand(mySearchContext.getProject(), this);
+
+ usage2ReplacementInfo = new HashMap<Usage, ReplacementInfo>();
+ replacer = new Replacer(mySearchContext.getProject(), ((ReplaceConfiguration)myConfiguration).getOptions());
+
+ return command;
+ }
+
+ protected String _getPresentableText() {
+ return SSRBundle.message("replaceusageview.text",
+ getConfiguration().getMatchOptions().getSearchPattern(),
+ ((ReplaceConfiguration)getConfiguration()).getOptions().getReplacement()
+ );
+ }
+
+ public Replacer getReplacer() {
+ return replacer;
+ }
+
+ public void addReplaceUsage(final Usage usage, final ReplacementInfo replacementInfo) {
+ usage2ReplacementInfo.put(usage,replacementInfo);
+ }
+
+ private boolean isValid(UsageInfo2UsageAdapter info) {
+ final UsageInfo usageInfo = info.getUsageInfo();
+ return !isExcluded(info) && usageInfo.getElement() != null && usageInfo.getElement().isValid();
+ }
+
+ @Override
+ protected void configureActions() {
+ final Runnable replaceRunnable = new Runnable() {
+ public void run() {
+ LocalHistoryAction labelAction = LocalHistory.getInstance().startAction(SSRBundle.message("structural.replace.title"));
+
+ doReplace();
+ getUsageView().close();
+
+ labelAction.finish();
+ }
+ };
+
+ //noinspection HardCodedStringLiteral
+ getUsageView().addPerformOperationAction(replaceRunnable, "Replace All", null, SSRBundle.message("do.replace.all.button"));
+
+ final Runnable replaceSelected = new Runnable() {
+ public void run() {
+ final Set<Usage> infos = getUsageView().getSelectedUsages();
+ if (infos == null || infos.isEmpty()) return;
+
+ LocalHistoryAction labelAction = LocalHistory.getInstance().startAction(SSRBundle.message("structural.replace.title"));
+
+ for (final Usage info : infos) {
+ final UsageInfo2UsageAdapter usage = (UsageInfo2UsageAdapter)info;
+
+ if (isValid(usage)) {
+ replaceOne(usage, false);
+ }
+ }
+
+ labelAction.finish();
+
+ if (getUsageView().getUsagesCount() > 0) {
+ for (Usage usage : getUsageView().getSortedUsages()) {
+ if (!isExcluded(usage)) {
+ getUsageView().selectUsages(new Usage[]{usage});
+ return;
+ }
+ }
+ }
+ }
+ };
+
+ getUsageView().addButtonToLowerPane(replaceSelected, SSRBundle.message("replace.selected.button"));
+
+ final Runnable previewReplacement = new Runnable() {
+ public void run() {
+ Set<Usage> selection = getUsageView().getSelectedUsages();
+
+ if (selection != null && !selection.isEmpty()) {
+ UsageInfo2UsageAdapter usage = (UsageInfo2UsageAdapter)selection.iterator().next();
+
+ if (isValid(usage)) {
+ replaceOne(usage, true);
+ }
+ }
+ }
+ };
+
+ getUsageView().addButtonToLowerPane(previewReplacement, SSRBundle.message("preview.replacement.button"));
+
+ super.configureActions();
+ }
+
+ private static void ensureFileWritable(final UsageInfo2UsageAdapter usage) {
+ final VirtualFile file = usage.getFile();
+
+ if (file != null && !file.isWritable()) {
+ ReadonlyStatusHandler.getInstance(usage.getElement().getProject()).ensureFilesWritable(file);
+ }
+ }
+
+ private void replaceOne(UsageInfo2UsageAdapter info, boolean doConfirm) {
+ ReplacementInfo replacementInfo = usage2ReplacementInfo.get(info);
+ boolean approved;
+
+ if (doConfirm) {
+ ReplacementPreviewDialog wrapper =
+ new ReplacementPreviewDialog(mySearchContext.getProject(), info.getUsageInfo(), replacementInfo.getReplacement());
+
+ wrapper.show();
+ approved = wrapper.isOK();
+ }
+ else {
+ approved = true;
+ }
+
+ if (approved) {
+ ensureFileWritable(info);
+ getUsageView().removeUsage(info);
+ getReplacer().replace(replacementInfo);
+
+ if (getUsageView().getUsagesCount() == 0) {
+ getUsageView().close();
+ }
+ }
+ }
+
+ private void doReplace() {
+ List<Usage> infos = getUsageView().getSortedUsages();
+ List<ReplacementInfo> results = new ArrayList<ReplacementInfo>(infos.size());
+
+ for (final Usage info : infos) {
+ UsageInfo2UsageAdapter usage = (UsageInfo2UsageAdapter)info;
+
+ if (isValid(usage)) {
+ results.add(usage2ReplacementInfo.get(usage));
+ }
+ }
+
+ getReplacer().replaceAll(results);
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/ui/ReplacementPreviewDialog.java b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/ui/ReplacementPreviewDialog.java
new file mode 100644
index 000000000000..5bec5a920b45
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/ui/ReplacementPreviewDialog.java
@@ -0,0 +1,132 @@
+package com.intellij.structuralsearch.plugin.replace.ui;
+
+import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer;
+import com.intellij.openapi.editor.Document;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.editor.EditorFactory;
+import com.intellij.openapi.editor.colors.EditorColors;
+import com.intellij.openapi.editor.colors.EditorColorsManager;
+import com.intellij.openapi.editor.markup.HighlighterLayer;
+import com.intellij.openapi.editor.markup.HighlighterTargetArea;
+import com.intellij.openapi.editor.markup.RangeHighlighter;
+import com.intellij.openapi.fileEditor.FileEditorManager;
+import com.intellij.openapi.fileEditor.OpenFileDescriptor;
+import com.intellij.openapi.fileTypes.FileType;
+import com.intellij.openapi.fileTypes.FileTypes;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.DialogWrapper;
+import com.intellij.openapi.util.Segment;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.PsiDocumentManager;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.util.PsiUtilCore;
+import com.intellij.structuralsearch.SSRBundle;
+import com.intellij.structuralsearch.StructuralSearchProfile;
+import com.intellij.structuralsearch.StructuralSearchUtil;
+import com.intellij.structuralsearch.plugin.ui.UIUtil;
+import com.intellij.usageView.UsageInfo;
+
+import javax.swing.*;
+import java.awt.*;
+
+/**
+ * Navigates through the search results
+ */
+public final class ReplacementPreviewDialog extends DialogWrapper {
+ private final FileType myFileType;
+ private Editor replacement;
+
+ private final Project project;
+ private RangeHighlighter hilighter;
+ private Editor editor;
+
+
+ public ReplacementPreviewDialog(final Project project, UsageInfo info, String replacementString) {
+ super(project,true);
+
+ setTitle(SSRBundle.message("structural.replace.preview.dialog.title"));
+ setOKButtonText(SSRBundle.message("replace.preview.oktext"));
+ this.project = project;
+ final PsiElement element = info.getElement();
+ final VirtualFile virtualFile = PsiUtilCore.getVirtualFile(element);
+ myFileType = virtualFile != null ? virtualFile.getFileType() : FileTypes.PLAIN_TEXT;
+ init();
+
+ Segment range = info.getSegment();
+ hilight(virtualFile, range.getStartOffset(), range.getEndOffset());
+ UIUtil.setContent(replacement, replacementString,0,-1,project);
+
+ final StructuralSearchProfile profile = StructuralSearchUtil.getProfileByPsiElement(element);
+ if (profile != null) {
+ UIUtil.updateHighlighter(replacement, profile);
+ }
+ }
+
+ private void hilight(VirtualFile file,int start, int end) {
+ removeHilighter();
+
+ editor = FileEditorManager.getInstance(project).openTextEditor(
+ new OpenFileDescriptor(project, file),
+ false
+ );
+ hilighter = editor.getMarkupModel().addRangeHighlighter(
+ start,
+ end,
+ HighlighterLayer.SELECTION - 100,
+ EditorColorsManager.getInstance().getGlobalScheme().getAttributes(EditorColors.SEARCH_RESULT_ATTRIBUTES),
+ HighlighterTargetArea.EXACT_RANGE
+ );
+ }
+
+ private void removeHilighter() {
+ if (hilighter!=null && hilighter.isValid()) {
+ hilighter.dispose();
+ hilighter = null;
+ editor = null;
+ }
+ }
+
+ protected String getDimensionServiceKey() {
+ return "#com.intellij.strucuturalsearch.plugin.replace.ReplacementPreviewDialog";
+ }
+
+ protected JComponent createCenterPanel() {
+ JComponent centerPanel = new JPanel( new BorderLayout() );
+
+ PsiFile file = null;
+ final StructuralSearchProfile profile = StructuralSearchUtil.getProfileByFileType(myFileType);
+ if (profile != null) {
+ file = profile.createCodeFragment(project, "", null);
+ }
+
+ if (file != null) {
+ final Document document = PsiDocumentManager.getInstance(project).getDocument(file);
+ replacement = UIUtil.createEditor(document, project, true, null);
+ DaemonCodeAnalyzer.getInstance(project).setHighlightingEnabled(file,false);
+ } else {
+ final EditorFactory factory = EditorFactory.getInstance();
+ final Document document = factory.createDocument("");
+ replacement = factory.createEditor(document, project, myFileType, false);
+ }
+
+ centerPanel.add(BorderLayout.NORTH,new JLabel(SSRBundle.message("replacement.code")) );
+ centerPanel.add(BorderLayout.CENTER,replacement.getComponent() );
+ centerPanel.setMaximumSize(new Dimension(640,480));
+
+ return centerPanel;
+ }
+
+ public void dispose() {
+ final PsiFile file = PsiDocumentManager.getInstance(project).getPsiFile(replacement.getDocument());
+ if (file != null) {
+ DaemonCodeAnalyzer.getInstance(project).setHighlightingEnabled(file, true);
+ }
+
+ EditorFactory.getInstance().releaseEditor(replacement);
+ removeHilighter();
+
+ super.dispose();
+ }
+}
+
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/Configuration.java b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/Configuration.java
new file mode 100644
index 000000000000..440d33164f1d
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/Configuration.java
@@ -0,0 +1,87 @@
+package com.intellij.structuralsearch.plugin.ui;
+
+import com.intellij.openapi.util.JDOMExternalizable;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.structuralsearch.MatchOptions;
+import org.jdom.Element;
+import org.jetbrains.annotations.NonNls;
+
+/**
+ * Created by IntelliJ IDEA.
+ * User: Maxim.Mossienko
+ * Date: Apr 14, 2004
+ * Time: 5:29:37 PM
+ * To change this template use File | Settings | File Templates.
+ */
+public abstract class Configuration implements JDOMExternalizable, Comparable<Configuration> {
+ public static final Configuration[] EMPTY_ARRAY = {};
+ @NonNls protected static final String NAME_ATTRIBUTE_NAME = "name";
+ private String name = "";
+ private String category = null;
+ private boolean predefined;
+
+ private static ConfigurationCreator configurationCreator;
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String value) {
+ name = value;
+ }
+
+ public String getCategory() {
+ return category;
+ }
+
+ public void setCategory(String category) {
+ this.category = category;
+ }
+
+ public void readExternal(Element element) {
+ name = element.getAttributeValue(NAME_ATTRIBUTE_NAME);
+ }
+
+ public void writeExternal(Element element) {
+ element.setAttribute(NAME_ATTRIBUTE_NAME,name);
+ }
+
+ public boolean isPredefined() {
+ return predefined;
+ }
+
+ public void setPredefined(boolean predefined) {
+ this.predefined = predefined;
+ }
+
+ public abstract MatchOptions getMatchOptions();
+
+ @Override
+ public int compareTo(Configuration other) {
+ int result = StringUtil.naturalCompare(getCategory(), other.getCategory());
+ return result != 0 ? result : StringUtil.naturalCompare(getName(), other.getName());
+ }
+
+ public boolean equals(Object configuration) {
+ if (!(configuration instanceof Configuration)) return false;
+ Configuration other = (Configuration)configuration;
+ if (category != null ? !category.equals(other.category) : other.category != null) {
+ return false;
+ }
+ return name.equals(other.name);
+ }
+
+ public int hashCode() {
+ return getMatchOptions().hashCode();
+ }
+
+ public static void setActiveCreator(ConfigurationCreator creator) {
+ configurationCreator = creator;
+ }
+
+ public static ConfigurationCreator getConfigurationCreator() {
+ return configurationCreator;
+ }
+
+ @NonNls public static final String CONTEXT_VAR_NAME = "__context__";
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/ConfigurationCreator.java b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/ConfigurationCreator.java
new file mode 100644
index 000000000000..74332fd72e74
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/ConfigurationCreator.java
@@ -0,0 +1,12 @@
+package com.intellij.structuralsearch.plugin.ui;
+
+/**
+ * Created by IntelliJ IDEA.
+ * User: Maxim.Mossienko
+ * Date: Apr 21, 2004
+ * Time: 7:46:16 PM
+ * To change this template use File | Settings | File Templates.
+ */
+public interface ConfigurationCreator {
+ Configuration createConfiguration();
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/ConfigurationManager.java b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/ConfigurationManager.java
new file mode 100644
index 000000000000..151e78d6863f
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/ConfigurationManager.java
@@ -0,0 +1,179 @@
+package com.intellij.structuralsearch.plugin.ui;
+
+import com.intellij.icons.AllIcons;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.Messages;
+import com.intellij.openapi.ui.NonEmptyInputValidator;
+import com.intellij.structuralsearch.SSRBundle;
+import com.intellij.structuralsearch.plugin.replace.ui.ReplaceConfiguration;
+import org.jdom.Element;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.*;
+
+/**
+ * Created by IntelliJ IDEA.
+ * User: maxim
+ * Date: 10.02.2004
+ * Time: 14:29:45
+ * To change this template use File | Settings | File Templates.
+ */
+public class ConfigurationManager {
+ @NonNls static final String SEARCH_TAG_NAME = "searchConfiguration";
+ @NonNls static final String REPLACE_TAG_NAME = "replaceConfiguration";
+ @NonNls private static final String SAVE_HISTORY_ATTR_NAME = "history";
+
+ private List<Configuration> configurations;
+ private LinkedList<Configuration> historyConfigurations;
+
+ public void addHistoryConfigurationToFront(Configuration configuration) {
+ if (historyConfigurations == null) historyConfigurations = new LinkedList<Configuration>();
+
+ if (historyConfigurations.indexOf(configuration) == -1) {
+ historyConfigurations.addFirst(configuration);
+ }
+ }
+
+ public void removeHistoryConfiguration(Configuration configuration) {
+ if (historyConfigurations != null) {
+ historyConfigurations.remove(configuration);
+ }
+ }
+
+ public void addConfiguration(Configuration configuration) {
+ if (configurations == null) configurations = new ArrayList<Configuration>();
+
+ if (configurations.indexOf(configuration) == -1) {
+ configurations.add(configuration);
+ }
+ }
+
+ public void removeConfiguration(Configuration configuration) {
+ if (configurations != null) {
+ configurations.remove(configuration);
+ }
+ }
+
+ public void saveConfigurations(Element element) {
+ writeConfigurations(element, configurations, historyConfigurations);
+ }
+
+ public static void writeConfigurations(final Element element,
+ final Collection<Configuration> configurations,
+ final Collection<Configuration> historyConfigurations) {
+ if (configurations != null) {
+ for (final Configuration configuration : configurations) {
+ saveConfiguration(element, configuration);
+ }
+ }
+
+ if (historyConfigurations != null) {
+ for (final Configuration historyConfiguration : historyConfigurations) {
+ final Element infoElement = saveConfiguration(element, historyConfiguration);
+ infoElement.setAttribute(SAVE_HISTORY_ATTR_NAME, "1");
+ }
+ }
+ }
+
+ public static Element saveConfiguration(Element element, final Configuration config) {
+ Element infoElement = new Element(config instanceof SearchConfiguration ? SEARCH_TAG_NAME : REPLACE_TAG_NAME);
+ element.addContent(infoElement);
+ config.writeExternal(infoElement);
+
+ return infoElement;
+ }
+
+ public void loadConfigurations(Element element) {
+ if (configurations != null) return;
+ ArrayList<Configuration> configurations = new ArrayList<Configuration>();
+ ArrayList<Configuration> historyConfigurations = new ArrayList<Configuration>();
+ readConfigurations(element, configurations, historyConfigurations);
+ for (Configuration configuration : historyConfigurations) {
+ addHistoryConfigurationToFront(configuration);
+ }
+ for (Configuration configuration : configurations) {
+ addConfiguration(configuration);
+ }
+ if (this.historyConfigurations != null) {
+ Collections.reverse(this.historyConfigurations);
+ }
+ }
+
+ public static void readConfigurations(final Element element, @NotNull Collection<Configuration> configurations, @NotNull Collection<Configuration> historyConfigurations) {
+ final List<Element> patterns = element.getChildren();
+
+ if (patterns != null && patterns.size() > 0) {
+ for (final Element pattern : patterns) {
+ final Configuration config = readConfiguration(pattern);
+ if (config == null) continue;
+
+ if (pattern.getAttribute(SAVE_HISTORY_ATTR_NAME) != null) {
+ historyConfigurations.add(config);
+ }
+ else {
+ configurations.add(config);
+ }
+ }
+ }
+ }
+
+ public static Configuration readConfiguration(final Element childElement) {
+ String s = childElement.getName();
+ final Configuration config =
+ s.equals(SEARCH_TAG_NAME) ? new SearchConfiguration() : s.equals(REPLACE_TAG_NAME) ? new ReplaceConfiguration():null;
+ if (config != null) config.readExternal(childElement);
+ return config;
+ }
+
+ public Collection<Configuration> getConfigurations() {
+ return configurations;
+ }
+
+ public static Configuration findConfigurationByName(final Collection<Configuration> configurations, final String name) {
+ for(Configuration config:configurations) {
+ if (config.getName().equals(name)) return config;
+ }
+
+ return null;
+ }
+
+ public Collection<Configuration> getHistoryConfigurations() {
+ return historyConfigurations;
+ }
+
+ public static @Nullable String findAppropriateName(@NotNull final Collection<Configuration> configurations, @NotNull String _name,
+ @NotNull final Project project) {
+ Configuration config;
+ String name = _name;
+
+ while ((config = findConfigurationByName(configurations, name)) != null) {
+ int i = Messages.showYesNoDialog(
+ project,
+ SSRBundle.message("overwrite.message"),
+ SSRBundle.message("overwrite.title"),
+ AllIcons.General.QuestionDialog
+ );
+
+ if (i == Messages.YES) {
+ configurations.remove(config);
+ break;
+ }
+ name = showSaveTemplateAsDialog(name, project);
+ if (name == null) break;
+ }
+ return name;
+ }
+
+ public static @Nullable String showSaveTemplateAsDialog(@NotNull String initial, @NotNull Project project) {
+ return Messages.showInputDialog(
+ project,
+ SSRBundle.message("template.name.button"),
+ SSRBundle.message("save.template.description.button"),
+ AllIcons.General.QuestionDialog,
+ initial,
+ new NonEmptyInputValidator()
+ );
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/DialogBase.java b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/DialogBase.java
new file mode 100644
index 000000000000..83d93ba0d05c
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/DialogBase.java
@@ -0,0 +1,152 @@
+package com.intellij.structuralsearch.plugin.ui;
+
+import com.intellij.openapi.MnemonicHelper;
+import com.intellij.CommonBundle;
+
+import javax.swing.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.KeyEvent;
+import java.awt.event.InputEvent;
+import java.awt.*;
+
+import org.jetbrains.annotations.NonNls;
+
+/**
+ * Base dialog class
+ */
+public abstract class DialogBase extends JDialog {
+ private JButton ok;
+ private JButton cancel;
+
+ private Action okAction;
+ private Action cancelAction;
+ private static Rectangle virtualBounds;
+
+ class OkAction extends AbstractAction {
+ OkAction() {
+ putValue(NAME, CommonBundle.getOkButtonText());
+ }
+ public void actionPerformed(ActionEvent e) {
+ doOKAction();
+ }
+ }
+
+ class CancelAction extends AbstractAction {
+ CancelAction() {
+ putValue(NAME,CommonBundle.getCancelButtonText());
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ doCancelAction();
+ }
+ }
+
+ protected DialogBase() {
+ this(null);
+ }
+
+ protected DialogBase(Frame frame) {
+ this(frame,true);
+ }
+
+ protected DialogBase(Frame frame,boolean modal) {
+ super(frame,modal);
+
+ new MnemonicHelper().register(getContentPane());
+
+ okAction = new OkAction();
+ cancelAction = new CancelAction();
+
+ ok = createJButtonForAction(okAction);
+ cancel = createJButtonForAction(cancelAction);
+
+ if (virtualBounds == null) {
+ GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
+ GraphicsDevice[] gs = ge.getScreenDevices();
+ virtualBounds = new Rectangle();
+
+ for (int j = 0; j < gs.length; j++) {
+ GraphicsDevice gd = gs[j];
+ GraphicsConfiguration[] gc = gd.getConfigurations();
+
+ for (int i=0; i < gc.length; i++) {
+ virtualBounds = virtualBounds.union(gc[i].getBounds());
+ }
+ }
+ }
+
+ @NonNls String cancelCommandName = "close";
+ KeyStroke escKeyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE,0);
+ ok.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(escKeyStroke, cancelCommandName);
+ ok.getActionMap().put(cancelCommandName, cancelAction);
+
+ @NonNls String startCommandName = "start";
+ KeyStroke enterKeyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER,InputEvent.CTRL_MASK);
+ ok.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(enterKeyStroke, startCommandName);
+ ok.getActionMap().put(startCommandName, okAction);
+ }
+
+ protected JButton getCancelButton() {
+ return cancel;
+ }
+
+ protected JButton getOkButton() {
+ return ok;
+ }
+
+ protected abstract JComponent createCenterPanel();
+
+ protected JComponent createSouthPanel() {
+ JPanel p = new JPanel( null );
+ p.setLayout( new BoxLayout(p,BoxLayout.X_AXIS) );
+ p.add(Box.createHorizontalGlue());
+ p.add(getOkButton());
+ p.add(getCancelButton());
+ return p;
+ }
+
+ public void init() {
+ getContentPane().setLayout(new BorderLayout());
+ getContentPane().add(BorderLayout.CENTER,createCenterPanel());
+ getContentPane().add(BorderLayout.SOUTH,createSouthPanel());
+ pack();
+
+ Dimension dim = getPreferredSize();
+ setLocation(
+ (int)(virtualBounds.getWidth()/2 - dim.getWidth()/2),
+ (int)(virtualBounds.getHeight()/2 - dim.getHeight()/2)
+ );
+ }
+
+ public void show() {
+ pack();
+ super.show();
+ }
+
+ protected void doCancelAction() {
+ setVisible(false);
+ }
+
+ protected void doOKAction() {
+ setVisible(false);
+ }
+
+ protected void setOKActionEnabled(boolean b) {
+ okAction.setEnabled(b);
+ }
+
+ protected void setOKButtonText(String text) {
+ okAction.putValue(Action.NAME,text);
+ }
+
+ protected void setCancelButtonText(String text) {
+ cancelAction.putValue(Action.NAME,text);
+ }
+
+ protected JButton createJButtonForAction(Action _action) {
+ JButton jb = new JButton( (String)_action.getValue(Action.NAME) );
+ jb.setAction(_action);
+
+ return jb;
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/EditVarConstraintsDialog.java b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/EditVarConstraintsDialog.java
new file mode 100644
index 000000000000..368240ff50be
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/EditVarConstraintsDialog.java
@@ -0,0 +1,635 @@
+package com.intellij.structuralsearch.plugin.ui;
+
+import com.intellij.codeInsight.template.impl.Variable;
+import com.intellij.find.impl.RegExHelpPopup;
+import com.intellij.ide.highlighter.HighlighterFactory;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.editor.Document;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.editor.EditorFactory;
+import com.intellij.openapi.editor.EditorSettings;
+import com.intellij.openapi.editor.colors.ex.DefaultColorSchemesManager;
+import com.intellij.openapi.editor.event.DocumentAdapter;
+import com.intellij.openapi.editor.event.DocumentEvent;
+import com.intellij.openapi.editor.ex.EditorEx;
+import com.intellij.openapi.fileTypes.FileType;
+import com.intellij.openapi.fileTypes.FileTypeManager;
+import com.intellij.openapi.fileTypes.FileTypes;
+import com.intellij.openapi.fileTypes.StdFileTypes;
+import com.intellij.openapi.help.HelpManager;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.ComponentWithBrowseButton;
+import com.intellij.openapi.ui.DialogWrapper;
+import com.intellij.openapi.ui.Messages;
+import com.intellij.openapi.ui.popup.JBPopup;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.psi.PsiDocumentManager;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.PsiFileFactory;
+import com.intellij.structuralsearch.MatchVariableConstraint;
+import com.intellij.structuralsearch.NamedScriptableDefinition;
+import com.intellij.structuralsearch.ReplacementVariableDefinition;
+import com.intellij.structuralsearch.SSRBundle;
+import com.intellij.structuralsearch.impl.matcher.predicates.ScriptSupport;
+import com.intellij.structuralsearch.plugin.replace.ReplaceOptions;
+import com.intellij.structuralsearch.plugin.replace.ui.ReplaceConfiguration;
+import com.intellij.ui.ComboboxWithBrowseButton;
+import com.intellij.ui.EditorTextField;
+import com.intellij.ui.components.labels.LinkLabel;
+import com.intellij.ui.components.labels.LinkListener;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.NotNull;
+
+import javax.swing.*;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+import javax.swing.text.BadLocationException;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.List;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+
+/**
+ * @author Maxim.Mossienko
+ * Date: Mar 25, 2004
+ * Time: 1:52:18 PM
+ */
+class EditVarConstraintsDialog extends DialogWrapper {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.structuralsearch.plugin.ui.EditVarConstraintsDialog");
+
+ private JTextField maxoccurs;
+ private JCheckBox applyWithinTypeHierarchy;
+ private JCheckBox notRegexp;
+ private EditorTextField regexp;
+ private JTextField minoccurs;
+ private JPanel mainForm;
+ private JCheckBox notWrite;
+ private JCheckBox notRead;
+ private JCheckBox write;
+ private JCheckBox read;
+ private JList parameterList;
+ private JCheckBox partOfSearchResults;
+ private JCheckBox notExprType;
+ private EditorTextField regexprForExprType;
+ private final SearchModel model;
+ private JCheckBox exprTypeWithinHierarchy;
+
+ private final List<Variable> variables;
+ private Variable current;
+ private JCheckBox wholeWordsOnly;
+ private JCheckBox formalArgTypeWithinHierarchy;
+ private JCheckBox invertFormalArgType;
+ private EditorTextField formalArgType;
+ private ComponentWithBrowseButton<EditorTextField> customScriptCode;
+ private JCheckBox maxoccursUnlimited;
+
+ private ComboboxWithBrowseButton withinCombo;
+ private JPanel containedInConstraints;
+ private JCheckBox invertWithinIn;
+ private JPanel expressionConstraints;
+ private JPanel occurencePanel;
+ private JPanel textConstraintsPanel;
+ private JLabel myRegExHelpLabel;
+
+ private static Project myProject;
+
+ EditVarConstraintsDialog(final Project project,SearchModel _model,List<Variable> _variables, boolean replaceContext, FileType fileType) {
+ super(project, false);
+
+ variables = _variables;
+ model = _model;
+
+ setTitle(SSRBundle.message("editvarcontraints.edit.variables"));
+
+ regexp.getDocument().addDocumentListener(new MyDocumentListener(notRegexp, applyWithinTypeHierarchy, wholeWordsOnly));
+ read.addChangeListener(new MyChangeListener(notRead, false));
+ write.addChangeListener(new MyChangeListener(notWrite, false));
+ regexprForExprType.getDocument().addDocumentListener(new MyDocumentListener(exprTypeWithinHierarchy, notExprType));
+ formalArgType.getDocument().addDocumentListener(new MyDocumentListener(formalArgTypeWithinHierarchy, invertFormalArgType));
+
+ partOfSearchResults.setEnabled(!replaceContext); // todo: this doesn't do anything
+ containedInConstraints.setVisible(false);
+ withinCombo.getComboBox().setEditable(true);
+
+ withinCombo.getButton().addActionListener(new ActionListener() {
+ public void actionPerformed(final ActionEvent e) {
+ final SelectTemplateDialog dialog = new SelectTemplateDialog(project, false, false);
+ dialog.show();
+ if (dialog.getExitCode() == OK_EXIT_CODE) {
+ final Configuration[] selectedConfigurations = dialog.getSelectedConfigurations();
+ if (selectedConfigurations.length == 1) {
+ withinCombo.getComboBox().getEditor().setItem(selectedConfigurations[0].getMatchOptions().getSearchPattern()); // TODO:
+ }
+ }
+ }
+ });
+
+ boolean hasContextVar = false;
+ for(Variable var:variables) {
+ if (Configuration.CONTEXT_VAR_NAME.equals(var.getName())) {
+ hasContextVar = true; break;
+ }
+ }
+
+ if (!hasContextVar) {
+ variables.add(new Variable(Configuration.CONTEXT_VAR_NAME, "", "", true));
+ }
+
+ if (fileType == StdFileTypes.JAVA) {
+
+ formalArgTypeWithinHierarchy.setEnabled(true);
+ invertFormalArgType.setEnabled(true);
+ formalArgType.setEnabled(true);
+
+ exprTypeWithinHierarchy.setEnabled(true);
+ notExprType.setEnabled(true);
+ regexprForExprType.setEnabled(true);
+
+ read.setEnabled(true);
+ notRead.setEnabled(false);
+ write.setEnabled(true);
+ notWrite.setEnabled(false);
+
+ applyWithinTypeHierarchy.setEnabled(true);
+ } else {
+ formalArgTypeWithinHierarchy.setEnabled(false);
+ invertFormalArgType.setEnabled(false);
+ formalArgType.setEnabled(false);
+
+ exprTypeWithinHierarchy.setEnabled(false);
+ notExprType.setEnabled(false);
+ regexprForExprType.setEnabled(false);
+
+ read.setEnabled(false);
+ notRead.setEnabled(false);
+ write.setEnabled(false);
+ notWrite.setEnabled(false);
+
+ applyWithinTypeHierarchy.setEnabled(false);
+ }
+
+ parameterList.setModel(
+ new AbstractListModel() {
+ public Object getElementAt(int index) {
+ return variables.get(index);
+ }
+
+ public int getSize() {
+ return variables.size();
+ }
+ }
+ );
+
+ parameterList.setSelectionMode( ListSelectionModel.SINGLE_SELECTION );
+
+ parameterList.getSelectionModel().addListSelectionListener(
+ new ListSelectionListener() {
+ boolean rollingBackSelection;
+
+ public void valueChanged(ListSelectionEvent e) {
+ if (e.getValueIsAdjusting()) return;
+ if (rollingBackSelection) {
+ rollingBackSelection=false;
+ return;
+ }
+ final Variable var = variables.get(parameterList.getSelectedIndex());
+ if (validateParameters()) {
+ if (current!=null) copyValuesFromUI(current);
+ ApplicationManager.getApplication().runWriteAction(new Runnable() { public void run() { copyValuesToUI(var); }});
+ current = var;
+ } else {
+ rollingBackSelection = true;
+ parameterList.setSelectedIndex(e.getFirstIndex()==parameterList.getSelectedIndex()?e.getLastIndex():e.getFirstIndex());
+ }
+ }
+ }
+ );
+
+ parameterList.setCellRenderer(
+ new DefaultListCellRenderer() {
+ public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
+ String name = ((Variable)value).getName();
+ if (Configuration.CONTEXT_VAR_NAME.equals(name)) name = SSRBundle.message("complete.match.variable.name");
+ if (isReplacementVariable(name)) {
+ name = stripReplacementVarDecoration(name);
+ }
+ return super.getListCellRendererComponent(list, name, index, isSelected, cellHasFocus);
+ }
+ }
+ );
+
+ maxoccursUnlimited.addChangeListener(new MyChangeListener(maxoccurs, true));
+
+ customScriptCode.getButton().addActionListener(new ActionListener() {
+ public void actionPerformed(final ActionEvent e) {
+ final EditScriptDialog dialog = new EditScriptDialog(project, customScriptCode.getChildComponent().getText());
+ dialog.show();
+ if (dialog.getExitCode() == OK_EXIT_CODE) {
+ customScriptCode.getChildComponent().setText(dialog.getScriptText());
+ }
+ }
+ });
+ init();
+
+ if (variables.size() > 0) parameterList.setSelectedIndex(0);
+ }
+
+ private static String stripReplacementVarDecoration(String name) {
+ name = name.substring(0, name.length() - ReplaceConfiguration.REPLACEMENT_VARIABLE_SUFFIX.length());
+ return name;
+ }
+
+ private static boolean isReplacementVariable(String name) {
+ return name.endsWith(ReplaceConfiguration.REPLACEMENT_VARIABLE_SUFFIX);
+ }
+
+ private boolean validateParameters() {
+ return validateRegExp(regexp) && validateRegExp(regexprForExprType) &&
+ validateIntOccurence(minoccurs) &&
+ validateScript(customScriptCode.getChildComponent()) &&
+ (maxoccursUnlimited.isSelected() || validateIntOccurence(maxoccurs));
+ }
+
+ protected JComponent createCenterPanel() {
+ return mainForm;
+ }
+
+ protected void doOKAction() {
+ if(validateParameters()) {
+ if (current!=null) copyValuesFromUI(current);
+ super.doOKAction();
+ }
+ }
+
+ void copyValuesFromUI(Variable var) {
+ String varName = var.getName();
+ Configuration configuration = model.getConfig();
+
+ if (isReplacementVariable(varName)) {
+ saveScriptInfo(getOrAddReplacementVariableDefinition(varName, configuration));
+ return;
+ }
+
+ MatchVariableConstraint varInfo = getOrAddVariableConstraint(varName, configuration);
+
+ varInfo.setInvertReadAccess(notRead.isSelected());
+ varInfo.setReadAccess(read.isSelected());
+ varInfo.setInvertWriteAccess(notWrite.isSelected());
+ varInfo.setWriteAccess(write.isSelected());
+ varInfo.setRegExp(regexp.getDocument().getText());
+ varInfo.setInvertRegExp(notRegexp.isSelected());
+
+ int minCount = Integer.parseInt( minoccurs.getText() );
+ varInfo.setMinCount(minCount);
+
+ int maxCount;
+ if (maxoccursUnlimited.isSelected()) maxCount = Integer.MAX_VALUE;
+ else maxCount = Integer.parseInt( maxoccurs.getText() );
+
+ varInfo.setMaxCount(maxCount);
+ varInfo.setWithinHierarchy(applyWithinTypeHierarchy.isSelected());
+ varInfo.setInvertRegExp(notRegexp.isSelected());
+
+ varInfo.setPartOfSearchResults(partOfSearchResults.isEnabled() && partOfSearchResults.isSelected());
+
+ varInfo.setInvertExprType(notExprType.isSelected());
+ varInfo.setNameOfExprType(regexprForExprType.getDocument().getText());
+ varInfo.setExprTypeWithinHierarchy(exprTypeWithinHierarchy.isSelected());
+ varInfo.setWholeWordsOnly(wholeWordsOnly.isSelected());
+ varInfo.setInvertFormalType(invertFormalArgType.isSelected());
+ varInfo.setFormalArgTypeWithinHierarchy(formalArgTypeWithinHierarchy.isSelected());
+ varInfo.setNameOfFormalArgType(formalArgType.getDocument().getText());
+ saveScriptInfo(varInfo);
+
+ final String withinConstraint = (String)withinCombo.getComboBox().getEditor().getItem();
+ varInfo.setWithinConstraint(withinConstraint.length() > 0 ? "\"" + withinConstraint +"\"":"");
+ varInfo.setInvertWithinConstraint(invertWithinIn.isSelected());
+ }
+
+ private static MatchVariableConstraint getOrAddVariableConstraint(String varName, Configuration configuration) {
+ MatchVariableConstraint varInfo = configuration.getMatchOptions().getVariableConstraint(varName);
+
+ if (varInfo == null) {
+ varInfo = new MatchVariableConstraint();
+ varInfo.setName(varName);
+ configuration.getMatchOptions().addVariableConstraint(varInfo);
+ }
+ return varInfo;
+ }
+
+ private static ReplacementVariableDefinition getOrAddReplacementVariableDefinition(String varName, Configuration configuration) {
+ ReplaceOptions replaceOptions = ((ReplaceConfiguration)configuration).getOptions();
+ String realVariableName = stripReplacementVarDecoration(varName);
+ ReplacementVariableDefinition variableDefinition = replaceOptions.getVariableDefinition(realVariableName);
+
+ if (variableDefinition == null) {
+ variableDefinition = new ReplacementVariableDefinition();
+ variableDefinition.setName(realVariableName);
+ replaceOptions.addVariableDefinition(variableDefinition);
+ }
+ return variableDefinition;
+ }
+
+ private void saveScriptInfo(NamedScriptableDefinition varInfo) {
+ varInfo.setScriptCodeConstraint("\"" + customScriptCode.getChildComponent().getText() + "\"");
+ }
+
+ private void copyValuesToUI(Variable var) {
+ Configuration configuration = model.getConfig();
+ String varName = var.getName();
+
+ if (isReplacementVariable(varName)) {
+ ReplacementVariableDefinition definition = ((ReplaceConfiguration)configuration).getOptions().getVariableDefinition(
+ stripReplacementVarDecoration(varName)
+ );
+
+ restoreScriptCode(definition);
+ setSearchConstraintsVisible(false);
+ return;
+ } else {
+ setSearchConstraintsVisible(true);
+ }
+
+ MatchVariableConstraint varInfo = configuration.getMatchOptions().getVariableConstraint(varName);
+
+ if (varInfo == null) {
+ notRead.setSelected(false);
+ notRegexp.setSelected(false);
+ read.setSelected(false);
+ notWrite.setSelected(false);
+ write.setSelected(false);
+ regexp.getDocument().setText("");
+
+ minoccurs.setText("1");
+ maxoccurs.setText("1");
+ maxoccursUnlimited.setSelected(false);
+ applyWithinTypeHierarchy.setSelected(false);
+ partOfSearchResults.setSelected(false);
+
+ regexprForExprType.getDocument().setText("");
+ notExprType.setSelected(false);
+ exprTypeWithinHierarchy.setSelected(false);
+ wholeWordsOnly.setSelected(false);
+
+ invertFormalArgType.setSelected(false);
+ formalArgTypeWithinHierarchy.setSelected(false);
+ formalArgType.getDocument().setText("");
+ customScriptCode.getChildComponent().setText("");
+
+ withinCombo.getComboBox().getEditor().setItem("");
+ invertWithinIn.setSelected(false);
+ } else {
+ notRead.setSelected(varInfo.isInvertReadAccess());
+ read.setSelected(varInfo.isReadAccess());
+ notWrite.setSelected(varInfo.isInvertWriteAccess());
+ write.setSelected(varInfo.isWriteAccess());
+
+ applyWithinTypeHierarchy.setSelected(varInfo.isWithinHierarchy());
+ regexp.getDocument().setText(varInfo.getRegExp());
+ //doProcessing(applyWithinTypeHierarchy,regexp);
+
+ notRegexp.setSelected(varInfo.isInvertRegExp());
+ minoccurs.setText(Integer.toString(varInfo.getMinCount()));
+
+ if(varInfo.getMaxCount() == Integer.MAX_VALUE) {
+ maxoccursUnlimited.setSelected(true);
+ maxoccurs.setText("");
+ } else {
+ maxoccursUnlimited.setSelected(false);
+ maxoccurs.setText(Integer.toString(varInfo.getMaxCount()));
+ }
+
+ partOfSearchResults.setSelected( partOfSearchResults.isEnabled() && varInfo.isPartOfSearchResults() );
+
+ exprTypeWithinHierarchy.setSelected(varInfo.isExprTypeWithinHierarchy());
+ regexprForExprType.getDocument().setText(varInfo.getNameOfExprType());
+
+ notExprType.setSelected( varInfo.isInvertExprType() );
+ wholeWordsOnly.setSelected( varInfo.isWholeWordsOnly() );
+
+ invertFormalArgType.setSelected( varInfo.isInvertFormalType() );
+ formalArgTypeWithinHierarchy.setSelected(varInfo.isFormalArgTypeWithinHierarchy());
+ formalArgType.getDocument().setText(varInfo.getNameOfFormalArgType());
+ restoreScriptCode(varInfo);
+
+ withinCombo.getComboBox().getEditor().setItem(StringUtil.stripQuotesAroundValue(varInfo.getWithinConstraint()));
+ invertWithinIn.setSelected(varInfo.isInvertWithinConstraint());
+ }
+
+ boolean isExprContext = true;
+ final boolean contextVar = Configuration.CONTEXT_VAR_NAME.equals(var.getName());
+ if (contextVar) isExprContext = false;
+ containedInConstraints.setVisible(contextVar);
+ expressionConstraints.setVisible(isExprContext);
+ partOfSearchResults.setEnabled(!contextVar); //?
+
+ occurencePanel.setVisible(!contextVar);
+ }
+
+ private void setSearchConstraintsVisible(boolean b) {
+ textConstraintsPanel.setVisible(b);
+ occurencePanel.setVisible(b);
+ expressionConstraints.setVisible(b);
+ partOfSearchResults.setVisible(b);
+ containedInConstraints.setVisible(b);
+ pack();
+ }
+
+ private void restoreScriptCode(NamedScriptableDefinition varInfo) {
+ customScriptCode.getChildComponent().setText(
+ varInfo != null ? StringUtil.stripQuotesAroundValue(varInfo.getScriptCodeConstraint()) : "");
+ }
+
+ protected String getDimensionServiceKey() {
+ return "#com.intellij.structuralsearch.plugin.ui.EditVarConstraintsDialog";
+ }
+
+ private static boolean validateRegExp(EditorTextField field) {
+ try {
+ final String s = field.getDocument().getText();
+ if (s.length() > 0) {
+ Pattern.compile(s);
+ }
+ } catch(PatternSyntaxException ex) {
+ Messages.showErrorDialog(SSRBundle.message("invalid.regular.expression"), SSRBundle.message("invalid.regular.expression"));
+ field.requestFocus();
+ return false;
+ }
+ return true;
+ }
+
+ private static boolean validateScript(EditorTextField field) {
+ final String text = field.getText();
+
+ if (text.length() > 0) {
+ final String message = ScriptSupport.checkValidScript(text);
+
+ if (message != null) {
+ Messages.showErrorDialog(message, SSRBundle.message("invalid.groovy.script"));
+ field.requestFocus();
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private static boolean validateIntOccurence(JTextField field) {
+ try {
+ int a = Integer.parseInt(field.getText());
+ if (a==-1) throw new NumberFormatException();
+ } catch(NumberFormatException ex) {
+ Messages.showErrorDialog(SSRBundle.message("invalid.occurence.count"), SSRBundle.message("invalid.occurence.count"));
+ field.requestFocus();
+ return false;
+ }
+ return true;
+ }
+
+ @NotNull
+ protected Action[] createActions() {
+ return new Action[]{getOKAction(), getCancelAction(), getHelpAction()};
+ }
+
+ protected void doHelpAction() {
+ HelpManager.getInstance().invokeHelp("reference.dialogs.search.replace.structural.editvariable");
+ }
+
+ private void createUIComponents() {
+ regexp = createRegexComponent();
+ regexprForExprType = createRegexComponent();
+ formalArgType = createRegexComponent();
+ customScriptCode = new ComponentWithBrowseButton<EditorTextField>(createScriptComponent(), null);
+
+ myRegExHelpLabel = new LinkLabel(SSRBundle.message("regular.expression.help.label"), null, new LinkListener() {
+ public void linkSelected(LinkLabel aSource, Object aLinkData) {
+ try {
+ final JBPopup helpPopup = RegExHelpPopup.createRegExHelpPopup();
+ helpPopup.showInCenterOf(mainForm);
+ }
+ catch (BadLocationException e) {
+ LOG.info(e);
+ }
+ }
+ });
+
+ myRegExHelpLabel.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 5));
+ }
+
+ private static EditorTextField createRegexComponent() {
+ @NonNls final String fileName = "1.regexp";
+ final FileType fileType = getFileType(fileName);
+ final Document doc = createDocument(fileName, fileType, "");
+ return new EditorTextField(doc, myProject, fileType);
+ }
+
+ private static EditorTextField createScriptComponent() {
+ @NonNls final String fileName = "1.groovy";
+ final FileType fileType = getFileType(fileName);
+ final Document doc = createDocument(fileName, fileType, "");
+ return new EditorTextField(doc, myProject, fileType);
+ }
+
+ private static Document createDocument(final String fileName, final FileType fileType, String text) {
+ final PsiFile file = PsiFileFactory.getInstance(myProject).createFileFromText(fileName, fileType, text, -1, true);
+
+ return PsiDocumentManager.getInstance(myProject).getDocument(file);
+ }
+
+ private static FileType getFileType(final String fileName) {
+ FileType fileType = FileTypeManager.getInstance().getFileTypeByFileName(fileName);
+ if (fileType == FileTypes.UNKNOWN) fileType = FileTypes.PLAIN_TEXT;
+ return fileType;
+ }
+
+ public static void setProject(final Project project) {
+ myProject = project;
+ }
+
+ private static class MyChangeListener implements ChangeListener {
+ private final JComponent component;
+ private final boolean inverted;
+
+ MyChangeListener(JComponent _component, boolean _inverted) {
+ component = _component;
+ inverted = _inverted;
+ }
+
+ public void stateChanged(ChangeEvent e) {
+ final JCheckBox jCheckBox = (JCheckBox)e.getSource();
+ component.setEnabled(inverted ^ jCheckBox.isSelected());
+ }
+ }
+
+ private static class MyDocumentListener extends DocumentAdapter {
+ private final JComponent[] components;
+
+ private MyDocumentListener(JComponent... _components) {
+ components = _components;
+ }
+
+ @Override
+ public void documentChanged(DocumentEvent e) {
+ final boolean enable = e.getDocument().getTextLength() > 0;
+ for (JComponent component : components) {
+ component.setEnabled(enable);
+ }
+ }
+ }
+
+ private static Editor createEditor(final Project project, final String text, final String fileName) {
+ final FileType fileType = getFileType(fileName);
+ final Document doc = createDocument(fileName, fileType, text);
+ final Editor editor = EditorFactory.getInstance().createEditor(doc, project);
+
+ ((EditorEx)editor).setEmbeddedIntoDialogWrapper(true);
+ final EditorSettings settings = editor.getSettings();
+ settings.setLineNumbersShown(false);
+ settings.setFoldingOutlineShown(false);
+ settings.setRightMarginShown(false);
+ settings.setLineMarkerAreaShown(false);
+ settings.setIndentGuidesShown(false);
+ ((EditorEx)editor).setHighlighter(HighlighterFactory.createHighlighter(fileType, DefaultColorSchemesManager.getInstance().getAllSchemes()[0], project));
+
+ return editor;
+ }
+
+ private static class EditScriptDialog extends DialogWrapper {
+ private final Editor editor;
+
+ public EditScriptDialog(final Project project, String text) {
+ super(project, true);
+ setTitle(SSRBundle.message("edit.groovy.script.constraint.title"));
+ editor = createEditor(project, text, "1.groovy");
+ init();
+ }
+
+ @Override
+ protected String getDimensionServiceKey() {
+ return getClass().getName();
+ }
+
+ @Override
+ public JComponent getPreferredFocusedComponent() {
+ return editor.getContentComponent();
+ }
+
+ protected JComponent createCenterPanel() {
+ return editor.getComponent();
+ }
+
+ String getScriptText() {
+ return editor.getDocument().getText();
+ }
+
+ @Override
+ protected void dispose() {
+ EditorFactory.getInstance().releaseEditor(editor);
+ super.dispose();
+ }
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/ExistingTemplatesComponent.java b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/ExistingTemplatesComponent.java
new file mode 100644
index 000000000000..3dd79e16175c
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/ExistingTemplatesComponent.java
@@ -0,0 +1,333 @@
+package com.intellij.structuralsearch.plugin.ui;
+
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.DialogWrapper;
+import com.intellij.structuralsearch.SSRBundle;
+import com.intellij.structuralsearch.StructuralSearchUtil;
+import com.intellij.structuralsearch.plugin.StructuralSearchPlugin;
+import com.intellij.ui.*;
+import com.intellij.ui.components.JBList;
+import com.intellij.ui.treeStructure.Tree;
+import com.intellij.util.containers.Convertor;
+import com.intellij.util.ui.tree.TreeUtil;
+
+import javax.swing.*;
+import javax.swing.tree.*;
+import java.awt.*;
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
+import java.awt.event.MouseEvent;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * Created by IntelliJ IDEA.
+ * User: Maxim.Mossienko
+ * Date: Apr 2, 2004
+ * Time: 1:27:54 PM
+ * To change this template use File | Settings | File Templates.
+ */
+public class ExistingTemplatesComponent {
+ private final Tree patternTree;
+ private final DefaultTreeModel patternTreeModel;
+ private final DefaultMutableTreeNode userTemplatesNode;
+ private final JComponent panel;
+ private final DefaultListModel historyModel;
+ private final JList historyList;
+ private final JComponent historyPanel;
+ private DialogWrapper owner;
+ private final Project project;
+
+ private ExistingTemplatesComponent(Project project) {
+
+ this.project = project;
+ final DefaultMutableTreeNode root;
+ patternTreeModel = new DefaultTreeModel(
+ root = new DefaultMutableTreeNode(null)
+ );
+
+ DefaultMutableTreeNode parent = null;
+ String lastCategory = null;
+ LinkedList<Object> nodesToExpand = new LinkedList<Object>();
+
+ final List<Configuration> predefined = StructuralSearchUtil.getPredefinedTemplates();
+ for (final Configuration info : predefined) {
+ final DefaultMutableTreeNode node = new DefaultMutableTreeNode(info);
+
+ if (lastCategory == null || !lastCategory.equals(info.getCategory())) {
+ if (info.getCategory().length() > 0) {
+ root.add(parent = new DefaultMutableTreeNode(info.getCategory()));
+ nodesToExpand.add(parent);
+ lastCategory = info.getCategory();
+ }
+ else {
+ root.add(node);
+ continue;
+ }
+ }
+
+ parent.add(node);
+ }
+
+ parent = new DefaultMutableTreeNode(SSRBundle.message("user.defined.category"));
+ userTemplatesNode = parent;
+ root.add(parent);
+ nodesToExpand.add(parent);
+
+ final ConfigurationManager configurationManager = StructuralSearchPlugin.getInstance(this.project).getConfigurationManager();
+ if (configurationManager.getConfigurations() != null) {
+ for (final Configuration config : configurationManager.getConfigurations()) {
+ parent.add(new DefaultMutableTreeNode(config));
+ }
+ }
+
+ patternTree = createTree(patternTreeModel);
+
+ for (final Object aNodesToExpand : nodesToExpand) {
+ patternTree.expandPath(
+ new TreePath(new Object[]{root, aNodesToExpand})
+ );
+ }
+
+ panel = ToolbarDecorator.createDecorator(patternTree)
+ .setAddAction(new AnActionButtonRunnable() {
+ @Override
+ public void run(AnActionButton button) {
+ addSelectedTreeNodeAndClose();
+ }
+ }).setRemoveAction(new AnActionButtonRunnable() {
+ @Override
+ public void run(AnActionButton button) {
+ Object selection = patternTree.getLastSelectedPathComponent();
+
+ if (selection instanceof DefaultMutableTreeNode) {
+ DefaultMutableTreeNode node = (DefaultMutableTreeNode)selection;
+
+ if (node.getUserObject() instanceof Configuration) {
+ Configuration configuration = (Configuration)node.getUserObject();
+ patternTreeModel.removeNodeFromParent(node);
+ configurationManager.removeConfiguration(configuration);
+ }
+ }
+ }
+ }).createPanel();
+
+ new JPanel(new BorderLayout());
+
+ configureSelectTemplateAction(patternTree);
+
+ historyModel = new DefaultListModel();
+ historyPanel = new JPanel(new BorderLayout());
+ historyPanel.add(
+ BorderLayout.NORTH,
+ new JLabel(SSRBundle.message("used.templates"))
+ );
+ Component view = historyList = new JBList(historyModel);
+ historyPanel.add(
+ BorderLayout.CENTER,
+ ScrollPaneFactory.createScrollPane(view)
+ );
+
+ historyList.setCellRenderer(
+ new ListCellRenderer()
+ );
+
+ historyList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+
+ new ListSpeedSearch(historyList);
+
+ if (configurationManager.getHistoryConfigurations() != null) {
+ for (final Configuration configuration : configurationManager.getHistoryConfigurations()) {
+ historyModel.addElement(configuration);
+ }
+
+ historyList.setSelectedIndex(0);
+ }
+
+ configureSelectTemplateAction(historyList);
+ }
+
+ private void configureSelectTemplateAction(JComponent component) {
+ component.addKeyListener(
+ new KeyAdapter() {
+ public void keyPressed(KeyEvent e) {
+ if (e.getKeyCode() == KeyEvent.VK_ENTER) {
+ owner.close(DialogWrapper.OK_EXIT_CODE);
+ }
+ }
+ }
+ );
+
+ new DoubleClickListener() {
+ @Override
+ protected boolean onDoubleClick(MouseEvent event) {
+ owner.close(DialogWrapper.OK_EXIT_CODE);
+ return true;
+ }
+ }.installOn(component);
+ }
+
+ private void addSelectedTreeNodeAndClose() {
+ addConfigurationToUserTemplates(
+ Configuration.getConfigurationCreator().createConfiguration()
+ );
+ owner.close(DialogWrapper.OK_EXIT_CODE);
+ }
+
+ private static Tree createTree(TreeModel treeModel) {
+ final Tree tree = new Tree(treeModel);
+
+ tree.setRootVisible(false);
+ tree.setShowsRootHandles(true);
+ tree.setDragEnabled(false);
+ tree.setEditable(false);
+ tree.getSelectionModel().setSelectionMode(TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION);
+
+ tree.setCellRenderer(new TreeCellRenderer());
+
+ new TreeSpeedSearch(
+ tree,
+ new Convertor<TreePath, String>() {
+ public String convert(TreePath object) {
+ DefaultMutableTreeNode node = (DefaultMutableTreeNode)object.getLastPathComponent();
+ Object displayValue = node.getUserObject();
+
+ if (displayValue instanceof Configuration) {
+ displayValue = ((Configuration)displayValue).getName();
+ }
+ else {
+ displayValue = "";
+ }
+ return displayValue.toString();
+ }
+ }
+ );
+
+ return tree;
+ }
+
+ public JTree getPatternTree() {
+ return patternTree;
+ }
+
+ public JComponent getTemplatesPanel() {
+ return panel;
+ }
+
+ public static ExistingTemplatesComponent getInstance(Project project) {
+ StructuralSearchPlugin plugin = StructuralSearchPlugin.getInstance(project);
+
+ if (plugin.getExistingTemplatesComponent() == null) {
+ plugin.setExistingTemplatesComponent(new ExistingTemplatesComponent(project));
+ }
+
+ return plugin.getExistingTemplatesComponent();
+ }
+
+ static class ListCellRenderer extends DefaultListCellRenderer {
+ public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
+ if (value instanceof Configuration) {
+ value = ((Configuration)value).getName();
+ }
+
+ Component comp = super.getListCellRendererComponent(
+ list,
+ value,
+ index,
+ isSelected,
+ cellHasFocus
+ );
+
+ return comp;
+ }
+ }
+
+ static class TreeCellRenderer extends DefaultTreeCellRenderer {
+ TreeCellRenderer() {
+ setOpenIcon(null);
+ setLeafIcon(null);
+ setClosedIcon(null);
+ }
+
+ public Component getTreeCellRendererComponent(JTree tree,
+ Object value,
+ boolean sel,
+ boolean expanded,
+ boolean leaf,
+ int row,
+ boolean hasFocus) {
+ DefaultMutableTreeNode treeNode = (DefaultMutableTreeNode)value;
+ Object displayValue = treeNode.getUserObject();
+
+ if (displayValue instanceof Configuration) {
+ displayValue = ((Configuration)displayValue).getName();
+ }
+
+ Component comp = super.getTreeCellRendererComponent(
+ tree,
+ displayValue,
+ sel,
+ expanded,
+ leaf,
+ row,
+ hasFocus
+ );
+
+ return comp;
+ }
+ }
+
+ void addConfigurationToHistory(Configuration configuration) {
+ //configuration.setName( configuration.getName() +" "+new Date());
+ historyModel.insertElementAt(configuration, 0);
+ ConfigurationManager configurationManager = StructuralSearchPlugin.getInstance(project).getConfigurationManager();
+ configurationManager.addHistoryConfigurationToFront(configuration);
+ historyList.setSelectedIndex(0);
+
+ if (historyModel.getSize() > 25) {
+ configurationManager.removeHistoryConfiguration(
+ (Configuration)historyModel.getElementAt(25)
+ );
+ // we add by one!
+ historyModel.removeElementAt(25);
+ }
+ }
+
+ private void insertNode(Configuration configuration, DefaultMutableTreeNode parent, int index) {
+ DefaultMutableTreeNode node;
+ patternTreeModel.insertNodeInto(
+ node = new DefaultMutableTreeNode(
+ configuration
+ ),
+ parent,
+ index
+ );
+
+ TreeUtil.selectPath(
+ patternTree,
+ new TreePath(new Object[]{patternTreeModel.getRoot(), parent, node})
+ );
+ }
+
+ void addConfigurationToUserTemplates(Configuration configuration) {
+ insertNode(configuration, userTemplatesNode, userTemplatesNode.getChildCount());
+ ConfigurationManager configurationManager = StructuralSearchPlugin.getInstance(project).getConfigurationManager();
+ configurationManager.addConfiguration(configuration);
+ }
+
+ boolean isConfigurationFromHistory(Configuration config) {
+ return historyModel.indexOf(config) != -1;
+ }
+
+ public JList getHistoryList() {
+ return historyList;
+ }
+
+ public JComponent getHistoryPanel() {
+ return historyPanel;
+ }
+
+ public void setOwner(DialogWrapper owner) {
+ this.owner = owner;
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/SearchCommand.java b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/SearchCommand.java
new file mode 100644
index 000000000000..8cb7e55ea290
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/SearchCommand.java
@@ -0,0 +1,142 @@
+package com.intellij.structuralsearch.plugin.ui;
+
+import com.intellij.notification.NotificationGroup;
+import com.intellij.openapi.application.ModalityState;
+import com.intellij.openapi.progress.ProgressIndicator;
+import com.intellij.openapi.progress.ProgressManager;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.MessageType;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.openapi.wm.ToolWindowId;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.PsiNameIdentifierOwner;
+import com.intellij.structuralsearch.*;
+import com.intellij.structuralsearch.impl.matcher.MatchResultImpl;
+import com.intellij.structuralsearch.plugin.StructuralSearchPlugin;
+import com.intellij.usageView.UsageInfo;
+import com.intellij.usages.Usage;
+import com.intellij.usages.UsageInfo2UsageAdapter;
+import com.intellij.util.Alarm;
+import com.intellij.util.ObjectUtils;
+import com.intellij.util.Processor;
+
+/**
+ * Created by IntelliJ IDEA.
+ * User: Maxim.Mossienko
+ * Date: Mar 15, 2004
+ * Time: 4:49:07 PM
+ * To change this template use File | Settings | File Templates.
+ */
+public class SearchCommand {
+ protected UsageViewContext context;
+ private MatchingProcess process;
+ protected Project project;
+
+ public SearchCommand(Project _project, UsageViewContext _context) {
+ project = _project;
+ context = _context;
+ }
+
+ public void findUsages(final Processor<Usage> processor) {
+ final ProgressIndicator progress = ProgressManager.getInstance().getProgressIndicator();
+
+ final MatchResultSink sink = new MatchResultSink() {
+ int count;
+
+ public void setMatchingProcess(MatchingProcess _process) {
+ process = _process;
+ findStarted();
+ }
+
+ public void processFile(PsiFile element) {
+ final VirtualFile virtualFile = element.getVirtualFile();
+ if (virtualFile != null)
+ progress.setText(SSRBundle.message("looking.in.progress.message", virtualFile.getPresentableName()));
+ }
+
+ public void matchingFinished() {
+ findEnded();
+ progress.setText(SSRBundle.message("found.progress.message", count));
+ }
+
+ public ProgressIndicator getProgressIndicator() {
+ return progress;
+ }
+
+ public void newMatch(MatchResult result) {
+ UsageInfo info;
+
+ if (MatchResult.MULTI_LINE_MATCH.equals(result.getName())) {
+ int start = -1;
+ int end = -1;
+ PsiElement parent = result.getMatchRef().getElement().getParent();
+
+ for (final MatchResult matchResult : ((MatchResultImpl)result).getMatches()) {
+ PsiElement el = matchResult.getMatchRef().getElement();
+ final int elementStart = el.getTextRange().getStartOffset();
+
+ if (start == -1 || start > elementStart) {
+ start = elementStart;
+ }
+ final int newend = elementStart + el.getTextLength();
+
+ if (newend > end) {
+ end = newend;
+ }
+ }
+
+ final int parentStart = parent.getTextRange().getStartOffset();
+ int startOffset = start - parentStart;
+ info = new UsageInfo(parent, startOffset, end - parentStart);
+ }
+ else {
+ PsiElement element = result.getMatch();
+ if (element instanceof PsiNameIdentifierOwner) {
+ element = ObjectUtils.notNull(((PsiNameIdentifierOwner)element).getNameIdentifier(), element);
+ }
+ info = new UsageInfo(element, result.getStart(), result.getEnd() == -1 ? element.getTextLength() : result.getEnd());
+ }
+
+ Usage usage = new UsageInfo2UsageAdapter(info);
+ processor.process(usage);
+ foundUsage(result, usage);
+ ++count;
+ }
+ };
+
+ try {
+ new Matcher(project).findMatches(sink, context.getConfiguration().getMatchOptions());
+ }
+ catch (final StructuralSearchException e) {
+ final Alarm alarm = new Alarm();
+ alarm.addRequest(
+ new Runnable() {
+ @Override
+ public void run() {
+ NotificationGroup.toolWindowGroup("Structural Search", ToolWindowId.FIND, true)
+ .createNotification(SSRBundle.message("problem", e.getMessage()), MessageType.ERROR).notify(project);
+ }
+ },
+ 100, ModalityState.NON_MODAL
+ );
+ }
+ }
+
+ public void stopAsyncSearch() {
+ if (process!=null) process.stop();
+ }
+
+ protected void findStarted() {
+ StructuralSearchPlugin.getInstance(project).setSearchInProgress(true);
+ }
+
+ protected void findEnded() {
+ if (!project.isDisposed()) {
+ StructuralSearchPlugin.getInstance(project).setSearchInProgress(false);
+ }
+ }
+
+ protected void foundUsage(MatchResult result, Usage usage) {
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/SearchConfiguration.java b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/SearchConfiguration.java
new file mode 100644
index 000000000000..afb2f9451bbe
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/SearchConfiguration.java
@@ -0,0 +1,39 @@
+package com.intellij.structuralsearch.plugin.ui;
+
+import com.intellij.structuralsearch.MatchOptions;
+import com.intellij.openapi.actionSystem.AnAction;
+import org.jdom.Element;
+import org.jdom.Attribute;
+import org.jdom.DataConversionException;
+
+/**
+ * Configuration of the search
+ */
+public class SearchConfiguration extends Configuration {
+ private MatchOptions matchOptions;
+
+ public SearchConfiguration() {
+ matchOptions = new MatchOptions();
+ }
+
+ public MatchOptions getMatchOptions() {
+ return matchOptions;
+ }
+
+ public void setMatchOptions(MatchOptions matchOptions) {
+ this.matchOptions = matchOptions;
+ }
+
+ public void readExternal(Element element) {
+ super.readExternal(element);
+
+ matchOptions.readExternal(element);
+ }
+
+ public void writeExternal(Element element) {
+ super.writeExternal(element);
+
+ matchOptions.writeExternal(element);
+ }
+
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/SearchContext.java b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/SearchContext.java
new file mode 100644
index 000000000000..62371e7e29d1
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/SearchContext.java
@@ -0,0 +1,59 @@
+package com.intellij.structuralsearch.plugin.ui;
+
+import com.intellij.openapi.actionSystem.CommonDataKeys;
+import com.intellij.openapi.actionSystem.DataContext;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.fileEditor.FileEditorManager;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.project.ProjectManager;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.PsiManager;
+import com.intellij.structuralsearch.impl.matcher.DataProvider;
+
+/**
+ * Context of the search to be done
+ */
+public final class SearchContext implements DataProvider, Cloneable {
+ private final PsiFile file;
+ private final Project project;
+
+ private SearchContext(Project project, PsiFile file) {
+ this.project = project;
+ this.file = file;
+ }
+
+ public PsiFile getFile() {
+ return file;
+ }
+
+ public Project getProject() {
+ return project;
+ }
+
+ public static SearchContext buildFromDataContext(DataContext context) {
+ Project project = CommonDataKeys.PROJECT.getData(context);
+ if (project == null) {
+ project = ProjectManager.getInstance().getDefaultProject();
+ }
+
+ PsiFile file = CommonDataKeys.PSI_FILE.getData(context);
+ final VirtualFile vFile = CommonDataKeys.VIRTUAL_FILE.getData(context);
+ if (vFile != null && (file == null || !vFile.equals(file.getContainingFile().getVirtualFile()))) {
+ file = PsiManager.getInstance(project).findFile(vFile);
+ }
+ return new SearchContext(project, file);
+ }
+
+ public Editor getEditor() {
+ return FileEditorManager.getInstance(project).getSelectedTextEditor();
+ }
+
+ protected Object clone() {
+ try {
+ return super.clone();
+ } catch(CloneNotSupportedException ex) {
+ return null;
+ }
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/SearchDialog.java b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/SearchDialog.java
new file mode 100644
index 000000000000..a3bee702563e
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/SearchDialog.java
@@ -0,0 +1,995 @@
+package com.intellij.structuralsearch.plugin.ui;
+
+import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer;
+import com.intellij.codeInsight.template.impl.Variable;
+import com.intellij.find.FindBundle;
+import com.intellij.find.FindProgressIndicator;
+import com.intellij.find.FindSettings;
+import com.intellij.ide.IdeBundle;
+import com.intellij.ide.util.scopeChooser.ScopeChooserCombo;
+import com.intellij.lang.Language;
+import com.intellij.lang.LanguageUtil;
+import com.intellij.openapi.application.Result;
+import com.intellij.openapi.application.WriteAction;
+import com.intellij.openapi.command.WriteCommandAction;
+import com.intellij.openapi.editor.Document;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.editor.EditorFactory;
+import com.intellij.openapi.editor.SelectionModel;
+import com.intellij.openapi.editor.event.DocumentEvent;
+import com.intellij.openapi.editor.event.DocumentListener;
+import com.intellij.openapi.fileEditor.FileEditorManager;
+import com.intellij.openapi.fileTypes.FileType;
+import com.intellij.openapi.fileTypes.LanguageFileType;
+import com.intellij.openapi.fileTypes.impl.FileTypeRenderer;
+import com.intellij.openapi.progress.ProgressIndicator;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.ComboBox;
+import com.intellij.openapi.ui.DialogWrapper;
+import com.intellij.openapi.util.Disposer;
+import com.intellij.openapi.util.Factory;
+import com.intellij.openapi.util.TextRange;
+import com.intellij.openapi.wm.ToolWindow;
+import com.intellij.openapi.wm.ToolWindowId;
+import com.intellij.openapi.wm.ToolWindowManager;
+import com.intellij.psi.PsiDocumentManager;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.codeStyle.CodeStyleManager;
+import com.intellij.psi.search.GlobalSearchScope;
+import com.intellij.psi.search.SearchScope;
+import com.intellij.structuralsearch.*;
+import com.intellij.structuralsearch.impl.matcher.MatcherImpl;
+import com.intellij.structuralsearch.plugin.StructuralSearchPlugin;
+import com.intellij.ui.ComboboxSpeedSearch;
+import com.intellij.ui.IdeBorderFactory;
+import com.intellij.ui.ListCellRendererWrapper;
+import com.intellij.ui.TitledSeparator;
+import com.intellij.usages.*;
+import com.intellij.util.Alarm;
+import com.intellij.util.Processor;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.NotNull;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.util.*;
+import java.util.List;
+
+/**
+ * Class to show the user the request for search
+ */
+@SuppressWarnings({"RefusedBequest", "AssignmentToStaticFieldFromInstanceMethod"})
+public class SearchDialog extends DialogWrapper implements ConfigurationCreator {
+ protected SearchContext searchContext;
+
+ // text for search
+ protected Editor searchCriteriaEdit;
+
+ // options of search scope
+ private ScopeChooserCombo myScopeChooserCombo;
+
+ private JCheckBox recursiveMatching;
+ private JCheckBox caseSensitiveMatch;
+
+ private JComboBox fileTypes;
+ private JComboBox contexts;
+ private JComboBox dialects;
+ private JLabel status;
+ private JLabel statusText;
+
+ protected SearchModel model;
+ private JCheckBox openInNewTab;
+ private final Alarm myAlarm = new Alarm(Alarm.ThreadToUse.SHARED_THREAD);
+
+ public static final String USER_DEFINED = SSRBundle.message("new.template.defaultname");
+ protected final ExistingTemplatesComponent existingTemplatesComponent;
+
+ private boolean useLastConfiguration;
+
+ private static boolean ourOpenInNewTab;
+
+ @NonNls private FileType ourFtSearchVariant = StructuralSearchUtil.getDefaultFileType();
+ private static Language ourDialect = null;
+ private static String ourContext = null;
+
+ private final boolean myShowScopePanel;
+ private final boolean myRunFindActionOnClose;
+ private boolean myDoingOkAction;
+
+ private String mySavedEditorText;
+ private JPanel myContentPanel;
+ private JComponent myEditorPanel;
+
+ public SearchDialog(SearchContext searchContext) {
+ this(searchContext, true, true);
+ }
+
+ public SearchDialog(SearchContext searchContext, boolean showScope, boolean runFindActionOnClose) {
+ super(searchContext.getProject(), true);
+
+ if (showScope) setModal(false);
+ myShowScopePanel = showScope;
+ myRunFindActionOnClose = runFindActionOnClose;
+ this.searchContext = (SearchContext)searchContext.clone();
+ setTitle(getDefaultTitle());
+
+ if (runFindActionOnClose) {
+ setOKButtonText(FindBundle.message("find.dialog.find.button"));
+ }
+
+ existingTemplatesComponent = ExistingTemplatesComponent.getInstance(this.searchContext.getProject());
+ model = new SearchModel(createConfiguration());
+
+ init();
+ }
+
+ protected UsageViewContext createUsageViewContext(Configuration configuration) {
+ return new UsageViewContext(searchContext, configuration);
+ }
+
+ public void setUseLastConfiguration(boolean useLastConfiguration) {
+ this.useLastConfiguration = useLastConfiguration;
+ }
+
+ public void setSearchPattern(final Configuration config) {
+ model.setShadowConfig(config);
+ setValuesFromConfig(config);
+ initiateValidation();
+ }
+
+ protected Editor createEditor(final SearchContext searchContext, String text) {
+ Editor editor = null;
+
+ if (fileTypes != null) {
+ final FileType fileType = (FileType)fileTypes.getSelectedItem();
+ final Language dialect = (Language)dialects.getSelectedItem();
+
+ final StructuralSearchProfile profile = StructuralSearchUtil.getProfileByFileType(fileType);
+ if (profile != null) {
+ editor = profile.createEditor(searchContext, fileType, dialect, text, useLastConfiguration);
+ }
+ }
+
+ if (editor == null) {
+ final EditorFactory factory = EditorFactory.getInstance();
+ final Document document = factory.createDocument("");
+ editor = factory.createEditor(document, searchContext.getProject());
+ editor.getSettings().setFoldingOutlineShown(false);
+ }
+
+ editor.getDocument().addDocumentListener(new DocumentListener() {
+ @Override
+ public void beforeDocumentChange(final DocumentEvent event) {
+ }
+
+ @Override
+ public void documentChanged(final DocumentEvent event) {
+ initiateValidation();
+ }
+ });
+
+ return editor;
+ }
+
+ private void initiateValidation() {
+ myAlarm.cancelAllRequests();
+ myAlarm.addRequest(new Runnable() {
+
+ @Override
+ public void run() {
+ try {
+ new WriteAction(){
+ @Override
+ protected void run(Result result) throws Throwable {
+ if (!isValid()) {
+ getOKAction().setEnabled(false);
+ }
+ else {
+ getOKAction().setEnabled(true);
+ reportMessage(null, null);
+ }
+ }
+ }.execute();
+ }
+ catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ }, 500);
+ }
+
+ protected void buildOptions(JPanel searchOptions) {
+ recursiveMatching = new JCheckBox(SSRBundle.message("recursive.matching.checkbox"), true);
+ if (isRecursiveSearchEnabled()) {
+ searchOptions.add(UIUtil.createOptionLine(recursiveMatching));
+ }
+
+ caseSensitiveMatch = new JCheckBox(FindBundle.message("find.options.case.sensitive"), true);
+ searchOptions.add(UIUtil.createOptionLine(caseSensitiveMatch));
+
+ final List<FileType> types = new ArrayList<FileType>();
+
+ for (FileType fileType : StructuralSearchUtil.getSuitableFileTypes()) {
+ if (StructuralSearchUtil.getProfileByFileType(fileType) != null) {
+ types.add(fileType);
+ }
+ }
+ Collections.sort(types, new Comparator<FileType>() {
+ @Override
+ public int compare(FileType o1, FileType o2) {
+ return o1.getName().compareToIgnoreCase(o2.getName());
+ }
+ });
+
+ final DefaultComboBoxModel comboBoxModel = new DefaultComboBoxModel(types.toArray(new FileType[types.size()]));
+ comboBoxModel.setSelectedItem(ourFtSearchVariant);
+ fileTypes = new ComboBox(comboBoxModel);
+ fileTypes.setRenderer(new FileTypeRenderer());
+ new ComboboxSpeedSearch(fileTypes) {
+ @Override
+ protected String getElementText(Object element) {
+ return ((FileType)element).getName();
+ }
+ };
+ fileTypes.addItemListener(new ItemListener() {
+ @Override
+ public void itemStateChanged(ItemEvent e) {
+ updateDialectsAndContexts();
+ updateEditor();
+ }
+ });
+
+ contexts = new JComboBox(new DefaultComboBoxModel());
+ contexts.setPreferredSize(new Dimension(60, -1));
+
+ dialects = new JComboBox(new DefaultComboBoxModel());
+ dialects.setRenderer(new ListCellRendererWrapper() {
+ @Override
+ public void customize(JList list, Object value, int index, boolean selected, boolean hasFocus) {
+ if (value == null) {
+ setText("None");
+ }
+ else if (value instanceof Language) {
+ setText(((Language)value).getDisplayName());
+ }
+ }
+ });
+ dialects.addItemListener(new ItemListener() {
+ @Override
+ public void itemStateChanged(ItemEvent e) {
+ updateEditor();
+ }
+ });
+ new ComboboxSpeedSearch(dialects);
+ dialects.setPreferredSize(new Dimension(120, -1));
+
+ final JLabel jLabel = new JLabel(SSRBundle.message("search.dialog.file.type.label"));
+ final JLabel jLabel2 = new JLabel(SSRBundle.message("search.dialog.context.label"));
+ final JLabel jLabel3 = new JLabel(SSRBundle.message("search.dialog.file.dialect.label"));
+ searchOptions.add(
+ UIUtil.createOptionLine(
+ new JComponent[]{
+ jLabel,
+ fileTypes,
+ (JComponent)Box.createHorizontalStrut(8),
+ jLabel2,
+ contexts,
+ (JComponent)Box.createHorizontalStrut(8),
+ jLabel3,
+ dialects,
+ }
+ )
+ );
+
+ jLabel.setLabelFor(fileTypes);
+ jLabel2.setLabelFor(contexts);
+ jLabel3.setLabelFor(dialects);
+
+ detectFileTypeAndDialect();
+
+ fileTypes.setSelectedItem(ourFtSearchVariant);
+ fileTypes.addItemListener(new ItemListener() {
+ @Override
+ public void itemStateChanged(ItemEvent e) {
+ if (e.getStateChange() == ItemEvent.SELECTED) initiateValidation();
+ }
+ });
+
+ dialects.setSelectedItem(ourDialect);
+ contexts.setSelectedItem(ourContext);
+
+ updateDialectsAndContexts();
+ }
+
+ private void updateEditor() {
+ if (myContentPanel != null) {
+ if (myEditorPanel != null) {
+ myContentPanel.remove(myEditorPanel);
+ }
+ disposeEditorContent();
+ myEditorPanel = createEditorContent();
+ myContentPanel.add(myEditorPanel, BorderLayout.CENTER);
+ myContentPanel.revalidate();
+ }
+ }
+
+ private void updateDialectsAndContexts() {
+ final FileType fileType = (FileType)fileTypes.getSelectedItem();
+ if (fileType instanceof LanguageFileType) {
+ Language language = ((LanguageFileType)fileType).getLanguage();
+ Language[] languageDialects = LanguageUtil.getLanguageDialects(language);
+ Arrays.sort(languageDialects, new Comparator<Language>() {
+ @Override
+ public int compare(Language o1, Language o2) {
+ return o1.getDisplayName().compareTo(o2.getDisplayName());
+ }
+ });
+ Language[] variants = new Language[languageDialects.length + 1];
+ variants[0] = null;
+ System.arraycopy(languageDialects, 0, variants, 1, languageDialects.length);
+ dialects.setModel(new DefaultComboBoxModel(variants));
+ dialects.setEnabled(variants.length > 1);
+ }
+
+ final StructuralSearchProfile profile = StructuralSearchUtil.getProfileByFileType(fileType);
+
+ if (profile instanceof StructuralSearchProfileBase) {
+ final String[] contextNames = ((StructuralSearchProfileBase)profile).getContextNames();
+ if (contextNames.length > 0) {
+ contexts.setModel(new DefaultComboBoxModel(contextNames));
+ contexts.setSelectedItem(contextNames[0]);
+ contexts.setEnabled(true);
+ return;
+ }
+ }
+ contexts.setSelectedItem(null);
+ contexts.setEnabled(false);
+ }
+
+ private void detectFileTypeAndDialect() {
+ final PsiFile file = searchContext.getFile();
+ if (file != null) {
+ PsiElement context = null;
+
+ if (searchContext.getEditor() != null) {
+ context = file.findElementAt(searchContext.getEditor().getCaretModel().getOffset());
+ if (context != null) {
+ context = context.getParent();
+ }
+ }
+ if (context == null) {
+ context = file;
+ }
+
+ FileType detectedFileType = null;
+
+ StructuralSearchProfile profile = StructuralSearchUtil.getProfileByPsiElement(context);
+ if (profile != null) {
+ FileType fileType = profile.detectFileType(context);
+ if (fileType != null) {
+ detectedFileType = fileType;
+ }
+ }
+
+ if (detectedFileType == null) {
+ for (FileType fileType : StructuralSearchUtil.getSuitableFileTypes()) {
+ if (fileType instanceof LanguageFileType && ((LanguageFileType)fileType).getLanguage().equals(context.getLanguage())) {
+ detectedFileType = fileType;
+ break;
+ }
+ }
+ }
+
+ ourFtSearchVariant = detectedFileType != null ?
+ detectedFileType :
+ StructuralSearchUtil.getDefaultFileType();
+
+ // todo: detect dialect
+
+ /*if (file.getLanguage() == StdLanguages.HTML ||
+ (file.getFileType() == StdFileTypes.JSP &&
+ contextLanguage == StdLanguages.HTML
+ )
+ ) {
+ ourFileType = "html";
+ }
+ else if (file.getLanguage() == StdLanguages.XHTML ||
+ (file.getFileType() == StdFileTypes.JSPX &&
+ contextLanguage == StdLanguages.HTML
+ )) {
+ ourFileType = "xml";
+ }
+ else {
+ ourFileType = DEFAULT_TYPE_NAME;
+ }*/
+ }
+ }
+
+ protected boolean isRecursiveSearchEnabled() {
+ return true;
+ }
+
+ public void setValuesFromConfig(Configuration configuration) {
+ //searchCriteriaEdit.putUserData(SubstitutionShortInfoHandler.CURRENT_CONFIGURATION_KEY, configuration);
+
+ setDialogTitle(configuration);
+ final MatchOptions matchOptions = configuration.getMatchOptions();
+
+ UIUtil.setContent(
+ searchCriteriaEdit,
+ matchOptions.getSearchPattern(),
+ 0,
+ searchCriteriaEdit.getDocument().getTextLength(),
+ searchContext.getProject()
+ );
+
+ model.getConfig().getMatchOptions().setSearchPattern(
+ matchOptions.getSearchPattern()
+ );
+
+ recursiveMatching.setSelected(
+ isRecursiveSearchEnabled() && matchOptions.isRecursiveSearch()
+ );
+
+ caseSensitiveMatch.setSelected(
+ matchOptions.isCaseSensitiveMatch()
+ );
+
+ model.getConfig().getMatchOptions().clearVariableConstraints();
+ if (matchOptions.hasVariableConstraints()) {
+ for (Iterator<String> i = matchOptions.getVariableConstraintNames(); i.hasNext(); ) {
+ final MatchVariableConstraint constraint = (MatchVariableConstraint)matchOptions.getVariableConstraint(i.next()).clone();
+ model.getConfig().getMatchOptions().addVariableConstraint(constraint);
+ }
+ }
+
+ MatchOptions options = configuration.getMatchOptions();
+ StructuralSearchProfile profile = StructuralSearchUtil.getProfileByFileType(options.getFileType());
+ assert profile != null;
+ fileTypes.setSelectedItem(options.getFileType());
+ dialects.setSelectedItem(options.getDialect());
+ if (options.getPatternContext() != null) {
+ contexts.setSelectedItem(options.getPatternContext());
+ }
+ }
+
+ private void setDialogTitle(final Configuration configuration) {
+ setTitle(getDefaultTitle() + " - " + configuration.getName());
+ }
+
+ @Override
+ public Configuration createConfiguration() {
+ SearchConfiguration configuration = new SearchConfiguration();
+ configuration.setName(USER_DEFINED);
+ return configuration;
+ }
+
+ protected void addOrReplaceSelection(final String selection) {
+ addOrReplaceSelectionForEditor(selection, searchCriteriaEdit);
+ }
+
+ protected final void addOrReplaceSelectionForEditor(final String selection, Editor editor) {
+ final Project project = searchContext.getProject();
+ UIUtil.setContent(editor, selection, 0, -1, project);
+ final Document document = editor.getDocument();
+ editor.getSelectionModel().setSelection(0, document.getTextLength());
+ final PsiDocumentManager documentManager = PsiDocumentManager.getInstance(project);
+ documentManager.commitDocument(document);
+ final PsiFile file = documentManager.getPsiFile(document);
+ if (file == null) return;
+
+ new WriteCommandAction(project, file) {
+ @Override protected void run(@NotNull Result result) throws Throwable {
+ CodeStyleManager.getInstance(project).adjustLineIndent(file, new TextRange(0, document.getTextLength()));
+ }
+ }.execute();
+ }
+
+ protected void runAction(final Configuration config, final SearchContext searchContext) {
+ createUsageView(searchContext, config);
+ }
+
+ protected void createUsageView(final SearchContext searchContext, final Configuration config) {
+ UsageViewManager manager = UsageViewManager.getInstance(searchContext.getProject());
+
+ final UsageViewContext context = createUsageViewContext(config);
+ final UsageViewPresentation presentation = new UsageViewPresentation();
+ presentation.setOpenInNewTab(openInNewTab.isSelected());
+ presentation.setScopeText(config.getMatchOptions().getScope().getDisplayName());
+ context.configure(presentation);
+
+ final FindUsagesProcessPresentation processPresentation = new FindUsagesProcessPresentation(presentation);
+ processPresentation.setShowNotFoundMessage(true);
+ processPresentation.setShowPanelIfOnlyOneUsage(true);
+
+ processPresentation.setProgressIndicatorFactory(
+ new Factory<ProgressIndicator>() {
+ @Override
+ public ProgressIndicator create() {
+ return new FindProgressIndicator(searchContext.getProject(), presentation.getScopeText()) {
+ @Override
+ public void cancel() {
+ context.getCommand().stopAsyncSearch();
+ super.cancel();
+ }
+ };
+ }
+ }
+ );
+
+ PsiDocumentManager.getInstance(getProject()).commitAllDocuments();
+
+ manager.searchAndShowUsages(
+ new UsageTarget[]{
+ context.getTarget()
+ },
+ new Factory<UsageSearcher>() {
+ @Override
+ public UsageSearcher create() {
+ return new UsageSearcher() {
+ @Override
+ public void generate(@NotNull final Processor<Usage> processor) {
+ context.getCommand().findUsages(processor);
+ }
+ };
+ }
+ },
+ processPresentation,
+ presentation,
+ new UsageViewManager.UsageViewStateListener() {
+ @Override
+ public void usageViewCreated(@NotNull UsageView usageView) {
+ context.setUsageView(usageView);
+ context.configureActions();
+ }
+
+ @Override
+ public void findingUsagesFinished(final UsageView usageView) {
+ }
+ }
+ );
+ }
+
+ protected String getDefaultTitle() {
+ return SSRBundle.message("structural.search.title");
+ }
+
+ protected JComponent createEditorContent() {
+ JPanel result = new JPanel(new BorderLayout());
+
+ result.add(BorderLayout.NORTH, new JLabel(SSRBundle.message("search.template")));
+ searchCriteriaEdit = createEditor(searchContext, mySavedEditorText != null ? mySavedEditorText : "");
+ result.add(BorderLayout.CENTER, searchCriteriaEdit.getComponent());
+ result.setMinimumSize(new Dimension(150, 100));
+
+ return result;
+ }
+
+ protected int getRowsCount() {
+ return 4;
+ }
+
+ @Override
+ protected JComponent createCenterPanel() {
+ myContentPanel = new JPanel(new BorderLayout());
+ myEditorPanel = createEditorContent();
+ myContentPanel.add(BorderLayout.CENTER, myEditorPanel);
+ myContentPanel.add(BorderLayout.SOUTH, Box.createVerticalStrut(8));
+ JComponent centerPanel = new JPanel(new BorderLayout());
+ {
+ JPanel panel = new JPanel(new BorderLayout());
+ panel.add(BorderLayout.CENTER, myContentPanel);
+ panel.add(BorderLayout.SOUTH, createTemplateManagementButtons());
+ centerPanel.add(BorderLayout.CENTER, panel);
+ }
+
+ JPanel optionsContent = new JPanel(new BorderLayout());
+ centerPanel.add(BorderLayout.SOUTH, optionsContent);
+
+ JPanel searchOptions = new JPanel();
+ searchOptions.setLayout(new GridLayout(getRowsCount(), 1, 0, 0));
+ searchOptions.setBorder(IdeBorderFactory.createTitledBorder(SSRBundle.message("ssdialog.options.group.border"),
+ true));
+
+ myScopeChooserCombo = new ScopeChooserCombo(
+ searchContext.getProject(),
+ true,
+ false,
+ FindSettings.getInstance().getDefaultScopeName()
+ );
+ Disposer.register(myDisposable, myScopeChooserCombo);
+ JPanel allOptions = new JPanel(new BorderLayout());
+ if (myShowScopePanel) {
+ JPanel scopePanel = new JPanel(new GridBagLayout());
+
+ TitledSeparator separator = new TitledSeparator(SSRBundle.message("search.dialog.scope.label"), myScopeChooserCombo.getComboBox());
+ scopePanel.add(separator, new GridBagConstraints(0, 0, 1, 1, 1, 1, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL,
+ new Insets(5, 0, 0, 0), 0, 0));
+
+ scopePanel.add(myScopeChooserCombo, new GridBagConstraints(0, 1, 1, 1, 1, 1, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL,
+ new Insets(0, 10, 0, 0), 0, 0));
+
+ allOptions.add(
+ scopePanel,
+ BorderLayout.SOUTH
+ );
+
+ myScopeChooserCombo.getComboBox().addItemListener(new ItemListener() {
+ @Override
+ public void itemStateChanged(ItemEvent e) {
+ initiateValidation();
+ }
+ });
+ }
+
+ buildOptions(searchOptions);
+
+ allOptions.add(searchOptions, BorderLayout.CENTER);
+ optionsContent.add(allOptions, BorderLayout.CENTER);
+
+ if (myRunFindActionOnClose) {
+ JPanel panel = new JPanel(new BorderLayout());
+ panel.setBorder(BorderFactory.createEmptyBorder(0, 4, 0, 0));
+ openInNewTab = new JCheckBox(FindBundle.message("find.open.in.new.tab.checkbox"));
+ openInNewTab.setSelected(ourOpenInNewTab);
+ ToolWindow findWindow = ToolWindowManager.getInstance(searchContext.getProject()).getToolWindow(ToolWindowId.FIND);
+ openInNewTab.setEnabled(findWindow != null && findWindow.isAvailable());
+ panel.add(openInNewTab, BorderLayout.EAST);
+
+ optionsContent.add(BorderLayout.SOUTH, panel);
+ }
+
+ updateEditor();
+ return centerPanel;
+ }
+
+
+ @Override
+ protected JComponent createSouthPanel() {
+ final JPanel statusPanel = new JPanel(new BorderLayout(5, 0));
+ statusPanel.add(super.createSouthPanel(), BorderLayout.NORTH);
+ statusPanel.add(statusText = new JLabel(SSRBundle.message("status.message")), BorderLayout.WEST);
+ statusPanel.add(status = new JLabel(), BorderLayout.CENTER);
+ return statusPanel;
+ }
+
+ private JPanel createTemplateManagementButtons() {
+ JPanel panel = new JPanel(null);
+ panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS));
+ panel.add(Box.createHorizontalGlue());
+
+ panel.add(
+ createJButtonForAction(new AbstractAction() {
+ {
+ putValue(NAME, SSRBundle.message("save.template.text.button"));
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ String name = showSaveTemplateAsDialog();
+
+ if (name != null) {
+ final Project project = searchContext.getProject();
+ final ConfigurationManager configurationManager = StructuralSearchPlugin.getInstance(project).getConfigurationManager();
+ final Collection<Configuration> configurations = configurationManager.getConfigurations();
+
+ if (configurations != null) {
+ name = ConfigurationManager.findAppropriateName(configurations, name, project);
+ if (name == null) return;
+ }
+
+ model.getConfig().setName(name);
+ setValuesToConfig(model.getConfig());
+ setDialogTitle(model.getConfig());
+
+ if (model.getShadowConfig() == null ||
+ model.getShadowConfig().isPredefined()) {
+ existingTemplatesComponent.addConfigurationToUserTemplates(model.getConfig());
+ }
+ else { // ???
+ setValuesToConfig(model.getShadowConfig());
+ model.getShadowConfig().setName(name);
+ }
+ }
+ }
+ })
+ );
+
+ panel.add(
+ Box.createHorizontalStrut(8)
+ );
+
+ panel.add(
+ createJButtonForAction(
+ new AbstractAction() {
+ {
+ putValue(NAME, SSRBundle.message("edit.variables.button"));
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ EditVarConstraintsDialog.setProject(searchContext.getProject());
+ new EditVarConstraintsDialog(
+ searchContext.getProject(),
+ model, getVariablesFromListeners(),
+ isReplaceDialog(),
+ (FileType)fileTypes.getSelectedItem()
+ ).show();
+ initiateValidation();
+ EditVarConstraintsDialog.setProject(null);
+ }
+ }
+ )
+ );
+
+ panel.add(
+ Box.createHorizontalStrut(8)
+ );
+
+ panel.add(
+ createJButtonForAction(
+ new AbstractAction() {
+ {
+ putValue(NAME, SSRBundle.message("history.button"));
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ SelectTemplateDialog dialog = new SelectTemplateDialog(searchContext.getProject(), true, isReplaceDialog());
+ dialog.show();
+
+ if (!dialog.isOK()) {
+ return;
+ }
+ Configuration[] configurations = dialog.getSelectedConfigurations();
+ if (configurations.length == 1) {
+ setSearchPattern(configurations[0]);
+ }
+ }
+ }
+ )
+ );
+
+ panel.add(
+ Box.createHorizontalStrut(8)
+ );
+
+ panel.add(
+ createJButtonForAction(
+ new AbstractAction() {
+ {
+ putValue(NAME, SSRBundle.message("copy.existing.template.button"));
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ SelectTemplateDialog dialog = new SelectTemplateDialog(searchContext.getProject(), false, isReplaceDialog());
+ dialog.show();
+
+ if (!dialog.isOK()) {
+ return;
+ }
+ Configuration[] configurations = dialog.getSelectedConfigurations();
+ if (configurations.length == 1) {
+ setSearchPattern(configurations[0]);
+ }
+ }
+ }
+ )
+ );
+
+ return panel;
+ }
+
+ protected List<Variable> getVariablesFromListeners() {
+ return getVarsFrom(searchCriteriaEdit);
+ }
+
+ protected static ArrayList<Variable> getVarsFrom(Editor searchCriteriaEdit) {
+ SubstitutionShortInfoHandler handler = searchCriteriaEdit.getUserData(UIUtil.LISTENER_KEY);
+ return new ArrayList<Variable>(handler.getVariables());
+ }
+
+ public final Project getProject() {
+ return searchContext.getProject();
+ }
+
+ public String showSaveTemplateAsDialog() {
+ return ConfigurationManager.showSaveTemplateAsDialog(
+ model.getShadowConfig() != null ? model.getShadowConfig().getName() : SSRBundle.message("user.defined.category"),
+ searchContext.getProject()
+ );
+ }
+
+ protected boolean isReplaceDialog() {
+ return false;
+ }
+
+ @Override
+ public void show() {
+ StructuralSearchPlugin.getInstance(getProject()).setDialogVisible(true);
+ Configuration.setActiveCreator(this);
+ searchCriteriaEdit.putUserData(
+ SubstitutionShortInfoHandler.CURRENT_CONFIGURATION_KEY,
+ model.getConfig()
+ );
+
+ if (!useLastConfiguration) {
+ final Editor editor = FileEditorManager.getInstance(searchContext.getProject()).getSelectedTextEditor();
+ boolean setSomeText = false;
+
+ if (editor != null) {
+ final SelectionModel selectionModel = editor.getSelectionModel();
+
+ if (selectionModel.hasSelection()) {
+ addOrReplaceSelection(selectionModel.getSelectedText());
+ existingTemplatesComponent.getPatternTree().setSelectionPath(null);
+ existingTemplatesComponent.getHistoryList().setSelectedIndex(-1);
+ setSomeText = true;
+ }
+ }
+
+ if (!setSomeText) {
+ int selection = existingTemplatesComponent.getHistoryList().getSelectedIndex();
+ if (selection != -1) {
+ setValuesFromConfig(
+ (Configuration)existingTemplatesComponent.getHistoryList().getSelectedValue()
+ );
+ }
+ }
+ }
+
+ initiateValidation();
+
+ super.show();
+ }
+
+ @Override
+ public JComponent getPreferredFocusedComponent() {
+ return searchCriteriaEdit.getContentComponent();
+ }
+
+ // Performs ok action
+ @Override
+ protected void doOKAction() {
+ SearchScope selectedScope = getSelectedScope();
+ if (selectedScope == null) return;
+
+ myDoingOkAction = true;
+ boolean result = isValid();
+ myDoingOkAction = false;
+ if (!result) return;
+
+ myAlarm.cancelAllRequests();
+ super.doOKAction();
+ if (!myRunFindActionOnClose) return;
+
+ FindSettings.getInstance().setDefaultScopeName(selectedScope.getDisplayName());
+ ourOpenInNewTab = openInNewTab.isSelected();
+
+ try {
+ if (model.getShadowConfig() != null) {
+ if (model.getShadowConfig().isPredefined()) {
+ model.getConfig().setName(
+ model.getShadowConfig().getName()
+ );
+ } //else {
+ // // user template, save it
+ // setValuesToConfig(model.getShadowConfig());
+ //}
+ }
+ existingTemplatesComponent.addConfigurationToHistory(model.getConfig());
+
+ runAction(model.getConfig(), searchContext);
+ }
+ catch (MalformedPatternException ex) {
+ reportMessage("this.pattern.is.malformed.message", searchCriteriaEdit, ex.getMessage());
+ }
+ }
+
+ public Configuration getConfiguration() {
+ return model.getConfig();
+ }
+
+ private SearchScope getSelectedScope() {
+ return myScopeChooserCombo.getSelectedScope();
+ }
+
+ protected boolean isValid() {
+ setValuesToConfig(model.getConfig());
+ boolean result = true;
+
+ try {
+ MatcherImpl.validate(searchContext.getProject(), model.getConfig().getMatchOptions());
+ }
+ catch (MalformedPatternException ex) {
+ if (myRunFindActionOnClose) {
+ reportMessage(
+ "this.pattern.is.malformed.message",
+ searchCriteriaEdit,
+ ex.getMessage() != null ? ex.getMessage() : ""
+ );
+ result = false;
+ }
+ }
+ catch (UnsupportedPatternException ex) {
+ reportMessage("this.pattern.is.unsupported.message", searchCriteriaEdit, ex.getMessage());
+ result = false;
+ }
+
+ //getOKAction().setEnabled(result);
+ return result;
+ }
+
+ protected void reportMessage(@NonNls String messageId, Editor editor, Object... params) {
+ final String message = messageId != null ? SSRBundle.message(messageId, params) : "";
+ status.setText(message);
+ status.setToolTipText(message);
+ status.revalidate();
+ statusText.setLabelFor(editor != null ? editor.getContentComponent() : null);
+ }
+
+ protected void setValuesToConfig(Configuration config) {
+
+ MatchOptions options = config.getMatchOptions();
+
+ boolean searchWithinHierarchy = IdeBundle.message("scope.class.hierarchy").equals(myScopeChooserCombo.getSelectedScopeName());
+ // We need to reset search within hierarchy scope during online validation since the scope works with user participation
+ options.setScope(
+ searchWithinHierarchy && !myDoingOkAction ? GlobalSearchScope.projectScope(getProject()) : myScopeChooserCombo.getSelectedScope());
+ options.setLooseMatching(true);
+ options.setRecursiveSearch(isRecursiveSearchEnabled() && recursiveMatching.isSelected());
+
+ ourFtSearchVariant = (FileType)fileTypes.getSelectedItem();
+ ourDialect = (Language)dialects.getSelectedItem();
+ ourContext = (String)contexts.getSelectedItem();
+ FileType fileType = ourFtSearchVariant;
+ options.setFileType(fileType);
+ options.setDialect(ourDialect);
+ options.setPatternContext(ourContext);
+
+ options.setSearchPattern(searchCriteriaEdit.getDocument().getText());
+ options.setCaseSensitiveMatch(caseSensitiveMatch.isSelected());
+ }
+
+ @Override
+ protected String getDimensionServiceKey() {
+ return "#com.intellij.structuralsearch.plugin.ui.SearchDialog";
+ }
+
+ @Override
+ public void dispose() {
+ Configuration.setActiveCreator(null);
+ disposeEditorContent();
+
+ myAlarm.cancelAllRequests();
+
+ super.dispose();
+ StructuralSearchPlugin.getInstance(getProject()).setDialogVisible(false);
+ }
+
+ protected void disposeEditorContent() {
+ mySavedEditorText = searchCriteriaEdit.getDocument().getText();
+
+ // this will remove from myExcludedSet
+ final PsiFile file = PsiDocumentManager.getInstance(searchContext.getProject()).getPsiFile(searchCriteriaEdit.getDocument());
+ if (file != null) {
+ DaemonCodeAnalyzer.getInstance(searchContext.getProject()).setHighlightingEnabled(file, true);
+ }
+
+ EditorFactory.getInstance().releaseEditor(searchCriteriaEdit);
+ }
+
+ @Override
+ protected String getHelpId() {
+ return "find.structuredSearch";
+ }
+
+ public SearchContext getSearchContext() {
+ return searchContext;
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/SearchModel.java b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/SearchModel.java
new file mode 100644
index 000000000000..812ca6b0dc9b
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/SearchModel.java
@@ -0,0 +1,29 @@
+package com.intellij.structuralsearch.plugin.ui;
+
+/**
+ * Created by IntelliJ IDEA.
+ * User: Maxim.Mossienko
+ * Date: Mar 25, 2004
+ * Time: 1:33:10 PM
+ * To change this template use File | Settings | File Templates.
+ */
+public class SearchModel {
+ private final Configuration config;
+ private Configuration shadowConfig;
+
+ public SearchModel(Configuration config) {
+ this.config = config;
+ }
+
+ public Configuration getConfig() {
+ return config;
+ }
+
+ public void setShadowConfig(Configuration shadowConfig) {
+ this.shadowConfig = shadowConfig;
+ }
+
+ public Configuration getShadowConfig() {
+ return shadowConfig;
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/SelectTemplateDialog.java b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/SelectTemplateDialog.java
new file mode 100644
index 000000000000..b74715ecf07a
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/SelectTemplateDialog.java
@@ -0,0 +1,294 @@
+package com.intellij.structuralsearch.plugin.ui;
+
+import com.intellij.codeInsight.template.TemplateContextType;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.editor.EditorFactory;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.DialogWrapper;
+import com.intellij.openapi.ui.Splitter;
+import com.intellij.structuralsearch.MatchOptions;
+import com.intellij.structuralsearch.SSRBundle;
+import com.intellij.structuralsearch.plugin.replace.ui.ReplaceConfiguration;
+import com.intellij.util.containers.ContainerUtil;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.*;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+import javax.swing.event.TreeSelectionEvent;
+import javax.swing.event.TreeSelectionListener;
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.TreePath;
+import java.awt.*;
+import java.util.ArrayList;
+import java.util.Collection;
+
+/**
+ * Created by IntelliJ IDEA.
+ * User: Maxim.Mossienko
+ * Date: Apr 23, 2004
+ * Time: 5:03:52 PM
+ * To change this template use File | Settings | File Templates.
+ */
+public class SelectTemplateDialog extends DialogWrapper {
+ private final boolean showHistory;
+ private Editor searchPatternEditor;
+ private Editor replacePatternEditor;
+ private final boolean replace;
+ private final Project project;
+ private final ExistingTemplatesComponent existingTemplatesComponent;
+
+ private MySelectionListener selectionListener;
+ private CardLayout myCardLayout;
+ private JPanel myPreviewPanel;
+ @NonNls private static final String PREVIEW_CARD = "Preview";
+ @NonNls private static final String SELECT_TEMPLATE_CARD = "SelectCard";
+
+ public SelectTemplateDialog(Project project, boolean showHistory, boolean replace) {
+ super(project, false);
+
+ this.project = project;
+ this.showHistory = showHistory;
+ this.replace = replace;
+ existingTemplatesComponent = ExistingTemplatesComponent.getInstance(this.project);
+
+ setTitle(SSRBundle.message(this.showHistory ? "used.templates.history.dialog.title" : "existing.templates.dialog.title"));
+ init();
+
+ if (this.showHistory) {
+ final int selection = existingTemplatesComponent.getHistoryList().getSelectedIndex();
+ if (selection != -1) {
+ setPatternFromList(selection);
+ }
+ }
+ else {
+ final TreePath selection = existingTemplatesComponent.getPatternTree().getSelectionPath();
+ if (selection != null) {
+ setPatternFromNode((DefaultMutableTreeNode)selection.getLastPathComponent());
+ }
+ else {
+ showPatternPreviewFromConfiguration(null);
+ }
+ }
+
+ setupListeners();
+ }
+
+ class MySelectionListener implements TreeSelectionListener, ListSelectionListener {
+ public void valueChanged(TreeSelectionEvent e) {
+ if (e.getNewLeadSelectionPath() != null) {
+ setPatternFromNode(
+ (DefaultMutableTreeNode)e.getNewLeadSelectionPath().getLastPathComponent()
+ );
+ }
+ }
+
+ public void valueChanged(ListSelectionEvent e) {
+ if (e.getValueIsAdjusting() || e.getLastIndex() == -1) return;
+ int selectionIndex = existingTemplatesComponent.getHistoryList().getSelectedIndex();
+ if (selectionIndex != -1) {
+ setPatternFromList(selectionIndex);
+ }
+ }
+ }
+
+ private void setPatternFromList(int index) {
+ showPatternPreviewFromConfiguration(
+ (Configuration)existingTemplatesComponent.getHistoryList().getModel().getElementAt(index)
+ );
+ }
+
+ protected JComponent createCenterPanel() {
+ final JPanel centerPanel = new JPanel(new BorderLayout());
+ Splitter splitter;
+
+ centerPanel.add(BorderLayout.CENTER, splitter = new Splitter(false, 0.3f));
+ centerPanel.add(splitter);
+
+ splitter.setFirstComponent(
+ showHistory ?
+ existingTemplatesComponent.getHistoryPanel() :
+ existingTemplatesComponent.getTemplatesPanel()
+ );
+ final JPanel panel;
+ splitter.setSecondComponent(
+ panel = new JPanel(new BorderLayout())
+ );
+
+ searchPatternEditor = UIUtil.createEditor(
+ EditorFactory.getInstance().createDocument(""),
+ project,
+ false,
+ true,
+ ContainerUtil.findInstance(TemplateContextType.EP_NAME.getExtensions(), TemplateContextType.class)
+ );
+
+ JComponent centerComponent;
+
+ if (replace) {
+ replacePatternEditor = UIUtil.createEditor(
+ EditorFactory.getInstance().createDocument(""),
+ project,
+ false,
+ true,
+ ContainerUtil.findInstance(TemplateContextType.EP_NAME.getExtensions(), TemplateContextType.class)
+ );
+ centerComponent = new Splitter(true);
+ ((Splitter)centerComponent).setFirstComponent(searchPatternEditor.getComponent());
+ ((Splitter)centerComponent).setSecondComponent(replacePatternEditor.getComponent());
+ }
+ else {
+ centerComponent = searchPatternEditor.getComponent();
+ }
+
+ myCardLayout = new CardLayout();
+ myPreviewPanel = new JPanel(myCardLayout);
+ myPreviewPanel.add(centerComponent, PREVIEW_CARD);
+ JPanel selectPanel = new JPanel(new GridBagLayout());
+ GridBagConstraints gb = new GridBagConstraints(0,0,0,0,0,0,GridBagConstraints.CENTER,GridBagConstraints.NONE, new Insets(0,0,0,0),0,0);
+ selectPanel.add(new JLabel(SSRBundle.message("selecttemplate.template.label.please.select.template")), gb);
+ myPreviewPanel.add(selectPanel, SELECT_TEMPLATE_CARD);
+
+ panel.add(BorderLayout.CENTER, myPreviewPanel);
+
+ panel.add(BorderLayout.NORTH, new JLabel(SSRBundle.message("selecttemplate.template.preview")));
+ return centerPanel;
+ }
+
+ public void dispose() {
+ EditorFactory.getInstance().releaseEditor(searchPatternEditor);
+ if (replacePatternEditor != null) EditorFactory.getInstance().releaseEditor(replacePatternEditor);
+ removeListeners();
+ super.dispose();
+ }
+
+ public JComponent getPreferredFocusedComponent() {
+ return showHistory ?
+ existingTemplatesComponent.getHistoryList() :
+ existingTemplatesComponent.getPatternTree();
+ }
+
+ protected String getDimensionServiceKey() {
+ return "#com.intellij.structuralsearch.plugin.ui.SelectTemplateDialog";
+ }
+
+ private void setupListeners() {
+ existingTemplatesComponent.setOwner(this);
+ selectionListener = new MySelectionListener();
+
+ if (showHistory) {
+ existingTemplatesComponent.getHistoryList().getSelectionModel().addListSelectionListener(
+ selectionListener
+ );
+ }
+ else {
+ existingTemplatesComponent.getPatternTree().getSelectionModel().addTreeSelectionListener(
+ selectionListener
+ );
+ }
+ }
+
+ private void removeListeners() {
+ existingTemplatesComponent.setOwner(null);
+ if (showHistory) {
+ existingTemplatesComponent.getHistoryList().getSelectionModel().removeListSelectionListener(
+ selectionListener
+ );
+ }
+ else {
+ existingTemplatesComponent.getPatternTree().getSelectionModel().removeTreeSelectionListener(selectionListener);
+ }
+ }
+
+ private void setPatternFromNode(DefaultMutableTreeNode node) {
+ if (node == null) return;
+ final Object userObject = node.getUserObject();
+ final Configuration configuration;
+
+ // root could be without search template
+ if (userObject instanceof Configuration) {
+ configuration = (Configuration)userObject;
+ }
+ else {
+ configuration = null;
+ }
+
+ showPatternPreviewFromConfiguration(configuration);
+ }
+
+ private void showPatternPreviewFromConfiguration(@Nullable final Configuration configuration) {
+ if (configuration == null) {
+ myCardLayout.show(myPreviewPanel, SELECT_TEMPLATE_CARD);
+ return;
+ }
+ else {
+ myCardLayout.show(myPreviewPanel, PREVIEW_CARD);
+ }
+ final MatchOptions matchOptions = configuration.getMatchOptions();
+
+ UIUtil.setContent(
+ searchPatternEditor,
+ matchOptions.getSearchPattern(),
+ 0,
+ searchPatternEditor.getDocument().getTextLength(),
+ project
+ );
+
+ searchPatternEditor.putUserData(SubstitutionShortInfoHandler.CURRENT_CONFIGURATION_KEY, configuration);
+
+ if (replace) {
+ String replacement;
+
+ if (configuration instanceof ReplaceConfiguration) {
+ replacement = ((ReplaceConfiguration)configuration).getOptions().getReplacement();
+ }
+ else {
+ replacement = configuration.getMatchOptions().getSearchPattern();
+ }
+
+ UIUtil.setContent(
+ replacePatternEditor,
+ replacement,
+ 0,
+ replacePatternEditor.getDocument().getTextLength(),
+ project
+ );
+
+ replacePatternEditor.putUserData(SubstitutionShortInfoHandler.CURRENT_CONFIGURATION_KEY, configuration);
+ }
+ }
+
+ @NotNull public Configuration[] getSelectedConfigurations() {
+ if (showHistory) {
+ Object[] selectedValues = existingTemplatesComponent.getHistoryList().getSelectedValues();
+ if (selectedValues == null) {
+ return new Configuration[0];
+ }
+ Collection<Configuration> configurations = new ArrayList<Configuration>();
+ for (Object selectedValue : selectedValues) {
+ if (selectedValue instanceof Configuration) {
+ configurations.add((Configuration)selectedValue);
+ }
+ }
+ return configurations.toArray(new Configuration[configurations.size()]);
+ }
+ else {
+ TreePath[] paths = existingTemplatesComponent.getPatternTree().getSelectionModel().getSelectionPaths();
+ if (paths == null) {
+ return new Configuration[0];
+ }
+ Collection<Configuration> configurations = new ArrayList<Configuration>();
+ for (TreePath path : paths) {
+
+ DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent();
+ final Object userObject = node.getUserObject();
+ if (userObject instanceof Configuration) {
+ configurations.add((Configuration)userObject);
+ }
+ }
+ return configurations.toArray(new Configuration[configurations.size()]);
+ }
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/SubstitutionShortInfoHandler.java b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/SubstitutionShortInfoHandler.java
new file mode 100644
index 000000000000..32f1302336f3
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/SubstitutionShortInfoHandler.java
@@ -0,0 +1,114 @@
+package com.intellij.structuralsearch.plugin.ui;
+
+import com.intellij.codeInsight.hint.TooltipGroup;
+import com.intellij.codeInsight.template.impl.TemplateImplUtil;
+import com.intellij.codeInsight.template.impl.Variable;
+import com.intellij.openapi.editor.Document;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.editor.LogicalPosition;
+import com.intellij.openapi.editor.event.*;
+import com.intellij.openapi.util.Key;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+
+/**
+ * Created by IntelliJ IDEA.
+ * User: Maxim.Mossienko
+ * Date: Apr 23, 2004
+ * Time: 5:20:56 PM
+ * To change this template use File | Settings | File Templates.
+ */
+public class SubstitutionShortInfoHandler implements DocumentListener, EditorMouseMotionListener, CaretListener {
+ private static final TooltipGroup SS_INFO_TOOLTIP_GROUP = new TooltipGroup("SS_INFO_TOOLTIP_GROUP", 0);
+
+ private long modificationTimeStamp;
+ private final ArrayList<Variable> variables = new ArrayList<Variable>();
+ private final Editor editor;
+ public static final Key<Configuration> CURRENT_CONFIGURATION_KEY = Key.create("SS.CurrentConfiguration");
+
+ SubstitutionShortInfoHandler(@NotNull Editor _editor) {
+ editor = _editor;
+ }
+
+ public void beforeDocumentChange(DocumentEvent event) {
+ }
+
+ public void documentChanged(DocumentEvent event) {
+ }
+
+ public void mouseMoved(EditorMouseEvent e) {
+ LogicalPosition position = editor.xyToLogicalPosition( e.getMouseEvent().getPoint() );
+
+ handleInputFocusMovement(position);
+ }
+
+ private void handleInputFocusMovement(LogicalPosition position) {
+ checkModelValidity();
+ String text = "";
+ final int offset = editor.logicalPositionToOffset(position);
+ final int length = editor.getDocument().getTextLength();
+ final CharSequence elements = editor.getDocument().getCharsSequence();
+
+ int start = offset-1;
+ int end = -1;
+ while(start >=0 && Character.isJavaIdentifierPart(elements.charAt(start)) && elements.charAt(start)!='$') start--;
+
+ if (start >=0 && elements.charAt(start)=='$') {
+ end = offset;
+
+ while(end < length && Character.isJavaIdentifierPart(elements.charAt(end)) && elements.charAt(end)!='$') end++;
+ if (end < length && elements.charAt(end)=='$') {
+ String varname = elements.subSequence(start + 1, end).toString();
+ Variable foundVar = null;
+
+ for(Iterator<Variable> i=variables.iterator();i.hasNext();) {
+ final Variable var = i.next();
+
+ if (var.getName().equals(varname)) {
+ foundVar = var;
+ break;
+ }
+ }
+
+ if (foundVar!=null) {
+ text = UIUtil.getShortParamString(editor.getUserData(CURRENT_CONFIGURATION_KEY),varname);
+ }
+ }
+ }
+
+ if (text.length() > 0) {
+ UIUtil.showTooltip(editor, start, end, text, SS_INFO_TOOLTIP_GROUP);
+ }
+ }
+
+ private void checkModelValidity() {
+ Document document = editor.getDocument();
+ if (modificationTimeStamp != document.getModificationStamp()) {
+ variables.clear();
+ variables.addAll(TemplateImplUtil.parseVariables(document.getCharsSequence()).values());
+ modificationTimeStamp = document.getModificationStamp();
+ }
+ }
+
+ public void mouseDragged(EditorMouseEvent e) {
+ }
+
+ public void caretPositionChanged(CaretEvent e) {
+ handleInputFocusMovement(e.getNewPosition());
+ }
+
+ @Override
+ public void caretAdded(CaretEvent e) {
+ }
+
+ @Override
+ public void caretRemoved(CaretEvent e) {
+ }
+
+ public ArrayList<Variable> getVariables() {
+ checkModelValidity();
+ return variables;
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/UIUtil.java b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/UIUtil.java
new file mode 100644
index 000000000000..aa3ca82725b0
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/UIUtil.java
@@ -0,0 +1,241 @@
+package com.intellij.structuralsearch.plugin.ui;
+
+import com.intellij.codeInsight.hint.TooltipController;
+import com.intellij.codeInsight.hint.TooltipGroup;
+import com.intellij.codeInsight.template.TemplateContextType;
+import com.intellij.codeInsight.template.impl.TemplateContext;
+import com.intellij.codeInsight.template.impl.TemplateEditorUtil;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.command.CommandProcessor;
+import com.intellij.openapi.editor.Document;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.editor.EditorFactory;
+import com.intellij.openapi.editor.EditorSettings;
+import com.intellij.openapi.editor.colors.EditorColors;
+import com.intellij.openapi.editor.colors.EditorColorsManager;
+import com.intellij.openapi.editor.colors.EditorColorsScheme;
+import com.intellij.openapi.editor.ex.EditorEx;
+import com.intellij.openapi.fileEditor.FileEditorManager;
+import com.intellij.openapi.fileEditor.OpenFileDescriptor;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.Key;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.psi.*;
+import com.intellij.structuralsearch.*;
+import com.intellij.structuralsearch.plugin.StructuralReplaceAction;
+import com.intellij.structuralsearch.plugin.StructuralSearchAction;
+import com.intellij.structuralsearch.plugin.replace.ui.ReplaceConfiguration;
+import com.intellij.structuralsearch.plugin.util.SmartPsiPointer;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.*;
+import java.awt.*;
+
+/**
+ * @author Maxim.Mossienko
+ * Date: Apr 21, 2004
+ * Time: 7:50:48 PM
+ */
+public class UIUtil {
+ static Key<SubstitutionShortInfoHandler> LISTENER_KEY = Key.create("sslistener.key");
+ private static final String MODIFY_EDITOR_CONTENT = SSRBundle.message("modify.editor.content.command.name");
+ @NonNls private static final String SS_GROUP = "structuralsearchgroup";
+
+ @NotNull
+ public static Editor createEditor(Document doc, final Project project, boolean editable, @Nullable TemplateContextType contextType) {
+ return createEditor(doc, project, editable, false, contextType);
+ }
+
+ @NotNull
+ public static Editor createEditor(@NotNull Document doc,
+ final Project project,
+ boolean editable,
+ boolean addToolTipForVariableHandler,
+ @Nullable TemplateContextType contextType) {
+ final Editor editor =
+ editable ? EditorFactory.getInstance().createEditor(doc, project) : EditorFactory.getInstance().createViewer(doc, project);
+
+ EditorSettings editorSettings = editor.getSettings();
+ editorSettings.setVirtualSpace(false);
+ editorSettings.setLineMarkerAreaShown(false);
+ editorSettings.setIndentGuidesShown(false);
+ editorSettings.setLineNumbersShown(false);
+ editorSettings.setFoldingOutlineShown(false);
+
+ EditorColorsScheme scheme = editor.getColorsScheme();
+ scheme.setColor(EditorColors.CARET_ROW_COLOR, null);
+ if (!editable) {
+ final EditorColorsScheme globalScheme = EditorColorsManager.getInstance().getGlobalScheme();
+ Color c = globalScheme.getColor(EditorColors.READONLY_BACKGROUND_COLOR);
+
+ if (c == null) {
+ c = globalScheme.getDefaultBackground();
+ }
+
+ ((EditorEx)editor).setBackgroundColor(c);
+ }
+ else {
+ ((EditorEx)editor).setEmbeddedIntoDialogWrapper(true);
+ }
+
+ if (contextType != null) {
+ TemplateContext context = new TemplateContext();
+ context.setEnabled(contextType, true);
+ TemplateEditorUtil.setHighlighter(editor, context);
+ }
+
+ if (addToolTipForVariableHandler) {
+ SubstitutionShortInfoHandler handler = new SubstitutionShortInfoHandler(editor);
+ editor.addEditorMouseMotionListener(handler);
+ editor.getDocument().addDocumentListener(handler);
+ editor.getCaretModel().addCaretListener(handler);
+ editor.putUserData(LISTENER_KEY, handler);
+ }
+
+ return editor;
+ }
+
+ public static JComponent createOptionLine(JComponent[] options) {
+ JPanel tmp = new JPanel();
+
+ tmp.setLayout(new BoxLayout(tmp, BoxLayout.X_AXIS));
+ for (int i = 0; i < options.length; i++) {
+ if (i != 0) {
+ tmp.add(Box.createHorizontalStrut(com.intellij.util.ui.UIUtil.DEFAULT_HGAP));
+ }
+ tmp.add(options[i]);
+ }
+ tmp.add(Box.createHorizontalGlue());
+
+ return tmp;
+ }
+
+ public static JComponent createOptionLine(JComponent option) {
+ return createOptionLine(new JComponent[]{option});
+ }
+
+ public static void setContent(final Editor editor, String val, final int from, final int end, final Project project) {
+ final String value = val != null ? val : "";
+
+ CommandProcessor.getInstance().executeCommand(project, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ public void run() {
+ editor.getDocument().replaceString(from, (end == -1) ? editor.getDocument().getTextLength() : end, value);
+ }
+ });
+ }
+ }, MODIFY_EDITOR_CONTENT, SS_GROUP);
+ }
+
+ static String getShortParamString(Configuration config, String varname) {
+ if (config == null) return "";
+ final MatchOptions options = config.getMatchOptions();
+
+
+ final MatchVariableConstraint constraint = options == null ? null : options.getVariableConstraint(varname);
+ NamedScriptableDefinition namedScriptableDefinition = constraint;
+
+ final ReplacementVariableDefinition replacementVariableDefinition =
+ config instanceof ReplaceConfiguration ? ((ReplaceConfiguration)config).getOptions().getVariableDefinition(varname) : null;
+ if (replacementVariableDefinition != null) namedScriptableDefinition = replacementVariableDefinition;
+
+ if (constraint == null && replacementVariableDefinition == null) {
+ return SSRBundle.message("no.constraints.specified.tooltip.message");
+ }
+
+ final StringBuilder buf = new StringBuilder();
+
+ if (constraint != null) {
+ if (constraint.isPartOfSearchResults()) {
+ append(buf, SSRBundle.message("target.tooltip.message"));
+ }
+ if (constraint.getRegExp() != null && constraint.getRegExp().length() > 0) {
+ append(buf, SSRBundle.message("text.tooltip.message", constraint.isInvertRegExp() ? SSRBundle.message("not.tooltip.message") : "",
+ constraint.getRegExp(),
+ constraint.isWithinHierarchy() || constraint.isStrictlyWithinHierarchy() ?
+ SSRBundle.message("within.hierarchy.tooltip.message") : ""));
+ }
+
+ if (constraint.getNameOfExprType() != null && constraint.getNameOfExprType().length() > 0) {
+ append(buf, SSRBundle.message("exprtype.tooltip.message",
+ constraint.isInvertExprType() ? SSRBundle.message("not.tooltip.message") : "",
+ constraint.getNameOfExprType(),
+ constraint.isExprTypeWithinHierarchy() ? SSRBundle.message("within.hierarchy.tooltip.message") : ""));
+ }
+
+ if (constraint.getMinCount() == constraint.getMaxCount()) {
+ append(buf, SSRBundle.message("occurs.tooltip.message", constraint.getMinCount()));
+ }
+ else {
+ append(buf, SSRBundle.message("min.occurs.tooltip.message", constraint.getMinCount(),
+ constraint.getMaxCount() == Integer.MAX_VALUE ?
+ StringUtil.decapitalize(SSRBundle.message("editvarcontraints.unlimited")) :
+ constraint.getMaxCount()));
+ }
+ }
+
+ final String script = namedScriptableDefinition.getScriptCodeConstraint();
+ if (script != null && script.length() > 2) {
+ final String str = SSRBundle.message("script.tooltip.message", StringUtil.stripQuotesAroundValue(script));
+ append(buf, str);
+ }
+
+ return buf.toString();
+ }
+
+ private static void append(final StringBuilder buf, final String str) {
+ if (buf.length() > 0) buf.append(", ");
+ buf.append(str);
+ }
+
+ public static void navigate(PsiElement result) {
+ FileEditorManager.getInstance(result.getProject()).openTextEditor(
+ new OpenFileDescriptor(result.getProject(), result.getContainingFile().getVirtualFile(), result.getTextOffset()), true);
+ }
+
+ public static void navigate(MatchResult result) {
+ final SmartPsiPointer ref = result.getMatchRef();
+
+ FileEditorManager.getInstance(ref.getProject())
+ .openTextEditor(new OpenFileDescriptor(ref.getProject(), ref.getFile(), ref.getOffset()), true);
+ }
+
+ public static void invokeAction(Configuration config, SearchContext context) {
+ if (config instanceof SearchConfiguration) {
+ StructuralSearchAction.triggerAction(config, context);
+ }
+ else {
+ StructuralReplaceAction.triggerAction(config, context);
+ }
+ }
+
+ static void showTooltip(@NotNull Editor editor, final int start, int end, @NotNull String text, @NotNull TooltipGroup group) {
+ Rectangle visibleArea = editor.getScrollingModel().getVisibleArea();
+ Point top = editor.logicalPositionToXY(editor.offsetToLogicalPosition(start));
+ final int documentLength = editor.getDocument().getTextLength();
+ if (end >= documentLength) end = documentLength;
+ Point bottom = editor.logicalPositionToXY(editor.offsetToLogicalPosition(end));
+
+ Point bestPoint = new Point(top.x, bottom.y + editor.getLineHeight());
+
+ if (!visibleArea.contains(bestPoint)) {
+ int defaultOffset = editor.logicalPositionToOffset(editor.xyToLogicalPosition(new Point(0, 0)));
+ bestPoint = editor.logicalPositionToXY(editor.offsetToLogicalPosition(defaultOffset));
+ }
+
+ Point p = SwingUtilities.convertPoint(editor.getContentComponent(), bestPoint, editor.getComponent().getRootPane().getLayeredPane());
+ TooltipController.getInstance().showTooltip(editor, p, text, false, group);
+ }
+
+ public static void updateHighlighter(Editor editor, StructuralSearchProfile profile) {
+ final TemplateContextType contextType = profile.getTemplateContextType();
+ if (contextType != null) {
+ TemplateContext context = new TemplateContext();
+ context.setEnabled(contextType, true);
+ TemplateEditorUtil.setHighlighter(editor, context);
+ }
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/UsageViewContext.java b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/UsageViewContext.java
new file mode 100644
index 000000000000..afd2ae1b029d
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/UsageViewContext.java
@@ -0,0 +1,190 @@
+package com.intellij.structuralsearch.plugin.ui;
+
+import com.intellij.navigation.ItemPresentation;
+import com.intellij.openapi.actionSystem.*;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.fileEditor.FileEditor;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.search.GlobalSearchScope;
+import com.intellij.structuralsearch.SSRBundle;
+import com.intellij.structuralsearch.plugin.replace.ui.ReplaceCommand;
+import com.intellij.usages.*;
+import org.jetbrains.annotations.NotNull;
+
+import javax.swing.*;
+import java.util.Set;
+
+/**
+ * Created by IntelliJ IDEA.
+ * User: Maxim.Mossienko
+ * Date: Mar 9, 2005
+ * Time: 2:47:49 PM
+ * To change this template use File | Settings | File Templates.
+ */
+public class UsageViewContext {
+ protected final SearchContext mySearchContext;
+ private UsageView myUsageView;
+ protected final Configuration myConfiguration;
+ private Set<Usage> myExcludedSet;
+ private SearchCommand myCommand;
+
+ protected UsageViewContext(SearchContext _searchContext,Configuration _configuration) {
+ myConfiguration = _configuration;
+ mySearchContext = _searchContext;
+ }
+
+ public boolean isExcluded(Usage usage) {
+ if (myExcludedSet == null) myExcludedSet = myUsageView.getExcludedUsages();
+ return myExcludedSet.contains(usage);
+ }
+
+ public UsageView getUsageView() {
+ return myUsageView;
+ }
+
+ public void setUsageView(final UsageView usageView) {
+ myUsageView = usageView;
+ }
+
+ public Configuration getConfiguration() {
+ return myConfiguration;
+ }
+
+ public SearchCommand getCommand() {
+ if (myCommand == null) myCommand = createCommand();
+ return myCommand;
+ }
+
+ protected SearchCommand createCommand() {
+ return new SearchCommand(mySearchContext.getProject(), this);
+ }
+
+ protected String _getPresentableText() {
+ return myConfiguration.getMatchOptions().getSearchPattern();
+ }
+
+ public UsageTarget getTarget() {
+ return new MyUsageTarget(_getPresentableText());
+ }
+
+ public void configure(@NotNull UsageViewPresentation presentation) {
+ String s = _getPresentableText();
+ if (s.length() > 15) s = s.substring(0,15) + "...";
+ final String usagesString = SSRBundle.message("occurrences.of", s);
+ presentation.setUsagesString(usagesString);
+ presentation.setTabText(StringUtil.capitalize(usagesString));
+ presentation.setUsagesWord(SSRBundle.message("occurrence"));
+ presentation.setCodeUsagesString(SSRBundle.message("found.occurrences"));
+ }
+
+ protected void configureActions() {}
+
+ private class MyUsageTarget implements ConfigurableUsageTarget,ItemPresentation, TypeSafeDataProvider {
+ private final String myPresentableText;
+
+ MyUsageTarget(String str) {
+ myPresentableText = str;
+ }
+
+ @Override
+ public String getPresentableText() {
+ return myPresentableText;
+ }
+
+ @Override
+ public String getLocationString() {
+ //noinspection HardCodedStringLiteral
+ return "Do Not Know Where";
+ }
+
+ @Override
+ public Icon getIcon(boolean open) {
+ return null;
+ }
+
+ @Override
+ public void findUsages() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void findUsagesInEditor(@NotNull FileEditor editor) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void highlightUsages(@NotNull PsiFile file, @NotNull Editor editor, boolean clearHighlights) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean isValid() {
+ return true;
+ }
+
+ @Override
+ public boolean isReadOnly() {
+ return true;
+ }
+
+ @Override
+ public VirtualFile[] getFiles() {
+ return null;
+ }
+
+ @Override
+ public void update() {
+ }
+
+ @Override
+ public String getName() {
+ //noinspection HardCodedStringLiteral
+ return "my name";
+ }
+
+ @Override
+ public ItemPresentation getPresentation() {
+ return this;
+ }
+
+ @Override
+ public void navigate(boolean requestFocus) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean canNavigate() {
+ return false;
+ }
+
+ @Override
+ public boolean canNavigateToSource() {
+ return false;
+ }
+
+ @Override
+ public void showSettings() {
+ UIUtil.invokeAction(myConfiguration, mySearchContext);
+ }
+
+ @Override
+ public KeyboardShortcut getShortcut() {
+ return ActionManager.getInstance().getKeyboardShortcut(getCommand() instanceof ReplaceCommand ? "StructuralSearchPlugin.StructuralReplaceAction":"StructuralSearchPlugin.StructuralSearchAction");
+ }
+
+ @NotNull
+ @Override
+ public String getLongDescriptiveName() {
+ return _getPresentableText();
+ }
+
+ @Override
+ public void calcData(DataKey key, DataSink sink) {
+ if (key == UsageView.USAGE_SCOPE) {
+ sink.put(UsageView.USAGE_SCOPE, GlobalSearchScope.allScope(mySearchContext.getProject()));
+ }
+ }
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/VarConstraints.form b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/VarConstraints.form
new file mode 100644
index 000000000000..20094b213e64
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/VarConstraints.form
@@ -0,0 +1,350 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="com.intellij.structuralsearch.plugin.ui.EditVarConstraintsDialog">
+ <grid id="53af4" binding="mainForm" layout-manager="GridLayoutManager" row-count="8" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
+ <margin top="0" left="0" bottom="0" right="0"/>
+ <constraints>
+ <xy x="24" y="82" width="889" height="681"/>
+ </constraints>
+ <properties/>
+ <border type="none"/>
+ <children>
+ <grid id="33d30" layout-manager="GridLayoutManager" row-count="1" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
+ <margin top="0" left="0" bottom="0" right="0"/>
+ <constraints>
+ <grid row="0" column="0" row-span="8" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties/>
+ <clientProperties>
+ <BorderFactoryClass class="java.lang.String" value="com.intellij.ui.IdeBorderFactory$PlainSmallWithoutIndent"/>
+ </clientProperties>
+ <border type="none" title-resource-bundle="messages/SSRBundle" title-key="var.constraints.variables.border"/>
+ <children>
+ <grid id="d6a7" layout-manager="GridLayoutManager" row-count="1" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
+ <margin top="0" left="0" bottom="0" right="0"/>
+ <constraints>
+ <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties/>
+ <border type="none"/>
+ <children>
+ <component id="e85ba" class="com.intellij.ui.components.JBList" binding="parameterList">
+ <constraints>
+ <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="6" hsize-policy="2" anchor="0" fill="3" indent="0" use-parent-layout="false">
+ <preferred-size width="150" height="50"/>
+ </grid>
+ </constraints>
+ <properties/>
+ </component>
+ </children>
+ </grid>
+ </children>
+ </grid>
+ <grid id="4cae8" binding="textConstraintsPanel" layout-manager="GridLayoutManager" row-count="2" column-count="3" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
+ <margin top="0" left="0" bottom="0" right="0"/>
+ <constraints>
+ <grid row="1" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties/>
+ <clientProperties>
+ <BorderFactoryClass class="java.lang.String" value="com.intellij.ui.IdeBorderFactory$PlainSmallWithIndent"/>
+ </clientProperties>
+ <border type="none" title-resource-bundle="messages/SSRBundle" title-key="var.constraints.text.constraints.border"/>
+ <children>
+ <component id="cc210" class="javax.swing.JLabel">
+ <constraints>
+ <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text resource-bundle="messages/SSRBundle" key="editvarcontraints.text.regular.expression"/>
+ </properties>
+ </component>
+ <component id="3b03c" class="com.intellij.ui.EditorTextField" binding="regexp" custom-create="true">
+ <constraints>
+ <grid row="0" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false">
+ <preferred-size width="150" height="-1"/>
+ </grid>
+ </constraints>
+ <properties/>
+ </component>
+ <component id="f7d70" class="javax.swing.JCheckBox" binding="applyWithinTypeHierarchy">
+ <constraints>
+ <grid row="1" column="0" row-span="1" col-span="2" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text resource-bundle="messages/SSRBundle" key="editvarcontraints.apply.constraint.within.type.hierarchy"/>
+ </properties>
+ </component>
+ <component id="b766f" class="javax.swing.JCheckBox" binding="notRegexp">
+ <constraints>
+ <grid row="0" column="2" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text resource-bundle="messages/SSRBundle" key="editvarcontraints.invert.condition"/>
+ </properties>
+ </component>
+ <component id="51cbf" class="javax.swing.JCheckBox" binding="wholeWordsOnly">
+ <constraints>
+ <grid row="1" column="2" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text resource-bundle="messages/SSRBundle" key="editvarcontraints.whole.words.only"/>
+ </properties>
+ </component>
+ </children>
+ </grid>
+ <grid id="bb81f" binding="expressionConstraints" layout-manager="GridLayoutManager" row-count="6" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
+ <margin top="0" left="0" bottom="0" right="0"/>
+ <constraints>
+ <grid row="4" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties/>
+ <clientProperties>
+ <BorderFactoryClass class="java.lang.String" value="com.intellij.ui.IdeBorderFactory$PlainSmallWithIndent"/>
+ </clientProperties>
+ <border type="none" title-resource-bundle="messages/SSRBundle" title-key="var.constraints.expression.constraints.border"/>
+ <children>
+ <component id="2bdb4" class="javax.swing.JCheckBox" binding="read">
+ <constraints>
+ <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text resource-bundle="messages/SSRBundle" key="editvarcontraints.value.is.read"/>
+ </properties>
+ </component>
+ <component id="aba7e" class="javax.swing.JCheckBox" binding="write">
+ <constraints>
+ <grid row="1" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text resource-bundle="messages/SSRBundle" key="editvarcontraints.value.is.written"/>
+ </properties>
+ </component>
+ <component id="5698c" class="javax.swing.JCheckBox" binding="notRead">
+ <constraints>
+ <grid row="0" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text resource-bundle="messages/SSRBundle" key="editvarcontraints.invert.condition"/>
+ </properties>
+ </component>
+ <component id="79e72" class="javax.swing.JCheckBox" binding="notWrite">
+ <constraints>
+ <grid row="1" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text resource-bundle="messages/SSRBundle" key="editvarcontraints.invert.condition"/>
+ </properties>
+ </component>
+ <component id="e9391" class="com.intellij.ui.EditorTextField" binding="regexprForExprType" custom-create="true">
+ <constraints>
+ <grid row="2" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false">
+ <preferred-size width="150" height="-1"/>
+ </grid>
+ </constraints>
+ <properties/>
+ </component>
+ <component id="59512" class="javax.swing.JCheckBox" binding="exprTypeWithinHierarchy">
+ <constraints>
+ <grid row="3" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text resource-bundle="messages/SSRBundle" key="editvarcontraints.apply.constraint.within.type.hierarchy"/>
+ </properties>
+ </component>
+ <component id="17342" class="javax.swing.JCheckBox" binding="notExprType">
+ <constraints>
+ <grid row="3" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text resource-bundle="messages/SSRBundle" key="editvarcontraints.invert.condition"/>
+ </properties>
+ </component>
+ <component id="4d6d0" class="javax.swing.JLabel">
+ <constraints>
+ <grid row="2" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text resource-bundle="messages/SSRBundle" key="editvarcontraints.text.regular.expression.for.java.expression.type"/>
+ </properties>
+ </component>
+ <component id="a97fa" class="javax.swing.JLabel">
+ <constraints>
+ <grid row="4" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text resource-bundle="messages/SSRBundle" key="editvarcontraints.text.regular.expression.for.formal.argument.type.of.the.method"/>
+ </properties>
+ </component>
+ <component id="d02c2" class="com.intellij.ui.EditorTextField" binding="formalArgType" custom-create="true">
+ <constraints>
+ <grid row="4" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false">
+ <preferred-size width="150" height="-1"/>
+ </grid>
+ </constraints>
+ <properties/>
+ </component>
+ <component id="bc6d0" class="javax.swing.JCheckBox" binding="invertFormalArgType">
+ <constraints>
+ <grid row="5" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text resource-bundle="messages/SSRBundle" key="editvarcontraints.invert.condition"/>
+ </properties>
+ </component>
+ <component id="2b0ea" class="javax.swing.JCheckBox" binding="formalArgTypeWithinHierarchy">
+ <constraints>
+ <grid row="5" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text resource-bundle="messages/SSRBundle" key="editvarcontraints.apply.constraint.within.type.hierarchy"/>
+ </properties>
+ </component>
+ </children>
+ </grid>
+ <component id="9a3a8" class="javax.swing.JCheckBox" binding="partOfSearchResults">
+ <constraints>
+ <grid row="6" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text resource-bundle="messages/SSRBundle" key="editvarcontraints.this.variable.is.target.of.the.search"/>
+ </properties>
+ </component>
+ <vspacer id="d06dd">
+ <constraints>
+ <grid row="7" column="1" row-span="1" col-span="1" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
+ </constraints>
+ </vspacer>
+ <grid id="2a918" layout-manager="GridLayoutManager" row-count="1" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
+ <margin top="0" left="0" bottom="0" right="0"/>
+ <constraints>
+ <grid row="5" column="1" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties/>
+ <border type="none"/>
+ <children>
+ <grid id="964fd" layout-manager="GridLayoutManager" row-count="1" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
+ <margin top="0" left="0" bottom="0" right="0"/>
+ <constraints>
+ <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties/>
+ <clientProperties>
+ <BorderFactoryClass class="java.lang.String" value="com.intellij.ui.IdeBorderFactory$PlainSmallWithIndent"/>
+ </clientProperties>
+ <border type="none" title-resource-bundle="messages/SSRBundle" title-key="var.constraints.script.constraints.border"/>
+ <children>
+ <component id="cdf9" class="javax.swing.JLabel">
+ <constraints>
+ <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text resource-bundle="messages/SSRBundle" key="script.option.text"/>
+ </properties>
+ </component>
+ <component id="e7633" class="com.intellij.openapi.ui.ComponentWithBrowseButton" binding="customScriptCode" custom-create="true">
+ <constraints>
+ <grid row="0" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false">
+ <preferred-size width="150" height="-1"/>
+ </grid>
+ </constraints>
+ <properties/>
+ </component>
+ </children>
+ </grid>
+ </children>
+ </grid>
+ <grid id="d8664" binding="occurencePanel" layout-manager="GridLayoutManager" row-count="2" column-count="4" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
+ <margin top="0" left="0" bottom="0" right="0"/>
+ <constraints>
+ <grid row="3" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties/>
+ <clientProperties>
+ <BorderFactoryClass class="java.lang.String" value="com.intellij.ui.IdeBorderFactory$PlainSmallWithIndent"/>
+ </clientProperties>
+ <border type="none" title-resource-bundle="messages/SSRBundle" title-key="var.constraints.occurrences.count.border"/>
+ <children>
+ <component id="e4b4a" class="javax.swing.JLabel">
+ <constraints>
+ <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text resource-bundle="messages/SSRBundle" key="editvarcontraints.minimum.count"/>
+ </properties>
+ </component>
+ <component id="f6a1a" class="javax.swing.JTextField" binding="minoccurs">
+ <constraints>
+ <grid row="0" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false">
+ <preferred-size width="50" height="-1"/>
+ <maximum-size width="50" height="-1"/>
+ </grid>
+ </constraints>
+ <properties>
+ <text value="1"/>
+ </properties>
+ </component>
+ <component id="881e7" class="javax.swing.JLabel">
+ <constraints>
+ <grid row="0" column="2" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text resource-bundle="messages/SSRBundle" key="editvarcontraints.maximum.count"/>
+ </properties>
+ </component>
+ <component id="42" class="javax.swing.JTextField" binding="maxoccurs">
+ <constraints>
+ <grid row="0" column="3" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false">
+ <preferred-size width="50" height="-1"/>
+ </grid>
+ </constraints>
+ <properties>
+ <columns value="0"/>
+ <text value="1"/>
+ </properties>
+ </component>
+ <component id="7c33b" class="javax.swing.JCheckBox" binding="maxoccursUnlimited">
+ <constraints>
+ <grid row="1" column="3" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text resource-bundle="messages/SSRBundle" key="editvarcontraints.unlimited"/>
+ </properties>
+ </component>
+ </children>
+ </grid>
+ <grid id="463bd" binding="containedInConstraints" layout-manager="GridLayoutManager" row-count="2" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
+ <margin top="0" left="0" bottom="0" right="0"/>
+ <constraints>
+ <grid row="2" column="1" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties/>
+ <clientProperties>
+ <BorderFactoryClass class="java.lang.String" value="com.intellij.ui.IdeBorderFactory$PlainSmallWithIndent"/>
+ </clientProperties>
+ <border type="none" title="Contained in constraints"/>
+ <children>
+ <component id="7003f" class="com.intellij.ui.ComboboxWithBrowseButton" binding="withinCombo">
+ <constraints>
+ <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="2" anchor="8" fill="1" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties/>
+ </component>
+ <component id="896c2" class="javax.swing.JCheckBox" binding="invertWithinIn">
+ <constraints>
+ <grid row="1" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text resource-bundle="messages/SSRBundle" key="editvarcontraints.invert.condition"/>
+ </properties>
+ </component>
+ </children>
+ </grid>
+ <component id="ce461" class="javax.swing.JLabel" binding="myRegExHelpLabel" custom-create="true">
+ <constraints>
+ <grid row="0" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="4" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties/>
+ </component>
+ </children>
+ </grid>
+</form>
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/util/CollectingMatchResultSink.java b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/util/CollectingMatchResultSink.java
new file mode 100644
index 000000000000..4caff77bdc06
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/util/CollectingMatchResultSink.java
@@ -0,0 +1,40 @@
+package com.intellij.structuralsearch.plugin.util;
+
+import com.intellij.openapi.progress.ProgressIndicator;
+import com.intellij.psi.PsiFile;
+import com.intellij.structuralsearch.MatchResult;
+import com.intellij.structuralsearch.MatchResultSink;
+import com.intellij.structuralsearch.MatchingProcess;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.LinkedList;
+import java.util.List;
+
+public class CollectingMatchResultSink implements MatchResultSink {
+ private final List<MatchResult> matches = new LinkedList<MatchResult>();
+
+ public void newMatch(MatchResult result) {
+ matches.add(result);
+ }
+
+ /* Notifies sink about starting the matching for given element
+ * @param element the current file
+ */
+ public void processFile(PsiFile element) {
+ }
+
+ public void matchingFinished() {
+ }
+
+ public ProgressIndicator getProgressIndicator() {
+ return null;
+ }
+
+ public void setMatchingProcess(MatchingProcess process) {
+ }
+
+ @NotNull
+ public List<MatchResult> getMatches() {
+ return matches;
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/util/SmartPsiPointer.java b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/util/SmartPsiPointer.java
new file mode 100644
index 000000000000..0fee846c77b0
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/util/SmartPsiPointer.java
@@ -0,0 +1,56 @@
+package com.intellij.structuralsearch.plugin.util;
+
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.SmartPointerManager;
+import com.intellij.psi.SmartPsiElementPointer;
+
+/**
+ * Reference to element have been matched
+ */
+public class SmartPsiPointer {
+ private SmartPsiElementPointer pointer;
+
+ public SmartPsiPointer(PsiElement element) {
+ pointer = element != null ? SmartPointerManager.getInstance(element.getProject()).createSmartPsiElementPointer(element):null;
+ }
+
+ public VirtualFile getFile() {
+ return pointer != null ? pointer.getVirtualFile():null;
+ }
+
+ public int getOffset() {
+ return pointer != null ? pointer.getElement().getTextRange().getStartOffset():-1;
+ }
+
+ public int getLength() {
+ return pointer != null ? pointer.getElement().getTextRange().getEndOffset():0;
+ }
+
+ public PsiElement getElement() {
+ return pointer != null ? pointer.getElement():null;
+ }
+
+ public void clear() {
+ pointer = null;
+ }
+
+ public Project getProject() {
+ return pointer != null ? pointer.getElement().getProject():null;
+ }
+
+ public boolean equals(Object o) {
+ if (o instanceof SmartPsiPointer) {
+ final SmartPsiPointer ref = ((SmartPsiPointer)o);
+ return ref.getFile().equals(getFile()) &&
+ ref.getOffset() == getOffset() &&
+ ref.getLength() == getLength();
+ }
+ return false;
+ }
+
+ public int hashCode() {
+ return pointer != null ? getElement().hashCode():0;
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/tokenindex/AnonymToken.java b/platform/structuralsearch/source/com/intellij/tokenindex/AnonymToken.java
new file mode 100644
index 000000000000..c364ccc3f9c5
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/tokenindex/AnonymToken.java
@@ -0,0 +1,34 @@
+package com.intellij.tokenindex;
+
+/**
+ * @author Eugene.Kudelevsky
+ */
+public class AnonymToken extends Token {
+ private final byte myType;
+
+ public AnonymToken(byte type, int start, int end) {
+ super(start, end);
+ myType = type;
+ }
+
+ public byte getType() {
+ return myType;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ AnonymToken that = (AnonymToken)o;
+
+ if (myType != that.myType) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return myType;
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/tokenindex/IndentToken.java b/platform/structuralsearch/source/com/intellij/tokenindex/IndentToken.java
new file mode 100644
index 000000000000..9409d185c808
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/tokenindex/IndentToken.java
@@ -0,0 +1,20 @@
+package com.intellij.tokenindex;
+
+/**
+ * @author Eugene.Kudelevsky
+ */
+public class IndentToken extends Token {
+ public IndentToken(int start, int end) {
+ super(start, end);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return obj instanceof IndentToken;
+ }
+
+ @Override
+ public int hashCode() {
+ return getClass().hashCode();
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/tokenindex/LanguageTokenizer.java b/platform/structuralsearch/source/com/intellij/tokenindex/LanguageTokenizer.java
new file mode 100644
index 000000000000..7e8986b25ada
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/tokenindex/LanguageTokenizer.java
@@ -0,0 +1,14 @@
+package com.intellij.tokenindex;
+
+import com.intellij.lang.LanguageExtension;
+
+/**
+ * @author Eugene.Kudelevsky
+ */
+public class LanguageTokenizer extends LanguageExtension<Tokenizer> {
+ public static final LanguageTokenizer INSTANCE = new LanguageTokenizer();
+
+ private LanguageTokenizer() {
+ super("com.intellij.tokenindex.tokenizer", null);
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/tokenindex/PathMarkerToken.java b/platform/structuralsearch/source/com/intellij/tokenindex/PathMarkerToken.java
new file mode 100644
index 000000000000..f74590f46bb1
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/tokenindex/PathMarkerToken.java
@@ -0,0 +1,41 @@
+package com.intellij.tokenindex;
+
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * @author Eugene.Kudelevsky
+ */
+public class PathMarkerToken extends Token {
+ private final String myPath;
+
+ public PathMarkerToken(@NotNull String path) {
+ super(-1, -1);
+ myPath = path;
+ }
+
+ public String getPath() {
+ return myPath;
+ }
+
+ @Override
+ public String toString() {
+ return myPath;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ PathMarkerToken that = (PathMarkerToken)o;
+
+ if (!myPath.equals(that.myPath)) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return myPath.hashCode();
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/tokenindex/PsiMarkerToken.java b/platform/structuralsearch/source/com/intellij/tokenindex/PsiMarkerToken.java
new file mode 100644
index 000000000000..c2e7c8b3cf89
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/tokenindex/PsiMarkerToken.java
@@ -0,0 +1,19 @@
+package com.intellij.tokenindex;
+
+import com.intellij.psi.PsiFile;
+
+/**
+ * @author Eugene.Kudelevsky
+ */
+public class PsiMarkerToken extends Token {
+ private final PsiFile myFile;
+
+ public PsiMarkerToken(PsiFile file) {
+ super(-1, -1);
+ myFile = file;
+ }
+
+ public PsiFile getFile() {
+ return myFile;
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/tokenindex/RecursiveTokenizingVisitor.java b/platform/structuralsearch/source/com/intellij/tokenindex/RecursiveTokenizingVisitor.java
new file mode 100644
index 000000000000..013f804a8225
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/tokenindex/RecursiveTokenizingVisitor.java
@@ -0,0 +1,86 @@
+package com.intellij.tokenindex;
+
+import com.intellij.lang.Language;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiRecursiveElementWalkingVisitor;
+import com.intellij.structuralsearch.StructuralSearchProfile;
+import com.intellij.structuralsearch.StructuralSearchUtil;
+import com.intellij.util.containers.HashSet;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author Eugene.Kudelevsky
+ */
+public class RecursiveTokenizingVisitor extends PsiRecursiveElementWalkingVisitor {
+ private final int myBaseOffset;
+ private final List<Token> myTokens;
+ private final Set<String> myLanguages = new HashSet<String>();
+ private final Set<Language> myAcceptableLanguages;
+
+ private Language myLastLanguage;
+ private StructuralSearchProfile myLastProfile;
+
+ public RecursiveTokenizingVisitor(List<Token> tokens, Set<Language> acceptableLanguages, int baseOffset) {
+ super(true);
+ myTokens = tokens;
+ myAcceptableLanguages = acceptableLanguages;
+ myBaseOffset = baseOffset;
+ }
+
+ public RecursiveTokenizingVisitor(List<Token> tokens, Set<Language> acceptableLanguages) {
+ this(tokens, acceptableLanguages, 0);
+ }
+
+ public RecursiveTokenizingVisitor() {
+ this(new ArrayList<Token>(), null);
+ }
+
+ public List<Token> getTokens() {
+ return myTokens;
+ }
+
+ public void addToken(Token token) {
+ myTokens.add(token);
+ }
+
+ public Set<String> getLanguages() {
+ return myLanguages;
+ }
+
+ @Override
+ public void visitElement(PsiElement element) {
+ Language language = element.getLanguage();
+ if (language != myLastLanguage) {
+ myLastLanguage = language;
+ myLastProfile = StructuralSearchUtil.getProfileByPsiElement(element);
+ }
+ if (myLastProfile != null) {
+ language = myLastProfile.getLanguage(element);
+ }
+ if (myAcceptableLanguages == null || myAcceptableLanguages.contains(language)) {
+ Tokenizer tokenizer = StructuralSearchUtil.getTokenizerForLanguage(language);
+ if (tokenizer != null) {
+ myLanguages.add(language.getID());
+ if (!tokenizer.visit(element, this)) {
+ return;
+ }
+ }
+ }
+ super.visitElement(element);
+ }
+
+ @Override
+ protected void elementFinished(PsiElement element) {
+ Tokenizer tokenizer = StructuralSearchUtil.getTokenizerForLanguage(element.getLanguage());
+ if (tokenizer != null) {
+ tokenizer.elementFinished(element, this);
+ }
+ }
+
+ public int getBaseOffset() {
+ return myBaseOffset;
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/tokenindex/TextToken.java b/platform/structuralsearch/source/com/intellij/tokenindex/TextToken.java
new file mode 100644
index 000000000000..729491935cfe
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/tokenindex/TextToken.java
@@ -0,0 +1,39 @@
+package com.intellij.tokenindex;
+
+/**
+ * @author Eugene.Kudelevsky
+ */
+public class TextToken extends Token {
+ private final int myHash;
+
+ public TextToken(int hash, int start, int end) {
+ super(start, end);
+ myHash = hash;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ TextToken textToken = (TextToken)o;
+
+ if (myHash != textToken.myHash) return false;
+
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return Integer.toString(myHash);
+ }
+
+ @Override
+ public int hashCode() {
+ return myHash;
+ }
+
+ public int getHash() {
+ return myHash;
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/tokenindex/Token.java b/platform/structuralsearch/source/com/intellij/tokenindex/Token.java
new file mode 100644
index 000000000000..2b5428d31826
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/tokenindex/Token.java
@@ -0,0 +1,22 @@
+package com.intellij.tokenindex;
+
+/**
+ * @author Eugene.Kudelevsky
+ */
+public abstract class Token {
+ private final int myStart;
+ private final int myEnd;
+
+ public Token(int start, int end) {
+ myStart = start;
+ myEnd = end;
+ }
+
+ public int getStart() {
+ return myStart;
+ }
+
+ public int getEnd() {
+ return myEnd;
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/tokenindex/TokenIndex.java b/platform/structuralsearch/source/com/intellij/tokenindex/TokenIndex.java
new file mode 100644
index 000000000000..4547092eb58f
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/tokenindex/TokenIndex.java
@@ -0,0 +1,187 @@
+package com.intellij.tokenindex;
+
+import com.intellij.lang.Language;
+import com.intellij.openapi.fileTypes.LanguageFileType;
+import com.intellij.openapi.vfs.JarFileSystem;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.structuralsearch.StructuralSearchUtil;
+import com.intellij.util.containers.HashMap;
+import com.intellij.util.indexing.*;
+import com.intellij.util.io.DataExternalizer;
+import com.intellij.util.io.KeyDescriptor;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author Eugene.Kudelevsky
+ */
+public class TokenIndex extends FileBasedIndexExtension<TokenIndexKey, List<Token>> {
+ private static final int FILE_BLOCK_SIZE = 100;
+
+ public static final ID<TokenIndexKey, List<Token>> INDEX_ID = ID.create("token.index");
+
+ private static final int VERSION = 3;
+
+ private final KeyDescriptor<TokenIndexKey> myKeyDescriptor = new TokenIndexKeyDescriptor();
+
+ private static final int ANONYM_TOKEN_ID = 0;
+ private static final int TEXT_TOKEN_ID = 1;
+ private static final int MARKER_TOKEN_ID = 2;
+ private static final int INDENT_TOKEN_ID = 3;
+
+ private final DataExternalizer<List<Token>> myDataExternalizer = new DataExternalizer<List<Token>>() {
+ @Override
+ public void save(@NotNull DataOutput out, List<Token> value) throws IOException {
+ out.writeInt(value.size());
+ for (Token token : value) {
+ if (token instanceof AnonymToken) {
+ out.writeByte(ANONYM_TOKEN_ID);
+ out.writeInt(token.getStart());
+ out.writeInt(token.getEnd());
+ out.writeByte(((AnonymToken)token).getType());
+ }
+ else if (token instanceof TextToken) {
+ out.writeByte(TEXT_TOKEN_ID);
+ out.writeInt(token.getStart());
+ out.writeInt(token.getEnd());
+ out.writeInt(((TextToken)token).getHash());
+ }
+ else if (token instanceof PathMarkerToken) {
+ out.writeByte(MARKER_TOKEN_ID);
+ out.writeUTF(((PathMarkerToken)token).getPath());
+ }
+ else if (token instanceof IndentToken) {
+ out.writeByte(INDENT_TOKEN_ID);
+ out.writeInt(token.getStart());
+ out.writeInt(token.getEnd());
+ }
+ else {
+ assert false : "Unsupported token type " + token.getClass();
+ }
+ }
+ }
+
+ @Override
+ public List<Token> read(@NotNull DataInput in) throws IOException {
+ List<Token> result = new ArrayList<Token>();
+ int n = in.readInt();
+ for (int i = 0; i < n; i++) {
+ byte tokenTypeId = in.readByte();
+ switch (tokenTypeId) {
+ case ANONYM_TOKEN_ID: {
+ int start = in.readInt();
+ int end = in.readInt();
+ byte anonymTokenTypeValue = in.readByte();
+ result.add(new AnonymToken(anonymTokenTypeValue, start, end));
+ break;
+ }
+ case TEXT_TOKEN_ID: {
+ int start = in.readInt();
+ int end = in.readInt();
+ int hash = in.readInt();
+ result.add(new TextToken(hash, start, end));
+ break;
+ }
+ case MARKER_TOKEN_ID: {
+ String path = in.readUTF();
+ result.add(new PathMarkerToken(path));
+ break;
+ }
+ case INDENT_TOKEN_ID:
+ int start = in.readInt();
+ int end = in.readInt();
+ result.add(new IndentToken(start, end));
+ break;
+ }
+ }
+ return result;
+ }
+ };
+
+ @NotNull
+ @Override
+ public ID<TokenIndexKey, List<Token>> getName() {
+ return INDEX_ID;
+ }
+
+ private static int getBlockId(String filePath) {
+ int h = filePath.hashCode();
+ if (h < 0) {
+ h = -h;
+ }
+ return h % FILE_BLOCK_SIZE;
+ }
+
+ @NotNull
+ @Override
+ public DataIndexer<TokenIndexKey, List<Token>, FileContent> getIndexer() {
+ return new DataIndexer<TokenIndexKey, List<Token>, FileContent>() {
+ @Override
+ @NotNull
+ public Map<TokenIndexKey, List<Token>> map(@NotNull FileContent inputData) {
+ if (true) return Collections.EMPTY_MAP; // TODO: Eugene index is VERY unefficient and leads to OME
+ Map<TokenIndexKey, List<Token>> result = new HashMap<TokenIndexKey, List<Token>>(1);
+ RecursiveTokenizingVisitor visitor = new RecursiveTokenizingVisitor();
+ inputData.getPsiFile().accept(visitor);
+ List<Token> tokens = visitor.getTokens();
+ if (tokens.size() > 0) {
+ String path = inputData.getFile().getPath();
+ tokens.add(new PathMarkerToken(path));
+ TokenIndexKey key = new TokenIndexKey(visitor.getLanguages(), getBlockId(path));
+ result.put(key, tokens);
+ }
+ return result;
+ }
+ };
+ }
+
+ @NotNull
+ @Override
+ public KeyDescriptor<TokenIndexKey> getKeyDescriptor() {
+ return myKeyDescriptor;
+ }
+
+ @NotNull
+ @Override
+ public DataExternalizer<List<Token>> getValueExternalizer() {
+ return myDataExternalizer;
+ }
+
+ @NotNull
+ @Override
+ public FileBasedIndex.InputFilter getInputFilter() {
+ return new FileBasedIndex.InputFilter() {
+ @Override
+ public boolean acceptInput(@NotNull VirtualFile file) {
+ if (file.getFileSystem() instanceof JarFileSystem) return false;
+ return file.getFileType() instanceof LanguageFileType;
+ }
+ };
+ }
+
+ @Override
+ public boolean dependsOnFileContent() {
+ return true;
+ }
+
+ @Override
+ public int getVersion() {
+ return VERSION;
+ }
+
+ @Override
+ public int getCacheSize() {
+ return 1;
+ }
+
+ public static boolean supports(Language language) {
+ return StructuralSearchUtil.getTokenizerForLanguage(language) != null;
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/tokenindex/TokenIndexKey.java b/platform/structuralsearch/source/com/intellij/tokenindex/TokenIndexKey.java
new file mode 100644
index 000000000000..8c6ec1d93875
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/tokenindex/TokenIndexKey.java
@@ -0,0 +1,60 @@
+package com.intellij.tokenindex;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Set;
+
+/**
+ * @author Eugene.Kudelevsky
+ */
+public class TokenIndexKey {
+ private final Set<String> myLanguages;
+ private final int myBlockId;
+
+ public TokenIndexKey(@NotNull Set<String> languages, int blockId) {
+ myLanguages = languages;
+ myBlockId = blockId;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ TokenIndexKey that = (TokenIndexKey)o;
+
+ if (myBlockId != that.myBlockId) return false;
+ if (!myLanguages.equals(that.myLanguages)) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = myLanguages.hashCode();
+ result = 31 * result + myBlockId;
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return myLanguages + ": " + myBlockId;
+ }
+
+ public Set<String> getLanguages() {
+ return myLanguages;
+ }
+
+ public boolean containsLanguage(String languageId) {
+ for (String language : myLanguages) {
+ if (language.contains(languageId)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public int getBlockId() {
+ return myBlockId;
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/tokenindex/TokenIndexKeyDescriptor.java b/platform/structuralsearch/source/com/intellij/tokenindex/TokenIndexKeyDescriptor.java
new file mode 100644
index 000000000000..901508ce371d
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/tokenindex/TokenIndexKeyDescriptor.java
@@ -0,0 +1,43 @@
+package com.intellij.tokenindex;
+
+import com.intellij.util.containers.HashSet;
+import com.intellij.util.io.KeyDescriptor;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.util.Set;
+
+/**
+ * @author Eugene.Kudelevsky
+ */
+public class TokenIndexKeyDescriptor implements KeyDescriptor<TokenIndexKey> {
+ public int getHashCode(TokenIndexKey value) {
+ return value.hashCode();
+ }
+
+ public boolean isEqual(TokenIndexKey val1, TokenIndexKey val2) {
+ return val1.equals(val2);
+ }
+
+ public void save(@NotNull DataOutput out, TokenIndexKey value) throws IOException {
+ Set<String> languages = value.getLanguages();
+ out.writeInt(languages.size());
+ for (String language : languages) {
+ out.writeUTF(language);
+ }
+ out.writeInt(value.getBlockId());
+ }
+
+ public TokenIndexKey read(@NotNull DataInput in) throws IOException {
+ int languagesCount = in.readInt();
+ Set<String> languages = new HashSet<String>();
+ for (int i = 0; i < languagesCount; i++) {
+ String languageId = in.readUTF();
+ languages.add(languageId);
+ }
+ int blockId = in.readInt();
+ return new TokenIndexKey(languages, blockId);
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/tokenindex/Tokenizer.java b/platform/structuralsearch/source/com/intellij/tokenindex/Tokenizer.java
new file mode 100644
index 000000000000..c1ce0c70e264
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/tokenindex/Tokenizer.java
@@ -0,0 +1,13 @@
+package com.intellij.tokenindex;
+
+import com.intellij.psi.PsiElement;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * @author Eugene.Kudelevsky
+ */
+public interface Tokenizer {
+ boolean visit(@NotNull PsiElement element, RecursiveTokenizingVisitor globalVisitor);
+
+ void elementFinished(@NotNull PsiElement element, RecursiveTokenizingVisitor globalVisitor);
+}