diff options
Diffstat (limited to 'platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/PatternCompiler.java')
-rw-r--r-- | platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/PatternCompiler.java | 512 |
1 files changed, 512 insertions, 0 deletions
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 |