blob: 748a98ead7de73b8605075e9713761cf31926871 [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.impl.smartPointers;
import com.intellij.lang.Language;
import com.intellij.lang.LanguageUtil;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.RangeMarker;
import com.intellij.openapi.fileEditor.FileDocumentManager;
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.util.PsiUtilCore;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.ref.Reference;
import java.lang.ref.SoftReference;
/**
* User: cdr
*/
public class SelfElementInfo implements SmartPointerElementInfo {
protected final VirtualFile myVirtualFile;
private Reference<RangeMarker> myMarkerRef; // create marker only in case of live document
private int mySyncStartOffset;
private int mySyncEndOffset;
protected boolean mySyncMarkerIsValid;
private final Class myType;
protected final Project myProject;
@SuppressWarnings({"UnusedDeclaration"})
private RangeMarker myRangeMarker; //maintain hard reference during modification
protected final Language myLanguage;
protected SelfElementInfo(@NotNull Project project, @NotNull PsiElement anchor) {
this(project, ProperTextRange.create(anchor.getTextRange()), anchor.getClass(), anchor.getContainingFile(),
LanguageUtil.getRootLanguage(anchor));
}
public SelfElementInfo(@NotNull Project project,
@NotNull ProperTextRange range,
@NotNull Class anchorClass,
@NotNull PsiFile containingFile,
@NotNull Language language) {
myLanguage = language;
myVirtualFile = PsiUtilCore.getVirtualFile(containingFile);
myType = anchorClass;
assert !PsiFile.class.isAssignableFrom(anchorClass) : "FileElementInfo must be used for files";
myProject = project;
PsiDocumentManager documentManager = PsiDocumentManager.getInstance(myProject);
Document document = documentManager.getCachedDocument(containingFile);
if (document != null && documentManager.isUncommited(document)) {
mySyncMarkerIsValid = false;
}
else {
mySyncMarkerIsValid = true;
setRange(range);
}
}
protected void setRange(@NotNull Segment range) {
mySyncStartOffset = range.getStartOffset();
mySyncEndOffset = range.getEndOffset();
}
@Override
public Document getDocumentToSynchronize() {
RangeMarker marker = getMarker();
if (marker != null) {
return marker.getDocument();
}
return myVirtualFile == null ? null : FileDocumentManager.getInstance().getCachedDocument(myVirtualFile);
}
// before change
@Override
public void fastenBelt(int offset, @Nullable RangeMarker[] cachedRangeMarkers) {
if (!mySyncMarkerIsValid) return;
RangeMarker marker = getMarker();
int actualEndOffset = marker == null || !marker.isValid() ? getSyncEndOffset() : marker.getEndOffset();
if (offset > actualEndOffset) {
return; // no need to update, the change is far after
}
if (marker == null) {
Document document = myVirtualFile == null ? null : FileDocumentManager.getInstance().getDocument(myVirtualFile);
if (document == null) {
mySyncMarkerIsValid = false;
}
else {
int start = Math.min(getSyncStartOffset(), document.getTextLength());
int end = Math.min(Math.max(getSyncEndOffset(), start), document.getTextLength());
// use supplied cached markers if available
if (cachedRangeMarkers != null) {
for (RangeMarker cachedRangeMarker : cachedRangeMarkers) {
if (cachedRangeMarker.isValid() &&
cachedRangeMarker.getStartOffset() == start &&
cachedRangeMarker.getEndOffset() == end) {
marker = cachedRangeMarker;
break;
}
}
}
else {
marker = document.createRangeMarker(start, end, true);
}
}
setMarker(marker);
}
else if (!marker.isValid()) {
mySyncMarkerIsValid = false;
setMarker(null);
marker = null;
}
myRangeMarker = marker; //make sure marker wont be gced
}
// after change
@Override
public void unfastenBelt(int offset) {
if (!mySyncMarkerIsValid) return;
RangeMarker marker = getMarker();
if (marker != null) {
if (marker.isValid()) {
setRange(marker);
assert mySyncEndOffset <= marker.getDocument().getTextLength() : "mySyncEndOffset: "+mySyncEndOffset+"; docLength: "+marker.getDocument().getTextLength()+"; marker: "+marker +"; "+marker.getClass();
}
else {
mySyncMarkerIsValid = false;
}
}
myRangeMarker = null; // clear hard ref to avoid leak, hold soft ref for not recreating marker later
}
@Override
public PsiElement restoreElement() {
if (!mySyncMarkerIsValid) return null;
PsiFile file = restoreFile();
if (file == null || !file.isValid()) return null;
return restoreFromFile(file);
}
protected PsiElement restoreFromFile(@NotNull PsiFile file) {
final int syncStartOffset = getSyncStartOffset();
final int syncEndOffset = getSyncEndOffset();
return findElementInside(file, syncStartOffset, syncEndOffset, myType, myLanguage);
}
@Override
public PsiFile restoreFile() {
return restoreFileFromVirtual(myVirtualFile, myProject, myLanguage);
}
protected static PsiElement findElementInside(@NotNull PsiFile file,
int syncStartOffset,
int syncEndOffset,
@NotNull Class type,
@NotNull Language language) {
PsiElement anchor = file.getViewProvider().findElementAt(syncStartOffset, language);
if (anchor == null) return null;
TextRange range = anchor.getTextRange();
if (range.getStartOffset() != syncStartOffset) return null;
while (range.getEndOffset() < syncEndOffset) {
anchor = anchor.getParent();
if (anchor == null || anchor.getTextRange() == null) break;
range = anchor.getTextRange();
}
while (range.getEndOffset() == syncEndOffset && anchor != null && !type.equals(anchor.getClass())) {
anchor = anchor.getParent();
if (anchor == null || anchor.getTextRange() == null) break;
range = anchor.getTextRange();
}
return range.getEndOffset() == syncEndOffset ? anchor : null;
}
private RangeMarker getMarker() {
return com.intellij.reference.SoftReference.dereference(myMarkerRef);
}
@Override
public void cleanup() {
RangeMarker marker = getMarker();
if (marker != null) marker.dispose();
unfastenBelt(0);
setMarker(null);
mySyncMarkerIsValid = false;
}
private void setMarker(RangeMarker marker) {
myMarkerRef = marker == null ? null : new SoftReference<RangeMarker>(marker);
}
@Nullable
public static PsiFile restoreFileFromVirtual(final VirtualFile virtualFile, @NotNull final Project project) {
return restoreFileFromVirtual(virtualFile, project, null);
}
@Nullable
public static PsiFile restoreFileFromVirtual(final VirtualFile virtualFile, @NotNull final Project project, @Nullable final Language language) {
if (virtualFile == null) return null;
return ApplicationManager.getApplication().runReadAction(new NullableComputable<PsiFile>() {
@Override
public PsiFile compute() {
VirtualFile child;
if (virtualFile.isValid()) {
child = virtualFile;
}
else {
VirtualFile vParent = virtualFile.getParent();
if (vParent == null || !vParent.isDirectory()) return null;
String name = virtualFile.getName();
child = vParent.findChild(name);
}
if (child == null || !child.isValid()) return null;
PsiFile file = PsiManager.getInstance(project).findFile(child);
if (file != null && language != null) {
return file.getViewProvider().getPsi(language);
}
return file;
}
});
}
@Nullable
public static PsiDirectory restoreDirectoryFromVirtual(final VirtualFile virtualFile, @NotNull final Project project) {
if (virtualFile == null) return null;
return ApplicationManager.getApplication().runReadAction(new Computable<PsiDirectory>() {
@Override
public PsiDirectory compute() {
VirtualFile child;
if (virtualFile.isValid()) {
child = virtualFile;
}
else {
VirtualFile vParent = virtualFile.getParent();
if (vParent == null || !vParent.isDirectory()) return null;
String name = virtualFile.getName();
child = vParent.findChild(name);
}
if (child == null || !child.isValid()) return null;
PsiDirectory file = PsiManager.getInstance(project).findDirectory(child);
if (file == null || !file.isValid()) return null;
return file;
}
});
}
protected int getSyncEndOffset() {
return mySyncEndOffset;
}
protected int getSyncStartOffset() {
return mySyncStartOffset;
}
@Override
public int elementHashCode() {
VirtualFile virtualFile = myVirtualFile;
return virtualFile == null ? 0 : virtualFile.hashCode();
}
@Override
public boolean pointsToTheSameElementAs(@NotNull SmartPointerElementInfo other) {
if (other instanceof SelfElementInfo) {
SelfElementInfo otherInfo = (SelfElementInfo)other;
return Comparing.equal(myVirtualFile, otherInfo.myVirtualFile)
&& myType == otherInfo.myType
&& mySyncMarkerIsValid
&& otherInfo.mySyncMarkerIsValid
&& mySyncStartOffset == otherInfo.mySyncStartOffset
&& mySyncEndOffset == otherInfo.mySyncEndOffset
;
}
return Comparing.equal(restoreElement(), other.restoreElement());
}
@Override
public VirtualFile getVirtualFile() {
return myVirtualFile;
}
@Override
public Segment getRange() {
if (!mySyncMarkerIsValid) return null;
return new TextRange(getSyncStartOffset(), getSyncEndOffset());
}
@NotNull
@Override
public Project getProject() {
return myProject;
}
}