/*
* This file is part of the Jikes RVM project (http://jikesrvm.org).
*
* This file is licensed to You under the Eclipse Public License (EPL);
* You may not use this file except in compliance with the License. You
* may obtain a copy of the License at
*
* http://www.opensource.org/licenses/eclipse-1.0.php
*
* See the COPYRIGHT.txt file distributed with this work for information
* regarding copyright ownership.
*/
package org.mmtk.harness.lang.ast;
import java.util.ArrayList;
import java.util.List;
import org.mmtk.harness.lang.Env;
import org.mmtk.harness.lang.Trace;
import org.mmtk.harness.lang.Visitor;
import org.mmtk.harness.lang.Trace.Item;
import org.mmtk.harness.lang.runtime.BoolValue;
import org.mmtk.harness.lang.runtime.IntValue;
import org.mmtk.harness.lang.runtime.ObjectValue;
import org.mmtk.harness.lang.runtime.PhantomReferenceValue;
import org.mmtk.harness.lang.runtime.SoftReferenceValue;
import org.mmtk.harness.lang.runtime.StringValue;
import org.mmtk.harness.lang.runtime.Value;
import org.mmtk.harness.lang.runtime.WeakReferenceValue;
import org.mmtk.harness.lang.type.Type;
/**
* A method that is implemented directly in Java rather than in the scripting language.
*
* This class also contains definitions of the built-in intrinsic methods.
*/
public class IntrinsicMethod extends Method {
/** The actual Java method */
private final java.lang.reflect.Method method;
/** The Java signature of the method */
private final Class<?>[] signature;
/************************************************************************
* helper methods for the constructors
*
*/
/**
* Find the appropriate scripting language type for a given Java type
*
* @param externalType The java type
* @return
*/
private static Type internalType(Class<?> externalType) {
if (externalType.equals(int.class) ||
externalType.equals(Integer.class) ||
externalType.equals(IntValue.class)) {
return Type.INT;
} else if (externalType.equals(String.class) ||
externalType.equals(StringValue.class)) {
return Type.STRING;
} else if (externalType.equals(ObjectValue.class)) {
return Type.OBJECT;
} else if (externalType.equals(boolean.class) ||
externalType.equals(Boolean.class) ||
externalType.equals(BoolValue.class)) {
return Type.BOOLEAN;
} else if (externalType.equals(void.class)) {
return Type.VOID;
} else if (externalType.equals(WeakReferenceValue.class)) {
return Type.WEAKREF;
} else if (externalType.equals(SoftReferenceValue.class)) {
return Type.SOFTREF;
} else if (externalType.equals(PhantomReferenceValue.class)) {
return Type.PHANTOMREF;
}
throw new RuntimeException("Invalid return type for intrinsic method, "+externalType.getCanonicalName());
}
/**
* Do a reflective method lookup. We look for a method with a first parameter
* of 'Env env', which is hidden from the script-language specification of the
* method.
*
* @param className
* @param methodName
* @param signature
* @return
*/
private static java.lang.reflect.Method getJavaMethod(String className,
String methodName, Class<?>[] signature) {
try {
Class<?> klass = Class.forName(className);
Class<?>[] realSignature = new Class<?>[signature.length+1];
realSignature[0] = Env.class;
System.arraycopy(signature, 0, realSignature, 1, signature.length);
return klass.getDeclaredMethod(methodName, realSignature);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* Turn a string into a class object, applying some simple mappings for
* primitives etc.
* @param param
* @return
*/
private static Class<?> classForParam(String param) {
Class<?> r;
if (param.equals("int")) {
r = int.class;
} else if (param.equals("long")) {
r = long.class;
} else if (param.equals("byte")) {
r = byte.class;
} else if (param.equals("short")) {
r = short.class;
} else if (param.equals("char")) {
r = char.class;
} else if (param.equals("boolean")) {
r = boolean.class;
} else try {
r = Class.forName(param);
} catch (ClassNotFoundException e) {
// As a last chance, try looking for the class in java.lang
try {
r = Class.forName("java.lang."+param);
} catch (ClassNotFoundException f) {
throw new RuntimeException(e);
}
}
return r;
}
/**
* Turn an array of strings (class names) into an array of Class objects
* @param params
* @return
*/
private static Class<?>[] classesForParams(List<String> params) {
Class<?>[] result = new Class<?>[params.size()];
for (int i=0; i < params.size(); i++) {
result[i] = classForParam(params.get(i));
}
return result;
}
/************************************************************************
*
* Constructors
*
*/
/**
* Constructor
* @param name The name of the method in the scripting language
* @param className The class that implements the method
* @param methodName The name of the method in the implementing class
* @param params The types of the parameters
*/
public IntrinsicMethod(String name, String className, String methodName, List<String> params) {
this(name,className, methodName,classesForParams(params));
}
/**
* Create an intrinsic that calls a static method.
* @param name Script language name of the method
* @param className Java class in which the intrinsic occurs
* @param methodName Java name of the method
* @param signature Java types of the parameters, excluding the mandatory Env first parameter
*/
public IntrinsicMethod(String name, String className, String methodName, Class<?>[] signature) {
this(name,getJavaMethod(className, methodName,signature),signature);
}
/**
* Create an intrinsic that calls a static method with no parameters
*
* @param name Script language name of the method
* @param className Java class in which the intrinsic occurs
* @param methodName Java name of the method
*/
public IntrinsicMethod(String name, String className, String methodName) {
this(name,className,methodName,new Class<?>[] {});
}
/**
* Internal 'helper' constructor
* @param name Script-language name of the method
* @param method Java Method
* @param signature Java signature (without the mandatory Env parameter)
*/
private IntrinsicMethod(String name, java.lang.reflect.Method method, Class<?>[] signature) {
this(name,method,internalType(method.getReturnType()),signature);
}
/**
* Internal constructor - ultimately all constructors call this one.
* @param name Script-language name of the method
* @param method Java Method
* @param returnType Script-language return type
* @param signature Java signature (without the mandatory Env parameter)
*/
private IntrinsicMethod(String name, java.lang.reflect.Method method, Type returnType, Class<?>[] signature) {
super(name,signature.length,returnType);
this.method = method;
this.signature = signature;
}
/**
* Marshall an array of values, adding the mandatory Env value.
* @param env
* @param values
* @return
*/
private Object[] marshall(Env env, Value[] values) {
assert values.length == signature.length : "Signature doesn't match params";
Object[] marshalled = new Object[values.length+1];
marshalled[0] = env;
for (int i=0; i < values.length; i++) {
marshalled[i+1] = values[i].marshall(signature[i]);
}
return marshalled;
}
/**
* Convert a return type from a Java type to a scripting language Value.
* @param obj
* @return
*/
private Value unMarshall(Object obj) {
if (returnType == Type.VOID) {
return null;
} else if (obj instanceof Integer) {
assert returnType == Type.INT : "mismatched return types";
return IntValue.valueOf(((Integer)obj).intValue());
} else if (obj instanceof String) {
assert returnType == Type.STRING : "mismatched return types";
return new StringValue((String)obj);
} else if (obj instanceof Boolean) {
assert returnType == Type.BOOLEAN : "mismatched return types";
return BoolValue.valueOf(((Boolean)obj).booleanValue());
} else if (obj instanceof Value) {
return (Value)obj;
}
throw new RuntimeException("Can't unmarshall a "+obj.getClass().getCanonicalName());
}
/**
* Invoke the intrinsic method.
* @param env
* @param values
* @return
*/
Object invoke(Env env, Value[] values) {
Trace.trace(Item.INTRINSIC,"Executing "+toString());
try {
Object result = method.invoke(null, marshall(env,values));
return result;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* Evaluate the method as an expression
* @see Method{@link #eval(Env, Value...)}
*
* Also used by the visitor to evaluate ...
*/
public Value eval(Env env, Value...values) {
return unMarshall(invoke(env,values));
}
/**
* Execute the method as a statement
*/
public void exec(Env env, Value...values) {
invoke(env,values);
}
/**
* Convert to a string
*/
@Override
public String toString() {
return method.getName();
}
/**
* Accept visitors
*/
@Override
public Object accept(Visitor v) {
return v.visit(this);
}
/**
* Return parameter types, in script-language terms.
*/
@Override
public List<Type> getParamTypes() {
List<Type> result = new ArrayList<Type>(params);
for (Class<?> paramClass : signature) {
result.add(internalType(paramClass));
}
return result;
}
}