diff options
Diffstat (limited to 'platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher')
52 files changed, 5619 insertions, 0 deletions
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/CompiledPattern.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/CompiledPattern.java new file mode 100644 index 000000000000..565f80ac01ce --- /dev/null +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/CompiledPattern.java @@ -0,0 +1,182 @@ +package com.intellij.structuralsearch.impl.matcher; + +import com.intellij.dupLocator.iterators.ArrayBackedNodeIterator; +import com.intellij.dupLocator.iterators.NodeIterator; +import com.intellij.openapi.util.Key; +import com.intellij.psi.PsiElement; +import com.intellij.psi.search.SearchScope; +import com.intellij.psi.util.PsiUtilCore; +import com.intellij.structuralsearch.StructuralSearchProfile; +import com.intellij.structuralsearch.StructuralSearchUtil; +import com.intellij.structuralsearch.impl.matcher.handlers.MatchingHandler; +import com.intellij.structuralsearch.impl.matcher.handlers.SimpleHandler; +import com.intellij.structuralsearch.impl.matcher.handlers.SubstitutionHandler; +import com.intellij.structuralsearch.impl.matcher.strategies.MatchingStrategy; +import org.jetbrains.annotations.Nullable; + +import java.util.HashMap; +import java.util.List; + +/** + * Class to hold compiled pattern information + */ +public abstract class CompiledPattern { + public static final String ALL_CLASS_UNMATCHED_CONTENT_VAR_ARTIFICIAL_NAME = "__class_unmatched__"; + private SearchScope scope; + private NodeIterator nodes; + private MatchingStrategy strategy; + private PsiElement targetNode; + private int optionsHashStamp; + private int nodeCount; + + // @todo support this property during matching (how many nodes should be advanced) + // when match is not successful (or successful partially) + //private int advancement = 1; + + public abstract String[] getTypedVarPrefixes(); + public abstract boolean isTypedVar(String str); + + public void setTargetNode(final PsiElement element) { + targetNode = element; + } + + public PsiElement getTargetNode() { + return targetNode; + } + + public int getOptionsHashStamp() { + return optionsHashStamp; + } + + public void setOptionsHashStamp(final int optionsHashStamp) { + this.optionsHashStamp = optionsHashStamp; + } + + public static final Key<Object> HANDLER_KEY = Key.create("ss.handler"); + + public MatchingStrategy getStrategy() { + return strategy; + } + + public void setStrategy(MatchingStrategy strategy) { + this.strategy = strategy; + } + + public int getNodeCount() { + return nodeCount; + } + + public NodeIterator getNodes() { + return nodes; + } + + public void setNodes(List<PsiElement> elements) { + this.nodes = new ArrayBackedNodeIterator(PsiUtilCore.toPsiElementArray(elements)); + this.nodeCount = elements.size(); + } + + public boolean isTypedVar(final PsiElement element) { + return element!=null && isTypedVar( element.getText() ); + } + + public boolean isRealTypedVar(PsiElement element) { + if (element!=null && element.getTextLength()>0) { + String str = getTypedVarString(element); + if (str.length()==0) { + return false; + } + return isTypedVar( str ); + } else { + return false; + } + } + + public String getTypedVarString(PsiElement element) { + final StructuralSearchProfile profile = StructuralSearchUtil.getProfileByPsiElement(element); + if (profile == null) { + return element.getText(); + } + return profile.getTypedVarString(element); + } + + private final HashMap<Object,MatchingHandler> handlers = new HashMap<Object,MatchingHandler>(); + + public MatchingHandler getHandlerSimple(PsiElement node) { + return handlers.get(node); + } + + private PsiElement last; + private MatchingHandler lastHandler; + + public MatchingHandler getHandler(PsiElement node) { + if (node==last) { + return lastHandler; + } + MatchingHandler handler = handlers.get(node); + + if (handler==null) { + handler = new SimpleHandler(); + setHandler(node,handler); + } + + last = node; + lastHandler = handler; + + return handler; + } + + public MatchingHandler getHandler(String name) { + return handlers.get(name); + } + + public void setHandler(PsiElement node, MatchingHandler handler) { + last = null; + handlers.put(node,handler); + } + + public SubstitutionHandler createSubstitutionHandler( + String name, String compiledName, boolean target,int minOccurs, int maxOccurs, boolean greedy) { + + SubstitutionHandler handler = (SubstitutionHandler) handlers.get(compiledName); + if (handler != null) return handler; + + handler = doCreateSubstitutionHandler(name, target, minOccurs, maxOccurs, greedy); + + handlers.put(compiledName,handler); + + return handler; + } + + protected SubstitutionHandler doCreateSubstitutionHandler(String name, boolean target, int minOccurs, int maxOccurs, boolean greedy) { + return new SubstitutionHandler(name, target, minOccurs, maxOccurs, greedy); + } + + public SearchScope getScope() { + return scope; + } + + public void setScope(SearchScope scope) { + this.scope = scope; + } + + public void clearHandlers() { + handlers.clear(); + last = null; + lastHandler = null; + } + + void clearHandlersState() { + for (final MatchingHandler h : handlers.values()) { + if (h != null) h.reset(); + } + } + + public boolean isToResetHandler(PsiElement element) { + return true; + } + + @Nullable + public String getAlternativeTextToMatch(PsiElement node, String previousText) { + return null; + } +} diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/DataProvider.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/DataProvider.java new file mode 100644 index 000000000000..e7327fad7e5e --- /dev/null +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/DataProvider.java @@ -0,0 +1,16 @@ +package com.intellij.structuralsearch.impl.matcher; + +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.project.Project; + +/** + * Created by IntelliJ IDEA. + * User: maxim + * Date: 07.01.2004 + * Time: 1:06:51 + * To change this template use Options | File Templates. + */ +public interface DataProvider { + Editor getEditor(); + Project getProject(); +} diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/GlobalMatchingVisitor.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/GlobalMatchingVisitor.java new file mode 100644 index 000000000000..5577db0d8540 --- /dev/null +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/GlobalMatchingVisitor.java @@ -0,0 +1,315 @@ +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; + } +} diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatchConstraintsSink.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatchConstraintsSink.java new file mode 100644 index 000000000000..2036c83d56f4 --- /dev/null +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatchConstraintsSink.java @@ -0,0 +1,85 @@ +package com.intellij.structuralsearch.impl.matcher; + +import com.intellij.psi.PsiElement; +import com.intellij.structuralsearch.MatchResultSink; +import com.intellij.structuralsearch.MatchingProcess; +import com.intellij.structuralsearch.MatchResult; +import com.intellij.structuralsearch.SSRBundle; +import com.intellij.psi.PsiFile; +import com.intellij.openapi.progress.ProgressIndicator; + +import javax.swing.*; +import java.util.HashMap; + +/** + * Sink to detect + */ +class MatchConstraintsSink implements MatchResultSink { + private final MatchResultSink delegate; + private MatchingProcess process; + private final boolean distinct; + private final boolean caseSensitive; + private int matchCount; + private final int maxMatches; + private final HashMap<Object, MatchResult> matches = new HashMap<Object, MatchResult>(); + + MatchConstraintsSink(MatchResultSink _delegate, int _maxMatches,boolean distinct, boolean _caseSensitive) { + delegate = _delegate; + maxMatches = _maxMatches; + this.distinct = distinct; + caseSensitive = _caseSensitive; + } + + public void newMatch(MatchResult result) { + if (distinct) { + String matchImage = result.getMatchImage(); + + if (!caseSensitive) matchImage = matchImage.toLowerCase(); + + if (matches.get(matchImage)!=null) { + return; + } + + matches.put(matchImage,result); + } + else { + final PsiElement match = result.getMatch(); + if (matches.containsKey(match)) { + return; + } + matches.put(match, result); + } + + delegate.newMatch(result); + ++matchCount; + + if (matchCount==maxMatches) { + JOptionPane.showMessageDialog(null, SSRBundle.message("search.produced.too.many.results.message")); + process.stop(); + } + } + + /* Notifies sink about starting the matching for given element + * @param element the current file + * @param task process continuation reference + */ + public void processFile(PsiFile element) { + delegate.processFile(element); + } + + public void setMatchingProcess(MatchingProcess matchingProcess) { + process = matchingProcess; + delegate.setMatchingProcess(process); + } + + public void matchingFinished() { + matchCount = 0; + matches.clear(); + delegate.matchingFinished(); + } + + public ProgressIndicator getProgressIndicator() { + return delegate.getProgressIndicator(); + } + +} diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatchContext.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatchContext.java new file mode 100644 index 000000000000..c4c8d21e3ab3 --- /dev/null +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatchContext.java @@ -0,0 +1,130 @@ +package com.intellij.structuralsearch.impl.matcher; + +import com.intellij.psi.PsiElement; +import com.intellij.structuralsearch.MatchOptions; +import com.intellij.structuralsearch.MatchResultSink; +import com.intellij.util.containers.Stack; + +import java.util.Collection; +import java.util.List; + +/** + * Global context of matching process + */ +public class MatchContext { + private MatchResultSink sink; + private final Stack<MatchResultImpl> previousResults = new Stack<MatchResultImpl>(); + private MatchResultImpl result; + private CompiledPattern pattern; + private MatchOptions options; + private GlobalMatchingVisitor matcher; + private boolean shouldRecursivelyMatch = true; + private boolean myWithAlternativePatternRoots = true; + + private List<PsiElement> myMatchedNodes; + + public List<PsiElement> getMatchedNodes() { + return myMatchedNodes; + } + + public void setMatchedNodes(final List<PsiElement> matchedNodes) { + myMatchedNodes = matchedNodes; + } + + public boolean isWithAlternativePatternRoots() { + return myWithAlternativePatternRoots; + } + + public void setWithAlternativePatternRoots(boolean withAlternativePatternRoots) { + myWithAlternativePatternRoots = withAlternativePatternRoots; + } + + public interface MatchedElementsListener { + void matchedElements(Collection<PsiElement> matchedElements); + void commitUnmatched(); + } + + private MatchedElementsListener myMatchedElementsListener; + + public void setMatcher(GlobalMatchingVisitor matcher) { + this.matcher = matcher; + } + + public GlobalMatchingVisitor getMatcher() { + return matcher; + } + + public MatchOptions getOptions() { + return options; + } + + public void setOptions(MatchOptions options) { + this.options = options; + } + + public MatchResultImpl getPreviousResult() { + return previousResults.isEmpty() ? null : previousResults.peek(); + } + + public MatchResultImpl getResult() { + if (result==null) result = new MatchResultImpl(); + return result; + } + + public void pushResult() { + previousResults.push(result); + result = null; + } + + public void popResult() { + result = previousResults.pop(); + } + + public void setResult(MatchResultImpl result) { + this.result = result; + if (result == null) { + pattern.clearHandlersState(); + } + } + + public boolean hasResult() { + return result!=null; + } + + public CompiledPattern getPattern() { + return pattern; + } + + public void setPattern(CompiledPattern pattern) { + this.pattern = pattern; + } + + public MatchResultSink getSink() { + return sink; + } + + public void setSink(MatchResultSink sink) { + this.sink = sink; + } + + void clear() { + result = null; + pattern = null; + } + + public boolean shouldRecursivelyMatch() { + return shouldRecursivelyMatch; + } + + public void setShouldRecursivelyMatch(boolean shouldRecursivelyMatch) { + this.shouldRecursivelyMatch = shouldRecursivelyMatch; + } + + public void setMatchedElementsListener(MatchedElementsListener _matchedElementsListener) { + myMatchedElementsListener = _matchedElementsListener; + } + + public MatchedElementsListener getMatchedElementsListener() { + return myMatchedElementsListener; + } +} diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatchPredicateProvider.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatchPredicateProvider.java new file mode 100644 index 000000000000..b75ffb5d0f5d --- /dev/null +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatchPredicateProvider.java @@ -0,0 +1,16 @@ +package com.intellij.structuralsearch.impl.matcher; + +import com.intellij.openapi.extensions.ExtensionPointName; +import com.intellij.structuralsearch.MatchOptions; +import com.intellij.structuralsearch.MatchVariableConstraint; +import com.intellij.structuralsearch.impl.matcher.handlers.MatchPredicate; + +import java.util.Set; + +public abstract class MatchPredicateProvider { + public static final ExtensionPointName<MatchPredicateProvider> EP_NAME = ExtensionPointName.create("com.intellij.structuralsearch.matchPredicateProvider"); + public abstract void collectPredicates(MatchVariableConstraint constraint, + String name, + MatchOptions options, + Set<MatchPredicate> predicates); +} diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatchResultImpl.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatchResultImpl.java new file mode 100644 index 000000000000..8761472d813a --- /dev/null +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatchResultImpl.java @@ -0,0 +1,207 @@ +package com.intellij.structuralsearch.impl.matcher; + +import com.intellij.psi.PsiElement; +import com.intellij.structuralsearch.MatchResult; +import com.intellij.structuralsearch.plugin.util.SmartPsiPointer; +import org.jetbrains.annotations.NonNls; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * Class describing the match result + */ +public final class MatchResultImpl extends MatchResult { + private String name; + private SmartPsiPointer matchRef; + private int start; + private int end = -1; + private String matchImage; + private List<MatchResult> matches; + private MatchResult parent; + private boolean target; + + @NonNls public static final String DEFAULT_NAME2 = "end of context match"; + @NonNls public static final String DEFAULT_NAME = "start of context match"; + private boolean myScopeMatch; + private boolean myMultipleMatch; + @NonNls private static final String NULL = "null"; + private MatchResultImpl myContext; + + MatchResultImpl() { + } + + public MatchResultImpl(String name, String image, SmartPsiPointer ref, boolean target) { + this(name,image,ref,0,-1,target); + } + + public MatchResultImpl(String name, String image, SmartPsiPointer ref, int start, int end,boolean target) { + matchRef = ref; + this.name = name; + matchImage = image; + this.target = target; + this.start = start; + this.end = end; + } + + public String getMatchImage() { + if (matchImage==null) { + matchImage = NULL; + } + return matchImage; + } + + public void setParent(MatchResult parent) { + this.parent = parent; + } + + public SmartPsiPointer getMatchRef() { + return matchRef; + } + + public PsiElement getMatch() { + return matchRef.getElement(); + } + + public void setMatchRef(SmartPsiPointer matchStart) { + matchRef = matchStart; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public List<MatchResult> getMatches() { + if (matches==null) matches = new ArrayList<MatchResult>(); + return matches; + } + + public List<MatchResult> getAllSons() { + return getMatches(); + } + + public boolean hasSons() { + return matches!=null; + } + + public boolean isScopeMatch() { + return myScopeMatch; + } + + public boolean isMultipleMatch() { + return myMultipleMatch; + } + + public void clear() { + if (matchRef != null) { + matchRef.clear(); + matchRef = null; + } + + if (matches != null) { + for (final MatchResult match : matches) { + ((MatchResultImpl)match).clear(); + } + matches = null; + } + + name = null; + matchImage = null; + } + + public void clearMatches() { + matches = null; + } + + public void setScopeMatch(final boolean scopeMatch) { + myScopeMatch = scopeMatch; + } + + public void setMultipleMatch(final boolean multipleMatch) { + myMultipleMatch = multipleMatch; + } + + public MatchResultImpl findSon(String name) { + if (matches!=null) { + // @todo this could be performance bottleneck, replace with hash lookup! + for (final MatchResult match : matches) { + final MatchResultImpl res = (MatchResultImpl)match; + + if (name.equals(res.getName())) { + return res; + } + } + } + return null; + } + + public MatchResultImpl removeSon(String typedVar) { + if (matches == null) return null; + + // @todo this could be performance bottleneck, replace with hash lookup! + for(Iterator<MatchResult> i=matches.iterator();i.hasNext();) { + final MatchResultImpl res = (MatchResultImpl)i.next(); + if (typedVar.equals(res.getName())) { + i.remove(); + return res; + } + } + + return null; + } + + public void addSon(MatchResultImpl result) { + getMatches().add(result); + } + + public void setMatchImage(String matchImage) { + this.matchImage = matchImage; + } + + public boolean isTarget() { + return target; + } + + public void setTarget(boolean target) { + this.target = target; + } + + public boolean isMatchImageNull() { + return matchImage==null; + } + + public int getStart() { + return start; + } + + public void setStart(int start) { + this.start = start; + } + + public int getEnd() { + return end; + } + + public void setEnd(int end) { + this.end = end; + } + + public void setContext(final MatchResultImpl context) { + myContext = context; + } + + public MatchResultImpl getContext() { + return myContext; + } + + @Override + public String toString() { + return "MatchResultImpl{name='" + name + '\'' + ", matchImage='" + matchImage + '\'' + "}"; + } +} + diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatchUtils.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatchUtils.java new file mode 100644 index 000000000000..38c31d16524f --- /dev/null +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatchUtils.java @@ -0,0 +1,42 @@ +package com.intellij.structuralsearch.impl.matcher; + +import com.intellij.psi.*; +import org.jetbrains.annotations.NonNls; + +/** + * Created by IntelliJ IDEA. + * User: maxim + * Date: 24.12.2003 + * Time: 22:10:20 + * To change this template use Options | File Templates. + */ +public class MatchUtils { + public static final String SPECIAL_CHARS = "*(){}[]^$\\.-|"; + + public static final boolean compareWithNoDifferenceToPackage(final String typeImage,@NonNls final String typeImage2) { + if (typeImage == null || typeImage2 == null) return typeImage == typeImage2; + return typeImage2.endsWith(typeImage) && ( + typeImage.length() == typeImage2.length() || + typeImage2.charAt(typeImage2.length()-typeImage.length()-1)=='.' // package separator + ); + } + + public static PsiElement getReferencedElement(final PsiElement element) { + if (element instanceof PsiReference) { + return ((PsiReference)element).resolve(); + } + + /*if (element instanceof PsiTypeElement) { + PsiType type = ((PsiTypeElement)element).getType(); + + if (type instanceof PsiArrayType) { + type = ((PsiArrayType)type).getComponentType(); + } + if (type instanceof PsiClassType) { + return ((PsiClassType)type).resolve(); + } + return null; + }*/ + return element; + } +} diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatcherImpl.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatcherImpl.java new file mode 100644 index 000000000000..d667b068ee48 --- /dev/null +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatcherImpl.java @@ -0,0 +1,737 @@ +package com.intellij.structuralsearch.impl.matcher; + +import com.intellij.dupLocator.iterators.ArrayBackedNodeIterator; +import com.intellij.dupLocator.iterators.NodeIterator; +import com.intellij.lang.Language; +import com.intellij.lang.StdLanguages; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ModalityState; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.fileTypes.FileType; +import com.intellij.openapi.fileTypes.FileTypes; +import com.intellij.openapi.fileTypes.LanguageFileType; +import com.intellij.openapi.progress.ProcessCanceledException; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.roots.ContentIterator; +import com.intellij.openapi.roots.ProjectFileIndex; +import com.intellij.openapi.util.Computable; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.*; +import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil; +import com.intellij.psi.search.DelegatingGlobalSearchScope; +import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.psi.search.LocalSearchScope; +import com.intellij.psi.search.SearchScope; +import com.intellij.structuralsearch.*; +import com.intellij.structuralsearch.impl.matcher.compiler.PatternCompiler; +import com.intellij.structuralsearch.impl.matcher.handlers.MatchingHandler; +import com.intellij.structuralsearch.impl.matcher.handlers.TopLevelMatchingHandler; +import com.intellij.structuralsearch.impl.matcher.iterators.SsrFilteringNodeIterator; +import com.intellij.structuralsearch.impl.matcher.strategies.MatchingStrategy; +import com.intellij.structuralsearch.plugin.ui.Configuration; +import com.intellij.structuralsearch.plugin.util.CollectingMatchResultSink; +import com.intellij.util.IncorrectOperationException; +import com.intellij.util.PairProcessor; +import com.intellij.util.SmartList; +import com.intellij.util.indexing.FileBasedIndex; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.lang.ref.SoftReference; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; + +/** + * This class makes program structure tree matching: + */ +public class MatcherImpl { + private static final Logger LOG = Logger.getInstance("#com.intellij.structuralsearch.impl.matcher.MatcherImpl"); + // project being worked on + private final Project project; + + // context of matching + private final MatchContext matchContext; + private boolean isTesting; + + // visitor to delegate the real work + private final GlobalMatchingVisitor visitor = new GlobalMatchingVisitor(); + private ProgressIndicator progress; + private final TaskScheduler scheduler = new TaskScheduler(); + + private int totalFilesToScan; + private int scannedFilesCount; + + public MatcherImpl(final Project project, final MatchOptions matchOptions) { + this.project = project; + matchContext = new MatchContext(); + matchContext.setMatcher(visitor); + + if (matchOptions != null) { + matchContext.setOptions(matchOptions); + cacheCompiledPattern(matchOptions, PatternCompiler.compilePattern(project,matchOptions)); + } + } + + static class LastMatchData { + CompiledPattern lastPattern; + MatchOptions lastOptions; + } + + private static SoftReference<LastMatchData> lastMatchData; + + protected MatcherImpl(Project project) { + this(project, null); + } + + public static void validate(Project project, MatchOptions options) { + PsiDocumentManager.getInstance(project).commitAllDocuments(); + + synchronized(MatcherImpl.class) { + final LastMatchData data = new LastMatchData(); + data.lastPattern = PatternCompiler.compilePattern(project, options); + data.lastOptions = options; + lastMatchData = new SoftReference<LastMatchData>(data); + } + + final StructuralSearchProfile profile = StructuralSearchUtil.getProfileByFileType(options.getFileType()); + profile.checkSearchPattern(project, options); + } + + public static class CompiledOptions { + public final List<Pair<MatchContext, Configuration>> matchContexts; + + public CompiledOptions(final List<Pair<MatchContext, Configuration>> matchContexts) { + this.matchContexts = matchContexts; + } + + public List<Pair<MatchContext, Configuration>> getMatchContexts() { + return matchContexts; + } + } + + public static boolean checkIfShouldAttemptToMatch(MatchContext context, NodeIterator matchedNodes) { + final CompiledPattern pattern = context.getPattern(); + final NodeIterator patternNodes = pattern.getNodes(); + try { + while (true) { + final PsiElement patternNode = patternNodes.current(); + if (patternNode == null) { + return true; + } + final PsiElement matchedNode = matchedNodes.current(); + if (matchedNode == null) { + return false; + } + final MatchingHandler matchingHandler = pattern.getHandler(patternNode); + if (matchingHandler == null || !matchingHandler.canMatch(patternNode, matchedNode)) { + return false; + } + matchedNodes.advance(); + patternNodes.advance(); + } + } finally { + patternNodes.reset(); + matchedNodes.reset(); + } + } + + public void processMatchesInElement(MatchContext context, Configuration configuration, + NodeIterator matchedNodes, + PairProcessor<MatchResult, Configuration> processor) { + try { + configureOptions(context, configuration, matchedNodes.current(), processor); + context.setShouldRecursivelyMatch(false); + visitor.matchContext(matchedNodes); + } finally { + matchedNodes.reset(); + } + } + + public void clearContext() { + matchContext.clear(); + } + + private void configureOptions(MatchContext context, + final Configuration configuration, + PsiElement psiFile, + final PairProcessor<MatchResult, Configuration> processor) { + if (psiFile == null) return; + LocalSearchScope scope = new LocalSearchScope(psiFile); + + matchContext.clear(); + matchContext.setMatcher(visitor); + + MatchOptions options = context.getOptions(); + matchContext.setOptions(options); + matchContext.setPattern(context.getPattern()); + matchContext.setShouldRecursivelyMatch(context.shouldRecursivelyMatch()); + visitor.setMatchContext(matchContext); + + matchContext.setSink( + new MatchConstraintsSink( + new MatchResultSink() { + public void newMatch(MatchResult result) { + processor.process(result, configuration); + } + + public void processFile(PsiFile element) { + } + + public void setMatchingProcess(MatchingProcess matchingProcess) { + } + + public void matchingFinished() { + } + + public ProgressIndicator getProgressIndicator() { + return null; + } + }, + options.getMaxMatchesCount(), + options.isDistinct(), + options.isCaseSensitiveMatch() + ) + ); + options.setScope(scope); + } + + public CompiledOptions precompileOptions(List<Configuration> configurations) { + List<Pair<MatchContext, Configuration>> contexts = new ArrayList<Pair<MatchContext, Configuration>>(); + + for (Configuration configuration : configurations) { + MatchContext matchContext = new MatchContext(); + matchContext.setMatcher(visitor); + MatchOptions matchOptions = configuration.getMatchOptions(); + matchContext.setOptions(matchOptions); + + try { + CompiledPattern compiledPattern = PatternCompiler.compilePattern(project, matchOptions); + matchContext.setPattern(compiledPattern); + contexts.add(Pair.create(matchContext, configuration)); + } + catch (UnsupportedPatternException ignored) {} + catch (MalformedPatternException ignored) {} + } + return new CompiledOptions(contexts); + } + + Project getProject() { + return project; + } + + /** + * Finds the matches of given pattern starting from given tree element. + * @throws MalformedPatternException + * @throws UnsupportedPatternException + */ + protected void findMatches(MatchResultSink sink, final MatchOptions options) throws MalformedPatternException, UnsupportedPatternException + { + CompiledPattern compiledPattern = prepareMatching(sink, options); + if (compiledPattern== null) { + return; + } + + matchContext.getSink().setMatchingProcess( scheduler ); + scheduler.init(); + progress = matchContext.getSink().getProgressIndicator(); + + if (/*TokenBasedSearcher.canProcess(compiledPattern)*/ false) { + //TokenBasedSearcher searcher = new TokenBasedSearcher(this); + //searcher.search(compiledPattern); + if (isTesting) { + matchContext.getSink().matchingFinished(); + return; + } + } + else { + if (isTesting) { + // testing mode; + final PsiElement[] elements = ((LocalSearchScope)options.getScope()).getScope(); + + PsiElement parent = elements[0].getParent(); + if (elements.length > 0 && matchContext.getPattern().getStrategy().continueMatching(parent != null ? parent : elements[0])) { + visitor.matchContext(new SsrFilteringNodeIterator(new ArrayBackedNodeIterator(elements))); + } + else { + for (PsiElement element : elements) { + match(element); + } + } + + matchContext.getSink().matchingFinished(); + return; + } + if (!findMatches(options, compiledPattern)) { + return; + } + } + + if (scheduler.getTaskQueueEndAction()==null) { + scheduler.setTaskQueueEndAction( + new Runnable() { + public void run() { + matchContext.getSink().matchingFinished(); + } + } + ); + } + + scheduler.executeNext(); + } + + private boolean findMatches(MatchOptions options, CompiledPattern compiledPattern) { + LanguageFileType languageFileType = (LanguageFileType)options.getFileType(); + final StructuralSearchProfile profile = StructuralSearchUtil.getProfileByLanguage(languageFileType.getLanguage()); + assert profile != null; + PsiElement node = compiledPattern.getNodes().current(); + final Language ourPatternLanguage = node != null ? profile.getLanguage(node) : ((LanguageFileType)options.getFileType()).getLanguage(); + final Language ourPatternLanguage2 = ourPatternLanguage == StdLanguages.XML ? StdLanguages.XHTML:null; + SearchScope searchScope = compiledPattern.getScope(); + boolean ourOptimizedScope = searchScope != null; + if (!ourOptimizedScope) searchScope = options.getScope(); + + if (searchScope instanceof GlobalSearchScope) { + final GlobalSearchScope scope = (GlobalSearchScope)searchScope; + + final ContentIterator ci = new ContentIterator() { + public boolean processFile(final VirtualFile fileOrDir) { + if (!fileOrDir.isDirectory() && scope.contains(fileOrDir) && fileOrDir.getFileType() != FileTypes.UNKNOWN) { + ++totalFilesToScan; + scheduler.addOneTask(new MatchOneVirtualFile(fileOrDir, profile, ourPatternLanguage, ourPatternLanguage2)); + } + return true; + } + }; + + ApplicationManager.getApplication().runReadAction(new Runnable() { + @Override + public void run() { + FileBasedIndex.getInstance().iterateIndexableFiles(ci, project, progress); + } + }); + progress.setText2(""); + } + else { + final PsiElement[] elementsToScan = ((LocalSearchScope)searchScope).getScope(); + totalFilesToScan = elementsToScan.length; + + for (int i = 0; i < elementsToScan.length; ++i) { + final PsiElement psiElement = elementsToScan[i]; + + if (psiElement == null) continue; + final Language language = psiElement.getLanguage(); + + PsiFile file = psiElement instanceof PsiFile ? (PsiFile)psiElement : psiElement.getContainingFile(); + + if (profile.isMyFile(file, language, ourPatternLanguage, ourPatternLanguage2)) { + scheduler.addOneTask(new MatchOnePsiFile(psiElement)); + } + if (ourOptimizedScope) elementsToScan[i] = null; // to prevent long PsiElement reference + } + } + return true; + } + + private CompiledPattern prepareMatching(final MatchResultSink sink, final MatchOptions options) { + CompiledPattern savedPattern = null; + + if (matchContext.getOptions() == options && matchContext.getPattern() != null && + matchContext.getOptions().hashCode() == matchContext.getPattern().getOptionsHashStamp()) { + savedPattern = matchContext.getPattern(); + } + + matchContext.clear(); + matchContext.setSink( + new MatchConstraintsSink( + sink, + options.getMaxMatchesCount(), + options.isDistinct(), + options.isCaseSensitiveMatch() + ) + ); + matchContext.setOptions(options); + matchContext.setMatcher(visitor); + visitor.setMatchContext(matchContext); + + CompiledPattern compiledPattern = savedPattern; + + if (compiledPattern == null) { + + synchronized(getClass()) { + final LastMatchData data = com.intellij.reference.SoftReference.dereference(lastMatchData); + if (data != null && options == data.lastOptions) { + compiledPattern = data.lastPattern; + } + lastMatchData = null; + } + + if (compiledPattern==null) { + compiledPattern = ApplicationManager.getApplication().runReadAction(new Computable<CompiledPattern>() { + @Override + public CompiledPattern compute() { + return PatternCompiler.compilePattern(project,options); + } + }); + } + } + + cacheCompiledPattern(options, compiledPattern); + return compiledPattern; + } + + private void cacheCompiledPattern(final MatchOptions options, final CompiledPattern compiledPattern) { + matchContext.setPattern(compiledPattern); + compiledPattern.setOptionsHashStamp(options.hashCode()); + } + + /** + * Finds the matches of given pattern starting from given tree element. + * @param sink match result destination + * @throws MalformedPatternException + * @throws UnsupportedPatternException + */ + protected void testFindMatches(MatchResultSink sink, MatchOptions options) + throws MalformedPatternException, UnsupportedPatternException { + isTesting = true; + try { + findMatches(sink,options); + } finally { + isTesting = false; + } + } + + /** + * Finds the matches of given pattern starting from given tree element. + * @param source string for search + * @param pattern to be searched + * @return list of matches found + * @throws MalformedPatternException + * @throws UnsupportedPatternException + */ + protected List<MatchResult> testFindMatches(String source, + String pattern, + MatchOptions options, + boolean filePattern, + FileType sourceFileType, + String sourceExtension, + boolean physicalSourceFile) + throws MalformedPatternException, UnsupportedPatternException { + + CollectingMatchResultSink sink = new CollectingMatchResultSink(); + + try { + PsiElement[] elements = MatcherImplUtil.createSourceTreeFromText(source, + filePattern ? PatternTreeContext.File : PatternTreeContext.Block, + sourceFileType, + sourceExtension, + project, physicalSourceFile); + + options.setSearchPattern(pattern); + options.setScope(new LocalSearchScope(elements)); + testFindMatches(sink, options); + } + catch (IncorrectOperationException e) { + MalformedPatternException exception = new MalformedPatternException(); + exception.initCause(e); + throw exception; + } + + return sink.getMatches(); + } + + protected List<MatchResult> testFindMatches(String source, String pattern, MatchOptions options, boolean filePattern) { + return testFindMatches(source, pattern, options, filePattern, options.getFileType(), null, false); + } + + class TaskScheduler implements MatchingProcess { + private LinkedList<Runnable> tasks = new LinkedList<Runnable>(); + private boolean ended; + private Runnable taskQueueEndAction; + + private boolean suspended; + + public void stop() { + ended = true; + } + + public void pause() { + suspended = true; + } + + public void resume() { + if (!suspended) return; + suspended = false; + executeNext(); + } + + public boolean isSuspended() { + return suspended; + } + + public boolean isEnded() { + return ended; + } + + void setTaskQueueEndAction(Runnable taskQueueEndAction) { + this.taskQueueEndAction = taskQueueEndAction; + } + Runnable getTaskQueueEndAction () { + return taskQueueEndAction; + } + + void addOneTask(Runnable runnable) { + tasks.add(runnable); + } + + private void executeNext() { + while(!suspended && !ended) { + if (tasks.isEmpty()) { + ended = true; + break; + } + + final Runnable task = tasks.removeFirst(); + try { + task.run(); + } + catch (ProcessCanceledException e) { + ended = true; + clearSchedule(); + throw e; + } + catch (StructuralSearchException e) { + ended = true; + clearSchedule(); + throw e; + } + catch (Throwable th) { + LOG.error(th); + } + } + + if (ended) clearSchedule(); + } + + private void init() { + ended = false; + suspended = false; + PsiManager.getInstance(project).startBatchFilesProcessingMode(); + } + + private void clearSchedule() { + if (tasks != null) { + taskQueueEndAction.run(); + if (!project.isDisposed()) { + PsiManager.getInstance(project).finishBatchFilesProcessingMode(); + } + tasks = null; + } + } + + } + + private class MatchOnePsiFile extends MatchOneFile { + private PsiElement file; + + MatchOnePsiFile(PsiElement file) { + this.file = file; + } + + @Nullable + @Override + protected List<PsiElement> getPsiElementsToProcess() { + PsiElement file = this.file; + this.file = null; + return new SmartList<PsiElement>(file); + } + } + + private abstract class MatchOneFile implements Runnable { + public void run() { + List<PsiElement> files = getPsiElementsToProcess(); + + if (progress!=null) { + progress.setFraction((double)scannedFilesCount/totalFilesToScan); + } + + ++scannedFilesCount; + + if (files == null || files.size() == 0) return; + final PsiFile psiFile = files.get(0).getContainingFile(); + + if (psiFile!=null) { + final Runnable action = new Runnable() { + public void run() { + ApplicationManager.getApplication().runWriteAction(new Runnable() { + public void run() { + if (project.isDisposed()) return; + final PsiDocumentManager manager = PsiDocumentManager.getInstance(project); + Document document = manager.getDocument(psiFile); + if (document != null) manager.commitDocument(document); + } + }); + } + }; + + if (ApplicationManager.getApplication().isDispatchThread()) { + action.run(); + } else { + ApplicationManager.getApplication().invokeAndWait( + action, + ModalityState.defaultModalityState() + ); + } + } + + if (project.isDisposed()) return; + + for(PsiElement file:files) { + if (file instanceof PsiFile) { + matchContext.getSink().processFile((PsiFile)file); + } + + final PsiElement finalFile = file; + ApplicationManager.getApplication().runReadAction( + new Runnable() { + public void run() { + PsiElement file = finalFile; + if (!file.isValid()) return; + file = StructuralSearchUtil.getProfileByLanguage(file.getLanguage()).extendMatchOnePsiFile(file); + match(file); + } + } + ); + } + } + + protected abstract @Nullable List<PsiElement> getPsiElementsToProcess(); + } + + // Initiates the matching process for given element + // @param element the current search tree element + public void match(PsiElement element) { + MatchingStrategy strategy = matchContext.getPattern().getStrategy(); + + if (strategy.continueMatching(element)) { + visitor.matchContext(new ArrayBackedNodeIterator(new PsiElement[] {element})); + return; + } + for(PsiElement el=element.getFirstChild();el!=null;el=el.getNextSibling()) { + match(el); + } + if (element instanceof PsiLanguageInjectionHost) { + InjectedLanguageUtil.enumerate(element, new PsiLanguageInjectionHost.InjectedPsiVisitor() { + @Override + public void visit(@NotNull PsiFile injectedPsi, @NotNull List<PsiLanguageInjectionHost.Shred> places) { + match(injectedPsi); + } + }); + } + } + + @Nullable + protected MatchResult isMatchedByDownUp(PsiElement element, final MatchOptions options) { + final CollectingMatchResultSink sink = new CollectingMatchResultSink(); + CompiledPattern compiledPattern = prepareMatching(sink, options); + + if (compiledPattern== null) { + assert false; + return null; + } + + PsiElement targetNode = compiledPattern.getTargetNode(); + PsiElement elementToStartMatching = null; + + if (targetNode == null) { + targetNode = compiledPattern.getNodes().current(); + if (targetNode != null) { + compiledPattern.getNodes().advance(); + assert !compiledPattern.getNodes().hasNext(); + compiledPattern.getNodes().rewind(); + + while (element.getClass() != targetNode.getClass()) { + element = element.getParent(); + if (element == null) return null; + } + + elementToStartMatching = element; + } + } else { + targetNode = StructuralSearchUtil.getProfileByPsiElement(element).extendMatchedByDownUp(targetNode); + + MatchingHandler handler = null; + + while (element.getClass() == targetNode.getClass() || + compiledPattern.isTypedVar(targetNode) && compiledPattern.getHandler(targetNode).canMatch(targetNode, element) + ) { + handler = compiledPattern.getHandler(targetNode); + handler.setPinnedElement(element); + elementToStartMatching = element; + if (handler instanceof TopLevelMatchingHandler) break; + element = element.getParent(); + targetNode = targetNode.getParent(); + + if (options.isLooseMatching()) { + element = StructuralSearchUtil.getProfileByPsiElement(element).updateCurrentNode(element); + targetNode = StructuralSearchUtil.getProfileByPsiElement(element).updateCurrentNode(targetNode); + } + } + + if (!(handler instanceof TopLevelMatchingHandler)) return null; + } + + assert targetNode != null : "Could not match down up when no target node"; + + match(elementToStartMatching); + matchContext.getSink().matchingFinished(); + final int matchCount = sink.getMatches().size(); + assert matchCount <= 1; + return matchCount > 0 ? sink.getMatches().get(0) : null; + } + + private class MatchOneVirtualFile extends MatchOneFile { + private final VirtualFile myFileOrDir; + private final StructuralSearchProfile myProfile; + private final Language myOurPatternLanguage; + private final Language myOurPatternLanguage2; + + public MatchOneVirtualFile(VirtualFile fileOrDir, + StructuralSearchProfile profile, + Language ourPatternLanguage, + Language ourPatternLanguage2) { + myFileOrDir = fileOrDir; + myProfile = profile; + myOurPatternLanguage = ourPatternLanguage; + myOurPatternLanguage2 = ourPatternLanguage2; + } + + @Nullable + @Override + protected List<PsiElement> getPsiElementsToProcess() { + return ApplicationManager.getApplication().runReadAction(new Computable<List<PsiElement>>() { + @Override + public List<PsiElement> compute() { + PsiFile file = PsiManager.getInstance(project).findFile(myFileOrDir); + if (file == null) { + return null; + } + + final FileViewProvider viewProvider = file.getViewProvider(); + List<PsiElement> elementsToProcess = new SmartList<PsiElement>(); + + for(Language lang: viewProvider.getLanguages()) { + if (myProfile.isMyFile(file, lang, myOurPatternLanguage, myOurPatternLanguage2)) { + elementsToProcess.add(viewProvider.getPsi(lang)); + } + } + + return elementsToProcess; + } + }); + } + } +} diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatcherImplUtil.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatcherImplUtil.java new file mode 100644 index 000000000000..ffc9ae78e876 --- /dev/null +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatcherImplUtil.java @@ -0,0 +1,67 @@ +package com.intellij.structuralsearch.impl.matcher; + +import com.intellij.lang.Language; +import com.intellij.openapi.fileTypes.FileType; +import com.intellij.openapi.fileTypes.LanguageFileType; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiElement; +import com.intellij.structuralsearch.MatchOptions; +import com.intellij.structuralsearch.StructuralSearchProfile; +import com.intellij.structuralsearch.StructuralSearchUtil; +import com.intellij.structuralsearch.impl.matcher.compiler.PatternCompiler; +import com.intellij.util.IncorrectOperationException; + +/** + * Created by IntelliJ IDEA. + * User: Maxim.Mossienko + * Date: Mar 19, 2004 + * Time: 6:56:25 PM + * To change this template use File | Settings | File Templates. + */ +public class MatcherImplUtil { + + public static void transform(MatchOptions options) { + if (options.hasVariableConstraints()) return; + PatternCompiler.transformOldPattern(options); + } + + public static PsiElement[] createTreeFromText(String text, PatternTreeContext context, FileType fileType, Project project) + throws IncorrectOperationException { + return createTreeFromText(text, context, fileType, null, null, project, false); + } + + public static PsiElement[] createSourceTreeFromText(String text, + PatternTreeContext context, + FileType fileType, + String extension, + Project project, + boolean physical) { + if (fileType instanceof LanguageFileType) { + Language language = ((LanguageFileType)fileType).getLanguage(); + StructuralSearchProfile profile = StructuralSearchUtil.getProfileByLanguage(language); + if (profile != null) { + return profile.createPatternTree(text, context, fileType, null, null, extension, project, physical); + } + } + return PsiElement.EMPTY_ARRAY; + } + + public static PsiElement[] createTreeFromText(String text, + PatternTreeContext context, + FileType fileType, + Language language, + String contextName, + Project project, + boolean physical) throws IncorrectOperationException { + if (language == null && fileType instanceof LanguageFileType) { + language = ((LanguageFileType)fileType).getLanguage(); + } + if (language != null) { + StructuralSearchProfile profile = StructuralSearchUtil.getProfileByLanguage(language); + if (profile != null) { + return profile.createPatternTree(text, context, fileType, language, contextName, null, project, physical); + } + } + return PsiElement.EMPTY_ARRAY; + } +} diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/PatternTreeContext.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/PatternTreeContext.java new file mode 100644 index 000000000000..7d93c9ae82e0 --- /dev/null +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/PatternTreeContext.java @@ -0,0 +1,8 @@ +package com.intellij.structuralsearch.impl.matcher; + +/** +* @author Eugene.Kudelevsky +*/ +public enum PatternTreeContext { + File, Block, Class +} diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/XmlCompiledPattern.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/XmlCompiledPattern.java new file mode 100644 index 000000000000..0c9f4945e3a5 --- /dev/null +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/XmlCompiledPattern.java @@ -0,0 +1,22 @@ +package com.intellij.structuralsearch.impl.matcher; + +import com.intellij.structuralsearch.impl.matcher.strategies.XmlMatchingStrategy; + +/** +* @author Eugene.Kudelevsky +*/ +public class XmlCompiledPattern extends CompiledPattern { + private static final String XML_TYPED_VAR_PREFIX = "__"; + + public XmlCompiledPattern() { + setStrategy(XmlMatchingStrategy.getInstance()); + } + + public String[] getTypedVarPrefixes() { + return new String[] {XML_TYPED_VAR_PREFIX}; + } + + public boolean isTypedVar(final String str) { + return str.startsWith(XML_TYPED_VAR_PREFIX); + } +} diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/XmlMatchingVisitor.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/XmlMatchingVisitor.java new file mode 100644 index 000000000000..d2e1e210534b --- /dev/null +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/XmlMatchingVisitor.java @@ -0,0 +1,130 @@ +package com.intellij.structuralsearch.impl.matcher; + +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiWhiteSpace; +import com.intellij.psi.XmlElementVisitor; +import com.intellij.psi.util.PsiUtilCore; +import com.intellij.psi.xml.*; +import com.intellij.structuralsearch.impl.matcher.handlers.MatchingHandler; +import com.intellij.structuralsearch.impl.matcher.handlers.SubstitutionHandler; +import com.intellij.dupLocator.iterators.ArrayBackedNodeIterator; + +import java.util.ArrayList; +import java.util.List; + +/** +* @author Eugene.Kudelevsky +*/ +public class XmlMatchingVisitor extends XmlElementVisitor { + private final GlobalMatchingVisitor myMatchingVisitor; + private final boolean myCaseSensitive; + + public XmlMatchingVisitor(GlobalMatchingVisitor matchingVisitor) { + myMatchingVisitor = matchingVisitor; + myCaseSensitive = myMatchingVisitor.getMatchContext().getOptions().isCaseSensitiveMatch(); + } + + @Override + public void visitElement(final PsiElement element) { + myMatchingVisitor.setResult(element.textMatches(element)); + } + + @Override public void visitXmlAttribute(XmlAttribute attribute) { + final XmlAttribute another = (XmlAttribute)myMatchingVisitor.getElement(); + final boolean isTypedVar = myMatchingVisitor.getMatchContext().getPattern().isTypedVar(attribute.getName()); + + myMatchingVisitor.setResult(matches(attribute.getName(), another.getName()) || isTypedVar); + if (myMatchingVisitor.getResult()) { + myMatchingVisitor.setResult(myMatchingVisitor.match(attribute.getValueElement(), another.getValueElement())); + } + + if (myMatchingVisitor.getResult() && isTypedVar) { + MatchingHandler handler = myMatchingVisitor.getMatchContext().getPattern().getHandler(attribute.getName()); + myMatchingVisitor.setResult(((SubstitutionHandler)handler).handle(another, myMatchingVisitor.getMatchContext())); + } + } + + @Override public void visitXmlAttributeValue(XmlAttributeValue value) { + final XmlAttributeValue another = (XmlAttributeValue)myMatchingVisitor.getElement(); + final String text = value.getValue(); + + final boolean isTypedVar = myMatchingVisitor.getMatchContext().getPattern().isTypedVar(text); + MatchingHandler handler; + + if (isTypedVar && (handler = myMatchingVisitor.getMatchContext().getPattern().getHandler( text )) instanceof SubstitutionHandler) { + String text2 = another.getText(); + int offset = text2.length() > 0 && ( text2.charAt(0) == '"' || text2.charAt(0) == '\'') ? 1:0; + myMatchingVisitor.setResult(((SubstitutionHandler)handler).handle(another, offset, text2.length() - offset, + myMatchingVisitor.getMatchContext())); + } else { + myMatchingVisitor.setResult(matches(text, another.getValue())); + } + } + + @Override public void visitXmlTag(XmlTag tag) { + final XmlTag another = (XmlTag)myMatchingVisitor.getElement(); + final boolean isTypedVar = myMatchingVisitor.getMatchContext().getPattern().isTypedVar(tag.getName()); + + myMatchingVisitor.setResult((matches(tag.getName(), another.getName()) || isTypedVar) && + myMatchingVisitor.matchInAnyOrder(tag.getAttributes(), another.getAttributes())); + + if(myMatchingVisitor.getResult()) { + final XmlTagChild[] contentChildren = tag.getValue().getChildren(); + + if (contentChildren.length > 0) { + PsiElement[] patternNodes = contentChildren; + PsiElement[] matchedNodes = another.getValue().getChildren(); + + if (contentChildren.length != 1) { + patternNodes = expandXmlTexts(patternNodes); + matchedNodes = expandXmlTexts(matchedNodes); + } + + myMatchingVisitor.setResult(myMatchingVisitor.matchSequentially( + new ArrayBackedNodeIterator(patternNodes), + new ArrayBackedNodeIterator(matchedNodes) + )); + } + } + + if (myMatchingVisitor.getResult() && isTypedVar) { + MatchingHandler handler = myMatchingVisitor.getMatchContext().getPattern().getHandler( tag.getName() ); + myMatchingVisitor.setResult(((SubstitutionHandler)handler).handle(another, myMatchingVisitor.getMatchContext())); + } + } + + private static PsiElement[] expandXmlTexts(PsiElement[] children) { + List<PsiElement> result = new ArrayList<PsiElement>(children.length); + for(PsiElement c:children) { + if (c instanceof XmlText) { + for(PsiElement p:c.getChildren()) { + if (!(p instanceof PsiWhiteSpace)) result.add(p); + } + } else if (!(c instanceof PsiWhiteSpace)) { + result.add(c); + } + } + return PsiUtilCore.toPsiElementArray(result); + } + + @Override public void visitXmlText(XmlText text) { + myMatchingVisitor.setResult(myMatchingVisitor.matchSequentially(text.getFirstChild(), myMatchingVisitor.getElement().getFirstChild())); + } + + @Override public void visitXmlToken(XmlToken token) { + if (token.getTokenType() == XmlTokenType.XML_DATA_CHARACTERS) { + String text = token.getText(); + final boolean isTypedVar = myMatchingVisitor.getMatchContext().getPattern().isTypedVar(text); + + if (isTypedVar) { + myMatchingVisitor.setResult(myMatchingVisitor.handleTypedElement(token, myMatchingVisitor.getElement())); + } else { + myMatchingVisitor.setResult(matches(text, myMatchingVisitor.getElement().getText())); + } + } + } + + private boolean matches(String a, String b) { + return myCaseSensitive ? a.equals(b) : a.equalsIgnoreCase(b); + } +} diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/CompileContext.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/CompileContext.java new file mode 100644 index 000000000000..09758a5e2191 --- /dev/null +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/CompileContext.java @@ -0,0 +1,71 @@ +package com.intellij.structuralsearch.impl.matcher.compiler; + +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.project.Project; +import com.intellij.structuralsearch.MatchOptions; +import com.intellij.structuralsearch.impl.matcher.CompiledPattern; + +/** + * Created by IntelliJ IDEA. + * User: maxim + * Date: 17.11.2004 + * Time: 19:26:37 + * To change this template use File | Settings | File Templates. + */ +public class CompileContext { + private OptimizingSearchHelper searchHelper; + + private CompiledPattern pattern; + private MatchOptions options; + private Project project; + + public void clear() { + if (searchHelper!=null) searchHelper.clear(); + + project = null; + pattern = null; + options = null; + } + + public void init(final CompiledPattern _result, final MatchOptions _options, final Project _project, final boolean _findMatchingFiles) { + options = _options; + project = _project; + pattern = _result; + + searchHelper = ApplicationManager.getApplication().isUnitTestMode() ? + new TestModeOptimizingSearchHelper(this) : + new FindInFilesOptimizingSearchHelper(this, _findMatchingFiles, _project); + } + + public OptimizingSearchHelper getSearchHelper() { + return searchHelper; + } + + void setSearchHelper(OptimizingSearchHelper searchHelper) { + this.searchHelper = searchHelper; + } + + public CompiledPattern getPattern() { + return pattern; + } + + void setPattern(CompiledPattern pattern) { + this.pattern = pattern; + } + + MatchOptions getOptions() { + return options; + } + + void setOptions(MatchOptions options) { + this.options = options; + } + + Project getProject() { + return project; + } + + void setProject(Project project) { + this.project = project; + } +} diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/DeleteNodesAction.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/DeleteNodesAction.java new file mode 100644 index 000000000000..4b5ad7c64545 --- /dev/null +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/DeleteNodesAction.java @@ -0,0 +1,59 @@ +package com.intellij.structuralsearch.impl.matcher.compiler; + +import com.intellij.psi.PsiElement; + +import java.util.ArrayList; + +/** + * Created by IntelliJ IDEA. + * User: maxim + * Date: 17.11.2004 + * Time: 19:24:40 + * To change this template use File | Settings | File Templates. + */ +class DeleteNodesAction implements Runnable { + private final ArrayList elements; + + DeleteNodesAction(ArrayList _elements) { + elements = _elements; + } + + private void delete(PsiElement first, PsiElement last) throws Exception { + if (last==first) { + first.delete(); + } else { + first.getParent().deleteChildRange(first,last); + } + } + public void run() { + try { + PsiElement first= null; + PsiElement last = null; + + for(int i = 0;i < elements.size(); ++i) { + final PsiElement el = (PsiElement)elements.get(i); + + if (!el.isValid()) continue; + + if (first==null) { + first = last = el; + } else if (last.getNextSibling()==el) { + last = el; + } else { + delete(first,last); + first = last = null; + --i; + continue; + } + } + + if (first!=null) { + delete(first,last); + } + } catch(Throwable ex) { + ex.printStackTrace(); + } finally { + elements.clear(); + } + } +} diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/FindInFilesOptimizingSearchHelper.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/FindInFilesOptimizingSearchHelper.java new file mode 100644 index 000000000000..ff7f06a36d5b --- /dev/null +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/FindInFilesOptimizingSearchHelper.java @@ -0,0 +1,103 @@ +package com.intellij.structuralsearch.impl.matcher.compiler; + +import com.intellij.lang.Language; +import com.intellij.lang.LanguageNamesValidation; +import com.intellij.lang.refactoring.NamesValidator; +import com.intellij.lexer.Lexer; +import com.intellij.openapi.fileTypes.FileType; +import com.intellij.openapi.fileTypes.LanguageFileType; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiFile; +import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.psi.search.PsiSearchHelper; +import com.intellij.util.Processor; +import gnu.trove.THashMap; + +import java.util.Set; + +/** + * @author Maxim.Mossienko +*/ +class FindInFilesOptimizingSearchHelper extends OptimizingSearchHelperBase { + private PsiSearchHelper helper; + private THashMap<PsiFile,PsiFile> filesToScan; + private THashMap<PsiFile,PsiFile> filesToScan2; + private Lexer javaLexer; + + private final boolean findMatchingFiles; + + FindInFilesOptimizingSearchHelper(CompileContext _context, boolean _findMatchngFiles, Project project) { + super(_context); + findMatchingFiles = _findMatchngFiles; + + if (findMatchingFiles) { + helper = PsiSearchHelper.SERVICE.getInstance(project); + + if (filesToScan == null) { + filesToScan = new THashMap<PsiFile,PsiFile>(); + filesToScan2 = new THashMap<PsiFile,PsiFile>(); + } + } + } + + public boolean doOptimizing() { + return findMatchingFiles; + } + + public void clear() { + super.clear(); + + if (filesToScan != null) { + filesToScan.clear(); + filesToScan2.clear(); + + helper = null; + } + } + + protected void doAddSearchWordInCode(final String refname) { + final FileType fileType = context.getOptions().getFileType(); + final Language language = fileType instanceof LanguageFileType ? ((LanguageFileType)fileType).getLanguage() : Language.ANY; + final NamesValidator namesValidator = LanguageNamesValidation.INSTANCE.forLanguage(language); + if (namesValidator.isKeyword(refname, context.getProject())) { + helper.processAllFilesWithWordInText(refname, (GlobalSearchScope)context.getOptions().getScope(), new MyFileProcessor(), true); + } else { + helper.processAllFilesWithWord(refname, (GlobalSearchScope)context.getOptions().getScope(), new MyFileProcessor(), true); + } + } + + protected void doAddSearchWordInText(final String refname) { + helper.processAllFilesWithWordInText(refname, (GlobalSearchScope)context.getOptions().getScope(), new MyFileProcessor(), true); + } + + protected void doAddSearchWordInComments(final String refname) { + helper.processAllFilesWithWordInComments(refname, (GlobalSearchScope)context.getOptions().getScope(), new MyFileProcessor()); + } + + protected void doAddSearchWordInLiterals(final String refname) { + helper.processAllFilesWithWordInLiterals(refname, (GlobalSearchScope)context.getOptions().getScope(), new MyFileProcessor()); + } + + public void endTransaction() { + super.endTransaction(); + THashMap<PsiFile,PsiFile> map = filesToScan; + if (map.size() > 0) map.clear(); + filesToScan = filesToScan2; + filesToScan2 = map; + } + + public Set<PsiFile> getFilesSetToScan() { + return filesToScan.keySet(); + } + + private class MyFileProcessor implements Processor<PsiFile> { + public boolean process(PsiFile file) { + if (scanRequest == 0 || + filesToScan.get(file)!=null) { + filesToScan2.put(file,file); + } + return true; + } + } + +} diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/GlobalCompilingVisitor.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/GlobalCompilingVisitor.java new file mode 100644 index 000000000000..130751adec67 --- /dev/null +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/GlobalCompilingVisitor.java @@ -0,0 +1,314 @@ +package com.intellij.structuralsearch.impl.matcher.compiler; + +import com.intellij.dupLocator.util.NodeFilter; +import com.intellij.openapi.extensions.Extensions; +import com.intellij.psi.PsiElement; +import com.intellij.structuralsearch.StructuralSearchProfile; +import com.intellij.structuralsearch.StructuralSearchUtil; +import com.intellij.structuralsearch.impl.matcher.filters.CompositeFilter; +import com.intellij.structuralsearch.impl.matcher.filters.LexicalNodesFilter; +import com.intellij.structuralsearch.impl.matcher.handlers.LiteralWithSubstitutionHandler; +import com.intellij.structuralsearch.impl.matcher.handlers.MatchingHandler; +import com.intellij.structuralsearch.impl.matcher.handlers.SubstitutionHandler; +import com.intellij.structuralsearch.impl.matcher.predicates.RegExpPredicate; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.Nullable; + +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static com.intellij.structuralsearch.MatchOptions.INSTANCE_MODIFIER_NAME; +import static com.intellij.structuralsearch.MatchOptions.MODIFIER_ANNOTATION_NAME; + +/** + * @author maxim + */ +public class GlobalCompilingVisitor { + @NonNls private static final String SUBSTITUTION_PATTERN_STR = "\\b(__\\$_\\w+)\\b"; + private static final Pattern ourSubstitutionPattern = Pattern.compile(SUBSTITUTION_PATTERN_STR); + private static final Set<String> ourReservedWords = new HashSet<String>(Arrays.asList(MODIFIER_ANNOTATION_NAME, INSTANCE_MODIFIER_NAME)) { + { + for (StructuralSearchProfile profile : Extensions.getExtensions(StructuralSearchProfile.EP_NAME)) { + addAll(profile.getReservedWords()); + } + } + }; + private static final Pattern ourAlternativePattern = Pattern.compile("^\\((.+)\\)$"); + @NonNls private static final String WORD_SEARCH_PATTERN_STR = ".*?\\b(.+?)\\b.*?"; + private static final Pattern ourWordSearchPattern = Pattern.compile(WORD_SEARCH_PATTERN_STR); + private CompileContext context; + private final ArrayList<PsiElement> myLexicalNodes = new ArrayList<PsiElement>(); + + private int myCodeBlockLevel; + + private static final NodeFilter ourFilter = LexicalNodesFilter.getInstance(); + + public static NodeFilter getFilter() { + return ourFilter; + } + + public void setHandler(PsiElement element, MatchingHandler handler) { + MatchingHandler realHandler = context.getPattern().getHandlerSimple(element); + + if (realHandler instanceof SubstitutionHandler) { + ((SubstitutionHandler)realHandler).setMatchHandler(handler); + } + else { + // @todo care about composite handler in this case of simple handler! + context.getPattern().setHandler(element, handler); + } + } + + public final void handle(PsiElement element) { + + if ((!ourFilter.accepts(element) || + StructuralSearchUtil.isIdentifier(element)) && + context.getPattern().isRealTypedVar(element) && + context.getPattern().getHandlerSimple(element) == null + ) { + String name = context.getPattern().getTypedVarString(element); + // name is the same for named element (clazz,methods, etc) and token (name of ... itself) + // @todo need fix this + final SubstitutionHandler handler; + + context.getPattern().setHandler( + element, + handler = (SubstitutionHandler)context.getPattern().getHandler(name) + ); + + if (handler != null && context.getOptions().getVariableConstraint(handler.getName()).isPartOfSearchResults()) { + handler.setTarget(true); + context.getPattern().setTargetNode(element); + } + } + } + + public CompileContext getContext() { + return context; + } + + public int getCodeBlockLevel() { + return myCodeBlockLevel; + } + + public void setCodeBlockLevel(int codeBlockLevel) { + this.myCodeBlockLevel = codeBlockLevel; + } + + static void setFilter(MatchingHandler handler, NodeFilter filter) { + if (handler.getFilter() != null && + handler.getFilter().getClass() != filter.getClass() + ) { + // for constructor we will have the same handler for class and method and tokens itselfa + handler.setFilter( + new CompositeFilter( + filter, + handler.getFilter() + ) + ); + } + else { + handler.setFilter(filter); + } + } + + public ArrayList<PsiElement> getLexicalNodes() { + return myLexicalNodes; + } + + public void addLexicalNode(PsiElement node) { + myLexicalNodes.add(node); + } + + void compile(PsiElement[] elements, CompileContext context) { + myCodeBlockLevel = 0; + this.context = context; + final StructuralSearchProfile profile = StructuralSearchUtil.getProfileByFileType(context.getOptions().getFileType()); + assert profile != null; + profile.compile(elements, this); + + if (context.getPattern().getStrategy() == null) { + System.out.println(); + } + } + + @Nullable + public MatchingHandler processPatternStringWithFragments(String pattern, OccurenceKind kind) { + return processPatternStringWithFragments(pattern, kind, ourSubstitutionPattern); + } + + @Nullable + public MatchingHandler processPatternStringWithFragments(String pattern, OccurenceKind kind, Pattern substitutionPattern) { + String content; + + if (kind == OccurenceKind.LITERAL) { + content = pattern.substring(1, pattern.length() - 1); + } + else if (kind == OccurenceKind.COMMENT) { + content = pattern; + } + else { + return null; + } + + @NonNls StringBuilder buf = new StringBuilder(content.length()); + Matcher matcher = substitutionPattern.matcher(content); + List<SubstitutionHandler> handlers = null; + int start = 0; + String word; + boolean hasLiteralContent = false; + + SubstitutionHandler handler = null; + while (matcher.find()) { + if (handlers == null) handlers = new ArrayList<SubstitutionHandler>(); + handler = (SubstitutionHandler)getContext().getPattern().getHandler(matcher.group(1)); + if (handler != null) handlers.add(handler); + + word = content.substring(start, matcher.start()); + + if (word.length() > 0) { + buf.append(StructuralSearchUtil.shieldSpecialChars(word)); + hasLiteralContent = true; + + processTokenizedName(word, false, kind); + } + + RegExpPredicate predicate = MatchingHandler.getSimpleRegExpPredicate(handler); + + if (predicate == null || !predicate.isWholeWords()) { + buf.append("(.*?)"); + } + else { + buf.append(".*?\\b(").append(predicate.getRegExp()).append(")\\b.*?"); + } + + if (isSuitablePredicate(predicate, handler)) { + processTokenizedName(predicate.getRegExp(), false, kind); + } + + start = matcher.end(); + } + + word = content.substring(start, content.length()); + + if (word.length() > 0) { + hasLiteralContent = true; + buf.append(StructuralSearchUtil.shieldSpecialChars(word)); + + processTokenizedName(word, false, kind); + } + + if (hasLiteralContent) { + if (kind == OccurenceKind.LITERAL) { + buf.insert(0, "[\"']"); + buf.append("[\"']"); + } + buf.append("$"); + } + + if (handlers != null) { + return hasLiteralContent ? new LiteralWithSubstitutionHandler(buf.toString(), handlers) : handler; + } + + return null; + } + + @Contract("null,_ -> false") + static boolean isSuitablePredicate(RegExpPredicate predicate, SubstitutionHandler handler) { + return predicate != null && handler.getMinOccurs() != 0 && predicate.couldBeOptimized(); + } + + public static void addFilesToSearchForGivenWord(String refname, + boolean endTransaction, + GlobalCompilingVisitor.OccurenceKind kind, + CompileContext compileContext) { + if (!compileContext.getSearchHelper().doOptimizing()) { + return; + } + if (ourReservedWords.contains(refname)) return; // skip our special annotations !!! + + boolean addedSomething = false; + + if (kind == GlobalCompilingVisitor.OccurenceKind.CODE) { + addedSomething = compileContext.getSearchHelper().addWordToSearchInCode(refname); + } + else if (kind == GlobalCompilingVisitor.OccurenceKind.COMMENT) { + addedSomething = compileContext.getSearchHelper().addWordToSearchInComments(refname); + } + else if (kind == GlobalCompilingVisitor.OccurenceKind.LITERAL) { + addedSomething = compileContext.getSearchHelper().addWordToSearchInLiterals(refname); + } + + if (addedSomething && endTransaction) { + compileContext.getSearchHelper().endTransaction(); + } + } + + public void processTokenizedName(String name, boolean skipComments, GlobalCompilingVisitor.OccurenceKind kind) { + WordTokenizer tokenizer = new WordTokenizer(name); + for (Iterator<String> i = tokenizer.iterator(); i.hasNext();) { + String nextToken = i.next(); + if (skipComments && + (nextToken.equals("/*") || nextToken.equals("/**") || nextToken.equals("*/") || nextToken.equals("*") || nextToken.equals("//")) + ) { + continue; + } + + Matcher matcher = ourAlternativePattern.matcher(nextToken); + if (matcher.matches()) { + StringTokenizer alternatives = new StringTokenizer(matcher.group(1), "|"); + while (alternatives.hasMoreTokens()) { + addFilesToSearchForGivenWord(alternatives.nextToken(), !alternatives.hasMoreTokens(), kind, getContext()); + } + } + else { + addFilesToSearchForGivenWord(nextToken, true, kind, getContext()); + } + } + } + + public enum OccurenceKind { + LITERAL, COMMENT, CODE + } + + private static class WordTokenizer { + private final List<String> myWords = new ArrayList<String>(); + + WordTokenizer(String text) { + final StringTokenizer tokenizer = new StringTokenizer(text); + Matcher matcher = null; + + while (tokenizer.hasMoreTokens()) { + String nextToken = tokenizer.nextToken(); + if (matcher == null) { + matcher = ourWordSearchPattern.matcher(nextToken); + } + else { + matcher.reset(nextToken); + } + + nextToken = (matcher.matches()) ? matcher.group(1) : nextToken; + int lastWordStart = 0; + int i; + for (i = 0; i < nextToken.length(); ++i) { + if (!Character.isJavaIdentifierStart(nextToken.charAt(i))) { + if (i != lastWordStart) { + myWords.add(nextToken.substring(lastWordStart, i)); + } + lastWordStart = i + 1; + } + } + + if (i != lastWordStart) { + myWords.add(nextToken.substring(lastWordStart, i)); + } + } + } + + Iterator<String> iterator() { + return myWords.iterator(); + } + } +} diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/OptimizingSearchHelper.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/OptimizingSearchHelper.java new file mode 100644 index 000000000000..8d6a08b6ef95 --- /dev/null +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/OptimizingSearchHelper.java @@ -0,0 +1,27 @@ +package com.intellij.structuralsearch.impl.matcher.compiler; + +import com.intellij.psi.PsiFile; + +import java.util.Set; + +/** + * @author Maxim.Mossienko +*/ +public interface OptimizingSearchHelper { + boolean doOptimizing(); + void clear(); + + boolean addWordToSearchInCode(final String refname); + + boolean addWordToSearchInText(final String refname); + + boolean addWordToSearchInComments(final String refname); + + boolean addWordToSearchInLiterals(final String refname); + + void endTransaction(); + + boolean isScannedSomething(); + + Set<PsiFile> getFilesSetToScan(); +} diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/OptimizingSearchHelperBase.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/OptimizingSearchHelperBase.java new file mode 100644 index 000000000000..2d6f2bc41b37 --- /dev/null +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/OptimizingSearchHelperBase.java @@ -0,0 +1,85 @@ +package com.intellij.structuralsearch.impl.matcher.compiler; + +import gnu.trove.THashSet; + +/** + * @author Maxim.Mossienko +*/ +abstract class OptimizingSearchHelperBase implements OptimizingSearchHelper { + private final THashSet<String> scanned; + private final THashSet<String> scannedText; + private final THashSet<String> scannedComments; + private final THashSet<String> scannedLiterals; + protected int scanRequest; + + + protected final CompileContext context; + + OptimizingSearchHelperBase(CompileContext _context) { + context = _context; + + scanRequest = 0; + scanned = new THashSet<String>(); + scannedText = new THashSet<String>(); + scannedComments = new THashSet<String>(); + scannedLiterals = new THashSet<String>(); + } + + public void clear() { + scanned.clear(); + scannedComments.clear(); + scannedLiterals.clear(); + } + + public boolean addWordToSearchInCode(final String refname) { + if (!scanned.contains(refname)) { + doAddSearchWordInCode(refname); + scanned.add( refname ); + return true; + } + + return false; + } + + public boolean addWordToSearchInText(final String refname) { + if (!scannedText.contains(refname)) { + doAddSearchWordInText(refname); + scannedText.add(refname); + return true; + } + return false; + } + + protected abstract void doAddSearchWordInCode(final String refname); + protected abstract void doAddSearchWordInText(final String refname); + + protected abstract void doAddSearchWordInComments(final String refname); + protected abstract void doAddSearchWordInLiterals(final String refname); + + public void endTransaction() { + scanRequest++; + } + + public boolean addWordToSearchInComments(final String refname) { + if (!scannedComments.contains(refname)) { + doAddSearchWordInComments(refname); + + scannedComments.add( refname ); + return true; + } + return false; + } + + public boolean addWordToSearchInLiterals(final String refname) { + if (!scannedLiterals.contains(refname)) { + doAddSearchWordInLiterals(refname); + scannedLiterals.add( refname ); + return true; + } + return false; + } + + public boolean isScannedSomething() { + return scanned.size() > 0 || scannedComments.size() > 0 || scannedLiterals.size() > 0; + } +} diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/PatternCompiler.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/PatternCompiler.java new file mode 100644 index 000000000000..e290ebb70793 --- /dev/null +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/PatternCompiler.java @@ -0,0 +1,512 @@ +package com.intellij.structuralsearch.impl.matcher.compiler; + +import com.intellij.codeInsight.template.Template; +import com.intellij.codeInsight.template.TemplateManager; +import com.intellij.dupLocator.util.NodeFilter; +import com.intellij.lang.Language; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.extensions.Extensions; +import com.intellij.openapi.fileTypes.FileType; +import com.intellij.openapi.fileTypes.LanguageFileType; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiErrorElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiRecursiveElementWalkingVisitor; +import com.intellij.psi.impl.source.PsiFileImpl; +import com.intellij.psi.impl.source.tree.LeafElement; +import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.psi.search.LocalSearchScope; +import com.intellij.psi.util.PsiUtilCore; +import com.intellij.structuralsearch.*; +import com.intellij.structuralsearch.impl.matcher.CompiledPattern; +import com.intellij.structuralsearch.impl.matcher.MatchPredicateProvider; +import com.intellij.structuralsearch.impl.matcher.MatcherImplUtil; +import com.intellij.structuralsearch.impl.matcher.PatternTreeContext; +import com.intellij.structuralsearch.impl.matcher.filters.LexicalNodesFilter; +import com.intellij.structuralsearch.impl.matcher.handlers.MatchPredicate; +import com.intellij.structuralsearch.impl.matcher.handlers.SubstitutionHandler; +import com.intellij.structuralsearch.impl.matcher.predicates.*; +import com.intellij.structuralsearch.plugin.ui.Configuration; +import com.intellij.util.IncorrectOperationException; +import gnu.trove.TIntArrayList; +import gnu.trove.TIntHashSet; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Compiles the handlers for usability + */ +public class PatternCompiler { + private static CompileContext lastTestingContext; + + public static void transformOldPattern(MatchOptions options) { + StringToConstraintsTransformer.transformOldPattern(options); + } + + public static CompiledPattern compilePattern(final Project project, final MatchOptions options) throws MalformedPatternException, + UnsupportedOperationException { + FileType fileType = options.getFileType(); + assert fileType instanceof LanguageFileType; + Language language = ((LanguageFileType)fileType).getLanguage(); + StructuralSearchProfile profile = StructuralSearchUtil.getProfileByLanguage(language); + assert profile != null; + CompiledPattern result = profile.createCompiledPattern(); + + final String[] prefixes = result.getTypedVarPrefixes(); + assert prefixes.length > 0; + + final CompileContext context = new CompileContext(); + if (ApplicationManager.getApplication().isUnitTestMode()) lastTestingContext = context; + + /*CompiledPattern result = options.getFileType() == StdFileTypes.JAVA ? + new JavaCompiledPattern() : + new XmlCompiledPattern();*/ + + try { + context.init(result, options, project, options.getScope() instanceof GlobalSearchScope); + + List<PsiElement> elements = compileByAllPrefixes(project, options, result, context, prefixes); + + context.getPattern().setNodes(elements); + + if (context.getSearchHelper().doOptimizing() && context.getSearchHelper().isScannedSomething()) { + final Set<PsiFile> set = context.getSearchHelper().getFilesSetToScan(); + final List<PsiFile> filesToScan = new ArrayList<PsiFile>(set.size()); + final GlobalSearchScope scope = (GlobalSearchScope)options.getScope(); + + for (final PsiFile file : set) { + if (!scope.contains(file.getVirtualFile())) { + continue; + } + + if (file instanceof PsiFileImpl) { + ((PsiFileImpl)file).clearCaches(); + } + filesToScan.add(file); + } + + if (filesToScan.size() == 0) { + throw new MalformedPatternException(SSRBundle.message("ssr.will.not.find.anything")); + } + result.setScope( + new LocalSearchScope(PsiUtilCore.toPsiElementArray(filesToScan)) + ); + } + } finally { + context.clear(); + } + + return result; + } + + public static String getLastFindPlan() { + return ((TestModeOptimizingSearchHelper)lastTestingContext.getSearchHelper()).getSearchPlan(); + } + + @NotNull + private static List<PsiElement> compileByAllPrefixes(Project project, + MatchOptions options, + CompiledPattern pattern, + CompileContext context, + String[] applicablePrefixes) { + if (applicablePrefixes.length == 0) { + return Collections.emptyList(); + } + + List<PsiElement> elements = doCompile(project, options, pattern, new ConstantPrefixProvider(applicablePrefixes[0]), context); + if (elements.isEmpty()) { + return elements; + } + + final PsiFile file = elements.get(0).getContainingFile(); + if (file == null) { + return elements; + } + + final PsiElement last = elements.get(elements.size() - 1); + final Pattern[] patterns = new Pattern[applicablePrefixes.length]; + + for (int i = 0; i < applicablePrefixes.length; i++) { + String s = StructuralSearchUtil.shieldSpecialChars(applicablePrefixes[i]); + patterns[i] = Pattern.compile(s + "\\w+\\b"); + } + + final int[] varEndOffsets = findAllTypedVarOffsets(file, patterns); + + final int patternEndOffset = last.getTextRange().getEndOffset(); + if (elements.size() == 0 || + checkErrorElements(file, patternEndOffset, patternEndOffset, varEndOffsets, true) != Boolean.TRUE) { + return elements; + } + + final int varCount = varEndOffsets.length; + final String[] prefixSequence = new String[varCount]; + + for (int i = 0; i < varCount; i++) { + prefixSequence[i] = applicablePrefixes[0]; + } + + final List<PsiElement> finalElements = + compileByPrefixes(project, options, pattern, context, applicablePrefixes, patterns, prefixSequence, 0); + return finalElements != null + ? finalElements + : doCompile(project, options, pattern, new ConstantPrefixProvider(applicablePrefixes[0]), context); + } + + @Nullable + private static List<PsiElement> compileByPrefixes(Project project, + MatchOptions options, + CompiledPattern pattern, + CompileContext context, + String[] applicablePrefixes, + Pattern[] substitutionPatterns, + String[] prefixSequence, + int index) { + if (index >= prefixSequence.length) { + final List<PsiElement> elements = doCompile(project, options, pattern, new ArrayPrefixProvider(prefixSequence), context); + if (elements.isEmpty()) { + return elements; + } + + final PsiElement parent = elements.get(0).getParent(); + final PsiElement last = elements.get(elements.size() - 1); + final int[] varEndOffsets = findAllTypedVarOffsets(parent.getContainingFile(), substitutionPatterns); + final int patternEndOffset = last.getTextRange().getEndOffset(); + return checkErrorElements(parent, patternEndOffset, patternEndOffset, varEndOffsets, false) != Boolean.TRUE + ? elements + : null; + } + + String[] alternativeVariant = null; + + for (String applicablePrefix : applicablePrefixes) { + prefixSequence[index] = applicablePrefix; + + List<PsiElement> elements = doCompile(project, options, pattern, new ArrayPrefixProvider(prefixSequence), context); + if (elements.isEmpty()) { + return elements; + } + + final PsiFile file = elements.get(0).getContainingFile(); + if (file == null) { + return elements; + } + + final int[] varEndOffsets = findAllTypedVarOffsets(file, substitutionPatterns); + final int offset = varEndOffsets[index]; + + final int patternEndOffset = elements.get(elements.size() - 1).getTextRange().getEndOffset(); + final Boolean result = checkErrorElements(file, offset, patternEndOffset, varEndOffsets, false); + + if (result == Boolean.TRUE) { + continue; + } + + if (result == Boolean.FALSE || (result == null && alternativeVariant == null)) { + final List<PsiElement> finalElements = + compileByPrefixes(project, options, pattern, context, applicablePrefixes, substitutionPatterns, prefixSequence, index + 1); + if (finalElements != null) { + if (result == Boolean.FALSE) { + return finalElements; + } + alternativeVariant = new String[prefixSequence.length]; + System.arraycopy(prefixSequence, 0, alternativeVariant, 0, prefixSequence.length); + } + } + } + + return alternativeVariant != null ? + compileByPrefixes(project, options, pattern, context, applicablePrefixes, substitutionPatterns, alternativeVariant, index + 1) : + null; + } + + @NotNull + private static int[] findAllTypedVarOffsets(final PsiFile file, final Pattern[] substitutionPatterns) { + final TIntHashSet result = new TIntHashSet(); + + file.accept(new PsiRecursiveElementWalkingVisitor() { + @Override + public void visitElement(PsiElement element) { + super.visitElement(element); + + if (element instanceof LeafElement) { + final String text = element.getText(); + + for (Pattern pattern : substitutionPatterns) { + final Matcher matcher = pattern.matcher(text); + + while (matcher.find()) { + result.add(element.getTextRange().getStartOffset() + matcher.end()); + } + } + } + } + }); + + final int[] resultArray = result.toArray(); + Arrays.sort(resultArray); + return resultArray; + } + + + /** + * False: there are no error elements before offset, except patternEndOffset + * Null: there are only error elements located exactly after template variables or at the end of the pattern + * True: otherwise + */ + @Nullable + private static Boolean checkErrorElements(PsiElement element, + final int offset, + final int patternEndOffset, + final int[] varEndOffsets, + final boolean strict) { + final TIntArrayList errorOffsets = new TIntArrayList(); + final boolean[] containsErrorTail = {false}; + final TIntHashSet varEndOffsetsSet = new TIntHashSet(varEndOffsets); + + element.accept(new PsiRecursiveElementWalkingVisitor() { + @Override + public void visitElement(PsiElement element) { + super.visitElement(element); + + if (!(element instanceof PsiErrorElement)) { + return; + } + + final int startOffset = element.getTextRange().getStartOffset(); + + if ((strict || !varEndOffsetsSet.contains(startOffset)) && startOffset != patternEndOffset) { + errorOffsets.add(startOffset); + } + + if (startOffset == offset) { + containsErrorTail[0] = true; + } + } + }); + + for (int i = 0; i < errorOffsets.size(); i++) { + final int errorOffset = errorOffsets.get(i); + if (errorOffset <= offset) { + return true; + } + } + return containsErrorTail[0] ? null : false; + } + + private interface PrefixProvider { + String getPrefix(int varIndex); + } + + private static class ConstantPrefixProvider implements PrefixProvider { + private final String myPrefix; + + private ConstantPrefixProvider(String prefix) { + myPrefix = prefix; + } + + @Override + public String getPrefix(int varIndex) { + return myPrefix; + } + } + + private static class ArrayPrefixProvider implements PrefixProvider { + private final String[] myPrefixes; + + private ArrayPrefixProvider(String[] prefixes) { + myPrefixes = prefixes; + } + + @Override + public String getPrefix(int varIndex) { + try { + return myPrefixes[varIndex]; + } catch (ArrayIndexOutOfBoundsException e) { + return null; + } + } + } + + private static List<PsiElement> doCompile(Project project, + MatchOptions options, + CompiledPattern result, + PrefixProvider prefixProvider, + CompileContext context) { + result.clearHandlers(); + context.init(result, options, project, options.getScope() instanceof GlobalSearchScope); + + final StringBuilder buf = new StringBuilder(); + + Template template = TemplateManager.getInstance(project).createTemplate("","",options.getSearchPattern()); + + int segmentsCount = template.getSegmentsCount(); + String text = template.getTemplateText(); + buf.setLength(0); + int prevOffset = 0; + + for(int i=0;i<segmentsCount;++i) { + final int offset = template.getSegmentOffset(i); + final String name = template.getSegmentName(i); + + final String prefix = prefixProvider.getPrefix(i); + if (prefix == null) { + throw new MalformedPatternException(); + } + + buf.append(text.substring(prevOffset,offset)); + buf.append(prefix); + buf.append(name); + + MatchVariableConstraint constraint = options.getVariableConstraint(name); + if (constraint==null) { + // we do not edited the constraints + constraint = new MatchVariableConstraint(); + constraint.setName( name ); + options.addVariableConstraint(constraint); + } + + SubstitutionHandler handler = result.createSubstitutionHandler( + name, + prefix + name, + constraint.isPartOfSearchResults(), + constraint.getMinCount(), + constraint.getMaxCount(), + constraint.isGreedy() + ); + + if(constraint.isWithinHierarchy()) { + handler.setSubtype(true); + } + + if(constraint.isStrictlyWithinHierarchy()) { + handler.setStrictSubtype(true); + } + + MatchPredicate predicate; + + if (!StringUtil.isEmptyOrSpaces(constraint.getRegExp())) { + predicate = new RegExpPredicate( + constraint.getRegExp(), + options.isCaseSensitiveMatch(), + name, + constraint.isWholeWordsOnly(), + constraint.isPartOfSearchResults() + ); + if (constraint.isInvertRegExp()) { + predicate = new NotPredicate(predicate); + } + addPredicate(handler,predicate); + } + + if (constraint.isReference()) { + predicate = new ReferencePredicate( constraint.getNameOfReferenceVar() ); + + if (constraint.isInvertReference()) { + predicate = new NotPredicate(predicate); + } + addPredicate(handler,predicate); + } + + Set<MatchPredicate> predicates = new LinkedHashSet<MatchPredicate>(); + for (MatchPredicateProvider matchPredicateProvider : Extensions.getExtensions(MatchPredicateProvider.EP_NAME)) { + matchPredicateProvider.collectPredicates(constraint, name, options, predicates); + } + for (MatchPredicate matchPredicate : predicates) { + addPredicate(handler, matchPredicate); + } + + addScriptConstraint(name, constraint, handler); + + if (!StringUtil.isEmptyOrSpaces(constraint.getContainsConstraint())) { + predicate = new ContainsPredicate(name, constraint.getContainsConstraint()); + if (constraint.isInvertContainsConstraint()) { + predicate = new NotPredicate(predicate); + } + addPredicate(handler,predicate); + } + + if (!StringUtil.isEmptyOrSpaces(constraint.getWithinConstraint())) { + assert false; + } + + prevOffset = offset; + } + + MatchVariableConstraint constraint = options.getVariableConstraint(Configuration.CONTEXT_VAR_NAME); + if (constraint != null) { + SubstitutionHandler handler = result.createSubstitutionHandler( + Configuration.CONTEXT_VAR_NAME, + Configuration.CONTEXT_VAR_NAME, + constraint.isPartOfSearchResults(), + constraint.getMinCount(), + constraint.getMaxCount(), + constraint.isGreedy() + ); + + if (!StringUtil.isEmptyOrSpaces(constraint.getWithinConstraint())) { + MatchPredicate predicate = new WithinPredicate(Configuration.CONTEXT_VAR_NAME, constraint.getWithinConstraint(), project); + if (constraint.isInvertWithinConstraint()) { + predicate = new NotPredicate(predicate); + } + addPredicate(handler,predicate); + } + + addScriptConstraint(Configuration.CONTEXT_VAR_NAME, constraint, handler); + } + + buf.append(text.substring(prevOffset,text.length())); + + PsiElement[] matchStatements; + + try { + final String pattern = buf.toString(); + matchStatements = MatcherImplUtil.createTreeFromText(pattern, PatternTreeContext.Block, options.getFileType(), + options.getDialect(), options.getPatternContext(), project, false); + if (matchStatements.length==0) throw new MalformedPatternException(pattern); + } catch (IncorrectOperationException e) { + throw new MalformedPatternException(e.getMessage()); + } + + NodeFilter filter = LexicalNodesFilter.getInstance(); + + GlobalCompilingVisitor compilingVisitor = new GlobalCompilingVisitor(); + compilingVisitor.compile(matchStatements,context); + ArrayList<PsiElement> elements = new ArrayList<PsiElement>(); + + for (PsiElement matchStatement : matchStatements) { + if (!filter.accepts(matchStatement)) { + elements.add(matchStatement); + } + } + + new DeleteNodesAction(compilingVisitor.getLexicalNodes()).run(); + return elements; + } + + private static void addScriptConstraint(String name, MatchVariableConstraint constraint, SubstitutionHandler handler) { + MatchPredicate predicate; + if (constraint.getScriptCodeConstraint()!= null && constraint.getScriptCodeConstraint().length() > 2) { + final String script = StringUtil.stripQuotesAroundValue(constraint.getScriptCodeConstraint()); + final String s = ScriptSupport.checkValidScript(script); + if (s != null) throw new MalformedPatternException("Script constraint for " + constraint.getName() + " has problem "+s); + predicate = new ScriptPredicate(name, script); + addPredicate(handler,predicate); + } + } + + static void addPredicate(SubstitutionHandler handler, MatchPredicate predicate) { + if (handler.getPredicate()==null) { + handler.setPredicate(predicate); + } else { + handler.setPredicate(new BinaryPredicate(handler.getPredicate(), predicate, false)); + } + } + +}
\ No newline at end of file diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/StringToConstraintsTransformer.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/StringToConstraintsTransformer.java new file mode 100644 index 000000000000..0d684503a14d --- /dev/null +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/StringToConstraintsTransformer.java @@ -0,0 +1,436 @@ +package com.intellij.structuralsearch.impl.matcher.compiler; + +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.structuralsearch.*; +import com.intellij.structuralsearch.plugin.ui.Configuration; +import org.jetbrains.annotations.NonNls; + +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; + +/** + * Created by IntelliJ IDEA. + * User: maxim + * Date: 17.11.2004 + * Time: 19:29:05 + * To change this template use File | Settings | File Templates. + */ +class StringToConstraintsTransformer { + @NonNls private static final String P_STR = "(\\w+)\\('(\\w+)\\)"; + private static final Pattern p = Pattern.compile(P_STR); + @NonNls private static final String P2_STR = "(\\w+)"; + private static final Pattern p2 = Pattern.compile(P2_STR); + @NonNls private static final String P3_STR = "(\\w+)\\(( ?(?:[\\\"\\*<>!\\.\\?\\:\\$\\\\\\(\\)\\[\\]\\w\\|\\+ =]*|(?:\\\"[^\\\"]*\\\")) ?)\\)"; + private static final Pattern p3 = Pattern.compile(P3_STR); + @NonNls private static final String REF = "ref"; + @NonNls private static final String READ = "read"; + @NonNls private static final String WRITE = "write"; + @NonNls private static final String REGEX = "regex"; + @NonNls private static final String REGEXW = "regexw"; + @NonNls private static final String EXPRTYPE = "exprtype"; + @NonNls private static final String FORMAL = "formal"; + @NonNls private static final String SCRIPT = "script"; + @NonNls private static final String CONTAINS = "contains"; + @NonNls private static final String WITHIN = "within"; + + static void transformOldPattern(MatchOptions options) { + final String pattern = options.getSearchPattern(); + + final StringBuilder buf = new StringBuilder(); + + StringBuilder miscBuffer = null; + int anonymousTypedVarsCount = 0; + + for(int index=0;index < pattern.length();++index) { + char ch = pattern.charAt(index); + + if (ch=='\'') { + // doubling ' + final int length = pattern.length(); + if (index + 1 < length && + pattern.charAt(index + 1)=='\'' + ) { + // ignore next ' + index++; + } else if (index + 2 < length && + pattern.charAt(index + 2)=='\'' + ) { + // eat simple character + buf.append(ch); + buf.append(pattern.charAt(++index)); + ch = pattern.charAt(++index); + } else if (index + 3 < length && + pattern.charAt(index + 1)=='\\' && + pattern.charAt(index + 3)=='\'' + ) { + // eat simple escape character + buf.append(ch); + buf.append(pattern.charAt(++index)); + buf.append(pattern.charAt(++index)); + ch = pattern.charAt(++index); + } else if (index + 7 < length && + pattern.charAt(index + 1)=='\\' && + pattern.charAt(index + 2)=='u' && + pattern.charAt(index + 7)=='\'') { + // eat simple escape character + buf.append(ch); + buf.append(pattern.charAt(++index)); + buf.append(pattern.charAt(++index)); + buf.append(pattern.charAt(++index)); + buf.append(pattern.charAt(++index)); + buf.append(pattern.charAt(++index)); + buf.append(pattern.charAt(++index)); + ch = pattern.charAt(++index); + } else { + // typed variable + + buf.append("$"); + if (miscBuffer == null) miscBuffer = new StringBuilder(); + else miscBuffer.setLength(0); + + // eat the name of typed var + for(++index; index< length && Character.isJavaIdentifierPart(ch = pattern.charAt(index)); ++index) { + miscBuffer.append(ch); + buf.append(ch); + } + + boolean anonymous = false; + + if (miscBuffer.length() == 0) throw new MalformedPatternException(SSRBundle.message("error.expected.character")); + if (miscBuffer.charAt(0)=='_') { + anonymous = true; + + if(miscBuffer.length() == 1) { + // anonymous var, make it unique for the case of constraints + anonymousTypedVarsCount++; + miscBuffer.append(anonymousTypedVarsCount); + buf.append(anonymousTypedVarsCount); + } else { + buf.deleteCharAt(buf.length()-miscBuffer.length()); + miscBuffer.deleteCharAt(0); + } + } + + + buf.append("$"); + String typedVar = miscBuffer.toString(); + int minOccurs = 1; + int maxOccurs = 1; + boolean greedy = true; + MatchVariableConstraint constraint = options.getVariableConstraint(typedVar); + boolean constraintCreated = false; + + if (constraint==null) { + constraint = new MatchVariableConstraint(); + constraint.setName( typedVar ); + constraintCreated = true; + } + + // Check the number of occurrences for typed variable + if (index < length) { + char possibleQuantifier = pattern.charAt(index); + + if (possibleQuantifier=='+') { + maxOccurs = Integer.MAX_VALUE; + ++index; + } else if (possibleQuantifier=='?') { + minOccurs = 0; + ++index; + } else if (possibleQuantifier=='*') { + minOccurs = 0; + maxOccurs = Integer.MAX_VALUE; + ++index; + } else if (possibleQuantifier=='{') { + ++index; + minOccurs = 0; + while (index < length && (ch = pattern.charAt(index)) >= '0' && ch <= '9') { + minOccurs *= 10; + minOccurs += (ch - '0'); + if (minOccurs < 0) throw new MalformedPatternException(SSRBundle.message("error.overflow")); + ++index; + } + + if (ch==',') { + ++index; + maxOccurs = 0; + + while (index < length && (ch = pattern.charAt(index)) >= '0' && ch <= '9') { + maxOccurs *= 10; + maxOccurs += (ch - '0'); + if (maxOccurs < 0) throw new MalformedPatternException(SSRBundle.message("error.overflow")); + ++index; + } + } else { + maxOccurs = Integer.MAX_VALUE; + } + + if (ch != '}') { + if (maxOccurs == Integer.MAX_VALUE) throw new MalformedPatternException(SSRBundle.message("error.expected.brace1")); + else throw new MalformedPatternException(SSRBundle.message("error.expected.brace2")); + } + ++index; + } + + if (index < length) { + ch = pattern.charAt(index); + if (ch=='?') { + greedy = false; + ++index; + } + } + } + + if (constraintCreated) { + constraint.setMinCount(minOccurs); + constraint.setMaxCount(maxOccurs); + constraint.setGreedy(greedy); + constraint.setPartOfSearchResults(!anonymous); + } + + if (index < length && pattern.charAt(index) == ':') { + ++index; + if (index >= length) throw new MalformedPatternException(SSRBundle.message("error.expected.condition", ':')); + ch = pattern.charAt(index); + if (ch == ':') { + // double colon instead of condition + buf.append(ch); + } + else { + index = eatTypedVarCondition(index, pattern, miscBuffer, constraint); + } + } + + if (constraintCreated) { + if (constraint.getWithinConstraint().length() > 0) { + constraint.setName(Configuration.CONTEXT_VAR_NAME); + } + options.addVariableConstraint(constraint); + } + + if (index == length) break; + // fall through to append white space + ch = pattern.charAt(index); + } + } + + buf.append(ch); + } + + options.setSearchPattern( buf.toString() ); + } + + private static int eatTypedVarCondition(int index, + String pattern, + StringBuilder miscBuffer, + MatchVariableConstraint constraint) { + final int length = pattern.length(); + + char ch = pattern.charAt(index); + if (ch == '+' || ch == '*') { + // this is type axis navigation relation + switch(ch) { + case '+': + constraint.setStrictlyWithinHierarchy(true); + break; + case '*': + constraint.setWithinHierarchy(true); + break; + } + + ++index; + if (index >= length) throw new MalformedPatternException(SSRBundle.message("error.expected.condition", ch)); + ch = pattern.charAt(index); + } + + if (ch == '[') { + // eat complete condition + + miscBuffer.setLength(0); + for(++index; index < length && ((ch = pattern.charAt(index))!=']' || pattern.charAt(index-1)=='\\'); ++index) { + miscBuffer.append(ch); + } + if (ch != ']') throw new MalformedPatternException(SSRBundle.message("error.expected.condition.or.bracket")); + ++index; + parseCondition(constraint, miscBuffer.toString()); + } else { + // eat reg exp constraint + miscBuffer.setLength(0); + index = handleRegExp(index, pattern, miscBuffer, constraint); + } + return index; + } + + private static int handleRegExp(int index, + String pattern, + StringBuilder miscBuffer, + MatchVariableConstraint constraint) { + char c = pattern.charAt(index - 1); + final int length = pattern.length(); + for(char ch; index < length && !Character.isWhitespace(ch = pattern.charAt(index)); ++index) { + miscBuffer.append(ch); + } + + if (miscBuffer.length() == 0) + if (c == ':') throw new MalformedPatternException(SSRBundle.message("error.expected.condition", c)); + else return index; + String regexp = miscBuffer.toString(); + + if (constraint.getRegExp()!=null && + constraint.getRegExp().length() > 0 && + !constraint.getRegExp().equals(regexp)) { + throw new MalformedPatternException(SSRBundle.message("error.two.different.type.constraints")); + } else { + try { + Pattern.compile(regexp); + } catch (PatternSyntaxException e) { + throw new MalformedPatternException(SSRBundle.message("invalid.regular.expression")); + } + constraint.setRegExp(regexp); + } + + return index; + } + + private static void parseCondition(MatchVariableConstraint constraint, String condition) { + if (condition.isEmpty()) throw new MalformedPatternException(SSRBundle.message("error.expected.condition", "[")); + final List<String> tokens = StringUtil.split(condition, "&&", true, false); + + for (String token : tokens) { + token = token.trim(); + if (token.isEmpty()) throw new MalformedPatternException(SSRBundle.message("error.expected.condition", "&&")); + boolean hasNot = false; + boolean consumed = false; + + if (StringUtil.startsWithChar(token, '!')) { + token = token.substring(1); + if (token.isEmpty()) throw new MalformedPatternException(SSRBundle.message("error.expected.condition", "!")); + hasNot = true; + } + + Matcher m = p.matcher(token); + + if (m.matches()) { + String option = m.group(1); + + if (option.equalsIgnoreCase(REF)) { + String name = m.group(2); + + constraint.setReference(true); + constraint.setInvertReference(hasNot); + constraint.setNameOfReferenceVar(name); + consumed = true; + } + } else { + m = p2.matcher(token); + + if (m.matches()) { + String option = m.group(1); + + if (option.equalsIgnoreCase(READ)) { + constraint.setReadAccess(true); + constraint.setInvertReadAccess(hasNot); + consumed = true; + } else if (option.equalsIgnoreCase(WRITE)) { + constraint.setWriteAccess(true); + constraint.setInvertWriteAccess(hasNot); + consumed = true; + } + } else { + m = p3.matcher(token); + + if (m.matches()) { + String option = m.group(1); + + if (option.equalsIgnoreCase(REGEX) || option.equalsIgnoreCase(REGEXW)) { + String typePattern = getSingleParameter(m, SSRBundle.message("reg.exp.should.be.delimited.with.spaces.error.message")); + if (typePattern.isEmpty()) throw new MalformedPatternException(SSRBundle.message("no.reg.exp.specified.error.message")); + + if (StringUtil.startsWithChar(typePattern, '*')) { + typePattern = typePattern.substring(1); + constraint.setWithinHierarchy(true); + } + try { + Pattern.compile(typePattern); + } catch (PatternSyntaxException e) { + throw new MalformedPatternException(SSRBundle.message("invalid.regular.expression")); + } + constraint.setRegExp( typePattern ); + constraint.setInvertRegExp( hasNot ); + consumed = true; + if (option.equalsIgnoreCase(REGEXW)) { + constraint.setWholeWordsOnly(true); + } + } else if (option.equalsIgnoreCase(EXPRTYPE)) { + String exprTypePattern = getSingleParameter(m, SSRBundle.message( + "reg.exp.in.expr.type.should.be.delimited.with.spaces.error.message")); + + if (StringUtil.startsWithChar(exprTypePattern, '*')) { + exprTypePattern = exprTypePattern.substring(1); + constraint.setExprTypeWithinHierarchy(true); + } + try { + Pattern.compile(exprTypePattern); + } catch (PatternSyntaxException e) { + throw new MalformedPatternException(SSRBundle.message("invalid.regular.expression")); + } + constraint.setNameOfExprType( exprTypePattern ); + constraint.setInvertExprType( hasNot ); + consumed = true; + } else if (option.equalsIgnoreCase(FORMAL)) { + String exprTypePattern = getSingleParameter(m, SSRBundle.message( + "reg.exp.in.formal.arg.type.should.be.delimited.with.spaces.error.message")); + + if (StringUtil.startsWithChar(exprTypePattern, '*')) { + exprTypePattern = exprTypePattern.substring(1); + constraint.setFormalArgTypeWithinHierarchy(true); + } + try { + Pattern.compile(exprTypePattern); + } catch (PatternSyntaxException e) { + throw new MalformedPatternException(SSRBundle.message("invalid.regular.expression")); + } + constraint.setNameOfFormalArgType( exprTypePattern ); + constraint.setInvertFormalType( hasNot ); + consumed = true; + } else if (option.equalsIgnoreCase(SCRIPT)) { + String script = getSingleParameter(m, SSRBundle.message("script.should.be.delimited.with.spaces.error.message")); + + constraint.setScriptCodeConstraint( script ); + consumed = true; + } else if (option.equalsIgnoreCase(CONTAINS)) { + if (hasNot) constraint.setInvertContainsConstraint(true); + String script = getSingleParameter(m, SSRBundle.message("script.should.be.delimited.with.spaces.error.message")); + + constraint.setContainsConstraint(script ); + consumed = true; + } else if (option.equalsIgnoreCase(WITHIN)) { + if (hasNot) constraint.setInvertWithinConstraint(true); + String script = getSingleParameter(m, SSRBundle.message("script.should.be.delimited.with.spaces.error.message")); + + constraint.setWithinConstraint(script); + consumed = true; + } + } + } + } + + if (!consumed) { + throw new UnsupportedPatternException( + SSRBundle.message("option.is.not.recognized.error.message", token) + ); + } + } + } + + private static String getSingleParameter(Matcher m, String errorMessage) { + final String value = m.group(2); + if (value.isEmpty()) return value; + + if (value.charAt(0)!=' ' || value.charAt(value.length()-1)!=' ') { + throw new MalformedPatternException(errorMessage); + } + return value.trim(); + } +} diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/TestModeOptimizingSearchHelper.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/TestModeOptimizingSearchHelper.java new file mode 100644 index 000000000000..e341b57da8ce --- /dev/null +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/TestModeOptimizingSearchHelper.java @@ -0,0 +1,70 @@ +package com.intellij.structuralsearch.impl.matcher.compiler; + +import com.intellij.psi.PsiFile; + +import java.util.Collections; +import java.util.Set; + +/** + * @author Maxim.Mossienko + */ +public class TestModeOptimizingSearchHelper extends OptimizingSearchHelperBase { + private static String lastString; + private final StringBuilder builder = new StringBuilder(); + private int lastLength; + + TestModeOptimizingSearchHelper(CompileContext _context) { + super(_context); + } + + public boolean doOptimizing() { + return true; + } + + public void clear() { + lastString = builder.toString(); + builder.setLength(0); + lastLength = 0; + } + + protected void doAddSearchWordInCode(final String refname) { + append(refname, "reserved in code:"); + } + + @Override + protected void doAddSearchWordInText(String refname) { + append(refname, "in text:"); + } + + private void append(final String refname, final String str) { + if (builder.length() == lastLength) builder.append("["); + else builder.append("|"); + builder.append(str).append(refname); + } + + protected void doAddSearchWordInComments(final String refname) { + append(refname, "in comments:"); + } + + protected void doAddSearchWordInLiterals(final String refname) { + append(refname, "in literals:"); + } + + public void endTransaction() { + super.endTransaction(); + builder.append("]"); + lastLength = builder.length(); + } + + public boolean isScannedSomething() { + return false; + } + + public Set<PsiFile> getFilesSetToScan() { + return Collections.emptySet(); + } + + public String getSearchPlan() { + return lastString; + } +} diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/XmlCompilingVisitor.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/XmlCompilingVisitor.java new file mode 100644 index 000000000000..99917ad23240 --- /dev/null +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/XmlCompilingVisitor.java @@ -0,0 +1,53 @@ +package com.intellij.structuralsearch.impl.matcher.compiler; + +import com.intellij.psi.PsiElement; +import com.intellij.psi.XmlRecursiveElementVisitor; +import com.intellij.psi.xml.XmlTag; +import com.intellij.psi.xml.XmlText; +import com.intellij.psi.xml.XmlToken; +import com.intellij.structuralsearch.impl.matcher.filters.TagValueFilter; +import com.intellij.structuralsearch.impl.matcher.handlers.MatchingHandler; +import com.intellij.structuralsearch.impl.matcher.handlers.TopLevelMatchingHandler; +import com.intellij.structuralsearch.impl.matcher.handlers.XmlTextHandler; +import com.intellij.structuralsearch.impl.matcher.strategies.XmlMatchingStrategy; + +/** +* @author Eugene.Kudelevsky +*/ +public class XmlCompilingVisitor extends XmlRecursiveElementVisitor { + private final GlobalCompilingVisitor myCompilingVisitor; + + public XmlCompilingVisitor(GlobalCompilingVisitor compilingVisitor) { + this.myCompilingVisitor = compilingVisitor; + } + + @Override public void visitElement(PsiElement element) { + myCompilingVisitor.handle(element); + super.visitElement(element); + } + + @Override public void visitXmlToken(XmlToken token) { + super.visitXmlToken(token); + + if (token.getParent() instanceof XmlText && myCompilingVisitor.getContext().getPattern().isRealTypedVar(token)) { + final MatchingHandler handler = myCompilingVisitor.getContext().getPattern().getHandler(token); + handler.setFilter(TagValueFilter.getInstance()); + + final XmlTextHandler parentHandler = new XmlTextHandler(); + myCompilingVisitor.getContext().getPattern().setHandler(token.getParent(), parentHandler); + parentHandler.setFilter(TagValueFilter.getInstance()); + } + } + + @Override public void visitXmlTag(XmlTag xmlTag) { + myCompilingVisitor.setCodeBlockLevel(myCompilingVisitor.getCodeBlockLevel() + 1); + super.visitXmlTag(xmlTag); + myCompilingVisitor.setCodeBlockLevel(myCompilingVisitor.getCodeBlockLevel() - 1); + + if (myCompilingVisitor.getCodeBlockLevel() == 1) { + myCompilingVisitor.getContext().getPattern().setStrategy(XmlMatchingStrategy.getInstance()); + myCompilingVisitor.getContext().getPattern() + .setHandler(xmlTag, new TopLevelMatchingHandler(myCompilingVisitor.getContext().getPattern().getHandler(xmlTag))); + } + } +} diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/filters/CompositeFilter.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/filters/CompositeFilter.java new file mode 100644 index 000000000000..db962ed8edf1 --- /dev/null +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/filters/CompositeFilter.java @@ -0,0 +1,27 @@ +package com.intellij.structuralsearch.impl.matcher.filters; + +import com.intellij.dupLocator.util.NodeFilter; +import com.intellij.psi.PsiElement; + +/** + * Created by IntelliJ IDEA. + * User: maxim + * Date: 28.12.2003 + * Time: 0:13:19 + * To change this template use Options | File Templates. + */ +public class CompositeFilter implements NodeFilter { + private final NodeFilter first; + private final NodeFilter second; + protected boolean result; + + public boolean accepts(PsiElement element) { + return first.accepts(element) || + second.accepts(element); + } + + public CompositeFilter(NodeFilter _first, NodeFilter _second) { + first = _first; + second = _second; + } +} diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/filters/DefaultFilter.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/filters/DefaultFilter.java new file mode 100644 index 000000000000..267f37699945 --- /dev/null +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/filters/DefaultFilter.java @@ -0,0 +1,16 @@ +package com.intellij.structuralsearch.impl.matcher.filters; + +import com.intellij.psi.PsiElement; +import com.intellij.psi.impl.source.tree.LeafElement; + +/** + * Default searching filter + */ +public class DefaultFilter { + public static boolean accepts(PsiElement element, PsiElement element2) { + if (element instanceof LeafElement && element2 instanceof LeafElement) { + return ((LeafElement)element).getElementType() == ((LeafElement)element2).getElementType(); + } + return element.getClass()==element2.getClass(); + } +} diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/filters/LexicalNodesFilter.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/filters/LexicalNodesFilter.java new file mode 100644 index 000000000000..3cef402b09d3 --- /dev/null +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/filters/LexicalNodesFilter.java @@ -0,0 +1,51 @@ +package com.intellij.structuralsearch.impl.matcher.filters; + +import com.intellij.dupLocator.util.NodeFilter; +import com.intellij.psi.PsiElement; +import com.intellij.structuralsearch.StructuralSearchProfile; +import com.intellij.structuralsearch.StructuralSearchUtil; + +/** + * Filter for lexical nodes + */ +public final class LexicalNodesFilter implements NodeFilter { + private boolean careKeyWords; + private boolean result; + + private LexicalNodesFilter() {} + + public static NodeFilter getInstance() { + return NodeFilterHolder.instance; + } + + public boolean getResult() { + return result; + } + + public void setResult(boolean result) { + this.result = result; + } + + private static class NodeFilterHolder { + private static final NodeFilter instance = new LexicalNodesFilter(); + } + + public boolean isCareKeyWords() { + return careKeyWords; + } + + public void setCareKeyWords(boolean careKeyWords) { + this.careKeyWords = careKeyWords; + } + + public boolean accepts(PsiElement element) { + result = false; + if (element!=null) { + final StructuralSearchProfile profile = StructuralSearchUtil.getProfileByPsiElement(element); + if (profile != null) { + element.accept(profile.getLexicalNodesFilter(this)); + } + } + return result; + } +} diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/filters/TagValueFilter.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/filters/TagValueFilter.java new file mode 100644 index 000000000000..97ed9627e3fc --- /dev/null +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/filters/TagValueFilter.java @@ -0,0 +1,43 @@ +package com.intellij.structuralsearch.impl.matcher.filters; + +import com.intellij.dupLocator.util.NodeFilter; +import com.intellij.psi.PsiElement; +import com.intellij.psi.XmlElementVisitor; +import com.intellij.psi.xml.XmlTag; +import com.intellij.psi.xml.XmlText; + +/** + * Created by IntelliJ IDEA. + * User: maxim.mossienko + * Date: Oct 12, 2005 + * Time: 4:44:19 PM + * To change this template use File | Settings | File Templates. + */ +public class TagValueFilter extends XmlElementVisitor implements NodeFilter { + private boolean result; + + @Override public void visitXmlText(XmlText text) { + result = true; + } + + @Override public void visitXmlTag(XmlTag tag) { + result = true; + } + + private static class NodeFilterHolder { + private static final NodeFilter instance = new TagValueFilter(); + } + + public static NodeFilter getInstance() { + return NodeFilterHolder.instance; + } + + private TagValueFilter() { + } + + public boolean accepts(PsiElement element) { + result = false; + if (element!=null) element.accept(this); + return result; + } +} diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/filters/XmlLexicalNodesFilter.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/filters/XmlLexicalNodesFilter.java new file mode 100644 index 000000000000..89c295ce1d80 --- /dev/null +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/filters/XmlLexicalNodesFilter.java @@ -0,0 +1,19 @@ +package com.intellij.structuralsearch.impl.matcher.filters; + +import com.intellij.psi.XmlElementVisitor; +import com.intellij.psi.xml.XmlText; + +/** +* @author Eugene.Kudelevsky +*/ +public class XmlLexicalNodesFilter extends XmlElementVisitor { + private final LexicalNodesFilter myLexicalNodesFilter; + + public XmlLexicalNodesFilter(LexicalNodesFilter lexicalNodesFilter) { + this.myLexicalNodesFilter = lexicalNodesFilter; + } + + @Override public void visitXmlText(XmlText text) { + myLexicalNodesFilter.setResult(true); + } +} diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/DelegatingHandler.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/DelegatingHandler.java new file mode 100644 index 000000000000..c95a85623753 --- /dev/null +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/DelegatingHandler.java @@ -0,0 +1,8 @@ +package com.intellij.structuralsearch.impl.matcher.handlers; + +/** + * @author Eugene.Kudelevsky + */ +public interface DelegatingHandler { + MatchingHandler getDelegate(); +} diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/LightTopLevelMatchingHandler.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/LightTopLevelMatchingHandler.java new file mode 100644 index 000000000000..34941cfa0257 --- /dev/null +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/LightTopLevelMatchingHandler.java @@ -0,0 +1,46 @@ +package com.intellij.structuralsearch.impl.matcher.handlers; + +import com.intellij.psi.PsiElement; +import com.intellij.structuralsearch.impl.matcher.MatchContext; +import com.intellij.dupLocator.iterators.NodeIterator; +import org.jetbrains.annotations.NotNull; + +public final class LightTopLevelMatchingHandler extends MatchingHandler implements DelegatingHandler { + private final MatchingHandler myDelegate; + + public LightTopLevelMatchingHandler(@NotNull MatchingHandler delegate) { + myDelegate = delegate; + } + + public boolean match(final PsiElement patternNode, final PsiElement matchedNode, final MatchContext matchContext) { + return myDelegate.match(patternNode, matchedNode, matchContext); + } + + @Override + public boolean canMatch(PsiElement patternNode, PsiElement matchedNode) { + return myDelegate.canMatch(patternNode, matchedNode); + } + + @Override + public boolean matchSequentially(final NodeIterator nodes, final NodeIterator nodes2, final MatchContext context) { + return myDelegate.matchSequentially(nodes, nodes2, context); + } + + public boolean match(final PsiElement patternNode, + final PsiElement matchedNode, final int start, final int end, final MatchContext context) { + return myDelegate.match(patternNode, matchedNode, start, end, context); + } + + public boolean isMatchSequentiallySucceeded(final NodeIterator nodes2) { + return true; + } + + @Override + public boolean shouldAdvanceTheMatchFor(final PsiElement patternElement, final PsiElement matchedElement) { + return myDelegate.shouldAdvanceTheMatchFor(patternElement, matchedElement); + } + + public MatchingHandler getDelegate() { + return myDelegate; + } +}
\ No newline at end of file diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/LiteralWithSubstitutionHandler.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/LiteralWithSubstitutionHandler.java new file mode 100644 index 000000000000..78967106755f --- /dev/null +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/LiteralWithSubstitutionHandler.java @@ -0,0 +1,49 @@ +package com.intellij.structuralsearch.impl.matcher.handlers; + +import com.intellij.psi.PsiElement; +import com.intellij.structuralsearch.impl.matcher.MatchContext; +import com.intellij.structuralsearch.impl.matcher.predicates.RegExpPredicate; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.List; + +/** + * Created by IntelliJ IDEA. + * User: Maxim.Mossienko + * Date: Jun 30, 2004 + * Time: 5:07:33 PM + * To change this template use File | Settings | File Templates. + */ +public class LiteralWithSubstitutionHandler extends MatchingHandler { + private final String matchExpression; + private Matcher matcher; + private final List<SubstitutionHandler> handlers; + + public LiteralWithSubstitutionHandler(String _matchedExpression,List<SubstitutionHandler> _handlers) { + matchExpression = _matchedExpression; + handlers = _handlers; + } + + public boolean match(PsiElement patternNode, PsiElement matchedNode, MatchContext context) { + final String text = RegExpPredicate.getMeaningfulText(matchedNode); + int offset = matchedNode.getText().indexOf(text); + if (matcher==null) { + matcher = Pattern.compile(matchExpression).matcher(text); + } else { + matcher.reset(text); + } + + while (matcher.find()) { + for (int i = 0; i < handlers.size(); ++i) { + SubstitutionHandler handler = handlers.get(i); + + if (!handler.handle(matchedNode,offset + matcher.start(i+1), offset + matcher.end(i+1),context)) { + return false; + } + } + return true; + } + return false; + } +} diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/MatchPredicate.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/MatchPredicate.java new file mode 100644 index 000000000000..cba96681ab71 --- /dev/null +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/MatchPredicate.java @@ -0,0 +1,27 @@ +package com.intellij.structuralsearch.impl.matcher.handlers; + +import com.intellij.psi.PsiElement; +import com.intellij.structuralsearch.impl.matcher.MatchContext; + +/** + * Root of handlers for pattern node matching. Handles simpliest type of the match. + */ +public abstract class MatchPredicate { + /** + * Matches given handler node against given value. + * @param matchedNode for matching + * @param context of the matching + * @return true if matching was successfull and false otherwise + */ + public boolean match(PsiElement patternNode,PsiElement matchedNode, int start, int end, MatchContext context) { + return match(patternNode,matchedNode,context); + } + + /** + * Matches given handler node against given value. + * @param matchedNode for matching + * @param context of the matching + * @return true if matching was successfull and false otherwise + */ + public abstract boolean match(PsiElement patternNode,PsiElement matchedNode, MatchContext context); +} diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/MatchingHandler.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/MatchingHandler.java new file mode 100644 index 000000000000..c53b08412b6b --- /dev/null +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/MatchingHandler.java @@ -0,0 +1,271 @@ +package com.intellij.structuralsearch.impl.matcher.handlers; + +import com.intellij.dupLocator.iterators.NodeIterator; +import com.intellij.dupLocator.util.NodeFilter; +import com.intellij.psi.*; +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.impl.matcher.filters.DefaultFilter; +import com.intellij.structuralsearch.impl.matcher.predicates.BinaryPredicate; +import com.intellij.structuralsearch.impl.matcher.predicates.NotPredicate; +import com.intellij.structuralsearch.impl.matcher.predicates.RegExpPredicate; +import com.intellij.structuralsearch.impl.matcher.strategies.MatchingStrategy; + +import java.util.HashSet; +import java.util.Set; + +/** + * Root of handlers for pattern node matching. Handles simplest type of the match. + */ +public abstract class MatchingHandler extends MatchPredicate { + protected NodeFilter filter; + private PsiElement pinnedElement; + + public void setFilter(NodeFilter filter) { + this.filter = filter; + } + + /** + * Matches given handler node against given value. + * @param matchedNode for matching + * @param context of the matching + * @return true if matching was successful and false otherwise + */ + public boolean match(PsiElement patternNode,PsiElement matchedNode, int start, int end, MatchContext context) { + return match(patternNode,matchedNode,context); + } + + /** + * Matches given handler node against given value. + * @param matchedNode for matching + * @param context of the matching + * @return true if matching was successful and false otherwise + */ + public boolean match(PsiElement patternNode,PsiElement matchedNode, MatchContext context) { + if (patternNode == null) { + return matchedNode == null; + } + + return canMatch(patternNode, matchedNode); + } + + public boolean canMatch(final PsiElement patternNode, final PsiElement matchedNode) { + if (filter!=null) { + return filter.accepts(matchedNode); + } else { + return DefaultFilter.accepts(patternNode, matchedNode); + } + } + + public boolean matchSequentially(NodeIterator nodes, NodeIterator nodes2, MatchContext context) { + PsiElement patternElement; + MatchingHandler handler; + MatchingStrategy strategy = context.getPattern().getStrategy(); + + skipIfNecessary(nodes, nodes2, strategy); + skipIfNecessary(nodes2, nodes, strategy); + + if (nodes2.hasNext() && + (handler = context.getPattern().getHandler(nodes.current())).match(patternElement = nodes.current(),nodes2.current(),context)) { + + nodes.advance(); + + if (shouldAdvanceTheMatchFor(patternElement, nodes2.current())) { + nodes2.advance(); + skipIfNecessary(nodes, nodes2, strategy); + } + skipIfNecessary(nodes2, nodes, strategy); + + if (nodes.hasNext()) { + final MatchingHandler nextHandler = context.getPattern().getHandler(nodes.current()); + + if (nextHandler.matchSequentially(nodes,nodes2,context)) { + // match was found! + return true; + } else { + // rewind, we was not able to match descendants + nodes.rewind(); + nodes2.rewind(); + } + } else { + // match was found + return handler.isMatchSequentiallySucceeded(nodes2); + } + } + return false; + } + + private static void skipIfNecessary(NodeIterator nodes, NodeIterator nodes2, MatchingStrategy strategy) { + while (strategy.shouldSkip(nodes2.current(), nodes.current())) { + nodes2.advance(); + } + } + + protected boolean isMatchSequentiallySucceeded(final NodeIterator nodes2) { + return !nodes2.hasNext(); + } + + private static MatchPredicate findRegExpPredicate(MatchPredicate start) { + if (start==null) return null; + if (start instanceof RegExpPredicate) return start; + + if(start instanceof BinaryPredicate) { + BinaryPredicate binary = (BinaryPredicate)start; + final MatchPredicate result = findRegExpPredicate(binary.getFirst()); + if (result!=null) return result; + + return findRegExpPredicate(binary.getSecond()); + } else if (start instanceof NotPredicate) { + return null; + } + return null; + } + + public static RegExpPredicate getSimpleRegExpPredicate(SubstitutionHandler handler) { + if (handler == null) return null; + return (RegExpPredicate)findRegExpPredicate(handler.getPredicate()); + } + + static class ClearStateVisitor extends PsiRecursiveElementWalkingVisitor { + private CompiledPattern pattern; + + ClearStateVisitor() { + super(true); + } + + @Override public void visitElement(PsiElement element) { + // We do not reset certain handlers because they are also bound to higher level nodes + // e.g. Identifier handler in name is also bound to PsiMethod + if (pattern.isToResetHandler(element)) { + MatchingHandler handler = pattern.getHandlerSimple(element); + if (handler instanceof SubstitutionHandler) { + handler.reset(); + } + } + super.visitElement(element); + } + + synchronized void clearState(CompiledPattern _pattern, PsiElement el) { + pattern = _pattern; + el.acceptChildren(this); + pattern = null; + } + } + + protected static ClearStateVisitor clearingVisitor = new ClearStateVisitor(); + + 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()) { + 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 (matchedElements == null || !matchedElements.contains(matchedNode)) { + + if (handler.match(patternNode, matchedNode, context)) { + 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); + 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); + } + } + } + + protected static boolean validateSatisfactionOfHandlers(NodeIterator nodes, MatchContext context) { + while(nodes.hasNext()) { + final PsiElement element = nodes.current(); + final MatchingHandler handler = context.getPattern().getHandler( element ); + + if (handler instanceof SubstitutionHandler) { + if (!((SubstitutionHandler)handler).validate( + context, StructuralSearchUtil.getProfileByPsiElement(element).getElementContextByPsi(element))) { + return false; + } + } else { + return false; + } + nodes.advance(); + } + return true; + } + + public NodeFilter getFilter() { + return filter; + } + + public boolean shouldAdvanceThePatternFor(PsiElement patternElement, PsiElement matchedElement) { + return true; + } + + public boolean shouldAdvanceTheMatchFor(PsiElement patternElement, PsiElement matchedElement) { + return true; + } + + public void reset() { + //pinnedElement = null; + } + + public PsiElement getPinnedNode(PsiElement context) { + return pinnedElement; + } + + public void setPinnedElement(final PsiElement pinnedElement) { + this.pinnedElement = pinnedElement; + } +}
\ No newline at end of file diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/SimpleHandler.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/SimpleHandler.java new file mode 100644 index 000000000000..3dffe792e308 --- /dev/null +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/SimpleHandler.java @@ -0,0 +1,20 @@ +package com.intellij.structuralsearch.impl.matcher.handlers; + +import com.intellij.psi.PsiElement; +import com.intellij.structuralsearch.impl.matcher.MatchContext; + +/** + * Root of handlers for pattern node matching. Handles simpliest type of the match. + */ +public final class SimpleHandler extends MatchingHandler { + /** + * Matches given handler node against given value. + * @param matchedNode for matching + * @param context of the matching + * @return true if matching was successfull and false otherwise + */ + public boolean match(PsiElement patternNode,PsiElement matchedNode, MatchContext context) { + if (!super.match(patternNode,matchedNode,context)) return false; + return context.getMatcher().match(patternNode,matchedNode); + } +} diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/SkippingHandler.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/SkippingHandler.java new file mode 100644 index 000000000000..389c4a7916c2 --- /dev/null +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/SkippingHandler.java @@ -0,0 +1,108 @@ +package com.intellij.structuralsearch.impl.matcher.handlers; + +import com.intellij.dupLocator.equivalence.EquivalenceDescriptor; +import com.intellij.dupLocator.iterators.NodeIterator; +import com.intellij.dupLocator.util.DuplocatorUtil; +import com.intellij.dupLocator.util.NodeFilter; +import com.intellij.psi.PsiElement; +import com.intellij.structuralsearch.impl.matcher.MatchContext; +import com.intellij.structuralsearch.impl.matcher.filters.LexicalNodesFilter; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * @author Eugene.Kudelevsky + */ +public class SkippingHandler extends MatchingHandler implements DelegatingHandler { + + private final MatchingHandler myDelegate; + + public SkippingHandler(@NotNull MatchingHandler delegate) { + myDelegate = delegate; + } + + public boolean match(PsiElement patternNode, PsiElement matchedNode, final MatchContext matchContext) { + if (patternNode == null || matchedNode == null || matchedNode.getClass() == patternNode.getClass()) { + return myDelegate.match(patternNode, matchedNode, matchContext); + } + + /*if (patternNode != null && matchedNode != null && patternNode.getClass() == matchedNode.getClass()) { + //return myDelegate.match(patternNode, matchedNode, matchContext); + }*/ + PsiElement newPatternNode = skipNodeIfNeccessary(patternNode); + matchedNode = skipNodeIfNeccessary(matchedNode); + + if (newPatternNode != patternNode) { + return matchContext.getPattern().getHandler(newPatternNode).match(newPatternNode, matchedNode, matchContext); + } + + return myDelegate.match(patternNode, matchedNode, matchContext); + } + + @Override + public boolean canMatch(PsiElement patternNode, PsiElement matchedNode) { + return myDelegate.canMatch(patternNode, matchedNode); + } + + @Override + public boolean matchSequentially(final NodeIterator nodes, final NodeIterator nodes2, final MatchContext context) { + return myDelegate.matchSequentially(nodes, nodes2, context); + } + + public boolean match(PsiElement patternNode, + PsiElement matchedNode, + final int start, + final int end, + final MatchContext context) { + if (patternNode == null || matchedNode == null || patternNode.getClass() == matchedNode.getClass()) { + return myDelegate.match(patternNode, matchedNode, start, end, context); + } + + PsiElement newPatternNode = skipNodeIfNeccessary(patternNode); + matchedNode = skipNodeIfNeccessary(matchedNode); + + if (newPatternNode != patternNode) { + return context.getPattern().getHandler(newPatternNode).match(newPatternNode, matchedNode, start, end, context); + } + + return myDelegate.match(patternNode, matchedNode, start, end, context); + } + + protected boolean isMatchSequentiallySucceeded(final NodeIterator nodes2) { + return myDelegate.isMatchSequentiallySucceeded(nodes2); + } + + @Override + public boolean shouldAdvanceTheMatchFor(PsiElement patternElement, PsiElement matchedElement) { + return true; + } + + public MatchingHandler getDelegate() { + return myDelegate; + } + + @Nullable + public static PsiElement getOnlyNonWhitespaceChild(PsiElement element) { + PsiElement onlyChild = null; + for (PsiElement child = element.getFirstChild(); child != null; child = child.getNextSibling()) { + if (DuplocatorUtil.isIgnoredNode(element) || child.getTextLength() == 0) { + continue; + } + if (onlyChild != null) { + return null; + } + onlyChild = child; + } + return onlyChild; + } + + @Nullable + public static PsiElement skipNodeIfNeccessary(PsiElement element) { + return skipNodeIfNeccessary(element, null, null); + } + + @Nullable + public static PsiElement skipNodeIfNeccessary(PsiElement element, EquivalenceDescriptor descriptor, NodeFilter filter) { + return DuplocatorUtil.skipNodeIfNeccessary(element, descriptor, filter != null ? filter : LexicalNodesFilter.getInstance()); + } +} 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; + } +} diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/SymbolHandler.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/SymbolHandler.java new file mode 100644 index 000000000000..4d1f9a852940 --- /dev/null +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/SymbolHandler.java @@ -0,0 +1,21 @@ +package com.intellij.structuralsearch.impl.matcher.handlers; + +import com.intellij.psi.PsiElement; +import com.intellij.structuralsearch.impl.matcher.MatchContext; + +/** + * Search handler for symbol search + */ +public class SymbolHandler extends MatchingHandler { + private final SubstitutionHandler handler; + + public SymbolHandler(SubstitutionHandler handler) { + this.handler = handler; + } + + public boolean match(PsiElement patternNode, PsiElement matchedNode, MatchContext context) { + // there is no need to do filtering since this is delegate of Substituion handler + + return handler.handle(matchedNode,context); + } +} diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/TopLevelMatchingHandler.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/TopLevelMatchingHandler.java new file mode 100644 index 000000000000..6711bcb1fc7f --- /dev/null +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/TopLevelMatchingHandler.java @@ -0,0 +1,85 @@ +package com.intellij.structuralsearch.impl.matcher.handlers; + +import com.intellij.dupLocator.iterators.NodeIterator; +import com.intellij.dupLocator.iterators.SiblingNodeIterator; +import com.intellij.psi.PsiComment; +import com.intellij.psi.PsiElement; +import com.intellij.structuralsearch.StructuralSearchUtil; +import com.intellij.structuralsearch.impl.matcher.MatchContext; +import com.intellij.structuralsearch.impl.matcher.iterators.SsrFilteringNodeIterator; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; + +public final class TopLevelMatchingHandler extends MatchingHandler implements DelegatingHandler { + private final MatchingHandler delegate; + + public TopLevelMatchingHandler(@NotNull MatchingHandler _delegate) { + delegate = _delegate; + setFilter(_delegate.getFilter()); + } + + public boolean match(final PsiElement patternNode, final PsiElement matchedNode, final MatchContext matchContext) { + final boolean matched = delegate.match(patternNode, matchedNode, matchContext); + + if (matched) { + List<PsiElement> matchedNodes = matchContext.getMatchedNodes(); + if (matchedNodes == null) { + matchedNodes = new ArrayList<PsiElement>(); + matchContext.setMatchedNodes(matchedNodes); + } + + PsiElement elementToAdd = matchedNode; + + if (patternNode instanceof PsiComment && StructuralSearchUtil.isDocCommentOwner(matchedNode)) { + // psicomment and psidoccomment are placed inside the psimember next to them so + // simple topdown matching should do additional "dances" to cover this case. + elementToAdd = matchedNode.getFirstChild(); + assert elementToAdd instanceof PsiComment; + } + + matchedNodes.add(elementToAdd); + } + + if ((!matched || matchContext.getOptions().isRecursiveSearch()) && + matchContext.getPattern().getStrategy().continueMatching(matchedNode) && + matchContext.shouldRecursivelyMatch() + ) { + matchContext.getMatcher().matchContext( + new SsrFilteringNodeIterator( + new SiblingNodeIterator(matchedNode.getFirstChild()) + ) + ); + } + return matched; + } + + @Override + public boolean canMatch(PsiElement patternNode, PsiElement matchedNode) { + return delegate.canMatch(patternNode, matchedNode); + } + + @Override + public boolean matchSequentially(final NodeIterator nodes, final NodeIterator nodes2, final MatchContext context) { + return delegate.matchSequentially(nodes, nodes2, context); + } + + public boolean match(final PsiElement patternNode, + final PsiElement matchedNode, final int start, final int end, final MatchContext context) { + return match(patternNode, matchedNode, context); + } + + public boolean isMatchSequentiallySucceeded(final NodeIterator nodes2) { + return true; + } + + @Override + public boolean shouldAdvanceTheMatchFor(final PsiElement patternElement, final PsiElement matchedElement) { + return delegate.shouldAdvanceTheMatchFor(patternElement, matchedElement); + } + + public MatchingHandler getDelegate() { + return delegate; + } +}
\ No newline at end of file diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/TypedSymbolHandler.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/TypedSymbolHandler.java new file mode 100644 index 000000000000..08e335614d5a --- /dev/null +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/TypedSymbolHandler.java @@ -0,0 +1,21 @@ +package com.intellij.structuralsearch.impl.matcher.handlers; + +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiDeclarationStatement; +import com.intellij.structuralsearch.impl.matcher.MatchContext; + +/** + * Search handler for typed symbol ('T<a*>) + */ +public class TypedSymbolHandler extends MatchingHandler { + public boolean match(PsiElement patternNode, PsiElement matchedNode, MatchContext context) { + if (!super.match(patternNode,matchedNode,context)) { + return false; + } + + return context.getMatcher().match( + patternNode.getFirstChild(), + matchedNode + ); + } +} diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/XmlTextHandler.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/XmlTextHandler.java new file mode 100644 index 000000000000..cc71c02ee403 --- /dev/null +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/XmlTextHandler.java @@ -0,0 +1,23 @@ +package com.intellij.structuralsearch.impl.matcher.handlers; + +import com.intellij.dupLocator.iterators.ArrayBackedNodeIterator; +import com.intellij.dupLocator.iterators.NodeIterator; +import com.intellij.psi.PsiElement; +import com.intellij.structuralsearch.impl.matcher.GlobalMatchingVisitor; +import com.intellij.structuralsearch.impl.matcher.MatchContext; +import com.intellij.structuralsearch.impl.matcher.iterators.SsrFilteringNodeIterator; + +/** + * Root of handlers for pattern node matching. Handles simpliest type of the match. + */ +public final class XmlTextHandler extends MatchingHandler { + public boolean matchSequentially(NodeIterator nodes, NodeIterator nodes2, MatchContext context) { + final PsiElement psiElement = nodes.current(); + + return GlobalMatchingVisitor.continueMatchingSequentially( + new SsrFilteringNodeIterator( new ArrayBackedNodeIterator(psiElement.getChildren()) ), + nodes2, + context + ); + } +} diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/iterators/SsrFilteringNodeIterator.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/iterators/SsrFilteringNodeIterator.java new file mode 100644 index 000000000000..a2b14ae83e17 --- /dev/null +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/iterators/SsrFilteringNodeIterator.java @@ -0,0 +1,20 @@ +package com.intellij.structuralsearch.impl.matcher.iterators; + +import com.intellij.dupLocator.iterators.FilteringNodeIterator; +import com.intellij.dupLocator.iterators.NodeIterator; +import com.intellij.dupLocator.iterators.SiblingNodeIterator; +import com.intellij.psi.PsiElement; +import com.intellij.structuralsearch.impl.matcher.filters.LexicalNodesFilter; + +/** + * @author Eugene.Kudelevsky + */ +public class SsrFilteringNodeIterator extends FilteringNodeIterator { + public SsrFilteringNodeIterator(final NodeIterator iterator) { + super(iterator, LexicalNodesFilter.getInstance()); + } + + public SsrFilteringNodeIterator(final PsiElement element) { + this(new SiblingNodeIterator(element)); + } +} diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/AbstractStringBasedPredicate.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/AbstractStringBasedPredicate.java new file mode 100644 index 000000000000..916fc1e15639 --- /dev/null +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/AbstractStringBasedPredicate.java @@ -0,0 +1,22 @@ +package com.intellij.structuralsearch.impl.matcher.predicates; + +import com.intellij.psi.PsiElement; +import com.intellij.structuralsearch.impl.matcher.MatchContext; +import com.intellij.structuralsearch.impl.matcher.handlers.MatchPredicate; + +/** + * @author Maxim.Mossienko + */ +public class AbstractStringBasedPredicate extends MatchPredicate { + protected final String myName; + protected final String myWithin; + + public AbstractStringBasedPredicate(String name, String within) { + myName = name; + myWithin = within; + } + + public boolean match(PsiElement patternNode, PsiElement matchedNode, MatchContext context) { + return match(patternNode, matchedNode, 0, -1, context); + } +}
\ No newline at end of file diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/BinaryPredicate.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/BinaryPredicate.java new file mode 100644 index 000000000000..b7d5432964d4 --- /dev/null +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/BinaryPredicate.java @@ -0,0 +1,38 @@ +package com.intellij.structuralsearch.impl.matcher.predicates; + +import com.intellij.psi.PsiElement; +import com.intellij.structuralsearch.impl.matcher.handlers.MatchPredicate; +import com.intellij.structuralsearch.impl.matcher.MatchContext; + +/** + * Binary predicate + */ +public final class BinaryPredicate extends MatchPredicate { + private final MatchPredicate first; + private final MatchPredicate second; + private final boolean or; + + public BinaryPredicate(MatchPredicate first, MatchPredicate second, boolean or) { + this.first = first; + this.second = second; + this.or = or; + } + + public boolean match(PsiElement patternNode, PsiElement matchedNode, MatchContext context) { + if (or) { + return first.match(patternNode,matchedNode,context) || + second.match(patternNode,matchedNode,context); + } else { + return first.match(patternNode,matchedNode,context) && + second.match(patternNode,matchedNode,context); + } + } + + public MatchPredicate getFirst() { + return first; + } + + public MatchPredicate getSecond() { + return second; + } +} diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/ContainsPredicate.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/ContainsPredicate.java new file mode 100644 index 000000000000..2ed894fe6b64 --- /dev/null +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/ContainsPredicate.java @@ -0,0 +1,18 @@ +package com.intellij.structuralsearch.impl.matcher.predicates; + +import com.intellij.psi.PsiElement; +import com.intellij.structuralsearch.impl.matcher.MatchContext; + +/** + * @author Maxim.Mossienko + */ +public class ContainsPredicate extends AbstractStringBasedPredicate { + + public ContainsPredicate(String name, String within) { + super(name, within); + } + + public boolean match(PsiElement node, PsiElement match, int start, int end, MatchContext context) { + return false; + } +}
\ No newline at end of file diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/NotPredicate.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/NotPredicate.java new file mode 100644 index 000000000000..3d28a635e3af --- /dev/null +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/NotPredicate.java @@ -0,0 +1,24 @@ +package com.intellij.structuralsearch.impl.matcher.predicates; + +import com.intellij.psi.PsiElement; +import com.intellij.structuralsearch.impl.matcher.MatchContext; +import com.intellij.structuralsearch.impl.matcher.handlers.MatchPredicate; + +/** + * Negates predicate + */ +public final class NotPredicate extends MatchPredicate { + private final MatchPredicate handler; + + public NotPredicate(final MatchPredicate _handler) { + handler = _handler; + } + + public boolean match(PsiElement patternNode, PsiElement matchedNode, MatchContext context) { + return !handler.match(patternNode,matchedNode,context); + } + + public MatchPredicate getHandler() { + return handler; + } +} diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/ReferencePredicate.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/ReferencePredicate.java new file mode 100644 index 000000000000..17570ed8f5df --- /dev/null +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/ReferencePredicate.java @@ -0,0 +1,31 @@ +package com.intellij.structuralsearch.impl.matcher.predicates; + +import com.intellij.psi.*; +import com.intellij.structuralsearch.StructuralSearchUtil; +import com.intellij.structuralsearch.impl.matcher.handlers.SubstitutionHandler; +import com.intellij.structuralsearch.impl.matcher.MatchContext; +import com.intellij.structuralsearch.impl.matcher.MatchUtils; + +/** + * Handles finding method + */ +public final class ReferencePredicate extends SubstitutionHandler { + public ReferencePredicate(String _name) { + super(_name, true, 1, 1, true); + } + + public boolean match(PsiElement node, PsiElement match, MatchContext context) { + if (StructuralSearchUtil.isIdentifier(match)) { + // since we pickup tokens + match = match.getParent(); + } + + PsiElement result = MatchUtils.getReferencedElement(match); + if (result == null) { + result = match; + //return false; + } + + return handle(result,context); + } +} diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/RegExpPredicate.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/RegExpPredicate.java new file mode 100644 index 000000000000..f7b8ecedcf3d --- /dev/null +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/RegExpPredicate.java @@ -0,0 +1,175 @@ +package com.intellij.structuralsearch.impl.matcher.predicates; + +import com.intellij.psi.*; +import com.intellij.structuralsearch.MalformedPatternException; +import com.intellij.structuralsearch.SSRBundle; +import com.intellij.structuralsearch.StructuralSearchProfile; +import com.intellij.structuralsearch.StructuralSearchUtil; +import com.intellij.structuralsearch.impl.matcher.MatchContext; +import com.intellij.structuralsearch.impl.matcher.MatchResultImpl; +import com.intellij.structuralsearch.impl.matcher.MatchUtils; +import com.intellij.structuralsearch.impl.matcher.handlers.MatchPredicate; +import com.intellij.structuralsearch.plugin.util.SmartPsiPointer; +import org.jetbrains.annotations.NonNls; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; + +/** + * Root of handlers for pattern node matching. Handles simpliest type of the match. + */ +public final class RegExpPredicate extends MatchPredicate { + private Pattern pattern; + private final String baseHandlerName; + private boolean simpleString; + private final boolean couldBeOptimized; + private final String regexp; + private final boolean caseSensitive; + private boolean multiline; + private final boolean wholeWords; + private final boolean target; + private NodeTextGenerator myNodeTextGenerator; + + public interface NodeTextGenerator { + String getText(PsiElement element); + } + + public RegExpPredicate(final String regexp, final boolean caseSensitive, final String _baseHandlerName, boolean _wholeWords, boolean _target) { + couldBeOptimized = containsRegExp(regexp); + if (!_wholeWords) { + simpleString = couldBeOptimized; + } + + this.regexp = regexp; + this.caseSensitive = caseSensitive; + this.wholeWords = _wholeWords; + baseHandlerName = _baseHandlerName; + + if (!simpleString) { + compilePattern(); + } + target = _target; + } + + private static boolean containsRegExp(final String regexp) { + for(int i=0;i<regexp.length();++i) { + if(MatchUtils.SPECIAL_CHARS.indexOf(regexp.charAt(i))!=-1) { + return false; + } + } + + return true; + } + + private void compilePattern() { + try { + @NonNls String realRegexp = regexp; + if (wholeWords) { + realRegexp = ".*?\\b(?:" + realRegexp + ")\\b.*?"; + } + + pattern = Pattern.compile( + realRegexp, + (caseSensitive ? 0: Pattern.CASE_INSENSITIVE) | (multiline ? Pattern.DOTALL:0) + ); + } catch(PatternSyntaxException ex) { + throw new MalformedPatternException(SSRBundle.message("error.incorrect.regexp.constraint", regexp, baseHandlerName)); + } + } + + public boolean couldBeOptimized() { + return couldBeOptimized; + } + + public String getRegExp() { + return regexp; + } + + /** + * Attempts to match given handler node against given node. + * @param matchedNode for matching + * @param context of the matching + * @return true if matching was successfull and false otherwise + */ + public boolean match(PsiElement node,PsiElement matchedNode, int start, int end, MatchContext context) { + if (matchedNode==null) return false; + String text; + + text = myNodeTextGenerator != null ? myNodeTextGenerator.getText(matchedNode) : getMeaningfulText(matchedNode); + + boolean result = doMatch(text, start, end, context, matchedNode); + + if (!result) { + + if(StructuralSearchUtil.isIdentifier(matchedNode)) { + matchedNode = matchedNode.getParent(); + } + + String alternativeText = context.getPattern().getAlternativeTextToMatch(matchedNode, text); + if (alternativeText != null) { + result = doMatch(alternativeText, start, end, context, matchedNode); + } + } + + return result; + } + + public static String getMeaningfulText(PsiElement matchedNode) { + final StructuralSearchProfile profile = StructuralSearchUtil.getProfileByPsiElement(matchedNode); + return profile != null ? profile.getMeaningfulText(matchedNode) : matchedNode.getText(); + } + + public boolean match(PsiElement patternNode, PsiElement matchedNode, MatchContext context) { + return match(patternNode,matchedNode,0,-1,context); + } + + boolean doMatch(String text, MatchContext context, PsiElement matchedElement) { + return doMatch(text,0,-1,context, matchedElement); + } + + boolean doMatch(String text, int from, int end, MatchContext context,PsiElement matchedElement) { + if (from > 0 || end != -1) { + text = text.substring(from, end == -1 || end >= text.length() ? text.length():end); + } + + if (simpleString) { + return (caseSensitive)?text.equals(regexp):text.equalsIgnoreCase(regexp); + } + + if(!multiline && text.contains("\n")) setMultiline(true); + final Matcher matcher = pattern.matcher(text); + + if (matcher.matches()) { + for (int i=1;i<=matcher.groupCount();++i) { + context.getResult().addSon( + new MatchResultImpl( + baseHandlerName + "_" + i, + matcher.group(i), + new SmartPsiPointer(matchedElement), + matcher.start(i), + matcher.end(i), + target + ) + ); + } + return true; + } else { + return false; + } + } + + + public void setNodeTextGenerator(final NodeTextGenerator nodeTextGenerator) { + myNodeTextGenerator = nodeTextGenerator; + } + + public void setMultiline(boolean b) { + multiline = b; + compilePattern(); + } + + public boolean isWholeWords() { + return wholeWords; + } +} diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/ScriptPredicate.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/ScriptPredicate.java new file mode 100644 index 000000000000..9310eb9e70a0 --- /dev/null +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/ScriptPredicate.java @@ -0,0 +1,28 @@ +package com.intellij.structuralsearch.impl.matcher.predicates; + +import com.intellij.psi.PsiElement; +import com.intellij.structuralsearch.impl.matcher.MatchContext; + +/** + * @author Maxim.Mossienko + */ +public class ScriptPredicate extends AbstractStringBasedPredicate { + private final ScriptSupport scriptSupport; + + public ScriptPredicate(String name, String within) { + super(name, within); + scriptSupport = new ScriptSupport(within, name); + } + + public boolean match(PsiElement node, PsiElement match, int start, int end, MatchContext context) { + if (match == null) return false; + + return Boolean.TRUE.equals( + Boolean.valueOf(scriptSupport.evaluate( + context.hasResult() ? context.getResult() : null, + match + )) + ); + } + +}
\ No newline at end of file diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/ScriptSupport.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/ScriptSupport.java new file mode 100644 index 000000000000..e4136ede2831 --- /dev/null +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/ScriptSupport.java @@ -0,0 +1,91 @@ +package com.intellij.structuralsearch.impl.matcher.predicates; + +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.psi.PsiElement; +import com.intellij.structuralsearch.MatchResult; +import com.intellij.structuralsearch.SSRBundle; +import com.intellij.structuralsearch.StructuralSearchException; +import com.intellij.structuralsearch.StructuralSearchUtil; +import com.intellij.structuralsearch.impl.matcher.MatchResultImpl; +import groovy.lang.Binding; +import groovy.lang.GroovyRuntimeException; +import groovy.lang.GroovyShell; +import groovy.lang.Script; +import org.codehaus.groovy.control.CompilationFailedException; +import org.codehaus.groovy.control.ErrorCollector; +import org.codehaus.groovy.control.MultipleCompilationErrorsException; +import org.codehaus.groovy.control.messages.Message; +import org.codehaus.groovy.control.messages.SyntaxErrorMessage; +import org.codehaus.groovy.syntax.SyntaxException; + +import java.io.File; +import java.io.IOException; +import java.util.List; + +/** + * @author Maxim.Mossienko + * Date: 11.06.2009 + * Time: 16:25:12 + */ +public class ScriptSupport { + private final Script script; + + public ScriptSupport(String text, String name) { + File scriptFile = new File(text); + GroovyShell shell = new GroovyShell(); + try { + script = scriptFile.exists() ? shell.parse(scriptFile):shell.parse(text, name); + } catch (Exception ex) { + Logger.getInstance(getClass().getName()).error(ex); + throw new RuntimeException(ex); + } + } + + public String evaluate(MatchResultImpl result, PsiElement context) { + try { + Binding binding = new Binding(); + + if (result != null) { + for(MatchResult r:result.getMatches()) { + binding.setVariable(r.getName(),r.getMatchRef().getElement()); + } + } + + if (context == null) { + context = result.getMatchRef().getElement(); + } + if (StructuralSearchUtil.isIdentifier(context)) context = context.getParent(); + binding.setVariable("__context__", context); + script.setBinding(binding); + + Object o = script.run(); + return String.valueOf(o); + } catch (GroovyRuntimeException ex) { + throw new StructuralSearchException(SSRBundle.message("groovy.script.error", ex.getMessage())); + } + } + + public static String checkValidScript(String scriptText) { + try { + final File scriptFile = new File(scriptText); + final GroovyShell shell = new GroovyShell(); + final Script script = scriptFile.exists() ? shell.parse(scriptFile) : shell.parse(scriptText); + return null; + } catch (IOException e) { + return e.getMessage(); + } catch (MultipleCompilationErrorsException e) { + final ErrorCollector errorCollector = e.getErrorCollector(); + final List<Message> errors = errorCollector.getErrors(); + for (Message error : errors) { + if (error instanceof SyntaxErrorMessage) { + final SyntaxErrorMessage errorMessage = (SyntaxErrorMessage)error; + final SyntaxException cause = errorMessage.getCause(); + return cause.getMessage(); + } + } + return e.getMessage(); + } catch (CompilationFailedException ex) { + return ex.getLocalizedMessage(); + } + } +} diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/WithinPredicate.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/WithinPredicate.java new file mode 100644 index 000000000000..3904eb397bce --- /dev/null +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/WithinPredicate.java @@ -0,0 +1,38 @@ +package com.intellij.structuralsearch.impl.matcher.predicates; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.psi.PsiElement; +import com.intellij.structuralsearch.MatchOptions; +import com.intellij.structuralsearch.MatchResult; +import com.intellij.structuralsearch.Matcher; +import com.intellij.structuralsearch.impl.matcher.MatchContext; +import com.intellij.structuralsearch.impl.matcher.compiler.PatternCompiler; + +/** + * @author Maxim.Mossienko + */ +public class WithinPredicate extends AbstractStringBasedPredicate { + private final MatchOptions myMatchOptions; + private Matcher matcher; + + public WithinPredicate(String name, String within, Project project) { + super(name, within); + myMatchOptions = new MatchOptions(); + + myMatchOptions.setLooseMatching(true); + final String unquoted = StringUtil.stripQuotesAroundValue(within); + if (!unquoted.equals(within)) { + myMatchOptions.setSearchPattern(unquoted); + PatternCompiler.transformOldPattern(myMatchOptions); + matcher = new Matcher(project, myMatchOptions); + } else { + assert false; + } + } + + public boolean match(PsiElement node, PsiElement match, int start, int end, MatchContext context) { + final MatchResult result = matcher.isMatchedByDownUp(match, myMatchOptions); + return result != null; + } +}
\ No newline at end of file diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/strategies/MatchingStrategy.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/strategies/MatchingStrategy.java new file mode 100644 index 000000000000..8f5b5bc2fc20 --- /dev/null +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/strategies/MatchingStrategy.java @@ -0,0 +1,13 @@ +package com.intellij.structuralsearch.impl.matcher.strategies; + +import com.intellij.psi.PsiElement; + + +/** + * CommonStrategy of metching process + */ +public interface MatchingStrategy { + boolean continueMatching(PsiElement start); + + boolean shouldSkip(PsiElement element, PsiElement elementToMatchWith); +} diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/strategies/XmlMatchingStrategy.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/strategies/XmlMatchingStrategy.java new file mode 100644 index 000000000000..b2ca87a176e8 --- /dev/null +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/strategies/XmlMatchingStrategy.java @@ -0,0 +1,42 @@ +package com.intellij.structuralsearch.impl.matcher.strategies; + +import com.intellij.dupLocator.util.NodeFilter; +import com.intellij.psi.PsiElement; +import com.intellij.psi.XmlElementVisitor; +import com.intellij.psi.xml.XmlTag; + +/** + * Base filtering strategy to find statements + */ +public class XmlMatchingStrategy extends XmlElementVisitor implements MatchingStrategy,NodeFilter { + protected boolean result; + + @Override public void visitXmlTag(final XmlTag element) { + result = true; + } + + public boolean continueMatching(final PsiElement start) { + return accepts(start); + } + + @Override + public boolean shouldSkip(PsiElement element, PsiElement elementToMatchWith) { + return false; + } + + protected XmlMatchingStrategy() {} + + private static class XmlMatchingStrategyHolder { + private static final XmlMatchingStrategy instance = new XmlMatchingStrategy(); + } + + public static MatchingStrategy getInstance() { + return XmlMatchingStrategyHolder.instance; + } + + public boolean accepts(PsiElement element) { + result = false; + if (element!=null) element.accept(this); + return result; + } +} |