resolve XML files too

This commit is contained in:
Alexey Kudravtsev
2014-08-13 13:18:39 +04:00
parent 965df2d452
commit 8a8ced48a6
5 changed files with 228 additions and 94 deletions
@@ -102,7 +102,7 @@ public class RefQueueIndex extends FileBasedIndexExtension<Void,Void> {
return new FileBasedIndex.InputFilter() {
@Override
public boolean acceptInput(@NotNull VirtualFile file) {
return true;
return !file.isDirectory();
}
};
}
@@ -41,7 +41,6 @@ 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.RefreshQueueImpl;
import com.intellij.openapi.vfs.newvfs.events.VFileEvent;
import com.intellij.openapi.vfs.newvfs.persistent.FSRecords;
import com.intellij.openapi.vfs.newvfs.persistent.PersistentFS;
@@ -49,6 +48,8 @@ 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;
@@ -85,6 +86,7 @@ public class RefResolveServiceImpl extends RefResolveService implements Runnable
private final ApplicationEx myApplication;
private volatile boolean myDisposed;
private volatile boolean upToDate;
private volatile boolean enabled = true;
private final FileWriter log;
private final ProjectFileIndex myProjectFileIndex;
@@ -162,10 +164,6 @@ public class RefResolveServiceImpl extends RefResolveService implements Runnable
}
private void init(@NotNull MessageBus messageBus, @NotNull PsiManager psiManager) {
//if (true) {
// upToDate = false;
// return;
//}
messageBus.connect().subscribe(VirtualFileManager.VFS_CHANGES, new BulkFileListener.Adapter(){
@Override
public void after(@NotNull List<? extends VFileEvent> events) {
@@ -198,31 +196,52 @@ public class RefResolveServiceImpl extends RefResolveService implements Runnable
messageBus.connect().subscribe(DumbService.DUMB_MODE, new DumbService.DumbModeListener() {
@Override
public void enteredDumbMode() {
wakeUp();
disable();
}
@Override
public void exitDumbMode() {
wakeUp();
enable();
}
});
myApplication.addApplicationListener(new ApplicationAdapter() {
@Override
public void beforeWriteActionStart(Object action) {
disable();
}
@Override
public void writeActionFinished(Object action) {
wakeUp();
enable();
}
@Override
public void applicationExiting() {
disable();
}
}, this);
VirtualFileManager.getInstance().addVirtualFileManagerListener(new VirtualFileManagerListener() {
@Override
public void beforeRefreshStart(boolean asynchronous) {
wakeUp();
disable();
}
@Override
public void afterRefreshFinish(boolean asynchronous) {
wakeUp();
enable();
}
}, this);
Disposer.register(this, HeavyProcessLatch.INSTANCE.addListener(new HeavyProcessLatch.HeavyProcessListener() {
@Override
public void processStarted() {
disable();
}
@Override
public void processFinished() {
enable();
}
}));
startThread();
}
@@ -233,10 +252,11 @@ public class RefResolveServiceImpl extends RefResolveService implements Runnable
}
private boolean toResolve(VirtualFile virtualFile, @NotNull Project project) {
if (virtualFile != null && virtualFile.isValid() &&
if (virtualFile != null &&
virtualFile.isValid() &&
project.isInitialized() &&
myProjectFileIndex.isContentSourceFile(virtualFile) &&
(virtualFile.isDirectory() || virtualFile.getFileType() == StdFileTypes.JAVA)) {
myProjectFileIndex.isInContent(virtualFile) &&
(virtualFile.isDirectory() || virtualFile.getFileType() == StdFileTypes.JAVA || virtualFile.getFileType() == StdFileTypes.XML)) {
return true;
}
@@ -286,7 +306,7 @@ public class RefResolveServiceImpl extends RefResolveService implements Runnable
synchronized (filesToResolve) {
if (!(file instanceof VirtualFileWithId)) return false;
int fileId = getAbsId(file);
countAndMarkUnresolved(file, new int[1]);
countAndMarkUnresolved(file, new THashSet<VirtualFile>(), true);
boolean alreadyAdded = fileIsInQueue.set(fileId);
if (!alreadyAdded) {
filesToResolve.add(file);
@@ -349,7 +369,11 @@ public class RefResolveServiceImpl extends RefResolveService implements Runnable
@Override
public void run() {
while (!myDisposed) {
if (!hasSomething()) {
boolean isEmpty;
synchronized (filesToResolve) {
isEmpty = filesToResolve.isEmpty();
}
if (!enabled || isEmpty) {
try {
waitForQueue();
}
@@ -407,10 +431,11 @@ public class RefResolveServiceImpl extends RefResolveService implements Runnable
}
filesToResolve.clear();
}
final Set<VirtualFile> toProcess = Collections.synchronizedSet(set);
final ConcurrentIntObjectMap<int[]> fileToForwardIds = new StripedLockIntObjectConcurrentHashMap<int[]>();
final int size = countAndMarkUnresolved(set);
if (size == 0) return;
Set<VirtualFile> files = countAndMarkUnresolved(set, false);
if (files.isEmpty()) return;
final int size = files.size();
final Set<VirtualFile> toProcess = Collections.synchronizedSet(files);
log("Started to resolve "+ size + " files (was queued "+queuedSize+")");
indicator.setIndeterminate(false);
@@ -422,31 +447,19 @@ public class RefResolveServiceImpl extends RefResolveService implements Runnable
double fraction = 1 - toProcess.size() * 1.0 / size;
indicator.setFraction(fraction);
try {
VfsUtilCore.visitChildrenRecursively(file, new VirtualFileVisitor() {
@Override
public boolean visitFile(@NotNull VirtualFile file) {
if (!toResolve(file, myProject)) {
return true;
}
int fileId = getAbsId(file);
int i = size - toProcess.size();
indicator.setText(i + "/" + size + ": Resolving " + file.getPresentableUrl());
int[] forwardIds = processFile(file, fileId, indicator);
if (forwardIds == null) {
//queueUpdate(file);
return false;
}
toProcess.remove(file);
fileToForwardIds.put(fileId, forwardIds);
return true;
}
@Nullable
@Override
public Iterable<VirtualFile> getChildrenIterable(@NotNull VirtualFile file) {
return ((NewVirtualFile)file).iterInDbChildren();
}
}, RuntimeException.class);
if (file.isDirectory() || !toResolve(file, myProject)) {
return true;
}
int fileId = getAbsId(file);
int i = size - toProcess.size();
indicator.setText(i + "/" + size + ": 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();
@@ -457,7 +470,7 @@ public class RefResolveServiceImpl extends RefResolveService implements Runnable
boolean success = true;
try {
success = JobLauncher
.getInstance().invokeConcurrentlyUnderProgress(new ArrayList<VirtualFile>(set), indicator, false, false, processor);
.getInstance().invokeConcurrentlyUnderProgress(new ArrayList<VirtualFile>(files), indicator, false, false, processor);
}
finally {
queue(toProcess, "re-added after fail. success=" + success);
@@ -472,52 +485,54 @@ public class RefResolveServiceImpl extends RefResolveService implements Runnable
return Math.abs(((VirtualFileWithId)file).getId());
}
private int countAndMarkUnresolved(@NotNull Collection<VirtualFile> files) {
final int[] count = new int[1];
@NotNull
private Set<VirtualFile> countAndMarkUnresolved(@NotNull Collection<VirtualFile> files, boolean inDbOnly) {
Set<VirtualFile> result = new THashSet<VirtualFile>();
for (VirtualFile file : files) {
countAndMarkUnresolved(file, count);
countAndMarkUnresolved(file, result, inDbOnly);
}
return count[0];
return result;
}
private void countAndMarkUnresolved(@NotNull VirtualFile file, @NotNull final int[] count) {
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, count);
doCountAndMarkUnresolved(file, result);
return true;
}
@Nullable
@Override
public Iterable<VirtualFile> getChildrenIterable(@NotNull VirtualFile file) {
return ((NewVirtualFile)file).iterInDbChildren();
return inDbOnly ? ((NewVirtualFile)file).iterInDbChildren() : null;
}
});
}
else {
doCountAndMarkUnresolved(file, count);
doCountAndMarkUnresolved(file, result);
}
}
private void doCountAndMarkUnresolved(@NotNull VirtualFile file, @NotNull int[] count) {
if (toResolve(file, myProject)) {
count[0]++;
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 boolean hasSomething() {
if (DumbService.isDumb(myProject) ||
myApplication.isWriteActionInProgress() ||
RefreshQueueImpl.isRefreshInProgress() ||
HeavyProcessLatch.INSTANCE.isRunning()) {
return false;
}
synchronized (filesToResolve) {
return !filesToResolve.isEmpty();
}
private void enable() {
enabled = true;
wakeUp();
}
private void disable() {
enabled = false;
wakeUp();
}
// returns list of resolved files if updated successfully, or null if write action or dumb mode started
@@ -623,24 +638,28 @@ public class RefResolveServiceImpl extends RefResolveService implements Runnable
public void run() {
indicator.checkCanceled();
psiFile.accept(new JavaRecursiveElementWalkingVisitor() {
@Override
public void visitReferenceElement(PsiJavaCodeReferenceElement reference) {
indicator.checkCanceled();
PsiElement element = reference.resolve();
if (element != null) {
resolved.add(element);
if (psiFile instanceof PsiJavaFile) {
psiFile.accept(new JavaRecursiveElementWalkingVisitor() {
@Override
public void visitReferenceElement(PsiJavaCodeReferenceElement reference) {
resolveReference(reference, indicator, resolved);
super.visitReferenceElement(reference);
}
refCount.incrementAndGet();
});
}
else if (psiFile instanceof XmlFile) {
psiFile.accept(new XmlRecursiveElementWalkingVisitor() {
@Override
public void visitXmlElement(XmlElement element) {
for (PsiReference reference : element.getReferences()) {
resolveReference(reference, indicator, resolved);
}
super.visitXmlElement(element);
}
});
}
super.visitReferenceElement(reference);
}
});
}
});
ApplicationUtil.tryRunReadAction(new Runnable() {
@Override
public void run() {
indicator.checkCanceled();
for (PsiElement element : resolved) {
PsiFile file = element.getContainingFile();
@@ -654,6 +673,15 @@ public class RefResolveServiceImpl extends RefResolveService implements Runnable
return forward;
}
private void resolveReference(@NotNull PsiReference reference, @NotNull ProgressIndicator indicator, @NotNull Set<PsiElement> resolved) {
indicator.checkCanceled();
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;
@@ -221,6 +221,9 @@ public class PersistentIntList implements Disposable {
capacity = 0;
}
else {
int[] oldIds = get(id);
checkSorted(oldIds);
assertPointer(pointer);
int storedListLength = data.getInt(pointer);
capacity = data.getInt(pointer + 4);
@@ -253,6 +256,9 @@ public class PersistentIntList implements Disposable {
i++;
}
}
int[] mergedInts = fromBytes(mergedBytes, outPtr);
checkSorted(mergedInts);
newListLength = outPtr / 4;
assertListLength(newListLength, newListLength);
if (newListLength <= capacity) {
@@ -274,13 +280,17 @@ public class PersistentIntList implements Disposable {
});
int[] ids = get(id);
for (int i = 1; i < ids.length; i++) {
assert ids[i] > ids[i - 1] : ids[i-1] + ", " + ids[i];
}
checkSorted(ids);
TIntHashSet set = new TIntHashSet(ids);
assert set.containsAll(values): "ids: "+Arrays.toString(ids)+";\n values:"+Arrays.toString(values);
}
private static void checkSorted(int[] oldIds) {
for (int i = 1; i < oldIds.length; i++) {
assert oldIds[i - 1] < oldIds[i] : oldIds[i-1] + ", " + oldIds[i];
}
}
private static byte[] toBytes(@NotNull int[] values) {
byte[] mergedBytes = new byte[4 * values.length];
for (int i = 0; i < values.length; i++) {
@@ -290,6 +300,16 @@ public class PersistentIntList implements Disposable {
return mergedBytes;
}
private static int[] fromBytes(@NotNull byte[] bytes, int length) {
assert length % 4 == 0;
int[] ints = new int[length/4];
for (int i = 0; i < length; i+=4) {
int value = Bits.getInt(bytes, i);
ints[i/4] = value;
}
return ints;
}
private static void storeArray(@NotNull RandomAccessDataFile data,
int storePointer,
int newListLength,
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2009 JetBrains s.r.o.
* 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.
@@ -19,19 +19,47 @@
*/
package com.intellij.util.io.storage;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.util.Disposer;
import com.intellij.util.EventDispatcher;
import org.jetbrains.annotations.NotNull;
import java.util.EventListener;
import java.util.concurrent.atomic.AtomicInteger;
public class HeavyProcessLatch {
public static final HeavyProcessLatch INSTANCE = new HeavyProcessLatch();
private int myHeavyProcessCounter = 0;
public synchronized void processStarted() {
myHeavyProcessCounter++;
private final AtomicInteger myHeavyProcessCounter = new AtomicInteger();
private final EventDispatcher<HeavyProcessListener> myEventDispatcher = EventDispatcher.create(HeavyProcessListener.class);
private HeavyProcessLatch() {
}
public synchronized void processFinished() {
myHeavyProcessCounter--;
public void processStarted() {
myHeavyProcessCounter.incrementAndGet();
myEventDispatcher.getMulticaster().processStarted();
}
public synchronized boolean isRunning() {
return myHeavyProcessCounter != 0;
public void processFinished() {
myHeavyProcessCounter.decrementAndGet();
myEventDispatcher.getMulticaster().processFinished();
}
public boolean isRunning() {
return myHeavyProcessCounter.get() != 0;
}
public interface HeavyProcessListener extends EventListener {
public void processStarted();
public void processFinished();
}
@NotNull
public Disposable addListener(@NotNull HeavyProcessListener listener) {
Disposable disposable = Disposer.newDisposable();
myEventDispatcher.addListener(listener, disposable);
return disposable;
}
}
@@ -0,0 +1,58 @@
/*
* 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.
*/
/*
* @author max
*/
package com.intellij.psi;
import java.util.List;
public class XmlRecursiveElementWalkingVisitor extends XmlElementVisitor {
private final boolean myVisitAllFileRoots;
private final PsiWalkingState myWalkingState = new PsiWalkingState(this){};
public XmlRecursiveElementWalkingVisitor() {
this(false);
}
public XmlRecursiveElementWalkingVisitor(final boolean visitAllFileRoots) {
myVisitAllFileRoots = visitAllFileRoots;
}
@Override
public void visitElement(final PsiElement element) {
myWalkingState.elementStarted(element);
}
@Override
public void visitFile(final PsiFile file) {
if (myVisitAllFileRoots) {
final FileViewProvider viewProvider = file.getViewProvider();
final List<PsiFile> allFiles = viewProvider.getAllFiles();
if (allFiles.size() > 1) {
if (file == viewProvider.getPsi(viewProvider.getBaseLanguage())) {
for (PsiFile lFile : allFiles) {
lFile.acceptChildren(this);
}
return;
}
}
}
super.visitFile(file);
}
}