blob: ed150d588c3c41f40aaecb7b8064dc19710b1658 [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.debugger.engine;
import com.intellij.debugger.DebuggerBundle;
import com.intellij.debugger.DebuggerContext;
import com.intellij.debugger.engine.evaluation.EvaluateException;
import com.intellij.debugger.engine.evaluation.EvaluateExceptionUtil;
import com.intellij.debugger.engine.evaluation.EvaluationContext;
import com.intellij.debugger.engine.evaluation.TextWithImports;
import com.intellij.execution.ExecutionException;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.fileTypes.LanguageFileType;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.LanguageLevelProjectExtension;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.ClassUtil;
import com.intellij.psi.util.InheritanceUtil;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.StringBuilderSpinAllocator;
import com.sun.jdi.*;
import org.jdom.Element;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
public abstract class DebuggerUtils {
private static final Logger LOG = Logger.getInstance("#com.intellij.debugger.engine.DebuggerUtils");
private static final Key<Method> TO_STRING_METHOD_KEY = new Key<Method>("CachedToStringMethod");
public static final Set<String> ourPrimitiveTypeNames = new HashSet<String>(Arrays.asList(
"byte", "short", "int", "long", "float", "double", "boolean", "char"
));
public static void cleanupAfterProcessFinish(DebugProcess debugProcess) {
debugProcess.putUserData(TO_STRING_METHOD_KEY, null);
}
@NonNls
public static String getValueAsString(final EvaluationContext evaluationContext, Value value) throws EvaluateException {
try {
if (value == null) {
return "null";
}
if (value instanceof StringReference) {
return ((StringReference)value).value();
}
if (isInteger(value)) {
long v = ((PrimitiveValue)value).longValue();
return String.valueOf(v);
}
if (isNumeric(value)) {
double v = ((PrimitiveValue)value).doubleValue();
return String.valueOf(v);
}
if (value instanceof BooleanValue) {
boolean v = ((PrimitiveValue)value).booleanValue();
return String.valueOf(v);
}
if (value instanceof CharValue) {
char v = ((PrimitiveValue)value).charValue();
return String.valueOf(v);
}
if (value instanceof ObjectReference) {
if (value instanceof ArrayReference) {
final StringBuilder builder = new StringBuilder();
builder.append("[");
for (Iterator<Value> iterator = ((ArrayReference)value).getValues().iterator(); iterator.hasNext();) {
final Value element = iterator.next();
builder.append(getValueAsString(evaluationContext, element));
if (iterator.hasNext()) {
builder.append(",");
}
}
builder.append("]");
return builder.toString();
}
final ObjectReference objRef = (ObjectReference)value;
final DebugProcess debugProcess = evaluationContext.getDebugProcess();
Method toStringMethod = debugProcess.getUserData(TO_STRING_METHOD_KEY);
if (toStringMethod == null) {
try {
ReferenceType refType = objRef.virtualMachine().classesByName(CommonClassNames.JAVA_LANG_OBJECT).get(0);
toStringMethod = findMethod(refType, "toString", "()Ljava/lang/String;");
debugProcess.putUserData(TO_STRING_METHOD_KEY, toStringMethod);
}
catch (Exception ignored) {
throw EvaluateExceptionUtil.createEvaluateException(
DebuggerBundle.message("evaluation.error.cannot.evaluate.tostring", objRef.referenceType().name()));
}
}
if (toStringMethod == null) {
throw EvaluateExceptionUtil.createEvaluateException(DebuggerBundle.message("evaluation.error.cannot.evaluate.tostring", objRef.referenceType().name()));
}
// while result must be of com.sun.jdi.StringReference type, it turns out that sometimes (jvm bugs?)
// it is a plain com.sun.tools.jdi.ObjectReferenceImpl
final Value result = debugProcess.invokeInstanceMethod(evaluationContext, objRef, toStringMethod, Collections.emptyList(), 0);
if (result == null) {
return "null";
}
return result instanceof StringReference ? ((StringReference)result).value() : result.toString();
}
throw EvaluateExceptionUtil.createEvaluateException(DebuggerBundle.message("evaluation.error.unsupported.expression.type"));
}
catch (ObjectCollectedException ignored) {
throw EvaluateExceptionUtil.OBJECT_WAS_COLLECTED;
}
}
public static final int MAX_DISPLAY_LABEL_LENGTH = 1024 * 5;
public static String convertToPresentationString(String str) {
if (str.length() > MAX_DISPLAY_LABEL_LENGTH) {
str = translateStringValue(str.substring(0, MAX_DISPLAY_LABEL_LENGTH));
StringBuilder buf = new StringBuilder();
buf.append(str);
if (!str.endsWith("...")) {
buf.append("...");
}
return buf.toString();
}
return translateStringValue(str);
}
@Nullable
public static Method findMethod(ReferenceType refType, @NonNls String methodName, @NonNls String methodSignature) {
if (refType instanceof ArrayType) {
// for array types methodByName() in JDI always returns empty list
final Method method = findMethod(refType.virtualMachine().classesByName(CommonClassNames.JAVA_LANG_OBJECT).get(0), methodName, methodSignature);
if (method != null) {
return method;
}
}
Method method = null;
if (methodSignature != null) {
if (refType instanceof ClassType) {
method = ((ClassType)refType).concreteMethodByName(methodName, methodSignature);
}
if (method == null) {
final List<Method> methods = refType.methodsByName(methodName, methodSignature);
if (methods.size() > 0) {
method = methods.get(0);
}
}
}
else {
List<Method> methods = null;
if (refType instanceof ClassType) {
methods = refType.methodsByName(methodName);
}
if (methods != null && methods.size() > 0) {
method = methods.get(0);
}
}
return method;
}
public static boolean isNumeric(Value value) {
return value != null &&
(isInteger(value) ||
value instanceof FloatValue ||
value instanceof DoubleValue
);
}
public static boolean isInteger(Value value) {
return value != null &&
(value instanceof ByteValue ||
value instanceof ShortValue ||
value instanceof LongValue ||
value instanceof IntegerValue
);
}
public static String translateStringValue(final String str) {
int length = str.length();
final StringBuilder buffer = StringBuilderSpinAllocator.alloc();
try {
StringUtil.escapeStringCharacters(length, str, buffer);
if (str.length() > length) {
buffer.append("...");
}
return buffer.toString();
}
finally {
StringBuilderSpinAllocator.dispose(buffer);
}
}
@Nullable
protected static ArrayClass getArrayClass(@NotNull String className) {
boolean searchBracket = false;
int dims = 0;
int pos;
for(pos = className.lastIndexOf(']'); pos >= 0; pos--){
char c = className.charAt(pos);
if (searchBracket) {
if (c == '[') {
dims++;
searchBracket = false;
}
else if (!Character.isWhitespace(c)) break;
}
else {
if (c == ']') {
searchBracket = true;
}
else if (!Character.isWhitespace(c)) break;
}
}
if (searchBracket) return null;
if(dims == 0) return null;
return new ArrayClass(className.substring(0, pos + 1), dims);
}
public static boolean instanceOf(@NotNull String subType ,@NotNull String superType, @Nullable Project project) {
if(project == null) {
return subType.equals(superType);
}
ArrayClass nodeClass = getArrayClass(subType);
ArrayClass rendererClass = getArrayClass(superType);
if (nodeClass == null || rendererClass == null) return false;
if (nodeClass.dims == rendererClass.dims) {
GlobalSearchScope scope = GlobalSearchScope.allScope(project);
PsiClass psiNodeClass = JavaPsiFacade.getInstance(project).findClass(nodeClass.className, scope);
PsiClass psiRendererClass = JavaPsiFacade.getInstance(project).findClass(rendererClass.className, scope);
return InheritanceUtil.isInheritorOrSelf(psiNodeClass, psiRendererClass, true);
}
else if (nodeClass.dims > rendererClass.dims) {
return rendererClass.className.equals(CommonClassNames.JAVA_LANG_OBJECT);
}
return false;
}
public static Type getSuperType(Type subType, String superType) {
if(CommonClassNames.JAVA_LANG_OBJECT.equals(superType)) {
List list = subType.virtualMachine().classesByName(CommonClassNames.JAVA_LANG_OBJECT);
if(list.size() > 0) {
return (ReferenceType)list.get(0);
}
return null;
}
return getSuperTypeInt(subType, superType);
}
private static boolean typeEquals(Type type, String typeName) {
return type.name().replace('$', '.').equals(typeName.replace('$', '.'));
}
private static Type getSuperTypeInt(Type subType, String superType) {
Type result;
if (subType == null) {
return null;
}
if (typeEquals(subType, superType)) {
return subType;
}
if (subType instanceof ClassType) {
try {
final ClassType clsType = (ClassType)subType;
result = getSuperType(clsType.superclass(), superType);
if (result != null) {
return result;
}
for (InterfaceType iface : clsType.allInterfaces()) {
if (typeEquals(iface, superType)) {
return iface;
}
}
}
catch (ClassNotPreparedException e) {
LOG.info(e);
}
return null;
}
if (subType instanceof InterfaceType) {
try {
for (InterfaceType iface : ((InterfaceType)subType).superinterfaces()) {
result = getSuperType(iface, superType);
if (result != null) {
return result;
}
}
}
catch (ClassNotPreparedException e) {
LOG.info(e);
}
}
else if (subType instanceof ArrayType) {
if (superType.endsWith("[]")) {
try {
String superTypeItem = superType.substring(0, superType.length() - 2);
Type subTypeItem = ((ArrayType)subType).componentType();
return instanceOf(subTypeItem, superTypeItem) ? subType : null;
}
catch (ClassNotLoadedException e) {
LOG.info(e);
}
}
}
else if (subType instanceof PrimitiveType) {
//noinspection HardCodedStringLiteral
if(superType.equals("java.lang.Primitive")) {
return subType;
}
}
//only for interfaces and arrays
if(CommonClassNames.JAVA_LANG_OBJECT.equals(superType)) {
List list = subType.virtualMachine().classesByName(CommonClassNames.JAVA_LANG_OBJECT);
if(list.size() > 0) {
return (ReferenceType)list.get(0);
}
}
return null;
}
public static boolean instanceOf(Type subType, String superType) {
return getSuperType(subType, superType) != null;
}
@Nullable
public static PsiClass findClass(@NotNull final String className, @NotNull Project project, final GlobalSearchScope scope) {
ApplicationManager.getApplication().assertReadAccessAllowed();
final PsiManager psiManager = PsiManager.getInstance(project);
final JavaPsiFacade javaPsiFacade = JavaPsiFacade.getInstance(psiManager.getProject());
if (getArrayClass(className) != null) {
return javaPsiFacade.getElementFactory().getArrayClass(LanguageLevelProjectExtension.getInstance(psiManager.getProject()).getLanguageLevel());
}
if(project.isDefault()) {
return null;
}
PsiClass psiClass = ClassUtil.findPsiClass(PsiManager.getInstance(project), className, null, true, scope);
if (psiClass == null) {
GlobalSearchScope globalScope = GlobalSearchScope.allScope(project);
if (!globalScope.equals(scope)) {
psiClass = ClassUtil.findPsiClass(PsiManager.getInstance(project), className, null, true, globalScope);
}
}
return psiClass;
}
@Nullable
public static PsiType getType(@NotNull String className, @NotNull Project project) {
ApplicationManager.getApplication().assertReadAccessAllowed();
final PsiManager psiManager = PsiManager.getInstance(project);
try {
if (getArrayClass(className) != null) {
return JavaPsiFacade.getInstance(psiManager.getProject()).getElementFactory().createTypeFromText(className, null);
}
if(project.isDefault()) {
return null;
}
final PsiClass aClass =
JavaPsiFacade.getInstance(psiManager.getProject()).findClass(className.replace('$', '.'), GlobalSearchScope.allScope(project));
if (aClass != null) {
return JavaPsiFacade.getInstance(psiManager.getProject()).getElementFactory().createType(aClass);
}
}
catch (IncorrectOperationException e) {
LOG.error(e);
}
return null;
}
public static void checkSyntax(PsiCodeFragment codeFragment) throws EvaluateException {
PsiElement[] children = codeFragment.getChildren();
if(children.length == 0) throw EvaluateExceptionUtil.createEvaluateException(DebuggerBundle.message("evaluation.error.empty.code.fragment"));
for (PsiElement child : children) {
if (child instanceof PsiErrorElement) {
throw EvaluateExceptionUtil.createEvaluateException(DebuggerBundle.message("evaluation.error.invalid.expression", child.getText()));
}
}
}
public static boolean hasSideEffects(PsiElement element) {
return hasSideEffectsOrReferencesMissingVars(element, null);
}
public static boolean hasSideEffectsOrReferencesMissingVars(PsiElement element, @Nullable final Set<String> visibleLocalVariables) {
final Ref<Boolean> rv = new Ref<Boolean>(Boolean.FALSE);
element.accept(new JavaRecursiveElementWalkingVisitor() {
@Override
public void visitPostfixExpression(final PsiPostfixExpression expression) {
rv.set(Boolean.TRUE);
}
@Override
public void visitReferenceExpression(final PsiReferenceExpression expression) {
final PsiElement psiElement = expression.resolve();
if (psiElement instanceof PsiLocalVariable) {
if (visibleLocalVariables != null) {
if (!visibleLocalVariables.contains(((PsiLocalVariable)psiElement).getName())) {
rv.set(Boolean.TRUE);
}
}
}
else if (psiElement instanceof PsiMethod) {
rv.set(Boolean.TRUE);
//final PsiMethod method = (PsiMethod)psiElement;
//if (!isSimpleGetter(method)) {
// rv.set(Boolean.TRUE);
//}
}
if (!rv.get().booleanValue()) {
super.visitReferenceExpression(expression);
}
}
@Override
public void visitPrefixExpression(final PsiPrefixExpression expression) {
final IElementType op = expression.getOperationTokenType();
if (JavaTokenType.PLUSPLUS.equals(op) || JavaTokenType.MINUSMINUS.equals(op)) {
rv.set(Boolean.TRUE);
}
else {
super.visitPrefixExpression(expression);
}
}
@Override
public void visitAssignmentExpression(final PsiAssignmentExpression expression) {
rv.set(Boolean.TRUE);
}
@Override
public void visitCallExpression(final PsiCallExpression callExpression) {
rv.set(Boolean.TRUE);
//final PsiMethod method = callExpression.resolveMethod();
//if (method == null || !isSimpleGetter(method)) {
// rv.set(Boolean.TRUE);
//}
//else {
// super.visitCallExpression(callExpression);
//}
}
});
return rv.get().booleanValue();
}
public abstract String findAvailableDebugAddress(boolean useSockets) throws ExecutionException;
public static boolean isSynthetic(TypeComponent typeComponent) {
if (typeComponent == null) {
return false;
}
VirtualMachine machine = typeComponent.virtualMachine();
return machine != null && machine.canGetSyntheticAttribute() && typeComponent.isSynthetic();
}
public static boolean isSimpleGetter(PsiMethod method){
final PsiCodeBlock body = method.getBody();
if(body == null){
return false;
}
final PsiStatement[] statements = body.getStatements();
if(statements.length != 1){
return false;
}
final PsiStatement statement = statements[0];
if(!(statement instanceof PsiReturnStatement)){
return false;
}
final PsiExpression value = ((PsiReturnStatement)statement).getReturnValue();
if(!(value instanceof PsiReferenceExpression)){
return false;
}
final PsiReferenceExpression reference = (PsiReferenceExpression)value;
final PsiExpression qualifier = reference.getQualifierExpression();
//noinspection HardCodedStringLiteral
if(qualifier != null && !"this".equals(qualifier.getText())) {
return false;
}
final PsiElement referent = reference.resolve();
if(referent == null) {
return false;
}
if(!(referent instanceof PsiField)) {
return false;
}
return ((PsiField)referent).getContainingClass().equals(method.getContainingClass());
}
public static boolean isPrimitiveType(final String typeName) {
return ourPrimitiveTypeNames.contains(typeName);
}
protected static class ArrayClass {
public String className;
public int dims;
public ArrayClass(String className, int dims) {
this.className = className;
this.dims = dims;
}
}
public static DebuggerUtils getInstance() {
return ServiceManager.getService(DebuggerUtils.class);
}
public abstract PsiExpression substituteThis(PsiExpression expressionWithThis, PsiExpression howToEvaluateThis, Value howToEvaluateThisValue, StackFrameContext context) throws EvaluateException;
public abstract DebuggerContext getDebuggerContext (DataContext context);
public abstract Element writeTextWithImports(TextWithImports text);
public abstract TextWithImports readTextWithImports (Element element);
public abstract void writeTextWithImports(Element root, @NonNls String name, TextWithImports value);
public abstract TextWithImports readTextWithImports (Element root, @NonNls String name);
public abstract TextWithImports createExpressionWithImports(@NonNls String expression);
public abstract PsiElement getContextElement(final StackFrameContext context);
public abstract PsiClass chooseClassDialog(String title, Project project);
/**
* Don't use directly, will be private in IDEA 14.
* @deprecated to remove in IDEA 15
*/
@Deprecated
public static boolean supportsJVMDebugging(FileType type) {
return type instanceof LanguageFileType && ((LanguageFileType)type).isJVMDebuggingSupported();
}
/**
* @deprecated Use {@link #isBreakpointAware(com.intellij.psi.PsiFile)}
* to remove in IDEA 15
*/
@Deprecated
public static boolean supportsJVMDebugging(@NotNull PsiFile file) {
return isBreakpointAware(file);
}
/**
* IDEA-122113
* Will be removed when Java debugger will be moved to XDebugger API
*/
public static boolean isDebugActionAware(@NotNull PsiFile file) {
return isDebugAware(file, false);
}
public static boolean isBreakpointAware(@NotNull PsiFile file) {
return isDebugAware(file, true);
}
@SuppressWarnings("deprecation")
private static boolean isDebugAware(@NotNull PsiFile file, boolean breakpointAware) {
FileType fileType = file.getFileType();
if (supportsJVMDebugging(fileType)) {
return true;
}
for (JavaDebugAware provider : JavaDebugAware.EP_NAME.getExtensions()) {
if (breakpointAware ? provider.isBreakpointAware(file) : provider.isActionAware(file)) {
return true;
}
}
for (JVMDebugProvider provider : JVMDebugProvider.EP_NAME.getExtensions()) {
if (provider.supportsJVMDebugging(file)) {
return true;
}
}
return false;
}
}