2 * Copyright 2000-2015 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package com.intellij.psi.impl.search;
19 import com.intellij.concurrency.AsyncFuture;
20 import com.intellij.concurrency.AsyncUtil;
21 import com.intellij.concurrency.JobLauncher;
22 import com.intellij.openapi.application.ApplicationManager;
23 import com.intellij.openapi.application.ex.ApplicationEx;
24 import com.intellij.openapi.application.ex.ApplicationUtil;
25 import com.intellij.openapi.diagnostic.Logger;
26 import com.intellij.openapi.fileEditor.FileDocumentManager;
27 import com.intellij.openapi.progress.EmptyProgressIndicator;
28 import com.intellij.openapi.progress.ProcessCanceledException;
29 import com.intellij.openapi.progress.ProgressIndicator;
30 import com.intellij.openapi.progress.ProgressIndicatorProvider;
31 import com.intellij.openapi.progress.util.TooManyUsagesStatus;
32 import com.intellij.openapi.project.DumbService;
33 import com.intellij.openapi.project.Project;
34 import com.intellij.openapi.roots.FileIndexFacade;
35 import com.intellij.openapi.util.*;
36 import com.intellij.openapi.util.text.StringUtil;
37 import com.intellij.openapi.vfs.VirtualFile;
38 import com.intellij.psi.*;
39 import com.intellij.psi.impl.PsiManagerEx;
40 import com.intellij.psi.impl.cache.CacheManager;
41 import com.intellij.psi.impl.cache.impl.id.IdIndex;
42 import com.intellij.psi.impl.cache.impl.id.IdIndexEntry;
43 import com.intellij.psi.search.*;
44 import com.intellij.psi.util.PsiUtilCore;
45 import com.intellij.usageView.UsageInfo;
46 import com.intellij.usageView.UsageInfoFactory;
47 import com.intellij.util.CommonProcessors;
48 import com.intellij.util.Function;
49 import com.intellij.util.Processor;
50 import com.intellij.util.SmartList;
51 import com.intellij.util.codeInsight.CommentUtilCore;
52 import com.intellij.util.containers.ContainerUtil;
53 import com.intellij.util.containers.MultiMap;
54 import com.intellij.util.indexing.FileBasedIndex;
55 import com.intellij.util.text.StringSearcher;
56 import gnu.trove.THashMap;
57 import gnu.trove.THashSet;
58 import gnu.trove.TIntProcedure;
59 import org.jetbrains.annotations.NotNull;
60 import org.jetbrains.annotations.Nullable;
62 import java.io.IOException;
64 import java.util.concurrent.atomic.AtomicBoolean;
65 import java.util.concurrent.atomic.AtomicInteger;
67 public class PsiSearchHelperImpl implements PsiSearchHelper {
68 private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.search.PsiSearchHelperImpl");
69 private final PsiManagerEx myManager;
72 PROCESS_INJECTED_PSI, CASE_SENSITIVE_SEARCH, PROCESS_ONLY_JAVA_IDENTIFIERS_IF_POSSIBLE
77 public SearchScope getUseScope(@NotNull PsiElement element) {
78 SearchScope scope = element.getUseScope();
79 for (UseScopeEnlarger enlarger : UseScopeEnlarger.EP_NAME.getExtensions()) {
80 final SearchScope additionalScope = enlarger.getAdditionalUseScope(element);
81 if (additionalScope != null) {
82 scope = scope.union(additionalScope);
85 for (UseScopeOptimizer optimizer : UseScopeOptimizer.EP_NAME.getExtensions()) {
86 final GlobalSearchScope scopeToExclude = optimizer.getScopeToExclude(element);
87 if (scopeToExclude != null) {
88 scope = scope.intersectWith(GlobalSearchScope.notScope(scopeToExclude));
94 public PsiSearchHelperImpl(@NotNull PsiManagerEx manager) {
100 public PsiElement[] findCommentsContainingIdentifier(@NotNull String identifier, @NotNull SearchScope searchScope) {
101 final List<PsiElement> results = Collections.synchronizedList(new ArrayList<PsiElement>());
102 processCommentsContainingIdentifier(identifier, searchScope, new CommonProcessors.CollectProcessor<PsiElement>(results));
103 return PsiUtilCore.toPsiElementArray(results);
107 public boolean processCommentsContainingIdentifier(@NotNull String identifier,
108 @NotNull SearchScope searchScope,
109 @NotNull final Processor<PsiElement> processor) {
110 TextOccurenceProcessor occurrenceProcessor = new TextOccurenceProcessor() {
112 public boolean execute(@NotNull PsiElement element, int offsetInElement) {
113 if (CommentUtilCore.isCommentTextElement(element)) {
114 if (element.findReferenceAt(offsetInElement) == null) {
115 return processor.process(element);
121 return processElementsWithWord(occurrenceProcessor, searchScope, identifier, UsageSearchContext.IN_COMMENTS, true);
125 public boolean processElementsWithWord(@NotNull TextOccurenceProcessor processor,
126 @NotNull SearchScope searchScope,
127 @NotNull String text,
129 boolean caseSensitive) {
130 return processElementsWithWord(processor, searchScope, text, searchContext, caseSensitive, shouldProcessInjectedPsi(searchScope));
134 public boolean processElementsWithWord(@NotNull TextOccurenceProcessor processor,
135 @NotNull SearchScope searchScope,
136 @NotNull String text,
138 boolean caseSensitive,
139 boolean processInjectedPsi) {
140 final EnumSet<Options> options = EnumSet.of(Options.PROCESS_ONLY_JAVA_IDENTIFIERS_IF_POSSIBLE);
141 if (caseSensitive) options.add(Options.CASE_SENSITIVE_SEARCH);
142 if (processInjectedPsi) options.add(Options.PROCESS_INJECTED_PSI);
144 return processElementsWithWord(processor, searchScope, text, searchContext, options, null);
149 public AsyncFuture<Boolean> processElementsWithWordAsync(@NotNull final TextOccurenceProcessor processor,
150 @NotNull SearchScope searchScope,
151 @NotNull final String text,
152 final short searchContext,
153 final boolean caseSensitively) {
154 boolean result = processElementsWithWord(processor, searchScope, text, searchContext, caseSensitively,
155 shouldProcessInjectedPsi(searchScope));
156 return AsyncUtil.wrapBoolean(result);
159 public boolean processElementsWithWord(@NotNull final TextOccurenceProcessor processor,
160 @NotNull SearchScope searchScope,
161 @NotNull final String text,
162 final short searchContext,
163 @NotNull EnumSet<Options> options,
164 @Nullable String containerName) {
165 if (text.isEmpty()) {
166 throw new IllegalArgumentException("Cannot search for elements with empty text");
168 ProgressIndicator progress = getOrCreateIndicator();
169 if (searchScope instanceof GlobalSearchScope) {
170 StringSearcher searcher = new StringSearcher(text, options.contains(Options.CASE_SENSITIVE_SEARCH), true,
171 searchContext == UsageSearchContext.IN_STRINGS,
172 options.contains(Options.PROCESS_ONLY_JAVA_IDENTIFIERS_IF_POSSIBLE));
174 return processElementsWithTextInGlobalScope(processor,
175 (GlobalSearchScope)searchScope,
177 searchContext, options.contains(Options.CASE_SENSITIVE_SEARCH), containerName, progress,
178 options.contains(Options.PROCESS_INJECTED_PSI));
180 LocalSearchScope scope = (LocalSearchScope)searchScope;
181 PsiElement[] scopeElements = scope.getScope();
182 final StringSearcher searcher = new StringSearcher(text, options.contains(Options.CASE_SENSITIVE_SEARCH), true,
183 searchContext == UsageSearchContext.IN_STRINGS,
184 options.contains(Options.PROCESS_ONLY_JAVA_IDENTIFIERS_IF_POSSIBLE));
185 Processor<PsiElement> localProcessor = localProcessor(processor, progress, options.contains(Options.PROCESS_INJECTED_PSI), searcher);
186 return JobLauncher.getInstance().invokeConcurrentlyUnderProgress(Arrays.asList(scopeElements), progress, true, true, localProcessor);
190 private static ProgressIndicator getOrCreateIndicator() {
191 ProgressIndicator progress = ProgressIndicatorProvider.getGlobalProgressIndicator();
192 if (progress == null) progress = new EmptyProgressIndicator();
196 public static boolean shouldProcessInjectedPsi(@NotNull SearchScope scope) {
197 return !(scope instanceof LocalSearchScope) || !((LocalSearchScope)scope).isIgnoreInjectedPsi();
201 private static Processor<PsiElement> localProcessor(@NotNull final TextOccurenceProcessor processor,
202 @NotNull final ProgressIndicator progress,
203 final boolean processInjectedPsi,
204 @NotNull final StringSearcher searcher) {
205 return new Processor<PsiElement>() {
207 public boolean process(final PsiElement scopeElement) {
208 return ApplicationManager.getApplication().runReadAction(new Computable<Boolean>() {
210 public Boolean compute() {
211 return LowLevelSearchUtil.processElementsContainingWordInElement(processor, scopeElement, searcher, processInjectedPsi, progress);
217 public String toString() {
218 return processor.toString();
223 private boolean processElementsWithTextInGlobalScope(@NotNull final TextOccurenceProcessor processor,
224 @NotNull final GlobalSearchScope scope,
225 @NotNull final StringSearcher searcher,
226 final short searchContext,
227 final boolean caseSensitively,
228 @Nullable String containerName,
229 @NotNull ProgressIndicator progress,
230 final boolean processInjectedPsi) {
231 if (Thread.holdsLock(PsiLock.LOCK)) {
232 throw new AssertionError("You must not run search from within updating PSI activity. Please consider invokeLatering it instead.");
234 progress.pushState();
235 progress.setText(PsiBundle.message("psi.scanning.files.progress"));
237 String text = searcher.getPattern();
238 Set<VirtualFile> fileSet = new THashSet<VirtualFile>();
239 getFilesWithText(scope, searchContext, caseSensitively, text, progress, fileSet);
241 progress.setText(PsiBundle.message("psi.search.for.word.progress", text));
243 final Processor<PsiElement> localProcessor = localProcessor(processor, progress, processInjectedPsi, searcher);
244 if (containerName != null) {
245 List<VirtualFile> intersectionWithContainerFiles = new ArrayList<VirtualFile>();
246 // intersectionWithContainerFiles holds files containing words from both `text` and `containerName`
247 getFilesWithText(scope, searchContext, caseSensitively, text+" "+containerName, progress, intersectionWithContainerFiles);
248 if (!intersectionWithContainerFiles.isEmpty()) {
249 int totalSize = fileSet.size();
250 boolean result = processPsiFileRoots(intersectionWithContainerFiles, totalSize, 0, progress,
254 fileSet.removeAll(intersectionWithContainerFiles);
255 if (!fileSet.isEmpty()) {
256 result = processPsiFileRoots(new ArrayList<VirtualFile>(fileSet), totalSize, intersectionWithContainerFiles.size(), progress, localProcessor);
264 boolean result = fileSet.isEmpty() || processPsiFileRoots(new ArrayList<VirtualFile>(fileSet), fileSet.size(), 0, progress, localProcessor);
270 * @param files to scan for references in this pass.
271 * @param totalSize the number of files to scan in both passes. Can be different from <code>files.size()</code> in case of
272 * two-pass scan, where we first scan files containing container name and then all the rest files.
273 * @param alreadyProcessedFiles the number of files scanned in previous pass.
274 * @return true if completed
276 private boolean processPsiFileRoots(@NotNull List<VirtualFile> files,
278 int alreadyProcessedFiles,
279 @NotNull final ProgressIndicator progress,
280 @NotNull final Processor<? super PsiFile> localProcessor) {
281 myManager.startBatchFilesProcessingMode();
283 final AtomicInteger counter = new AtomicInteger(alreadyProcessedFiles);
284 final AtomicBoolean canceled = new AtomicBoolean(false);
286 boolean completed = true;
288 List<VirtualFile> failedList = new SmartList<VirtualFile>();
289 final List<VirtualFile> failedFiles = Collections.synchronizedList(failedList);
290 final Processor<VirtualFile> processor = new Processor<VirtualFile>() {
292 public boolean process(final VirtualFile vfile) {
294 TooManyUsagesStatus.getFrom(progress).pauseProcessingIfTooManyUsages();
295 processVirtualFile(vfile, progress, localProcessor, canceled, counter, totalSize);
297 catch (ApplicationUtil.CannotRunReadActionException action) {
298 failedFiles.add(vfile);
300 return !canceled.get();
303 if (ApplicationManager.getApplication().isWriteAccessAllowed() || ((ApplicationEx)ApplicationManager.getApplication()).isWriteActionPending()) {
304 // no point in processing in separate threads - they are doomed to fail to obtain read action anyway
305 completed &= ContainerUtil.process(files, processor);
308 completed &= JobLauncher.getInstance().invokeConcurrentlyUnderProgress(files, progress, false, false, processor);
311 if (failedFiles.isEmpty()) {
314 // we failed to run read action in job launcher thread
315 // run read action in our thread instead to wait for a write action to complete and resume parallel processing
316 DumbService.getInstance(myManager.getProject()).runReadActionInSmartMode(EmptyRunnable.getInstance());
322 myManager.finishBatchFilesProcessingMode();
326 private void processVirtualFile(@NotNull final VirtualFile vfile,
327 @NotNull final ProgressIndicator progress,
328 @NotNull final Processor<? super PsiFile> localProcessor,
329 @NotNull final AtomicBoolean canceled,
330 @NotNull AtomicInteger counter,
331 int totalSize) throws ApplicationUtil.CannotRunReadActionException {
332 final PsiFile file = ApplicationUtil.tryRunReadAction(new Computable<PsiFile>() {
334 public PsiFile compute() {
335 return vfile.isValid() ? myManager.findFile(vfile) : null;
338 if (file != null && !(file instanceof PsiBinaryFile)) {
339 // load contents outside read action
340 if (FileDocumentManager.getInstance().getCachedDocument(vfile) == null) {
341 // cache bytes in vfs
343 vfile.contentsToByteArray();
345 catch (IOException ignored) {
348 ApplicationUtil.tryRunReadAction(new Computable<Void>() {
350 public Void compute() {
351 final Project project = myManager.getProject();
352 if (project.isDisposed()) throw new ProcessCanceledException();
353 if (DumbService.isDumb(project)) throw new ApplicationUtil.CannotRunReadActionException();
355 List<PsiFile> psiRoots = file.getViewProvider().getAllFiles();
356 Set<PsiFile> processed = new THashSet<PsiFile>(psiRoots.size() * 2, (float)0.5);
357 for (final PsiFile psiRoot : psiRoots) {
358 progress.checkCanceled();
359 assert psiRoot != null : "One of the roots of file " + file + " is null. All roots: " + psiRoots + "; ViewProvider: " +
360 file.getViewProvider() + "; Virtual file: " + file.getViewProvider().getVirtualFile();
361 if (!processed.add(psiRoot)) continue;
362 if (!psiRoot.isValid()) {
366 if (!localProcessor.process(psiRoot)) {
375 if (progress.isRunning()) {
376 double fraction = (double)counter.incrementAndGet() / totalSize;
377 progress.setFraction(fraction);
381 private void getFilesWithText(@NotNull GlobalSearchScope scope,
382 final short searchContext,
383 final boolean caseSensitively,
384 @NotNull String text,
385 @NotNull final ProgressIndicator progress,
386 @NotNull Collection<VirtualFile> result) {
387 myManager.startBatchFilesProcessingMode();
389 Processor<VirtualFile> processor = new CommonProcessors.CollectProcessor<VirtualFile>(result){
391 public boolean process(VirtualFile file) {
392 progress.checkCanceled();
393 return super.process(file);
396 boolean success = processFilesWithText(scope, searchContext, caseSensitively, text, processor);
397 // success == false means exception in index
400 myManager.finishBatchFilesProcessingMode();
404 public boolean processFilesWithText(@NotNull final GlobalSearchScope scope,
405 final short searchContext,
406 final boolean caseSensitively,
407 @NotNull String text,
408 @NotNull final Processor<VirtualFile> processor) {
409 List<IdIndexEntry> entries = getWordEntries(text, caseSensitively);
410 if (entries.isEmpty()) return true;
412 Condition<Integer> contextMatches = new Condition<Integer>() {
414 public boolean value(Integer integer) {
415 return (integer.intValue() & searchContext) != 0;
418 return processFilesContainingAllKeys(myManager.getProject(), scope, contextMatches, entries, processor);
423 public PsiFile[] findFilesWithPlainTextWords(@NotNull String word) {
424 return CacheManager.SERVICE.getInstance(myManager.getProject()).getFilesWithWord(word, UsageSearchContext.IN_PLAIN_TEXT,
425 GlobalSearchScope.projectScope(myManager.getProject()),
431 public boolean processUsagesInNonJavaFiles(@NotNull String qName,
432 @NotNull PsiNonJavaFileReferenceProcessor processor,
433 @NotNull GlobalSearchScope searchScope) {
434 return processUsagesInNonJavaFiles(null, qName, processor, searchScope);
438 public boolean processUsagesInNonJavaFiles(@Nullable final PsiElement originalElement,
439 @NotNull String qName,
440 @NotNull final PsiNonJavaFileReferenceProcessor processor,
441 @NotNull final GlobalSearchScope initialScope) {
442 if (qName.isEmpty()) {
443 throw new IllegalArgumentException("Cannot search for elements with empty text. Element: "+originalElement+ "; "+(originalElement == null ? null : originalElement.getClass()));
445 final ProgressIndicator progress = getOrCreateIndicator();
447 int dotIndex = qName.lastIndexOf('.');
448 int dollarIndex = qName.lastIndexOf('$');
449 int maxIndex = Math.max(dotIndex, dollarIndex);
450 final String wordToSearch = maxIndex >= 0 ? qName.substring(maxIndex + 1) : qName;
451 final GlobalSearchScope theSearchScope = ApplicationManager.getApplication().runReadAction(new Computable<GlobalSearchScope>() {
453 public GlobalSearchScope compute() {
454 if (originalElement != null && myManager.isInProject(originalElement) && initialScope.isSearchInLibraries()) {
455 return initialScope.intersectWith(GlobalSearchScope.projectScope(myManager.getProject()));
460 PsiFile[] files = ApplicationManager.getApplication().runReadAction(new Computable<PsiFile[]>() {
462 public PsiFile[] compute() {
463 return CacheManager.SERVICE.getInstance(myManager.getProject()).getFilesWithWord(wordToSearch, UsageSearchContext.IN_PLAIN_TEXT, theSearchScope, true);
467 final StringSearcher searcher = new StringSearcher(qName, true, true, false);
468 final int patternLength = searcher.getPattern().length();
470 progress.pushState();
471 progress.setText(PsiBundle.message("psi.search.in.non.java.files.progress"));
473 final SearchScope useScope = originalElement == null ? null : ApplicationManager.getApplication().runReadAction(new Computable<SearchScope>() {
475 public SearchScope compute() {
476 return getUseScope(originalElement);
480 final Ref<Boolean> cancelled = new Ref<Boolean>(Boolean.FALSE);
481 for (int i = 0; i < files.length; i++) {
482 progress.checkCanceled();
483 final PsiFile psiFile = files[i];
484 if (psiFile instanceof PsiBinaryFile) continue;
486 final CharSequence text = ApplicationManager.getApplication().runReadAction(new Computable<CharSequence>() {
488 public CharSequence compute() {
489 return psiFile.getViewProvider().getContents();
493 LowLevelSearchUtil.processTextOccurrences(text, 0, text.length(), searcher, progress, new TIntProcedure() {
495 public boolean execute(final int index) {
496 boolean isReferenceOK = ApplicationManager.getApplication().runReadAction(new Computable<Boolean>() {
498 public Boolean compute() {
499 PsiReference referenceAt = psiFile.findReferenceAt(index);
500 return referenceAt == null || useScope == null ||
501 !PsiSearchScopeUtil.isInScope(useScope.intersectWith(initialScope), psiFile);
504 if (isReferenceOK && !processor.process(psiFile, index, index + patternLength)) {
505 cancelled.set(Boolean.TRUE);
512 if (cancelled.get()) break;
513 progress.setFraction((double)(i + 1) / files.length);
517 return !cancelled.get();
521 public boolean processAllFilesWithWord(@NotNull String word,
522 @NotNull GlobalSearchScope scope,
523 @NotNull Processor<PsiFile> processor,
524 final boolean caseSensitively) {
525 return CacheManager.SERVICE.getInstance(myManager.getProject()).processFilesWithWord(processor, word, UsageSearchContext.IN_CODE, scope, caseSensitively);
529 public boolean processAllFilesWithWordInText(@NotNull final String word,
530 @NotNull final GlobalSearchScope scope,
531 @NotNull final Processor<PsiFile> processor,
532 final boolean caseSensitively) {
533 return CacheManager.SERVICE.getInstance(myManager.getProject()).processFilesWithWord(processor, word, UsageSearchContext.IN_PLAIN_TEXT, scope, caseSensitively);
537 public boolean processAllFilesWithWordInComments(@NotNull String word,
538 @NotNull GlobalSearchScope scope,
539 @NotNull Processor<PsiFile> processor) {
540 return CacheManager.SERVICE.getInstance(myManager.getProject()).processFilesWithWord(processor, word, UsageSearchContext.IN_COMMENTS, scope, true);
544 public boolean processAllFilesWithWordInLiterals(@NotNull String word,
545 @NotNull GlobalSearchScope scope,
546 @NotNull Processor<PsiFile> processor) {
547 return CacheManager.SERVICE.getInstance(myManager.getProject()).processFilesWithWord(processor, word, UsageSearchContext.IN_STRINGS, scope, true);
550 private static class RequestWithProcessor {
551 @NotNull private final PsiSearchRequest request;
552 @NotNull private Processor<PsiReference> refProcessor;
554 private RequestWithProcessor(@NotNull PsiSearchRequest first, @NotNull Processor<PsiReference> second) {
556 refProcessor = second;
559 private boolean uniteWith(@NotNull final RequestWithProcessor another) {
560 if (request.equals(another.request)) {
561 final Processor<PsiReference> myProcessor = refProcessor;
562 if (myProcessor != another.refProcessor) {
563 refProcessor = new Processor<PsiReference>() {
565 public boolean process(PsiReference psiReference) {
566 return myProcessor.process(psiReference) && another.refProcessor.process(psiReference);
576 public String toString() {
577 return request.toString();
582 public boolean processRequests(@NotNull SearchRequestCollector collector, @NotNull Processor<PsiReference> processor) {
583 final Map<SearchRequestCollector, Processor<PsiReference>> collectors = ContainerUtil.newHashMap();
584 collectors.put(collector, processor);
586 ProgressIndicator progress = getOrCreateIndicator();
587 appendCollectorsFromQueryRequests(collectors);
590 MultiMap<Set<IdIndexEntry>, RequestWithProcessor> globals = new MultiMap<Set<IdIndexEntry>, RequestWithProcessor>();
591 final List<Computable<Boolean>> customs = ContainerUtil.newArrayList();
592 final Set<RequestWithProcessor> locals = ContainerUtil.newLinkedHashSet();
593 Map<RequestWithProcessor, Processor<PsiElement>> localProcessors = new THashMap<RequestWithProcessor, Processor<PsiElement>>();
594 distributePrimitives(collectors, locals, globals, customs, localProcessors, progress);
595 result = processGlobalRequestsOptimized(globals, progress, localProcessors);
597 for (RequestWithProcessor local : locals) {
598 result = processSingleRequest(local.request, local.refProcessor);
602 for (Computable<Boolean> custom : customs) {
603 result = custom.compute();
610 while(appendCollectorsFromQueryRequests(collectors));
616 public AsyncFuture<Boolean> processRequestsAsync(@NotNull SearchRequestCollector collector, @NotNull Processor<PsiReference> processor) {
617 return AsyncUtil.wrapBoolean(processRequests(collector, processor));
620 private static boolean appendCollectorsFromQueryRequests(@NotNull Map<SearchRequestCollector, Processor<PsiReference>> collectors) {
621 boolean changed = false;
622 Deque<SearchRequestCollector> queue = new LinkedList<SearchRequestCollector>(collectors.keySet());
623 while (!queue.isEmpty()) {
624 final SearchRequestCollector each = queue.removeFirst();
625 for (QuerySearchRequest request : each.takeQueryRequests()) {
627 assert !collectors.containsKey(request.collector) || collectors.get(request.collector) == request.processor;
628 collectors.put(request.collector, request.processor);
629 queue.addLast(request.collector);
636 private boolean processGlobalRequestsOptimized(@NotNull MultiMap<Set<IdIndexEntry>, RequestWithProcessor> singles,
637 @NotNull ProgressIndicator progress,
638 @NotNull final Map<RequestWithProcessor, Processor<PsiElement>> localProcessors) {
639 if (singles.isEmpty()) {
643 if (singles.size() == 1) {
644 final Collection<? extends RequestWithProcessor> requests = singles.values();
645 if (requests.size() == 1) {
646 final RequestWithProcessor theOnly = requests.iterator().next();
647 return processSingleRequest(theOnly.request, theOnly.refProcessor);
651 progress.pushState();
652 progress.setText(PsiBundle.message("psi.scanning.files.progress"));
656 // intersectionCandidateFiles holds files containing words from all requests in `singles` and words in corresponding container names
657 final MultiMap<VirtualFile, RequestWithProcessor> intersectionCandidateFiles = createMultiMap();
658 // restCandidateFiles holds files containing words from all requests in `singles` but EXCLUDING words in corresponding container names
659 final MultiMap<VirtualFile, RequestWithProcessor> restCandidateFiles = createMultiMap();
660 collectFiles(singles, progress, intersectionCandidateFiles, restCandidateFiles);
662 if (intersectionCandidateFiles.isEmpty() && restCandidateFiles.isEmpty()) {
666 final Set<String> allWords = new TreeSet<String>();
667 for (RequestWithProcessor singleRequest : localProcessors.keySet()) {
668 allWords.add(singleRequest.request.word);
670 progress.setText(PsiBundle.message("psi.search.for.word.progress", getPresentableWordsDescription(allWords)));
672 if (intersectionCandidateFiles.isEmpty()) {
673 result = processCandidates(localProcessors, restCandidateFiles, progress, restCandidateFiles.size(), 0);
676 int totalSize = restCandidateFiles.size() + intersectionCandidateFiles.size();
677 result = processCandidates(localProcessors, intersectionCandidateFiles, progress, totalSize, 0);
679 result = processCandidates(localProcessors, restCandidateFiles, progress, totalSize, intersectionCandidateFiles.size());
690 private boolean processCandidates(@NotNull final Map<RequestWithProcessor, Processor<PsiElement>> localProcessors,
691 @NotNull final MultiMap<VirtualFile, RequestWithProcessor> candidateFiles,
692 @NotNull ProgressIndicator progress,
694 int alreadyProcessedFiles) {
695 List<VirtualFile> files = new ArrayList<VirtualFile>(candidateFiles.keySet());
697 return processPsiFileRoots(files, totalSize, alreadyProcessedFiles, progress, new Processor<PsiFile>() {
699 public boolean process(final PsiFile psiRoot) {
700 final VirtualFile vfile = psiRoot.getVirtualFile();
701 for (final RequestWithProcessor singleRequest : candidateFiles.get(vfile)) {
702 Processor<PsiElement> localProcessor = localProcessors.get(singleRequest);
703 if (!localProcessor.process(psiRoot)) {
713 private static String getPresentableWordsDescription(@NotNull Set<String> allWords) {
714 final StringBuilder result = new StringBuilder();
715 for (String string : allWords) {
716 if (string != null && !string.isEmpty()) {
717 if (result.length() > 50) {
718 result.append("...");
721 if (result.length() != 0) result.append(", ");
722 result.append(string);
725 return result.toString();
729 private static TextOccurenceProcessor adaptProcessor(@NotNull PsiSearchRequest singleRequest,
730 @NotNull final Processor<PsiReference> consumer) {
731 final SearchScope searchScope = singleRequest.searchScope;
732 final boolean ignoreInjectedPsi = searchScope instanceof LocalSearchScope && ((LocalSearchScope)searchScope).isIgnoreInjectedPsi();
733 final RequestResultProcessor wrapped = singleRequest.processor;
734 return new TextOccurenceProcessor() {
736 public boolean execute(@NotNull PsiElement element, int offsetInElement) {
737 if (ignoreInjectedPsi && element instanceof PsiLanguageInjectionHost) return true;
739 return wrapped.processTextOccurrence(element, offsetInElement, consumer);
743 public String toString() {
744 return consumer.toString();
749 private void collectFiles(@NotNull MultiMap<Set<IdIndexEntry>, RequestWithProcessor> singles,
750 @NotNull ProgressIndicator progress,
751 @NotNull final MultiMap<VirtualFile, RequestWithProcessor> intersectionResult,
752 @NotNull final MultiMap<VirtualFile, RequestWithProcessor> restResult) {
753 for (final Set<IdIndexEntry> keys : singles.keySet()) {
754 if (keys.isEmpty()) {
758 final Collection<RequestWithProcessor> data = singles.get(keys);
759 final GlobalSearchScope commonScope = uniteScopes(data);
760 final Set<VirtualFile> intersectionWithContainerNameFiles = intersectionWithContainerNameFiles(commonScope, data, keys);
762 List<VirtualFile> files = new ArrayList<VirtualFile>();
763 Processor<VirtualFile> processor = new CommonProcessors.CollectProcessor<VirtualFile>(files);
764 processFilesContainingAllKeys(myManager.getProject(), commonScope, null, keys, processor);
765 for (final VirtualFile file : files) {
766 progress.checkCanceled();
767 for (final IdIndexEntry entry : keys) {
768 DumbService.getInstance(myManager.getProject()).runReadActionInSmartMode(new Runnable() {
771 FileBasedIndex.getInstance().processValues(IdIndex.NAME, entry, file, new FileBasedIndex.ValueProcessor<Integer>() {
773 public boolean process(VirtualFile file, Integer value) {
774 int mask = value.intValue();
775 for (RequestWithProcessor single : data) {
776 final PsiSearchRequest request = single.request;
777 if ((mask & request.searchContext) != 0 && ((GlobalSearchScope)request.searchScope).contains(file)) {
778 MultiMap<VirtualFile, RequestWithProcessor> result =
779 intersectionWithContainerNameFiles == null || !intersectionWithContainerNameFiles.contains(file) ? restResult : intersectionResult;
780 result.putValue(file, single);
793 @Nullable("null means we did not find common container files")
794 private Set<VirtualFile> intersectionWithContainerNameFiles(@NotNull GlobalSearchScope commonScope,
795 @NotNull Collection<RequestWithProcessor> data,
796 @NotNull Set<IdIndexEntry> keys) {
797 String commonName = null;
798 short searchContext = 0;
799 boolean caseSensitive = true;
800 for (RequestWithProcessor r : data) {
801 String name = r.request.containerName;
803 if (commonName == null) {
804 commonName = r.request.containerName;
805 searchContext = r.request.searchContext;
806 caseSensitive = r.request.caseSensitive;
808 else if (commonName.equals(name)) {
809 searchContext |= r.request.searchContext;
810 caseSensitive &= r.request.caseSensitive;
817 if (commonName == null) return null;
818 Set<VirtualFile> containerFiles = new THashSet<VirtualFile>();
820 List<IdIndexEntry> entries = getWordEntries(commonName, caseSensitive);
821 if (entries.isEmpty()) return null;
822 entries.addAll(keys); // should find words from both text and container names
824 final short finalSearchContext = searchContext;
825 Condition<Integer> contextMatches = new Condition<Integer>() {
827 public boolean value(Integer context) {
828 return (context.intValue() & finalSearchContext) != 0;
831 processFilesContainingAllKeys(myManager.getProject(), commonScope, contextMatches, entries, new CommonProcessors.CollectProcessor<VirtualFile>(containerFiles));
833 return containerFiles;
837 private static MultiMap<VirtualFile, RequestWithProcessor> createMultiMap() {
838 // usually there is just one request
839 return MultiMap.createSmart();
843 private static GlobalSearchScope uniteScopes(@NotNull Collection<RequestWithProcessor> requests) {
844 Set<GlobalSearchScope> scopes = ContainerUtil.map2LinkedSet(requests, new Function<RequestWithProcessor, GlobalSearchScope>() {
846 public GlobalSearchScope fun(RequestWithProcessor r) {
847 return (GlobalSearchScope)r.request.searchScope;
850 return GlobalSearchScope.union(scopes.toArray(new GlobalSearchScope[scopes.size()]));
853 private static void distributePrimitives(@NotNull Map<SearchRequestCollector, Processor<PsiReference>> collectors,
854 @NotNull Set<RequestWithProcessor> locals,
855 @NotNull MultiMap<Set<IdIndexEntry>, RequestWithProcessor> singles,
856 @NotNull List<Computable<Boolean>> customs,
857 @NotNull Map<RequestWithProcessor, Processor<PsiElement>> localProcessors,
858 @NotNull ProgressIndicator progress) {
859 for (final Map.Entry<SearchRequestCollector, Processor<PsiReference>> entry : collectors.entrySet()) {
860 final Processor<PsiReference> processor = entry.getValue();
861 SearchRequestCollector collector = entry.getKey();
862 for (final PsiSearchRequest primitive : collector.takeSearchRequests()) {
863 final SearchScope scope = primitive.searchScope;
864 if (scope instanceof LocalSearchScope) {
865 registerRequest(locals, primitive, processor);
868 Set<IdIndexEntry> key = new HashSet<IdIndexEntry>(getWordEntries(primitive.word, primitive.caseSensitive));
869 registerRequest(singles.getModifiable(key), primitive, processor);
872 for (final Processor<Processor<PsiReference>> customAction : collector.takeCustomSearchActions()) {
873 customs.add(new Computable<Boolean>() {
875 public Boolean compute() {
876 return customAction.process(processor);
882 for (Map.Entry<Set<IdIndexEntry>, Collection<RequestWithProcessor>> entry : singles.entrySet()) {
883 for (RequestWithProcessor singleRequest : entry.getValue()) {
884 PsiSearchRequest primitive = singleRequest.request;
885 StringSearcher searcher = new StringSearcher(primitive.word, primitive.caseSensitive, true, false);
886 final TextOccurenceProcessor adapted = adaptProcessor(primitive, singleRequest.refProcessor);
888 Processor<PsiElement> localProcessor = localProcessor(adapted, progress, true, searcher);
890 assert !localProcessors.containsKey(singleRequest) || localProcessors.get(singleRequest) == localProcessor;
891 localProcessors.put(singleRequest, localProcessor);
896 private static void registerRequest(@NotNull Collection<RequestWithProcessor> collection,
897 @NotNull PsiSearchRequest primitive,
898 @NotNull Processor<PsiReference> processor) {
899 RequestWithProcessor singleRequest = new RequestWithProcessor(primitive, processor);
901 for (RequestWithProcessor existing : collection) {
902 if (existing.uniteWith(singleRequest)) {
906 collection.add(singleRequest);
909 private boolean processSingleRequest(@NotNull PsiSearchRequest single, @NotNull Processor<PsiReference> consumer) {
910 final EnumSet<Options> options = EnumSet.of(Options.PROCESS_ONLY_JAVA_IDENTIFIERS_IF_POSSIBLE);
911 if (single.caseSensitive) options.add(Options.CASE_SENSITIVE_SEARCH);
912 if (shouldProcessInjectedPsi(single.searchScope)) options.add(Options.PROCESS_INJECTED_PSI);
914 return processElementsWithWord(adaptProcessor(single, consumer), single.searchScope, single.word, single.searchContext, options,
915 single.containerName);
920 public SearchCostResult isCheapEnoughToSearch(@NotNull String name,
921 @NotNull final GlobalSearchScope scope,
922 @Nullable final PsiFile fileToIgnoreOccurrencesIn,
923 @Nullable final ProgressIndicator progress) {
924 final AtomicInteger count = new AtomicInteger();
925 final ProgressIndicator indicator = progress == null ? new EmptyProgressIndicator() : progress;
926 final Processor<VirtualFile> processor = new Processor<VirtualFile>() {
927 private final VirtualFile virtualFileToIgnoreOccurrencesIn =
928 fileToIgnoreOccurrencesIn == null ? null : fileToIgnoreOccurrencesIn.getVirtualFile();
931 public boolean process(VirtualFile file) {
932 indicator.checkCanceled();
933 if (Comparing.equal(file, virtualFileToIgnoreOccurrencesIn)) return true;
934 final int value = count.incrementAndGet();
938 List<IdIndexEntry> keys = getWordEntries(name, true);
939 boolean cheap = keys.isEmpty() || processFilesContainingAllKeys(myManager.getProject(), scope, null, keys, processor);
942 return SearchCostResult.TOO_MANY_OCCURRENCES;
945 return count.get() == 0 ? SearchCostResult.ZERO_OCCURRENCES : SearchCostResult.FEW_OCCURRENCES;
948 private static boolean processFilesContainingAllKeys(@NotNull Project project,
949 @NotNull final GlobalSearchScope scope,
950 @Nullable final Condition<Integer> checker,
951 @NotNull final Collection<IdIndexEntry> keys,
952 @NotNull final Processor<VirtualFile> processor) {
953 final FileIndexFacade index = FileIndexFacade.getInstance(project);
954 return DumbService.getInstance(project).runReadActionInSmartMode(new Computable<Boolean>() {
956 public Boolean compute() {
957 return FileBasedIndex.getInstance().processFilesContainingAllKeys(IdIndex.NAME, keys, scope, checker, new Processor<VirtualFile>() {
959 public boolean process(VirtualFile file) {
960 return !index.shouldBeFound(scope, file) || processor.process(file);
968 private static List<IdIndexEntry> getWordEntries(@NotNull String name, final boolean caseSensitively) {
969 List<String> words = StringUtil.getWordsInStringLongestFirst(name);
970 if (words.isEmpty()) {
971 String trimmed = name.trim();
972 if (StringUtil.isNotEmpty(trimmed)) {
973 words = Collections.singletonList(trimmed);
976 if (words.isEmpty()) return Collections.emptyList();
977 return ContainerUtil.map2List(words, new Function<String, IdIndexEntry>() {
979 public IdIndexEntry fun(String word) {
980 return new IdIndexEntry(word, caseSensitively);
985 public static boolean processTextOccurrences(@NotNull final PsiElement element,
986 @NotNull String stringToSearch,
987 @NotNull GlobalSearchScope searchScope,
988 @NotNull final Processor<UsageInfo> processor,
989 @NotNull final UsageInfoFactory factory) {
990 PsiSearchHelper helper = ApplicationManager.getApplication().runReadAction(new Computable<PsiSearchHelper>() {
992 public PsiSearchHelper compute() {
993 return SERVICE.getInstance(element.getProject());
997 return helper.processUsagesInNonJavaFiles(element, stringToSearch, new PsiNonJavaFileReferenceProcessor() {
999 public boolean process(final PsiFile psiFile, final int startOffset, final int endOffset) {
1001 UsageInfo usageInfo = ApplicationManager.getApplication().runReadAction(new Computable<UsageInfo>() {
1003 public UsageInfo compute() {
1004 return factory.createUsageInfo(psiFile, startOffset, endOffset);
1007 return usageInfo == null || processor.process(usageInfo);
1009 catch (ProcessCanceledException e) {
1012 catch (Exception e) {