| 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; |
| } |
| } |