summaryrefslogtreecommitdiff
path: root/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/SubstitutionHandler.java
diff options
context:
space:
mode:
Diffstat (limited to 'platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/SubstitutionHandler.java')
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/SubstitutionHandler.java557
1 files changed, 557 insertions, 0 deletions
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;
+ }
+}