blob: e58d53e5864e08c88126b9cd97f368fe013941c4 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2008, 2012 SAP AG and IBM Corporation.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* SAP AG - initial API and implementation
* Andrew Johnson - matching of overloaded methods
*******************************************************************************/
package org.eclipse.mat.parser.internal.oql.compiler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.regex.Pattern;
import org.eclipse.mat.SnapshotException;
import org.eclipse.mat.parser.internal.Messages;
import org.eclipse.mat.parser.internal.oql.compiler.CompilerImpl.ConstantExpression;
import org.eclipse.mat.snapshot.model.IObject;
import org.eclipse.mat.util.IProgressListener.OperationCanceledException;
import org.eclipse.mat.util.MessageUtil;
import org.eclipse.mat.util.PatternUtil;
class MethodCallExpression extends Expression
{
String name;
List<Expression> parameters;
public MethodCallExpression(String name, List<Expression> parameters)
{
this.name = name;
this.parameters = parameters;
}
@Override
public Object compute(EvaluationContext ctx) throws SnapshotException, OperationCanceledException
{
Object subject = ctx.getSubject();
if (subject == null)
return null;
if (subject.getClass().isArray())
subject = PathExpression.asList(subject);
// compute arguments
Object[] arguments = new Object[parameters.size()];
for (int ii = 0; ii < arguments.length; ii++)
arguments[ii] = parameters.get(ii).compute(ctx);
// special handling for #toString() and IObjects
if (subject instanceof IObject && "toString".equals(this.name) && parameters.isEmpty()) //$NON-NLS-1$
{
String name = ((IObject) subject).getClassSpecificName();
return name != null ? name : ((IObject) subject).getTechnicalName();
}
/*
* Finding the right method is tricky as the arguments have already been boxed.
* E.g. consider overloaded methods
* remove(int)
* remove(Object)
* with argument Integer(1).
*/
List<Method> extraMethods = new ArrayList<Method>();
final Class<? extends Object> subjectClass = subject.getClass();
Method[] methods;
methods = subjectClass.getMethods();
if (!Modifier.isPublic(subjectClass.getModifiers()))
{
// Non-public class public methods are only accessible via
// interfaces. For example java.util.Arrays$ArrayList.get()
for (Class<?> superClass = subjectClass; superClass != null; superClass = superClass.getSuperclass())
{
for (Class<?> c : superClass.getInterfaces())
{
firstChoiceMethods(extraMethods, c, arguments);
}
}
firstChoiceMethods(extraMethods, subjectClass, arguments);
for (Class<?> superClass = subjectClass; superClass != null; superClass = superClass.getSuperclass())
{
for (Class<?> c : superClass.getInterfaces())
{
extraMethods.addAll(Arrays.asList(c.getMethods()));
}
}
}
else
{
firstChoiceMethods(extraMethods, subjectClass, arguments);
}
if (extraMethods.size() > 0)
{
// Then add the original methods
extraMethods.addAll(Arrays.asList(methods));
// Remove duplicates
extraMethods = new ArrayList<Method>(new LinkedHashSet<Method>(extraMethods));
methods = extraMethods.toArray(new Method[extraMethods.size()]);
}
nextMethod: for (int ii = 0; ii < methods.length; ii++)
{
if (methods[ii].getName().equals(this.name))
{
Class<?>[] parameterTypes = methods[ii].getParameterTypes();
if (parameterTypes.length == arguments.length)
{
Object savedArgs[] = null;
for (int jj = 0; jj < arguments.length; jj++)
{
if (arguments[jj] == ConstantExpression.NULL)
{
arguments[jj] = null;
}
if (arguments[jj] != null && !isConvertible(parameterTypes[jj], arguments[jj]))
{
// we do some special magic here...
if (parameterTypes[jj].isAssignableFrom(Pattern.class))
{
if (savedArgs == null)
savedArgs = new Object[arguments.length];
savedArgs[jj] = arguments[jj];
arguments[jj] = Pattern.compile(PatternUtil.smartFix(String.valueOf(arguments[jj]),
false));
}
else
{
if (savedArgs != null)
{
// Restore arguments
for (int ia = 0; ia < savedArgs.length; ++ia)
{
if (savedArgs[ia] != null)
arguments[ia] = savedArgs[ia];
}
}
continue nextMethod;
}
}
}
try
{
return methods[ii].invoke(subject, arguments);
}
catch (IllegalArgumentException e)
{
throw new SnapshotException(Arrays.toString(arguments), e);
}
catch (IllegalAccessException e)
{
throw new SnapshotException(methods[ii].toString(), e);
}
catch (InvocationTargetException e)
{
throw new SnapshotException(e);
}
}
}
}
StringBuilder argTypes = new StringBuilder();
for (Object arg : arguments)
{
if (argTypes.length() > 0)
argTypes.append(", "); //$NON-NLS-1$
argTypes.append(arg != null ? unboxedType(arg.getClass()).getName() : null);
}
throw new SnapshotException(MessageUtil.format(Messages.MethodCallExpression_Error_MethodNotFound,
new Object[] { this.name, argTypes, subject, subject != null ? subject.getClass().getName() : null }));
}
/**
* Try for a good match on the method.
* Also try unboxed arguments.
*/
private void firstChoiceMethods(List<Method> extraMethods, final Class<? extends Object> subjectClass,
Object[] arguments)
{
// find appropriate method
Class<?>[] argumentTypes1 = new Class<?>[arguments.length];
Class<?>[] argumentTypes2 = new Class<?>[arguments.length];
int i = 0;
boolean unbox = false;
for (Object args : arguments)
{
if (args != null)
argumentTypes1[i] = args.getClass();
argumentTypes2[i] = unboxedType(argumentTypes1[i]);
if (argumentTypes2 != argumentTypes1)
unbox = true;
i++;
}
try
{
Method m1 = subjectClass.getMethod(name, argumentTypes1);
extraMethods.add(m1);
}
catch (SecurityException e1)
{
}
catch (NoSuchMethodException e1)
{
}
if (unbox) try
{
Method m1 = subjectClass.getMethod(name, argumentTypes2);
extraMethods.add(m1);
}
catch (SecurityException e1)
{
}
catch (NoSuchMethodException e1)
{
}
}
private Class<?> unboxedType(Class<?>arg)
{
if (arg == Boolean.class)
{
arg = boolean.class;
}
if (arg == Byte.class)
{
arg = byte.class;
}
else if (arg == Short.class)
{
arg = short.class;
}
else if (arg == Character.class)
{
arg = char.class;
}
else if (arg == Integer.class)
{
arg = int.class;
}
else if (arg == Long.class)
{
arg = long.class;
}
else if (arg == Float.class)
{
arg = float.class;
}
else if (arg == Double.class)
{
arg =double.class;
}
return arg;
}
/**
* Can method invocation convert the argument via unboxing/widening conversion?
*/
private boolean isConvertible(Class<?>parameterType, Object argument)
{
Class<?> argumentType = argument.getClass();
if (parameterType.isAssignableFrom(argumentType))
return true;
if (argumentType == Boolean.class && (
parameterType == boolean.class || parameterType == Boolean.class))
return true;
if (argumentType == Byte.class && (
parameterType == byte.class || parameterType == Byte.class
|| parameterType == short.class || parameterType == Short.class
|| parameterType == int.class || parameterType == Integer.class
|| parameterType == long.class || parameterType == Long.class
|| parameterType == float.class || parameterType == Float.class
|| parameterType == double.class || parameterType == Double.class))
return true;
if (argumentType == Short.class && (
parameterType == short.class || parameterType == Short.class
|| parameterType == int.class || parameterType == Integer.class
|| parameterType == long.class || parameterType == Long.class
|| parameterType == float.class || parameterType == Float.class
|| parameterType == double.class || parameterType == Double.class))
return true;
if (argumentType == Character.class && (
parameterType == char.class || parameterType == Character.class
|| parameterType == int.class || parameterType == Integer.class
|| parameterType == long.class || parameterType == Long.class
|| parameterType == float.class || parameterType == Float.class
|| parameterType == double.class || parameterType == Double.class))
return true;
if (argumentType == Integer.class && (
parameterType == int.class || parameterType == Integer.class
|| parameterType == long.class || parameterType == Long.class
|| parameterType == float.class || parameterType == Float.class
|| parameterType == double.class || parameterType == Double.class))
return true;
if (argumentType == Long.class && (
parameterType == long.class || parameterType == Long.class
|| parameterType == float.class || parameterType == Float.class
|| parameterType == double.class || parameterType == Double.class))
return true;
if (argumentType == Float.class && (
parameterType == float.class || parameterType == Float.class
|| parameterType == double.class || parameterType == Double.class))
return true;
if (argumentType == Double.class && (
parameterType == double.class || parameterType == Double.class))
return true;
return false;
}
@Override
public boolean isContextDependent(EvaluationContext ctx)
{
for (Expression element : this.parameters)
{
boolean isContextDependent = element.isContextDependent(ctx);
if (isContextDependent)
return true;
}
return false;
}
@Override
public String toString()
{
StringBuilder buf = new StringBuilder(256);
buf.append(name);
buf.append("(");//$NON-NLS-1$
for (Iterator<Expression> iter = this.parameters.iterator(); iter.hasNext();)
{
Expression element = iter.next();
buf.append(element);
if (iter.hasNext())
buf.append(",");//$NON-NLS-1$
}
buf.append(")");//$NON-NLS-1$
return buf.toString();
}
}