/* * Copyright 2000-2017 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.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.getMatch()); } 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.hasResult() ? context.getResult().findSon(name) : null; 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) { if (result.isMultipleMatch()) { final int size = result.getAllSons().size(); if (matchedOccurs >= size) { return false; } if (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 final MatchResultImpl sonresult = new MatchResultImpl( substitution.getName(), substitution.getMatchImage(), substitution.getMatchRef(), substitution.getStart(), substitution.getEnd(), target ); 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); } 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.hasResult() ? context.getResult().findSon(name) : null; if (minOccurs >= 1 && ( substitution == null || StructuralSearchUtil.getProfileByFileType(context.getOptions().getFileType()).getElementContextByPsi(substitution.getMatch()) != 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(); 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<>(); 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 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; } }