blob: f24556fe5c2583cd88084078e0dc82203abf53f4 [file] [log] [blame]
/*
* Copyright 2000-2009 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;
import com.intellij.lang.ASTNode;
import com.intellij.lang.FileASTNode;
import com.intellij.lang.Language;
import com.intellij.openapi.diagnostic.Attachment;
import com.intellij.openapi.diagnostic.ExceptionWithAttachments;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.UserDataHolder;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.util.PsiUtilCore;
import com.intellij.util.ExceptionUtil;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.ref.SoftReference;
/**
* @author mike
*/
public class PsiInvalidElementAccessException extends RuntimeException implements ExceptionWithAttachments {
private static final Key<Object> INVALIDATION_TRACE = Key.create("INVALIDATION_TRACE");
private static final Key<Boolean> REPORTING_EXCEPTION = Key.create("REPORTING_EXCEPTION");
private final SoftReference<PsiElement> myElementReference; // to prevent leaks, since exceptions are stored in IdeaLogger
private final Attachment[] myDiagnostic;
private final String myMessage;
public PsiInvalidElementAccessException(PsiElement element) {
this(element, null, null);
}
public PsiInvalidElementAccessException(PsiElement element, @Nullable String message) {
this(element, message, null);
}
public PsiInvalidElementAccessException(PsiElement element, @Nullable Throwable cause) {
this(element, null, cause);
}
public PsiInvalidElementAccessException(@Nullable PsiElement element, @Nullable String message, @Nullable Throwable cause) {
super(null, cause);
myElementReference = new SoftReference<PsiElement>(element);
if (element == null) {
myMessage = message;
myDiagnostic = Attachment.EMPTY_ARRAY;
} else if (element == PsiUtilCore.NULL_PSI_ELEMENT) {
myMessage = "NULL_PSI_ELEMENT ;" + message;
myDiagnostic = Attachment.EMPTY_ARRAY;
} else {
boolean recursiveInvocation = Boolean.TRUE.equals(element.getUserData(REPORTING_EXCEPTION));
element.putUserData(REPORTING_EXCEPTION, Boolean.TRUE);
try {
Object trace = recursiveInvocation ? null : findInvalidationTrace(element.getNode());
myMessage = getMessageWithReason(element, message, recursiveInvocation, trace);
if (trace == null) {
myDiagnostic = Attachment.EMPTY_ARRAY;
} else {
String diagnostic = trace instanceof Throwable ? ExceptionUtil.getThrowableText((Throwable)trace) : trace.toString();
myDiagnostic = new Attachment[]{new Attachment("diagnostic.txt", diagnostic)};
}
}
finally {
element.putUserData(REPORTING_EXCEPTION, null);
}
}
}
private static String getMessageWithReason(@NotNull PsiElement element, @Nullable String message, boolean recursiveInvocation, @Nullable Object trace) {
String reason = "Element: " + element.getClass();
if (!recursiveInvocation) {
String traceText = !isTrackingInvalidation() ? "disabled" :
trace != null ? "see attachment" :
"no info";
reason += " because: " + reason(element) + "\ninvalidated at: " + traceText;
}
return reason + (message == null ? "" : "; " + message);
}
@Override
public String getMessage() {
return myMessage;
}
@NotNull
@Override
public Attachment[] getAttachments() {
return myDiagnostic;
}
public static Object findInvalidationTrace(@Nullable ASTNode element) {
while (element != null) {
Object trace = element.getUserData(INVALIDATION_TRACE);
if (trace != null) {
return trace;
}
ASTNode parent = element.getTreeParent();
if (parent == null && element instanceof FileASTNode) {
PsiElement psi = element.getPsi();
trace = psi == null ? null : psi.getUserData(INVALIDATION_TRACE);
if (trace != null) {
return trace;
}
}
element = parent;
}
return null;
}
@NonNls
@NotNull
private static String reason(@NotNull PsiElement root){
if (root == PsiUtilCore.NULL_PSI_ELEMENT) return "NULL_PSI_ELEMENT";
PsiElement element = root instanceof PsiFile ? root : root.getParent();
if (element == null) return "parent is null";
while (element != null && !(element instanceof PsiFile) && element.getParent() != null) {
element = element.getParent();
}
PsiFile file = element instanceof PsiFile ? (PsiFile)element : null;
if (file == null) return "containing file is null";
FileViewProvider provider = file.getViewProvider();
VirtualFile vFile = provider.getVirtualFile();
if (!vFile.isValid()) return vFile+" is invalid";
if (!provider.isPhysical()) {
PsiElement context = file.getContext();
if (context != null && !context.isValid()) {
return "invalid context: " + reason(context);
}
}
PsiManager manager = file.getManager();
if (manager.getProject().isDisposed()) return "project is disposed";
Language language = file.getLanguage();
if (language != provider.getBaseLanguage()) return "File language:"+language+" != Provider base language:"+provider.getBaseLanguage();
FileViewProvider provider1 = manager.findViewProvider(vFile);
if (provider != provider1) return "different providers: "+provider+"("+Integer.toHexString(System.identityHashCode(provider))+"); "+provider1+"("+Integer.toHexString(System.identityHashCode(provider1))+")";
if (!provider.isPhysical()) {
return "non-physical provider: " + provider; // "dummy" file?
}
return "psi is outdated";
}
public static void setInvalidationTrace(UserDataHolder element, Object trace) {
element.putUserData(INVALIDATION_TRACE, trace);
}
public static boolean isTrackingInvalidation() {
return Registry.is("psi.track.invalidation");
}
@Nullable
public PsiElement getPsiElement() {
return myElementReference.get();
}
}