/*
* Copyright 2000-2016 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.impl.smartPointers;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.editor.event.DocumentEvent;
import com.intellij.openapi.editor.impl.FrozenDocument;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.LowMemoryWatcher;
import com.intellij.openapi.util.Segment;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiElement;
import com.intellij.util.CommonProcessors;
import com.intellij.util.Processor;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.*;
class SmartPointerTracker {
private static final ReferenceQueue<SmartPsiElementPointerImpl> ourQueue = new ReferenceQueue<>();
private int nextAvailableIndex;
private int size;
private PointerReference[] references = new PointerReference[10];
private final MarkerCache markerCache = new MarkerCache(this);
private boolean mySorted;
static {
LowMemoryWatcher.register(() -> processQueue(), ApplicationManager.getApplication());
}
synchronized boolean addReference(@NotNull PointerReference reference, @NotNull SmartPsiElementPointerImpl pointer) {
if (!isActual(reference.file, reference.key)) {
// this pointer list has been removed by another thread; clients should get/create an up-to-date list and try adding to it
return false;
}
if (needsExpansion() || isTooSparse()) {
resize();
assert isActual(reference.file, reference.key);
}
assert references[nextAvailableIndex] == null : references[nextAvailableIndex];
storePointerReference(references, nextAvailableIndex++, reference);
size++;
mySorted = false;
if (((SelfElementInfo)pointer.getElementInfo()).hasRange()) {
markerCache.rangeChanged();
}
return true;
}
boolean isActual(VirtualFile file, Key<SmartPointerTracker> key) {
return file.getUserData(key) == this;
}
private boolean needsExpansion() {
return nextAvailableIndex >= references.length;
}
private boolean isTooSparse() {
return nextAvailableIndex > size * 2;
}
private void resize() {
PointerReference[] newReferences = new PointerReference[size * 3 / 2 + 1];
int index = 0;
// don't use processAlivePointers/removeReference since it can unregister the whole pointer list, and we're not prepared to that
for (PointerReference ref : references) {
if (ref != null) {
storePointerReference(newReferences, index++, ref);
}
}
assert index == size : index + " != " + size;
references = newReferences;
nextAvailableIndex = index;
}
synchronized void removeReference(@NotNull PointerReference reference, @NotNull Key<SmartPointerTracker> expectedKey) {
int index = reference.index;
if (index < 0) return;
assertActual(expectedKey, reference.file, reference.key);
assert references[index] == reference : "At " + index + " expected " + reference + ", found " + references[index];
references[index].index = -1;
references[index] = null;
if (--size == 0) {
reference.file.replace(reference.key, this, null);
}
}
private void assertActual(Key<SmartPointerTracker> expectedKey, VirtualFile file, Key<SmartPointerTracker> refKey) {
assert isActual(file, refKey) : "Smart pointer list mismatch mismatch:" +
" ref.key=" + expectedKey +
", manager.key=" + refKey +
(file.getUserData(refKey) != null ? "; has another pointer list" : "");
}
private void processAlivePointers(@NotNull Processor<SmartPsiElementPointerImpl> processor) {
for (int i = 0; i < nextAvailableIndex; i++) {
PointerReference ref = references[i];
if (ref == null) continue;
assert isActual(ref.file, ref.key);
SmartPsiElementPointerImpl pointer = ref.get();
if (pointer == null) {
removeReference(ref, ref.key);
continue;
}
if (!processor.process(pointer)) {
return;
}
}
}
private void ensureSorted() {
if (!mySorted) {
List<SmartPsiElementPointerImpl> pointers = new ArrayList<>();
processAlivePointers(new CommonProcessors.CollectProcessor<>(pointers));
assert size == pointers.size();
pointers
.sort((p1, p2) -> MarkerCache.INFO_COMPARATOR.compare((SelfElementInfo)p1.getElementInfo(), (SelfElementInfo)p2.getElementInfo()));
for (int i = 0; i < pointers.size(); i++) {
storePointerReference(references, i, pointers.get(i).pointerReference);
}
Arrays.fill(references, pointers.size(), nextAvailableIndex, null);
nextAvailableIndex = pointers.size();
mySorted = true;
}
}
synchronized void updateMarkers(FrozenDocument frozen, List<DocumentEvent> events) {
boolean stillSorted = markerCache.updateMarkers(frozen, events);
if (!stillSorted) {
mySorted = false;
}
}
@Nullable
synchronized Segment getUpdatedRange(SelfElementInfo info, FrozenDocument document, List<DocumentEvent> events) {
return markerCache.getUpdatedRange(info, document, events);
}
synchronized void switchStubToAst(AnchorElementInfo info, PsiElement element) {
info.switchToTreeRange(element);
markerCache.rangeChanged();
mySorted = false;
}
synchronized void fastenBelts() {
processQueue();
processAlivePointers(pointer -> {
pointer.getElementInfo().fastenBelt();
return true;
});
}
synchronized void updatePointerTargetsAfterReparse() {
processAlivePointers(pointer -> {
if (!(pointer instanceof SmartPsiFileRangePointerImpl)) {
updatePointerTarget(pointer, pointer.getPsiRange());
}
return true;
});
}
// after reparse and its complex tree diff, the element might have "moved" to other range
// but if an element of the same type can still be found at the old range, let's point there
private static <E extends PsiElement> void updatePointerTarget(@NotNull SmartPsiElementPointerImpl<E> pointer, @Nullable Segment pointerRange) {
E cachedElement = pointer.getCachedElement();
if (cachedElement == null || cachedElement.isValid() && pointerRange != null && pointerRange.equals(cachedElement.getTextRange())) {
return;
}
pointer.cacheElement(pointer.doRestoreElement());
}
private static void storePointerReference(PointerReference[] references, int index, PointerReference ref) {
references[index] = ref;
ref.index = index;
}
synchronized List<SelfElementInfo> getSortedInfos() {
ensureSorted();
final List<SelfElementInfo> infos = ContainerUtil.newArrayListWithCapacity(size);
processAlivePointers(pointer -> {
SelfElementInfo info = (SelfElementInfo)pointer.getElementInfo();
if (!info.hasRange()) return false;
infos.add(info);
return true;
});
return infos;
}
@TestOnly
synchronized int getSize() {
return size;
}
static class PointerReference extends WeakReference<SmartPsiElementPointerImpl> {
@NotNull private final VirtualFile file;
@NotNull private final Key<SmartPointerTracker> key;
private int index = -2;
PointerReference(@NotNull SmartPsiElementPointerImpl<?> pointer,
@NotNull VirtualFile containingFile,
@NotNull Key<SmartPointerTracker> key) {
super(pointer, ourQueue);
file = containingFile;
this.key = key;
pointer.pointerReference = this;
}
}
static void processQueue() {
while (true) {
PointerReference reference = (PointerReference)ourQueue.poll();
if (reference == null) break;
SmartPointerTracker pointers = reference.file.getUserData(reference.key);
if (pointers != null) {
pointers.removeReference(reference, reference.key);
}
}
}
}