/*
* 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.lang.LanguageUtil;
import com.intellij.lang.injection.InjectedLanguageManager;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.*;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.impl.FreeThreadedFileViewProvider;
import com.intellij.psi.impl.PsiManagerEx;
import com.intellij.psi.impl.source.PsiFileImpl;
import com.intellij.psi.impl.source.tree.ForeignLeafPsiElement;
import com.intellij.psi.tree.IStubFileElementType;
import com.intellij.psi.util.PsiTreeUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.ref.Reference;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
class SmartPsiElementPointerImpl<E extends PsiElement> implements SmartPointerEx<E> {
private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.smartPointers.SmartPsiElementPointerImpl");
private Reference<E> myElement;
private final SmartPointerElementInfo myElementInfo;
private byte myReferenceCount = 1;
@Nullable SmartPointerTracker.PointerReference pointerReference;
SmartPsiElementPointerImpl(@NotNull Project project, @NotNull E element, @Nullable PsiFile containingFile, boolean forInjected) {
this(element, createElementInfo(project, element, containingFile, forInjected));
}
SmartPsiElementPointerImpl(@NotNull E element,
@NotNull SmartPointerElementInfo elementInfo) {
ApplicationManager.getApplication().assertReadAccessAllowed();
myElementInfo = elementInfo;
cacheElement(element);
}
@Override
public boolean equals(Object obj) {
return obj instanceof SmartPsiElementPointer && pointsToTheSameElementAs(this, (SmartPsiElementPointer)obj);
}
@Override
public int hashCode() {
return myElementInfo.elementHashCode();
}
@Override
@NotNull
public Project getProject() {
return myElementInfo.getProject();
}
@Override
@Nullable
public E getElement() {
E element = getCachedElement();
if (element == null || !element.isValid()) {
element = doRestoreElement();
cacheElement(element);
}
return element;
}
@Nullable
E doRestoreElement() {
//noinspection unchecked
E element = (E)myElementInfo.restoreElement();
if (element != null && !element.isValid()) {
return null;
}
return element;
}
void cacheElement(@Nullable E element) {
myElement = element == null ? null :
PsiManagerEx.getInstanceEx(getProject()).isBatchFilesProcessingMode() ? new WeakReference<>(element) :
new SoftReference<>(element);
}
@Override
public E getCachedElement() {
return com.intellij.reference.SoftReference.dereference(myElement);
}
@Override
public PsiFile getContainingFile() {
PsiFile file = getElementInfo().restoreFile();
if (file != null) {
return file;
}
final Document doc = myElementInfo.getDocumentToSynchronize();
if (doc == null) {
final E resolved = getElement();
return resolved == null ? null : resolved.getContainingFile();
}
return PsiDocumentManager.getInstance(getProject()).getPsiFile(doc);
}
@Override
public VirtualFile getVirtualFile() {
return myElementInfo.getVirtualFile();
}
@Override
public Segment getRange() {
return myElementInfo.getRange();
}
@Nullable
@Override
public Segment getPsiRange() {
return myElementInfo.getPsiRange();
}
@NotNull
private static <E extends PsiElement> SmartPointerElementInfo createElementInfo(@NotNull Project project,
@NotNull E element,
PsiFile containingFile, boolean forInjected) {
SmartPointerElementInfo elementInfo = doCreateElementInfo(project, element, containingFile, forInjected);
if (ApplicationManager.getApplication().isUnitTestMode()) {
PsiElement restored = elementInfo.restoreElement();
if (!element.equals(restored)) {
// likely cause: PSI having isPhysical==true, but which can't be restored by containing file and range. To fix, make isPhysical return false
LOG.error("Cannot restore " + element + " of " + element.getClass() + " from " + elementInfo + "; restored=" + restored + " in " + element.getProject());
}
}
return elementInfo;
}
@NotNull
private static <E extends PsiElement> SmartPointerElementInfo doCreateElementInfo(@NotNull Project project,
@NotNull E element,
PsiFile containingFile, boolean forInjected) {
if (element instanceof PsiDirectory) {
return new DirElementInfo((PsiDirectory)element);
}
if (element instanceof PsiCompiledElement || containingFile == null || !containingFile.isPhysical() || !element.isPhysical()) {
if (element instanceof StubBasedPsiElement && element instanceof PsiCompiledElement) {
if (element instanceof PsiFile) {
return new FileElementInfo((PsiFile)element);
}
PsiAnchor.StubIndexReference stubReference = PsiAnchor.createStubReference(element, containingFile);
if (stubReference != null) {
return new ClsElementInfo(stubReference);
}
}
return new HardElementInfo(project, element);
}
FileViewProvider viewProvider = containingFile.getViewProvider();
if (viewProvider instanceof FreeThreadedFileViewProvider) {
PsiLanguageInjectionHost hostContext = InjectedLanguageManager.getInstance(containingFile.getProject()).getInjectionHost(containingFile);
TextRange elementRange = element.getTextRange();
if (hostContext != null && elementRange != null) {
SmartPsiElementPointer<PsiLanguageInjectionHost> hostPointer = SmartPointerManager.getInstance(project).createSmartPsiElementPointer(hostContext);
return new InjectedSelfElementInfo(project, element, elementRange, containingFile, hostPointer);
}
}
SmartPointerElementInfo info = createAnchorInfo(element, containingFile);
if (info != null) {
return info;
}
if (element instanceof PsiFile) {
return new FileElementInfo((PsiFile)element);
}
TextRange elementRange = element.getTextRange();
if (elementRange == null) {
return new HardElementInfo(project, element);
}
if (elementRange.isEmpty() && PsiTreeUtil.findChildOfType(element, ForeignLeafPsiElement.class) != null) {
// PSI built on C-style macro expansions. It has empty ranges, no text, but complicated structure. It can't be reliably
// restored by just one offset in a file, so hold it on a hard reference
return new HardElementInfo(project, element);
}
ProperTextRange proper = ProperTextRange.create(elementRange);
return new SelfElementInfo(project, proper, Identikit.fromPsi(element, LanguageUtil.getRootLanguage(element)), containingFile, forInjected);
}
@Nullable
private static SmartPointerElementInfo createAnchorInfo(@NotNull PsiElement element, @NotNull PsiFile containingFile) {
if (element instanceof StubBasedPsiElement && containingFile instanceof PsiFileImpl) {
IStubFileElementType stubType = ((PsiFileImpl)containingFile).getElementTypeForStubBuilder();
if (stubType != null && stubType.shouldBuildStubFor(containingFile.getViewProvider().getVirtualFile())) {
StubBasedPsiElement stubPsi = (StubBasedPsiElement)element;
int stubId = PsiAnchor.calcStubIndex(stubPsi);
if (stubId != -1) {
return new AnchorElementInfo(element, (PsiFileImpl)containingFile, stubId, stubPsi.getElementType());
}
}
}
Pair<Identikit.ByAnchor, PsiElement> pair = Identikit.withAnchor(element, LanguageUtil.getRootLanguage(containingFile));
if (pair != null) {
return new AnchorElementInfo(pair.second, containingFile, pair.first);
}
return null;
}
@NotNull
SmartPointerElementInfo getElementInfo() {
return myElementInfo;
}
static boolean pointsToTheSameElementAs(@NotNull SmartPsiElementPointer pointer1, @NotNull SmartPsiElementPointer pointer2) {
if (pointer1 == pointer2) return true;
if (pointer1 instanceof SmartPsiElementPointerImpl && pointer2 instanceof SmartPsiElementPointerImpl) {
SmartPsiElementPointerImpl impl1 = (SmartPsiElementPointerImpl)pointer1;
SmartPsiElementPointerImpl impl2 = (SmartPsiElementPointerImpl)pointer2;
SmartPointerElementInfo elementInfo1 = impl1.getElementInfo();
SmartPointerElementInfo elementInfo2 = impl2.getElementInfo();
if (!elementInfo1.pointsToTheSameElementAs(elementInfo2)) return false;
PsiElement cachedElement1 = impl1.getCachedElement();
PsiElement cachedElement2 = impl2.getCachedElement();
return cachedElement1 == null || cachedElement2 == null || Comparing.equal(cachedElement1, cachedElement2);
}
return Comparing.equal(pointer1.getElement(), pointer2.getElement());
}
synchronized int incrementAndGetReferenceCount(int delta) {
if (myReferenceCount == Byte.MAX_VALUE) return Byte.MAX_VALUE; // saturated
if (myReferenceCount == 0) return -1; // disposed, not to be reused again
return myReferenceCount += delta;
}
@Override
public String toString() {
return myElementInfo.toString();
}
}