blob: 0c84e3bb24366603f1fddc49954a43b17e672a68 [file] [log] [blame]
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;
}
}