diff options
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.java | 557 |
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; + } +} |