/*
* Copyright 2000-2015 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.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.event.DocumentEvent;
import com.intellij.openapi.editor.impl.FrozenDocument;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.ProperTextRange;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.impl.PsiDocumentManagerBase;
import com.intellij.psi.util.PsiUtilCore;
import com.intellij.reference.SoftReference;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;
import java.lang.ref.Reference;
import java.util.List;
public class SmartPointerManagerImpl extends SmartPointerManager {
private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.smartPointers.SmartPointerManagerImpl");
private final Project myProject;
private final Key<SmartPointerTracker> POINTERS_KEY;
private final PsiDocumentManagerBase myPsiDocManager;
public SmartPointerManagerImpl(Project project) {
myProject = project;
myPsiDocManager = (PsiDocumentManagerBase)PsiDocumentManager.getInstance(myProject);
POINTERS_KEY = Key.create("SMART_POINTERS for "+project);
}
public void fastenBelts(@NotNull VirtualFile file) {
SmartPointerTracker pointers = getTracker(file);
if (pointers != null) pointers.fastenBelts();
}
private static final Key<Reference<SmartPsiElementPointerImpl>> CACHED_SMART_POINTER_KEY = Key.create("CACHED_SMART_POINTER_KEY");
@Override
@NotNull
public <E extends PsiElement> SmartPsiElementPointer<E> createSmartPsiElementPointer(@NotNull E element) {
ApplicationManager.getApplication().assertReadAccessAllowed();
PsiFile containingFile = element.getContainingFile();
return createSmartPsiElementPointer(element, containingFile);
}
@Override
@NotNull
public <E extends PsiElement> SmartPsiElementPointer<E> createSmartPsiElementPointer(@NotNull E element, PsiFile containingFile) {
return createSmartPsiElementPointer(element, containingFile, false);
}
@NotNull
public <E extends PsiElement> SmartPsiElementPointer<E> createSmartPsiElementPointer(@NotNull E element,
PsiFile containingFile,
boolean forInjected) {
if (containingFile != null && !containingFile.isValid() || containingFile == null && !element.isValid()) {
PsiUtilCore.ensureValid(element);
LOG.error("Invalid element:" + element);
}
SmartPointerTracker.processQueue();
SmartPsiElementPointerImpl<E> pointer = getCachedPointer(element);
if (pointer != null && pointer.incrementAndGetReferenceCount(1) > 0) {
return pointer;
}
pointer = new SmartPsiElementPointerImpl<>(myProject, element, containingFile, forInjected);
if (containingFile != null) {
trackPointer(pointer, containingFile.getViewProvider().getVirtualFile());
}
element.putUserData(CACHED_SMART_POINTER_KEY, new SoftReference<>(pointer));
return pointer;
}
private static <E extends PsiElement> SmartPsiElementPointerImpl<E> getCachedPointer(@NotNull E element) {
Reference<SmartPsiElementPointerImpl> data = element.getUserData(CACHED_SMART_POINTER_KEY);
SmartPsiElementPointerImpl cachedPointer = SoftReference.dereference(data);
if (cachedPointer != null) {
PsiElement cachedElement = cachedPointer.getElement();
if (cachedElement == null || cachedElement != element) {
return null;
}
}
//noinspection unchecked
return cachedPointer;
}
@Override
@NotNull
public SmartPsiFileRange createSmartPsiFileRangePointer(@NotNull PsiFile file, @NotNull TextRange range) {
return createSmartPsiFileRangePointer(file, range, false);
}
@NotNull
public SmartPsiFileRange createSmartPsiFileRangePointer(@NotNull PsiFile file,
@NotNull TextRange range,
boolean forInjected) {
PsiUtilCore.ensureValid(file);
SmartPointerTracker.processQueue();
SmartPsiFileRangePointerImpl pointer = new SmartPsiFileRangePointerImpl(file, ProperTextRange.create(range), forInjected);
trackPointer(pointer, file.getViewProvider().getVirtualFile());
return pointer;
}
private <E extends PsiElement> void trackPointer(@NotNull SmartPsiElementPointerImpl<E> pointer, @NotNull VirtualFile containingFile) {
SmartPointerElementInfo info = pointer.getElementInfo();
if (!(info instanceof SelfElementInfo)) return;
SmartPointerTracker.PointerReference reference = new SmartPointerTracker.PointerReference(pointer, containingFile, POINTERS_KEY);
while (true) {
SmartPointerTracker pointers = getTracker(containingFile);
if (pointers == null) {
pointers = containingFile.putUserDataIfAbsent(POINTERS_KEY, new SmartPointerTracker());
}
if (pointers.addReference(reference, pointer)) {
break;
}
}
}
@Override
public void removePointer(@NotNull SmartPsiElementPointer pointer) {
if (!(pointer instanceof SmartPsiElementPointerImpl) || myProject.isDisposed()) {
return;
}
PsiFile containingFile = pointer.getContainingFile();
int refCount = ((SmartPsiElementPointerImpl)pointer).incrementAndGetReferenceCount(-1);
if (refCount == -1) {
LOG.error("Double smart pointer removal: " + pointer);
return;
}
if (refCount == 0) {
PsiElement element = ((SmartPointerEx)pointer).getCachedElement();
if (element != null) {
element.putUserData(CACHED_SMART_POINTER_KEY, null);
}
SmartPointerElementInfo info = ((SmartPsiElementPointerImpl)pointer).getElementInfo();
info.cleanup();
if (containingFile == null) return;
assert containingFile.getProject() == myProject : "Project mismatch: expected " + myProject + ", got " + containingFile.getProject();
VirtualFile vFile = containingFile.getViewProvider().getVirtualFile();
SmartPointerTracker pointers = getTracker(vFile);
SmartPointerTracker.PointerReference reference = ((SmartPsiElementPointerImpl)pointer).pointerReference;
if (pointers != null && reference != null) {
pointers.removeReference(reference, POINTERS_KEY);
}
}
}
@Nullable
SmartPointerTracker getTracker(@NotNull VirtualFile containingFile) {
return containingFile.getUserData(POINTERS_KEY);
}
@TestOnly
public int getPointersNumber(@NotNull PsiFile containingFile) {
VirtualFile file = containingFile.getViewProvider().getVirtualFile();
SmartPointerTracker pointers = getTracker(file);
return pointers == null ? 0 : pointers.getSize();
}
@Override
public boolean pointToTheSameElement(@NotNull SmartPsiElementPointer pointer1, @NotNull SmartPsiElementPointer pointer2) {
return SmartPsiElementPointerImpl.pointsToTheSameElementAs(pointer1, pointer2);
}
public void updatePointers(Document document, FrozenDocument frozen, List<DocumentEvent> events) {
VirtualFile file = FileDocumentManager.getInstance().getFile(document);
SmartPointerTracker list = file == null ? null : getTracker(file);
if (list != null) list.updateMarkers(frozen, events);
}
public void updatePointerTargetsAfterReparse(@NotNull VirtualFile file) {
SmartPointerTracker list = getTracker(file);
if (list != null) list.updatePointerTargetsAfterReparse();
}
Project getProject() {
return myProject;
}
PsiDocumentManagerBase getPsiDocumentManager() {
return myPsiDocManager;
}
}