blob: 80e42e3b61e24ca4d32ab800f8e35a9065e3ab7d [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.
*/
/*
* Class MethodEvaluator
* @author Jeka
*/
package com.intellij.debugger.engine.evaluation.expression;
import com.intellij.debugger.DebuggerBundle;
import com.intellij.debugger.engine.DebugProcess;
import com.intellij.debugger.engine.DebugProcessImpl;
import com.intellij.debugger.engine.DebuggerUtils;
import com.intellij.debugger.engine.JVMName;
import com.intellij.debugger.engine.evaluation.*;
import com.intellij.debugger.impl.DebuggerUtilsEx;
import com.intellij.debugger.jdi.VirtualMachineProxyImpl;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.rt.debugger.DefaultMethodInvoker;
import com.sun.jdi.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class MethodEvaluator implements Evaluator {
private static final Logger LOG = Logger.getInstance("#com.intellij.debugger.engine.evaluation.expression.MethodEvaluator");
private final JVMName myClassName;
private final JVMName myMethodSignature;
private final String myMethodName;
private final Evaluator[] myArgumentEvaluators;
private final Evaluator myObjectEvaluator;
private final boolean myCheckDefaultInterfaceMethod;
public MethodEvaluator(Evaluator objectEvaluator, JVMName className, String methodName, JVMName signature, Evaluator[] argumentEvaluators) {
this(objectEvaluator, className, methodName, signature, argumentEvaluators, false);
}
public MethodEvaluator(Evaluator objectEvaluator, JVMName className, String methodName, JVMName signature, Evaluator[] argumentEvaluators, boolean checkDefaultInterfaceMethod) {
myObjectEvaluator = new DisableGC(objectEvaluator);
myClassName = className;
myMethodName = methodName;
myMethodSignature = signature;
myArgumentEvaluators = argumentEvaluators;
myCheckDefaultInterfaceMethod = checkDefaultInterfaceMethod;
}
@Override
public Modifier getModifier() {
return null;
}
@Override
public Object evaluate(EvaluationContextImpl context) throws EvaluateException {
if(!context.getDebugProcess().isAttached()) return null;
DebugProcessImpl debugProcess = context.getDebugProcess();
final boolean requiresSuperObject =
myObjectEvaluator instanceof SuperEvaluator ||
(myObjectEvaluator instanceof DisableGC && ((DisableGC)myObjectEvaluator).getDelegate() instanceof SuperEvaluator);
final Object object = myObjectEvaluator.evaluate(context);
if (LOG.isDebugEnabled()) {
LOG.debug("MethodEvaluator: object = " + object);
}
if(object == null) {
throw EvaluateExceptionUtil.createEvaluateException(new NullPointerException());
}
if (!(object instanceof ObjectReference || object instanceof ClassType)) {
throw EvaluateExceptionUtil.createEvaluateException(DebuggerBundle.message("evaluation.error.evaluating.method", myMethodName));
}
List args = new ArrayList(myArgumentEvaluators.length);
for (Evaluator evaluator : myArgumentEvaluators) {
args.add(evaluator.evaluate(context));
}
try {
ReferenceType referenceType = null;
if(object instanceof ObjectReference) {
// it seems that if we have an object of the class, the class must be ready, so no need to use findClass here
referenceType = ((ObjectReference)object).referenceType();
}
else if(object instanceof ClassType) {
final ClassType qualifierType = (ClassType)object;
referenceType = debugProcess.findClass(context, qualifierType.name(), context.getClassLoader());
}
else {
final String className = myClassName != null? myClassName.getName(debugProcess) : null;
if (className != null) {
referenceType = debugProcess.findClass(context, className, context.getClassLoader());
}
}
if (referenceType == null) {
throw new EvaluateRuntimeException(EvaluateExceptionUtil.createEvaluateException(
DebuggerBundle.message("evaluation.error.cannot.evaluate.qualifier", myMethodName))
);
}
final String signature = myMethodSignature != null ? myMethodSignature.getName(debugProcess) : null;
final String methodName = DebuggerUtilsEx.methodName(referenceType.name(), myMethodName, signature);
if (object instanceof ClassType) {
if(referenceType instanceof ClassType) {
Method jdiMethod;
if(myMethodSignature != null) {
jdiMethod = ((ClassType)referenceType).concreteMethodByName(myMethodName, myMethodSignature.getName(debugProcess));
}
else {
List list = referenceType.methodsByName(myMethodName);
jdiMethod = (Method)(list.size() > 0 ? list.get(0) : null);
}
if (jdiMethod != null && jdiMethod.isStatic()) {
return debugProcess.invokeMethod(context, (ClassType)referenceType, jdiMethod, args);
}
}
throw EvaluateExceptionUtil.createEvaluateException(DebuggerBundle.message("evaluation.error.no.static.method", methodName));
}
// object should be an ObjectReference
final ObjectReference objRef = (ObjectReference)object;
ReferenceType _refType = referenceType;
if (requiresSuperObject && (referenceType instanceof ClassType)) {
_refType = ((ClassType)referenceType).superclass();
}
Method jdiMethod = DebuggerUtils.findMethod(_refType, myMethodName, signature);
if (signature == null) {
// we know nothing about expected method's signature, so trying to match my method name and parameter count
// dummy matching, may be improved with types matching later
// IMPORTANT! using argumentTypeNames() instead of argumentTypes() to avoid type resolution inside JDI, which may be time-consuming
if (jdiMethod == null || jdiMethod.argumentTypeNames().size() != args.size()) {
for (Method method : _refType.methodsByName(myMethodName)) {
if (method.argumentTypeNames().size() == args.size()) {
jdiMethod = method;
break;
}
}
}
}
if (jdiMethod == null) {
throw EvaluateExceptionUtil.createEvaluateException(DebuggerBundle.message("evaluation.error.no.instance.method", methodName));
}
if (requiresSuperObject) {
return debugProcess.invokeInstanceMethod(context, objRef, jdiMethod, args, ObjectReference.INVOKE_NONVIRTUAL);
}
// fix for default methods in interfaces, see IDEA-124066
if (myCheckDefaultInterfaceMethod && jdiMethod.declaringType() instanceof InterfaceType) {
return invokeDefaultMethod(debugProcess, context, objRef, myMethodName);
}
else {
return debugProcess.invokeMethod(context, objRef, jdiMethod, args);
}
}
catch (Exception e) {
if (LOG.isDebugEnabled()) {
LOG.debug(e);
}
throw EvaluateExceptionUtil.createEvaluateException(e);
}
}
// only methods without arguments for now
private static Value invokeDefaultMethod(DebugProcess debugProcess, EvaluationContext evaluationContext,
Value obj, String name)
throws EvaluateException, ClassNotLoadedException, InvalidTypeException {
ClassType invokerClass = (ClassType)debugProcess.findClass(
evaluationContext, DefaultMethodInvoker.class.getName(),
evaluationContext.getClassLoader());
if (invokerClass != null) {
List<Method> methods = invokerClass.methodsByName("invoke");
if (!methods.isEmpty()) {
return debugProcess.invokeMethod(evaluationContext, invokerClass, methods.get(0),
Arrays.asList(obj, ((VirtualMachineProxyImpl)debugProcess.getVirtualMachineProxy()).mirrorOf(name)));
}
}
return null;
}
}