1 package com.intellij.structuralsearch.impl.matcher.compiler;
3 import com.intellij.codeInsight.template.Template;
4 import com.intellij.codeInsight.template.TemplateManager;
5 import com.intellij.dupLocator.util.NodeFilter;
6 import com.intellij.lang.Language;
7 import com.intellij.openapi.application.ApplicationManager;
8 import com.intellij.openapi.extensions.Extensions;
9 import com.intellij.openapi.fileTypes.FileType;
10 import com.intellij.openapi.fileTypes.LanguageFileType;
11 import com.intellij.openapi.project.Project;
12 import com.intellij.openapi.util.text.StringUtil;
13 import com.intellij.psi.*;
14 import com.intellij.psi.impl.source.PsiFileImpl;
15 import com.intellij.psi.impl.source.tree.LeafElement;
16 import com.intellij.psi.search.GlobalSearchScope;
17 import com.intellij.psi.search.LocalSearchScope;
18 import com.intellij.psi.util.PsiUtilCore;
19 import com.intellij.structuralsearch.*;
20 import com.intellij.structuralsearch.impl.matcher.CompiledPattern;
21 import com.intellij.structuralsearch.impl.matcher.MatchPredicateProvider;
22 import com.intellij.structuralsearch.impl.matcher.MatcherImplUtil;
23 import com.intellij.structuralsearch.impl.matcher.PatternTreeContext;
24 import com.intellij.structuralsearch.impl.matcher.filters.LexicalNodesFilter;
25 import com.intellij.structuralsearch.impl.matcher.handlers.MatchPredicate;
26 import com.intellij.structuralsearch.impl.matcher.handlers.MatchingHandler;
27 import com.intellij.structuralsearch.impl.matcher.handlers.SubstitutionHandler;
28 import com.intellij.structuralsearch.impl.matcher.predicates.*;
29 import com.intellij.structuralsearch.plugin.ui.Configuration;
30 import com.intellij.util.IncorrectOperationException;
31 import gnu.trove.TIntArrayList;
32 import gnu.trove.TIntHashSet;
33 import org.jetbrains.annotations.NotNull;
34 import org.jetbrains.annotations.Nullable;
37 import java.util.regex.Matcher;
38 import java.util.regex.Pattern;
41 * Compiles the handlers for usability
43 public class PatternCompiler {
44 private static CompileContext lastTestingContext;
46 public static void transformOldPattern(MatchOptions options) {
47 StringToConstraintsTransformer.transformOldPattern(options);
50 public static CompiledPattern compilePattern(final Project project, final MatchOptions options) throws MalformedPatternException,
51 UnsupportedOperationException {
52 FileType fileType = options.getFileType();
53 assert fileType instanceof LanguageFileType;
54 Language language = ((LanguageFileType)fileType).getLanguage();
55 StructuralSearchProfile profile = StructuralSearchUtil.getProfileByLanguage(language);
56 assert profile != null;
57 CompiledPattern result = profile.createCompiledPattern();
59 final String[] prefixes = result.getTypedVarPrefixes();
60 assert prefixes.length > 0;
62 final CompileContext context = new CompileContext();
63 if (ApplicationManager.getApplication().isUnitTestMode()) lastTestingContext = context;
65 /*CompiledPattern result = options.getFileType() == StdFileTypes.JAVA ?
66 new JavaCompiledPattern() :
67 new XmlCompiledPattern();*/
70 context.init(result, options, project, options.getScope() instanceof GlobalSearchScope);
72 List<PsiElement> elements = compileByAllPrefixes(project, options, result, context, prefixes);
74 final CompiledPattern pattern = context.getPattern();
75 checkForUnknownVariables(pattern, elements);
76 pattern.setNodes(elements);
78 if (context.getSearchHelper().doOptimizing() && context.getSearchHelper().isScannedSomething()) {
79 final Set<PsiFile> set = context.getSearchHelper().getFilesSetToScan();
80 final List<PsiFile> filesToScan = new ArrayList<PsiFile>(set.size());
81 final GlobalSearchScope scope = (GlobalSearchScope)options.getScope();
83 for (final PsiFile file : set) {
84 if (!scope.contains(file.getVirtualFile())) {
88 if (file instanceof PsiFileImpl) {
89 ((PsiFileImpl)file).clearCaches();
91 filesToScan.add(file);
94 if (filesToScan.size() == 0) {
95 throw new MalformedPatternException(SSRBundle.message("ssr.will.not.find.anything"));
98 new LocalSearchScope(PsiUtilCore.toPsiElementArray(filesToScan))
108 private static void checkForUnknownVariables(final CompiledPattern pattern, List<PsiElement> elements) {
109 for (PsiElement element : elements) {
110 element.accept(new PsiRecursiveElementWalkingVisitor() {
112 public void visitElement(PsiElement element) {
113 if (element.getUserData(CompiledPattern.HANDLER_KEY) != null) {
116 super.visitElement(element);
118 if (!(element instanceof LeafElement) || !pattern.isTypedVar(element)) {
121 final MatchingHandler handler = pattern.getHandler(pattern.getTypedVarString(element));
122 if (handler == null) {
123 throw new MalformedPatternException();
130 public static String getLastFindPlan() {
131 return ((TestModeOptimizingSearchHelper)lastTestingContext.getSearchHelper()).getSearchPlan();
135 private static List<PsiElement> compileByAllPrefixes(Project project,
136 MatchOptions options,
137 CompiledPattern pattern,
138 CompileContext context,
139 String[] applicablePrefixes) {
140 if (applicablePrefixes.length == 0) {
141 return Collections.emptyList();
144 List<PsiElement> elements = doCompile(project, options, pattern, new ConstantPrefixProvider(applicablePrefixes[0]), context);
145 if (elements.isEmpty()) {
149 final PsiFile file = elements.get(0).getContainingFile();
154 final PsiElement last = elements.get(elements.size() - 1);
155 final Pattern[] patterns = new Pattern[applicablePrefixes.length];
157 for (int i = 0; i < applicablePrefixes.length; i++) {
158 String s = StructuralSearchUtil.shieldSpecialChars(applicablePrefixes[i]);
159 patterns[i] = Pattern.compile(s + "\\w+\\b");
162 final int[] varEndOffsets = findAllTypedVarOffsets(file, patterns);
164 final int patternEndOffset = last.getTextRange().getEndOffset();
165 if (elements.size() == 0 ||
166 checkErrorElements(file, patternEndOffset, patternEndOffset, varEndOffsets, true) != Boolean.TRUE) {
170 final int varCount = varEndOffsets.length;
171 final String[] prefixSequence = new String[varCount];
173 for (int i = 0; i < varCount; i++) {
174 prefixSequence[i] = applicablePrefixes[0];
177 final List<PsiElement> finalElements =
178 compileByPrefixes(project, options, pattern, context, applicablePrefixes, patterns, prefixSequence, 0);
179 return finalElements != null
181 : doCompile(project, options, pattern, new ConstantPrefixProvider(applicablePrefixes[0]), context);
185 private static List<PsiElement> compileByPrefixes(Project project,
186 MatchOptions options,
187 CompiledPattern pattern,
188 CompileContext context,
189 String[] applicablePrefixes,
190 Pattern[] substitutionPatterns,
191 String[] prefixSequence,
193 if (index >= prefixSequence.length) {
194 final List<PsiElement> elements = doCompile(project, options, pattern, new ArrayPrefixProvider(prefixSequence), context);
195 if (elements.isEmpty()) {
199 final PsiElement parent = elements.get(0).getParent();
200 final PsiElement last = elements.get(elements.size() - 1);
201 final int[] varEndOffsets = findAllTypedVarOffsets(parent.getContainingFile(), substitutionPatterns);
202 final int patternEndOffset = last.getTextRange().getEndOffset();
203 return checkErrorElements(parent, patternEndOffset, patternEndOffset, varEndOffsets, false) != Boolean.TRUE
208 String[] alternativeVariant = null;
210 for (String applicablePrefix : applicablePrefixes) {
211 prefixSequence[index] = applicablePrefix;
213 List<PsiElement> elements = doCompile(project, options, pattern, new ArrayPrefixProvider(prefixSequence), context);
214 if (elements.isEmpty()) {
218 final PsiFile file = elements.get(0).getContainingFile();
223 final int[] varEndOffsets = findAllTypedVarOffsets(file, substitutionPatterns);
224 final int offset = varEndOffsets[index];
226 final int patternEndOffset = elements.get(elements.size() - 1).getTextRange().getEndOffset();
227 final Boolean result = checkErrorElements(file, offset, patternEndOffset, varEndOffsets, false);
229 if (result == Boolean.TRUE) {
233 if (result == Boolean.FALSE || (result == null && alternativeVariant == null)) {
234 final List<PsiElement> finalElements =
235 compileByPrefixes(project, options, pattern, context, applicablePrefixes, substitutionPatterns, prefixSequence, index + 1);
236 if (finalElements != null) {
237 if (result == Boolean.FALSE) {
238 return finalElements;
240 alternativeVariant = new String[prefixSequence.length];
241 System.arraycopy(prefixSequence, 0, alternativeVariant, 0, prefixSequence.length);
246 return alternativeVariant != null ?
247 compileByPrefixes(project, options, pattern, context, applicablePrefixes, substitutionPatterns, alternativeVariant, index + 1) :
252 private static int[] findAllTypedVarOffsets(final PsiFile file, final Pattern[] substitutionPatterns) {
253 final TIntHashSet result = new TIntHashSet();
255 file.accept(new PsiRecursiveElementWalkingVisitor() {
257 public void visitElement(PsiElement element) {
258 super.visitElement(element);
260 if (element instanceof LeafElement) {
261 final String text = element.getText();
263 for (Pattern pattern : substitutionPatterns) {
264 final Matcher matcher = pattern.matcher(text);
266 while (matcher.find()) {
267 result.add(element.getTextRange().getStartOffset() + matcher.end());
274 final int[] resultArray = result.toArray();
275 Arrays.sort(resultArray);
281 * False: there are no error elements before offset, except patternEndOffset
282 * Null: there are only error elements located exactly after template variables or at the end of the pattern
286 private static Boolean checkErrorElements(PsiElement element,
288 final int patternEndOffset,
289 final int[] varEndOffsets,
290 final boolean strict) {
291 final TIntArrayList errorOffsets = new TIntArrayList();
292 final boolean[] containsErrorTail = {false};
293 final TIntHashSet varEndOffsetsSet = new TIntHashSet(varEndOffsets);
295 element.accept(new PsiRecursiveElementWalkingVisitor() {
297 public void visitErrorElement(PsiErrorElement element) {
298 super.visitErrorElement(element);
300 final int startOffset = element.getTextRange().getStartOffset();
302 if ((strict || !varEndOffsetsSet.contains(startOffset)) && startOffset != patternEndOffset) {
303 errorOffsets.add(startOffset);
306 if (startOffset == offset) {
307 containsErrorTail[0] = true;
312 for (int i = 0; i < errorOffsets.size(); i++) {
313 final int errorOffset = errorOffsets.get(i);
314 if (errorOffset <= offset) {
318 return containsErrorTail[0] ? null : false;
321 private interface PrefixProvider {
322 String getPrefix(int varIndex);
325 private static class ConstantPrefixProvider implements PrefixProvider {
326 private final String myPrefix;
328 private ConstantPrefixProvider(String prefix) {
333 public String getPrefix(int varIndex) {
338 private static class ArrayPrefixProvider implements PrefixProvider {
339 private final String[] myPrefixes;
341 private ArrayPrefixProvider(String[] prefixes) {
342 myPrefixes = prefixes;
346 public String getPrefix(int varIndex) {
347 if (varIndex >= myPrefixes.length) return null;
348 return myPrefixes[varIndex];
352 private static List<PsiElement> doCompile(Project project,
353 MatchOptions options,
354 CompiledPattern result,
355 PrefixProvider prefixProvider,
356 CompileContext context) {
357 result.clearHandlers();
358 context.init(result, options, project, options.getScope() instanceof GlobalSearchScope);
360 final StringBuilder buf = new StringBuilder();
362 Template template = TemplateManager.getInstance(project).createTemplate("","",options.getSearchPattern());
364 int segmentsCount = template.getSegmentsCount();
365 String text = template.getTemplateText();
369 for(int i=0;i<segmentsCount;++i) {
370 final int offset = template.getSegmentOffset(i);
371 final String name = template.getSegmentName(i);
373 final String prefix = prefixProvider.getPrefix(i);
374 if (prefix == null) {
375 throw new MalformedPatternException();
378 buf.append(text.substring(prevOffset,offset));
382 MatchVariableConstraint constraint = options.getVariableConstraint(name);
383 if (constraint==null) {
384 // we do not edited the constraints
385 constraint = new MatchVariableConstraint();
386 constraint.setName( name );
387 options.addVariableConstraint(constraint);
390 SubstitutionHandler handler = result.createSubstitutionHandler(
393 constraint.isPartOfSearchResults(),
394 constraint.getMinCount(),
395 constraint.getMaxCount(),
396 constraint.isGreedy()
399 if(constraint.isWithinHierarchy()) {
400 handler.setSubtype(true);
403 if(constraint.isStrictlyWithinHierarchy()) {
404 handler.setStrictSubtype(true);
407 MatchPredicate predicate;
409 if (!StringUtil.isEmptyOrSpaces(constraint.getRegExp())) {
410 predicate = new RegExpPredicate(
411 constraint.getRegExp(),
412 options.isCaseSensitiveMatch(),
414 constraint.isWholeWordsOnly(),
415 constraint.isPartOfSearchResults()
417 if (constraint.isInvertRegExp()) {
418 predicate = new NotPredicate(predicate);
420 addPredicate(handler,predicate);
423 if (constraint.isReference()) {
424 predicate = new ReferencePredicate( constraint.getNameOfReferenceVar() );
426 if (constraint.isInvertReference()) {
427 predicate = new NotPredicate(predicate);
429 addPredicate(handler,predicate);
432 Set<MatchPredicate> predicates = new LinkedHashSet<MatchPredicate>();
433 for (MatchPredicateProvider matchPredicateProvider : Extensions.getExtensions(MatchPredicateProvider.EP_NAME)) {
434 matchPredicateProvider.collectPredicates(constraint, name, options, predicates);
436 for (MatchPredicate matchPredicate : predicates) {
437 addPredicate(handler, matchPredicate);
440 addScriptConstraint(project, name, constraint, handler);
442 if (!StringUtil.isEmptyOrSpaces(constraint.getContainsConstraint())) {
443 predicate = new ContainsPredicate(name, constraint.getContainsConstraint());
444 if (constraint.isInvertContainsConstraint()) {
445 predicate = new NotPredicate(predicate);
447 addPredicate(handler,predicate);
450 if (!StringUtil.isEmptyOrSpaces(constraint.getWithinConstraint())) {
457 MatchVariableConstraint constraint = options.getVariableConstraint(Configuration.CONTEXT_VAR_NAME);
458 if (constraint != null) {
459 SubstitutionHandler handler = result.createSubstitutionHandler(
460 Configuration.CONTEXT_VAR_NAME,
461 Configuration.CONTEXT_VAR_NAME,
462 constraint.isPartOfSearchResults(),
463 constraint.getMinCount(),
464 constraint.getMaxCount(),
465 constraint.isGreedy()
468 if (!StringUtil.isEmptyOrSpaces(constraint.getWithinConstraint())) {
469 MatchPredicate predicate =
470 new WithinPredicate(Configuration.CONTEXT_VAR_NAME, constraint.getWithinConstraint(), options.getFileType(), project);
471 if (constraint.isInvertWithinConstraint()) {
472 predicate = new NotPredicate(predicate);
474 addPredicate(handler,predicate);
477 addScriptConstraint(project, Configuration.CONTEXT_VAR_NAME, constraint, handler);
480 buf.append(text.substring(prevOffset,text.length()));
482 PsiElement[] matchStatements;
485 matchStatements = MatcherImplUtil.createTreeFromText(buf.toString(), PatternTreeContext.Block, options.getFileType(),
486 options.getDialect(), options.getPatternContext(), project, false);
487 if (matchStatements.length==0) throw new MalformedPatternException();
488 } catch (IncorrectOperationException e) {
489 throw new MalformedPatternException(e.getMessage());
492 NodeFilter filter = LexicalNodesFilter.getInstance();
494 GlobalCompilingVisitor compilingVisitor = new GlobalCompilingVisitor();
495 compilingVisitor.compile(matchStatements,context);
496 ArrayList<PsiElement> elements = new ArrayList<PsiElement>();
498 for (PsiElement matchStatement : matchStatements) {
499 if (!filter.accepts(matchStatement)) {
500 elements.add(matchStatement);
504 new DeleteNodesAction(compilingVisitor.getLexicalNodes()).run();
508 private static void addScriptConstraint(Project project, String name, MatchVariableConstraint constraint, SubstitutionHandler handler) {
509 if (constraint.getScriptCodeConstraint()!= null && constraint.getScriptCodeConstraint().length() > 2) {
510 final String script = StringUtil.stripQuotesAroundValue(constraint.getScriptCodeConstraint());
511 final String s = ScriptSupport.checkValidScript(script);
512 if (s != null) throw new MalformedPatternException("Script constraint for " + constraint.getName() + " has problem "+s);
513 MatchPredicate predicate = new ScriptPredicate(project, name, script);
514 addPredicate(handler,predicate);
518 static void addPredicate(SubstitutionHandler handler, MatchPredicate predicate) {
519 if (handler.getPredicate()==null) {
520 handler.setPredicate(predicate);
522 handler.setPredicate(new BinaryPredicate(handler.getPredicate(), predicate, false));