blob: 92c6b4349189c895d4242379bd281643d42e0a71 [file] [log] [blame]
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.psi;
import com.intellij.concurrency.JobLauncher;
import com.intellij.ide.PowerSaveMode;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.ApplicationAdapter;
import com.intellij.openapi.application.PathManager;
import com.intellij.openapi.application.RuntimeInterruptedException;
import com.intellij.openapi.application.ex.ApplicationEx;
import com.intellij.openapi.application.ex.ApplicationUtil;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.fileTypes.StdFileTypes;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.progress.EmptyProgressIndicator;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.progress.impl.BackgroundableProcessIndicator;
import com.intellij.openapi.progress.impl.ProgressManagerImpl;
import com.intellij.openapi.progress.util.ProgressIndicatorBase;
import com.intellij.openapi.progress.util.ProgressIndicatorUtils;
import com.intellij.openapi.project.DumbService;
import com.intellij.openapi.project.IndexNotReadyException;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectCoreUtil;
import com.intellij.openapi.roots.ProjectFileIndex;
import com.intellij.openapi.startup.StartupManager;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.EmptyRunnable;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.*;
import com.intellij.openapi.vfs.newvfs.BulkFileListener;
import com.intellij.openapi.vfs.newvfs.NewVirtualFile;
import com.intellij.openapi.vfs.newvfs.events.VFileEvent;
import com.intellij.openapi.vfs.newvfs.persistent.FSRecords;
import com.intellij.openapi.vfs.newvfs.persistent.PersistentFS;
import com.intellij.psi.impl.PersistentIntList;
import com.intellij.psi.impl.file.impl.ResolveScopeManagerImpl;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.util.PsiUtilCore;
import com.intellij.psi.xml.XmlElement;
import com.intellij.psi.xml.XmlFile;
import com.intellij.util.ArrayUtil;
import com.intellij.util.ExceptionUtil;
import com.intellij.util.Function;
import com.intellij.util.Processor;
import com.intellij.util.containers.ConcurrentBitSet;
import com.intellij.util.containers.ConcurrentIntObjectMap;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.StripedLockIntObjectConcurrentHashMap;
import com.intellij.util.io.storage.HeavyProcessLatch;
import com.intellij.util.messages.MessageBus;
import gnu.trove.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jps.model.java.JavaSourceRootType;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.text.DateFormat;
import java.util.*;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
public class RefResolveServiceImpl extends RefResolveService implements Runnable, Disposable {
private static final Logger LOG = Logger.getInstance("#com.intellij.psi.RefResolveService");
private final AtomicInteger fileCount = new AtomicInteger();
private final AtomicLong bytesSize = new AtomicLong();
private final AtomicLong refCount = new AtomicLong();
private final PersistentIntList storage;
private final Deque<VirtualFile> filesToResolve = new ArrayDeque<VirtualFile>();
private final ConcurrentBitSet fileIsInQueue = new ConcurrentBitSet();
private final ConcurrentBitSet fileIsResolved;
private final ApplicationEx myApplication;
private volatile boolean myDisposed;
private volatile boolean upToDate;
private final AtomicInteger enableVetoes = new AtomicInteger(); // number of disable() calls. To enable the service, there should be at least corresponding number of enable() calls.
private final FileWriter log;
private final ProjectFileIndex myProjectFileIndex;
public RefResolveServiceImpl(final Project project,
final MessageBus messageBus,
final PsiManager psiManager,
StartupManager startupManager,
ApplicationEx application,
ProjectFileIndex projectFileIndex) throws IOException {
super(project);
((FutureTask)resolveProcess).run();
myApplication = application;
myProjectFileIndex = projectFileIndex;
if (ResolveScopeManagerImpl.ENABLED_REF_BACK) {
File indexFile = new File(getStorageDirectory(), "index");
File dataFile = new File(getStorageDirectory(), "data");
fileIsResolved = ConcurrentBitSet.readFrom(new File(getStorageDirectory(), "bitSet"));
final boolean initial = !indexFile.exists() || !dataFile.exists();
storage = new PersistentIntList(indexFile, dataFile, initial);
Disposer.register(this, storage);
if (!application.isUnitTestMode()) {
startupManager.runWhenProjectIsInitialized(new Runnable() {
@Override
public void run() {
init(messageBus, psiManager);
}
});
}
log = new FileWriter(new File(getStorageDirectory(), "log.txt"));
Disposer.register(this, new Disposable() {
@Override
public void dispose() {
try {
save();
log.close();
}
catch (IOException e) {
LOG.error(e);
}
}
});
}
else {
log = null;
fileIsResolved = null;
storage = null;
}
}
public static List<VirtualFile> toVf(@NotNull int[] ids) {
List<VirtualFile> res = new ArrayList<VirtualFile>();
for (int id : ids) {
VirtualFile file = PersistentFS.getInstance().findFileById(id);
if (file != null) {
res.add(file);
}
}
return res;
}
public static String toVfString(@NotNull int[] backIds) {
List<VirtualFile> list = toVf(backIds);
return toVfString(list);
}
private static String toVfString(@NotNull Collection<VirtualFile> list) {
List<VirtualFile> sub = new ArrayList<VirtualFile>(list).subList(0, Math.min(list.size(), 100));
return list.size() + " files: " + StringUtil.join(sub, new Function<VirtualFile, String>() {
@Override
public String fun(VirtualFile file) {
return file.getName();
}
}, ", ")+(list.size()==sub.size() ? "" : "...");
}
private void init(@NotNull MessageBus messageBus, @NotNull PsiManager psiManager) {
messageBus.connect().subscribe(VirtualFileManager.VFS_CHANGES, new BulkFileListener.Adapter(){
@Override
public void after(@NotNull List<? extends VFileEvent> events) {
fileCount.set(0);
List<VirtualFile> files = ContainerUtil.mapNotNull(events, new Function<VFileEvent, VirtualFile>() {
@Override
public VirtualFile fun(VFileEvent event) {
return event.getFile();
}
});
queue(files, "VFS events " + events.size());
}
});
psiManager.addPsiTreeChangeListener(new PsiTreeChangeAdapter() {
@Override
public void childrenChanged(@NotNull PsiTreeChangeEvent event) {
PsiFile file = event.getFile();
VirtualFile virtualFile = PsiUtilCore.getVirtualFile(file);
if (virtualFile != null) {
queue(Collections.singletonList(virtualFile), event);
}
}
@Override
public void propertyChanged(@NotNull PsiTreeChangeEvent event) {
childrenChanged(event);
}
});
messageBus.connect().subscribe(DumbService.DUMB_MODE, new DumbService.DumbModeListener() {
@Override
public void enteredDumbMode() {
disable();
}
@Override
public void exitDumbMode() {
enable();
}
});
messageBus.connect().subscribe(PowerSaveMode.TOPIC, new PowerSaveMode.Listener() {
@Override
public void powerSaveStateChanged() {
if (PowerSaveMode.isEnabled()) {
enable();
}
else {
disable();
}
}
});
myApplication.addApplicationListener(new ApplicationAdapter() {
@Override
public void beforeWriteActionStart(Object action) {
disable();
}
@Override
public void writeActionFinished(Object action) {
enable();
}
@Override
public void applicationExiting() {
disable();
}
}, this);
VirtualFileManager.getInstance().addVirtualFileManagerListener(new VirtualFileManagerListener() {
@Override
public void beforeRefreshStart(boolean asynchronous) {
disable();
}
@Override
public void afterRefreshFinish(boolean asynchronous) {
enable();
}
}, this);
HeavyProcessLatch.INSTANCE.addListener(this, new HeavyProcessLatch.HeavyProcessListener() {
@Override
public void processStarted() {
}
@Override
public void processFinished() {
wakeUp();
}
});
startThread();
}
// return true if file was added to queue
private boolean queueIfNeeded(VirtualFile virtualFile, @NotNull Project project) {
return toResolve(virtualFile, project) && queueUpdate(virtualFile);
}
private boolean toResolve(VirtualFile virtualFile, @NotNull Project project) {
if (virtualFile != null &&
virtualFile.isValid() &&
project.isInitialized() &&
myProjectFileIndex.isInContent(virtualFile)) {
if (virtualFile.isDirectory()) return true;
if (virtualFile.getFileType() == StdFileTypes.JAVA) return true;
if (virtualFile.getFileType() == StdFileTypes.XML && !ProjectCoreUtil.isProjectOrWorkspaceFile(virtualFile)) return true;
}
// else mark it as resolved so we will not have to check it again
if (virtualFile instanceof VirtualFileWithId) {
int id = getAbsId(virtualFile);
fileIsResolved.set(id);
}
return false;
}
@NotNull
private File getStorageDirectory() {
String dirName = myProject.getName() + "."+Integer.toHexString(myProject.getPresentableUrl().hashCode());
File dir = new File(PathManager.getSystemPath(), "refs/" + dirName);
FileUtil.createDirectory(dir);
return dir;
}
private void log(String m) {
//System.out.println(m);
logf(m);
}
private void logf(String m) {
try {
log.write(DateFormat.getDateTimeInstance().format(new Date()) + " "+m+" ; gap="+storage.gap+"\n");
}
catch (IOException e) {
LOG.error(e);
}
}
private void flushLog() {
try {
log.flush();
}
catch (IOException e) {
LOG.error(e);
}
}
// return true if file was added to queue
private boolean queueUpdate(@NotNull VirtualFile file) {
synchronized (filesToResolve) {
if (!(file instanceof VirtualFileWithId)) return false;
int fileId = getAbsId(file);
countAndMarkUnresolved(file, new THashSet<VirtualFile>(), true);
boolean alreadyAdded = fileIsInQueue.set(fileId);
if (!alreadyAdded) {
filesToResolve.add(file);
}
upToDate = false;
wakeUpUnderLock();
return !alreadyAdded;
}
}
private void wakeUp() {
synchronized (filesToResolve) {
wakeUpUnderLock();
}
}
private void wakeUpUnderLock() {
filesToResolve.notifyAll();
}
private void waitForQueue() throws InterruptedException {
synchronized (filesToResolve) {
filesToResolve.wait(1000);
}
}
private void startThread() {
new Thread(this, "Ref resolve service").start();
upToDate = true;
queueUnresolvedFilesSinceLastRestart();
}
private void queueUnresolvedFilesSinceLastRestart() {
PersistentFS fs = PersistentFS.getInstance();
int maxId = FSRecords.getMaxId();
TIntArrayList list = new TIntArrayList();
for (int id= fileIsResolved.nextClearBit(1); id >= 0 && id < maxId; id = fileIsResolved.nextClearBit(id + 1)) {
int nextSetBit = fileIsResolved.nextSetBit(id);
int endOfRun = Math.min(maxId, nextSetBit == -1 ? maxId : nextSetBit);
do {
VirtualFile virtualFile = fs.findFileById(id);
if (queueIfNeeded(virtualFile, myProject)) {
list.add(id);
}
}
while (++id < endOfRun);
}
log("Initially added to resolve " + toVfString(list.toNativeArray()));
}
@Override
public void dispose() {
myDisposed = true;
}
private void save() throws IOException {
fileIsResolved.writeTo(new File(getStorageDirectory(), "bitSet"));
}
private volatile Future<?> resolveProcess = new FutureTask<Object>(EmptyRunnable.getInstance(), null); // write from EDT only
private volatile ProgressIndicator resolveIndicator = new EmptyProgressIndicator();
@Override
public void run() {
while (!myDisposed) {
boolean isEmpty;
synchronized (filesToResolve) {
isEmpty = filesToResolve.isEmpty();
}
if (enableVetoes.get() > 0 || isEmpty || !resolveProcess.isDone() || HeavyProcessLatch.INSTANCE.isRunning()) {
try {
waitForQueue();
}
catch (InterruptedException e) {
break;
}
continue;
}
final Set<VirtualFile> files = countFilesToResolve();
if (files.isEmpty()) continue;
upToDate = false;
myApplication.invokeLater(new Runnable() {
@Override
public void run() {
if (!resolveProcess.isDone()) return;
log("Started to resolve " + files.size() + " files");
Task.Backgroundable backgroundable = new Task.Backgroundable(myProject, "Resolving files...", true) {
@Override
public void run(@NotNull final ProgressIndicator indicator) {
if (!myApplication.isDisposed()) {
try {
processBatch(indicator, files);
}
catch (RuntimeInterruptedException ignore) {
// see future.cancel() in disable()
int i = 0;
}
}
}
};
ProgressIndicator indicator;
if (files.size() > 1) {
//show progress
indicator = new BackgroundableProcessIndicator(backgroundable);
}
else {
indicator = new MyProgress();
}
resolveIndicator = indicator;
resolveProcess = ProgressManagerImpl.runProcessWithProgressAsynchronously(backgroundable, indicator, null);
}
}, myProject.getDisposed());
flushLog();
}
}
private volatile int resolvedInPreviousBatch;
private void processBatch(@NotNull final ProgressIndicator indicator, @NotNull Set<VirtualFile> files) {
assert !myApplication.isDispatchThread();
final int resolvedInPreviousBatch = this.resolvedInPreviousBatch;
final int totalSize = files.size() + resolvedInPreviousBatch;
final ConcurrentIntObjectMap<int[]> fileToForwardIds = new StripedLockIntObjectConcurrentHashMap<int[]>();
final Set<VirtualFile> toProcess = Collections.synchronizedSet(files);
indicator.setIndeterminate(false);
ProgressIndicatorUtils.forceWriteActionPriority(indicator, (Disposable)indicator);
long start = System.currentTimeMillis();
Processor<VirtualFile> processor = new Processor<VirtualFile>() {
@Override
public boolean process(VirtualFile file) {
double fraction = 1 - toProcess.size() * 1.0 / totalSize;
indicator.setFraction(fraction);
try {
if (file.isDirectory() || !toResolve(file, myProject)) {
return true;
}
int fileId = getAbsId(file);
int i = totalSize - toProcess.size();
indicator.setText(i + "/" + totalSize + ": Resolving " + file.getPresentableUrl());
int[] forwardIds = processFile(file, fileId, indicator);
if (forwardIds == null) {
//queueUpdate(file);
return false;
}
toProcess.remove(file);
fileToForwardIds.put(fileId, forwardIds);
}
catch (RuntimeException e) {
indicator.checkCanceled();
}
return true;
}
};
boolean success = true;
try {
success = JobLauncher
.getInstance().invokeConcurrentlyUnderProgress(new ArrayList<VirtualFile>(files), indicator, false, false, processor);
}
finally {
this.resolvedInPreviousBatch = toProcess.isEmpty() ? 0 : totalSize - toProcess.size();
queue(toProcess, "re-added after fail. success=" + success);
storeIds(fileToForwardIds);
long end = System.currentTimeMillis();
log("Resolved batch of " + (totalSize - toProcess.size()) + " from " + totalSize + " files in " + ((end - start) / 1000) + "sec. (Gap: " + storage.gap+")");
synchronized (filesToResolve) {
upToDate = filesToResolve.isEmpty();
log("upToDate = " + upToDate);
}
}
}
@NotNull
private Set<VirtualFile> countFilesToResolve() {
Set<VirtualFile> set;
synchronized (filesToResolve) {
int queuedSize = filesToResolve.size();
set = new THashSet<VirtualFile>(queuedSize);
// someone might have cleared this bit to mark file as processed
for (VirtualFile file : filesToResolve) {
if (fileIsInQueue.clear(getAbsId(file))) {
set.add(file);
}
}
filesToResolve.clear();
}
return countAndMarkUnresolved(set, false);
}
private static int getAbsId(@NotNull VirtualFile file) {
return Math.abs(((VirtualFileWithId)file).getId());
}
@NotNull
private Set<VirtualFile> countAndMarkUnresolved(@NotNull Collection<VirtualFile> files, boolean inDbOnly) {
Set<VirtualFile> result = new THashSet<VirtualFile>();
for (VirtualFile file : files) {
countAndMarkUnresolved(file, result, inDbOnly);
}
return result;
}
private void countAndMarkUnresolved(@NotNull VirtualFile file, @NotNull final Set<VirtualFile> result, final boolean inDbOnly) {
if (file.isDirectory()) {
VfsUtilCore.visitChildrenRecursively(file, new VirtualFileVisitor() {
@Override
public boolean visitFile(@NotNull VirtualFile file) {
doCountAndMarkUnresolved(file, result);
return true;
}
@Nullable
@Override
public Iterable<VirtualFile> getChildrenIterable(@NotNull VirtualFile file) {
return inDbOnly ? ((NewVirtualFile)file).iterInDbChildren() : null;
}
});
}
else {
doCountAndMarkUnresolved(file, result);
}
}
private void doCountAndMarkUnresolved(@NotNull VirtualFile file, @NotNull Set<VirtualFile> result) {
if (file.isDirectory()) {
fileIsResolved.set(getAbsId(file));
}
else if (toResolve(file, myProject)) {
result.add(file);
fileIsResolved.clear(getAbsId(file));
}
}
private void enable() {
// decrement but only if it's positive
int vetoes;
do {
vetoes = enableVetoes.get();
if (vetoes == 0) break;
} while(!enableVetoes.compareAndSet(vetoes, vetoes-1));
wakeUp();
}
private void disable() {
//resolveIndicator.cancel();
//resolveProcess.cancel(true);
enableVetoes.incrementAndGet();
wakeUp();
}
// returns list of resolved files if updated successfully, or null if write action or dumb mode started
private int[] processFile(@NotNull final VirtualFile file,
int fileId,
@NotNull final ProgressIndicator indicator) {
final TIntHashSet forward;
try {
forward = calcForwardRefs(file, indicator);
}
catch (IndexNotReadyException e) {
return null;
}
catch (ApplicationUtil.CannotRunReadActionException e) {
return null;
}
catch (ProcessCanceledException e) {
throw e;
}
catch (Exception e) {
log(ExceptionUtil.getThrowableText(e));
flushLog();
return null;
}
int[] forwardIds = forward.toArray();
fileIsResolved.set(fileId);
logf(" ---- "+file.getPresentableUrl() + " processed. forwardIds: "+ toVfString(forwardIds));
return forwardIds;
}
private void storeIds(@NotNull ConcurrentIntObjectMap<int[]> fileToForwardIds) {
int forwardSize = 0;
int backwardSize = 0;
final TIntObjectHashMap<TIntArrayList> fileToBackwardIds = new TIntObjectHashMap<TIntArrayList>(fileToForwardIds.size());
for (StripedLockIntObjectConcurrentHashMap.IntEntry<int[]> entry : fileToForwardIds.entries()) {
int fileId = entry.getKey();
int[] forwardIds = entry.getValue();
forwardSize += forwardIds.length;
for (int forwardId : forwardIds) {
TIntArrayList backIds = fileToBackwardIds.get(forwardId);
if (backIds == null) {
backIds = new TIntArrayList();
fileToBackwardIds.put(forwardId, backIds);
}
backIds.add(fileId);
backwardSize++;
}
}
log("backwardSize = " + backwardSize);
log("forwardSize = " + forwardSize);
log("fileToForwardIds.size() = "+fileToForwardIds.size());
log("fileToBackwardIds.size() = "+fileToBackwardIds.size());
assert forwardSize == backwardSize;
// wrap in read action so that sudden quit (in write action) would not interrupt us
myApplication.runReadAction(new Runnable() {
@Override
public void run() {
fileToBackwardIds.forEachEntry(new TIntObjectProcedure<TIntArrayList>() {
@Override
public boolean execute(int fileId, TIntArrayList backIds) {
storage.addAll(fileId, backIds.toNativeArray());
return true;
}
});
}
});
}
@NotNull
private TIntHashSet calcForwardRefs(@NotNull final VirtualFile virtualFile, @NotNull final ProgressIndicator indicator)
throws IndexNotReadyException, ApplicationUtil.CannotRunReadActionException {
if (myProject.isDisposed()) throw new ProcessCanceledException();
if (fileCount.incrementAndGet() % 100 == 0) {
PsiManager.getInstance(myProject).dropResolveCaches();
synchronized (storage) {
storage.flush();
}
try {
log.flush();
}
catch (IOException e) {
LOG.error(e);
}
}
final TIntHashSet forward = new TIntHashSet();
final PsiFile psiFile = ApplicationUtil.tryRunReadAction(new Computable<PsiFile>() {
@Override
public PsiFile compute() {
return PsiManager.getInstance(myProject).findFile(virtualFile);
}
});
final int fileId = getAbsId(virtualFile);
if (psiFile != null) {
bytesSize.addAndGet(virtualFile.getLength());
final Set<PsiElement> resolved = new THashSet<PsiElement>();
ApplicationUtil.tryRunReadAction(new Runnable() {
@Override
public void run() {
indicator.checkCanceled();
if (psiFile instanceof PsiJavaFile) {
psiFile.accept(new JavaRecursiveElementWalkingVisitor() {
@Override
public void visitReferenceElement(PsiJavaCodeReferenceElement reference) {
indicator.checkCanceled();
resolveReference(reference, resolved);
super.visitReferenceElement(reference);
}
});
}
else if (psiFile instanceof XmlFile) {
psiFile.accept(new XmlRecursiveElementWalkingVisitor() {
@Override
public void visitXmlElement(XmlElement element) {
for (PsiReference reference : element.getReferences()) {
indicator.checkCanceled();
resolveReference(reference, resolved);
}
super.visitXmlElement(element);
}
});
}
indicator.checkCanceled();
for (PsiElement element : resolved) {
PsiFile file = element.getContainingFile();
addIdAndSuperClasses(file, forward);
}
}
});
}
forward.remove(fileId);
return forward;
}
private void resolveReference(@NotNull PsiReference reference, @NotNull Set<PsiElement> resolved) {
PsiElement element = reference.resolve();
if (element != null) {
resolved.add(element);
}
refCount.incrementAndGet();
}
private static void addIdAndSuperClasses(PsiFile file, @NotNull TIntHashSet forward) {
if (file instanceof PsiJavaFile && file.getName().equals("Object.class") && ((PsiJavaFile)file).getPackageName().equals("java.lang")) {
return;
}
VirtualFile virtualFile = PsiUtilCore.getVirtualFile(file);
if (virtualFile instanceof VirtualFileWithId && forward.add(getAbsId(virtualFile)) && file instanceof PsiClassOwner) {
for (PsiClass aClass : ((PsiClassOwner)file).getClasses()) {
for (PsiClass superClass : aClass.getSupers()) {
addIdAndSuperClasses(superClass.getContainingFile(), forward);
}
}
}
}
@Override
@Nullable
public int[] getBackwardIds(@NotNull VirtualFileWithId file) {
if (!upToDate) return null;
int fileId = getAbsId((VirtualFile)file);
return storage.get(fileId);
}
private String prevLog = "";
private static final Set<JavaSourceRootType> SOURCE_ROOTS = ContainerUtil.newTroveSet(JavaSourceRootType.SOURCE, JavaSourceRootType.TEST_SOURCE);
@NotNull
@Override
public GlobalSearchScope restrictByBackwardIds(@NotNull final VirtualFile virtualFile, @NotNull GlobalSearchScope scope) {
final int[] backIds = RefResolveService.getInstance(myProject).getBackwardIds((VirtualFileWithId)virtualFile);
if (backIds == null) {
return scope;
}
String files = toVfString(backIds);
String log = "Restricting scope of " + virtualFile.getName() + " to " + files;
if (!log.equals(prevLog)) {
log(log);
flushLog();
prevLog = log;
}
GlobalSearchScope restrictedByBackwardIds = new GlobalSearchScope() {
@Override
public boolean contains(@NotNull VirtualFile file) {
if (!(file instanceof VirtualFileWithId)
|| file.equals(virtualFile)
|| ArrayUtil.indexOf(backIds, getAbsId(file)) != -1) return true;
return false & !myProjectFileIndex.isUnderSourceRootOfType(file, SOURCE_ROOTS); // filter out source file which we know for sure does not reference the element
}
@Override
public int compare(@NotNull VirtualFile file1, @NotNull VirtualFile file2) {
return 0;
}
@Override
public boolean isSearchInModuleContent(@NotNull Module aModule) {
return true;
}
@Override
public boolean isSearchInLibraries() {
return false;
}
};
return scope.intersectWith(restrictedByBackwardIds);
}
@Override
public boolean queue(@NotNull Collection<VirtualFile> files, Object reason) {
if (files.isEmpty()) {
return false;
}
boolean queued = false;
List<VirtualFile> added = new ArrayList<VirtualFile>(files.size());
for (VirtualFile file : files) {
boolean wasAdded = queueIfNeeded(file, myProject);
if (wasAdded) {
added.add(file);
}
queued |= wasAdded;
}
if (queued) {
log("Queued to resolve (from " + reason + "): " + toVfString(added));
flushLog();
}
return queued;
}
private static class MyProgress extends ProgressIndicatorBase implements Disposable{
@Override
public void dispose() {
}
}
}