/*
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.oracle.truffle.api.interop.java;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.util.Collection;
import java.util.LinkedHashSet;
import com.oracle.truffle.api.CallTarget;
import com.oracle.truffle.api.CompilerAsserts;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.interop.ArityException;
import com.oracle.truffle.api.interop.ForeignAccess;
import com.oracle.truffle.api.interop.InteropException;
import com.oracle.truffle.api.interop.Message;
import com.oracle.truffle.api.interop.TruffleObject;
import com.oracle.truffle.api.interop.UnknownIdentifierException;
import com.oracle.truffle.api.interop.UnsupportedMessageException;
import com.oracle.truffle.api.interop.UnsupportedTypeException;
import com.oracle.truffle.api.interop.java.JavaInteropReflectFactory.MethodNodeGen;
import com.oracle.truffle.api.nodes.DirectCallNode;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.nodes.RootNode;
final class JavaInteropReflect {
private static final Object[] EMPTY = {};
@CompilerDirectives.TruffleBoundary
static Object readField(JavaObject object, String name) throws NoSuchFieldError, SecurityException, IllegalArgumentException, IllegalAccessException {
Object obj = object.obj;
final boolean onlyStatic = obj == null;
Object val;
try {
final Field field = object.clazz.getField(name);
final boolean isStatic = (field.getModifiers() & Modifier.STATIC) != 0;
if (onlyStatic != isStatic) {
throw UnknownIdentifierException.raise(name);
}
val = field.get(obj);
} catch (NoSuchFieldException ex) {
for (Method m : object.clazz.getMethods()) {
final boolean isStatic = (m.getModifiers() & Modifier.STATIC) != 0;
if (onlyStatic != isStatic) {
continue;
}
if (m.getName().equals(name) && m.getDeclaringClass() != Object.class) {
return new JavaFunctionObject(m, obj);
}
}
int signature = name.indexOf("__");
if (signature != -1) {
for (Method m : object.clazz.getMethods()) {
final boolean isStatic = (m.getModifiers() & Modifier.STATIC) != 0;
if (onlyStatic != isStatic) {
continue;
}
if (name.startsWith(m.getName())) {
final String fullName = jniName(m);
if (fullName.equals(name)) {
return new JavaFunctionObject(m, obj);
}
}
}
}
throw UnknownIdentifierException.raise(name);
}
return JavaInterop.asTruffleObject(val);
}
static boolean isField(JavaObject object, String name) {
Object obj = object.obj;
final boolean onlyStatic = obj == null;
try {
final Field field = object.clazz.getField(name);
final boolean isStatic = (field.getModifiers() & Modifier.STATIC) != 0;
if (onlyStatic != isStatic) {
return false;
}
return true;
} catch (NoSuchFieldException | SecurityException ex) {
return false;
}
}
static boolean isMethod(JavaObject object, String name) {
Object obj = object.obj;
final boolean onlyStatic = obj == null;
for (Method m : object.clazz.getMethods()) {
final boolean isStatic = (m.getModifiers() & Modifier.STATIC) != 0;
if (onlyStatic != isStatic) {
continue;
}
if (m.getName().equals(name)) {
return true;
}
}
return false;
}
static boolean isJNIMethod(JavaObject object, String name) {
Object obj = object.obj;
final boolean onlyStatic = obj == null;
for (Method m : object.clazz.getMethods()) {
final boolean isStatic = (m.getModifiers() & Modifier.STATIC) != 0;
if (onlyStatic != isStatic) {
continue;
}
if (jniName(m).equals(name)) {
return true;
}
}
return false;
}
@CompilerDirectives.TruffleBoundary
static Method findMethod(JavaObject object, String name, Object[] args) {
for (Method m : object.clazz.getMethods()) {
if (m.getName().equals(name) && m.getDeclaringClass() != Object.class) {
if (m.getParameterTypes().length == args.length || m.isVarArgs()) {
return m;
}
}
}
return null;
}
private JavaInteropReflect() {
}
@CompilerDirectives.TruffleBoundary
static Object newConstructor(final Class<?> clazz, Object[] args) throws IllegalStateException, SecurityException {
IllegalStateException ex = new IllegalStateException("No suitable constructor found for " + clazz);
for (Constructor<?> constructor : clazz.getConstructors()) {
try {
Object ret = constructor.newInstance(args);
if (ToPrimitiveNode.temporary().isPrimitive(ret)) {
return ret;
}
return JavaInterop.asTruffleObject(ret);
} catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException instEx) {
ex = new IllegalStateException(instEx);
}
}
throw ex;
}
@CompilerDirectives.TruffleBoundary
static Field findField(JavaObject receiver, String name) {
try {
return receiver.clazz.getField(name);
} catch (NoSuchFieldException ex) {
throw new RuntimeException(ex);
}
}
@CompilerDirectives.TruffleBoundary
static void setField(Object obj, Field f, Object convertedValue) {
try {
f.set(obj, convertedValue);
} catch (IllegalAccessException ex) {
throw new RuntimeException(ex);
}
}
@CompilerDirectives.TruffleBoundary
static <T> T asJavaFunction(Class<T> functionalType, TruffleObject function) {
final SingleHandler handler = new SingleHandler(function);
Object obj = Proxy.newProxyInstance(functionalType.getClassLoader(), new Class<?>[]{functionalType}, handler);
return functionalType.cast(obj);
}
@CompilerDirectives.TruffleBoundary
static TruffleObject asTruffleViaReflection(Object obj) throws IllegalArgumentException {
if (Proxy.isProxyClass(obj.getClass())) {
InvocationHandler h = Proxy.getInvocationHandler(obj);
if (h instanceof TruffleHandler) {
return ((TruffleHandler) h).obj;
}
}
return new JavaObject(obj, obj.getClass());
}
static Object newProxyInstance(Class<?> clazz, TruffleObject obj) throws IllegalArgumentException {
return Proxy.newProxyInstance(clazz.getClassLoader(), new Class<?>[]{clazz}, new TruffleHandler(obj));
}
@CompilerDirectives.TruffleBoundary
static String[] findUniquePublicMemberNames(Class<?> c, boolean onlyInstance, boolean includeInternal) throws SecurityException {
Class<?> clazz = c;
while ((clazz.getModifiers() & Modifier.PUBLIC) == 0) {
clazz = clazz.getSuperclass();
}
final Field[] fields = clazz.getFields();
Collection<String> names = new LinkedHashSet<>();
for (Field field : fields) {
if (((field.getModifiers() & Modifier.STATIC) == 0) != onlyInstance) {
continue;
}
names.add(field.getName());
}
final Method[] methods = clazz.getMethods();
for (Method method : methods) {
if (method.getDeclaringClass() == Object.class) {
continue;
}
if (((method.getModifiers() & Modifier.STATIC) == 0) != onlyInstance) {
continue;
}
names.add(method.getName());
if (includeInternal) {
names.add(jniName(method));
}
}
return names.toArray(new String[0]);
}
@CompilerDirectives.TruffleBoundary
static RuntimeException reraise(InteropException ex) {
CompilerDirectives.transferToInterpreter();
throw ex.raise();
}
private static final class SingleHandler implements InvocationHandler {
private final TruffleObject symbol;
private CallTarget target;
SingleHandler(TruffleObject obj) {
super();
this.symbol = obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] arguments) throws Throwable {
Object ret;
if (method.isVarArgs()) {
if (arguments.length == 1) {
ret = call((Object[]) arguments[0], method);
} else {
final int allButOne = arguments.length - 1;
Object[] last = (Object[]) arguments[allButOne];
Object[] merge = new Object[allButOne + last.length];
System.arraycopy(arguments, 0, merge, 0, allButOne);
System.arraycopy(last, 0, merge, allButOne, last.length);
ret = call(merge, method);
}
} else {
ret = call(arguments, method);
}
return toJava(ret, method);
}
private Object call(Object[] arguments, Method method) {
CompilerAsserts.neverPartOfCompilation();
Object[] args = arguments == null ? EMPTY : arguments;
if (target == null) {
target = JavaInterop.ACCESSOR.engine().lookupOrRegisterComputation(symbol, null, JavaInteropReflect.class);
if (target == null) {
Node executeMain = Message.createExecute(args.length).createNode();
RootNode symbolNode = new ToJavaNode.TemporaryRoot(executeMain);
target = JavaInterop.ACCESSOR.engine().lookupOrRegisterComputation(symbol, symbolNode, JavaInteropReflect.class);
}
}
for (int i = 0; i < args.length; i++) {
if (args[i] instanceof TruffleObject) {
continue;
}
if (ToPrimitiveNode.temporary().isPrimitive(args[i])) {
continue;
}
arguments[i] = JavaInterop.asTruffleObject(args[i]);
}
return target.call(symbol, TypeAndClass.forReturnType(method), args);
}
}
private static final class TruffleHandler implements InvocationHandler {
final TruffleObject obj;
TruffleHandler(TruffleObject obj) {
this.obj = obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] arguments) throws Throwable {
CompilerAsserts.neverPartOfCompilation();
Object[] args = arguments == null ? EMPTY : arguments;
for (int i = 0; i < args.length; i++) {
args[i] = JavaInterop.asTruffleValue(args[i]);
}
if (Object.class == method.getDeclaringClass()) {
return method.invoke(obj, args);
}
CallTarget call = JavaInterop.ACCESSOR.engine().lookupOrRegisterComputation(obj, null, method);
if (call == null) {
Message message = findMessage(method, method.getAnnotation(MethodMessage.class), args.length);
TypeAndClass<?> convertTo = TypeAndClass.forReturnType(method);
MethodNode methodNode = MethodNodeGen.create(method.getName(), message, convertTo);
call = JavaInterop.ACCESSOR.engine().lookupOrRegisterComputation(obj, methodNode, method);
}
return call.call(obj, args);
}
}
abstract static class MethodNode extends RootNode {
private final String name;
private final TypeAndClass<?> returnType;
@CompilerDirectives.CompilationFinal private Message message;
@Child private ToJavaNode toJavaNode;
@Child private Node node;
MethodNode(String name, Message message, TypeAndClass<?> returnType) {
super(null);
this.name = name;
this.toJavaNode = ToJavaNodeGen.create();
this.message = message;
this.returnType = returnType;
}
@Override
public final Object execute(VirtualFrame frame) {
try {
TruffleObject receiver = (TruffleObject) frame.getArguments()[0];
Object[] params = (Object[]) frame.getArguments()[1];
Object res = executeImpl(receiver, params);
if (!returnType.clazz.isInterface()) {
res = JavaInterop.ACCESSOR.engine().findOriginalObject(res);
}
return toJavaNode.execute(res, returnType);
} catch (InteropException ex) {
throw reraise(ex);
}
}
private Object handleMessage(Node messageNode, TruffleObject obj, Object[] args) throws InteropException {
if (message == Message.WRITE) {
ForeignAccess.sendWrite(messageNode, obj, name, args[0]);
return null;
}
if (message == Message.HAS_SIZE || message == Message.IS_BOXED || message == Message.IS_EXECUTABLE || message == Message.IS_NULL || message == Message.GET_SIZE) {
return ForeignAccess.send(messageNode, obj);
}
if (message == Message.KEY_INFO) {
return ForeignAccess.sendKeyInfo(messageNode, obj, name);
}
if (message == Message.READ) {
return ForeignAccess.sendRead(messageNode, obj, name);
}
if (message == Message.UNBOX) {
return ForeignAccess.sendUnbox(messageNode, obj);
}
if (Message.createExecute(0).equals(message)) {
return ForeignAccess.sendExecute(messageNode, obj, args);
}
if (Message.createInvoke(0).equals(message)) {
return ForeignAccess.sendInvoke(messageNode, obj, name, args);
}
if (Message.createNew(0).equals(message)) {
return ForeignAccess.sendNew(messageNode, obj, args);
}
if (message == null) {
return ((InvokeAndReadExecNode) messageNode).executeDispatch(obj, name, args);
}
CompilerDirectives.transferToInterpreter();
throw UnsupportedMessageException.raise(message);
}
Node node(Object[] args) {
if (node == null) {
CompilerDirectives.transferToInterpreterAndInvalidate();
this.node = insert(createNode(args));
}
return node;
}
Node createNode(Object[] args) {
if (message == null) {
return JavaInteropReflectFactory.InvokeAndReadExecNodeGen.create(returnType, args.length);
}
return message.createNode();
}
protected abstract Object executeImpl(TruffleObject receiver, Object[] arguments) throws InteropException;
@SuppressWarnings("unused")
@Specialization(guards = "acceptCached(receiver, foreignAccess, canHandleCall)", limit = "8")
protected Object doCached(TruffleObject receiver, Object[] arguments,
@Cached("receiver.getForeignAccess()") ForeignAccess foreignAccess,
@Cached("createInlinedCallNode(createCanHandleTarget(foreignAccess))") DirectCallNode canHandleCall,
@Cached("createNode(arguments)") Node messageNode) {
try {
return handleMessage(messageNode, receiver, arguments);
} catch (InteropException ex) {
throw reraise(ex);
}
}
protected static boolean acceptCached(TruffleObject receiver, ForeignAccess foreignAccess, DirectCallNode canHandleCall) {
if (canHandleCall != null) {
return (boolean) canHandleCall.call(new Object[]{receiver});
} else if (foreignAccess != null) {
return JavaInterop.ACCESSOR.interop().canHandle(foreignAccess, receiver);
} else {
return false;
}
}
static DirectCallNode createInlinedCallNode(CallTarget target) {
if (target == null) {
return null;
}
DirectCallNode callNode = DirectCallNode.create(target);
callNode.forceInlining();
return callNode;
}
@Specialization
Object doGeneric(TruffleObject receiver, Object[] arguments) {
try {
return handleMessage(node(arguments), receiver, arguments);
} catch (InteropException ex) {
throw reraise(ex);
}
}
@CompilerDirectives.TruffleBoundary
CallTarget createCanHandleTarget(ForeignAccess access) {
return JavaInterop.ACCESSOR.interop().canHandleTarget(access);
}
}
abstract static class InvokeAndReadExecNode extends Node {
private final TypeAndClass<?> returnType;
@Child private Node invokeNode;
@Child private Node isExecNode;
@Child private ToPrimitiveNode primitive;
@Child private Node readNode;
@Child private Node execNode;
InvokeAndReadExecNode(TypeAndClass<?> returnType, int arity) {
this.returnType = returnType;
this.invokeNode = Message.createInvoke(arity).createNode();
}
abstract Object executeDispatch(TruffleObject obj, String name, Object[] args);
@Specialization(rewriteOn = InteropException.class)
Object doInvoke(TruffleObject obj, String name, Object[] args) throws InteropException {
return ForeignAccess.sendInvoke(invokeNode, obj, name, args);
}
@Specialization(rewriteOn = UnsupportedMessageException.class)
Object doReadExec(TruffleObject obj, String name, Object[] args) throws UnsupportedMessageException {
try {
if (readNode == null || primitive == null || isExecNode == null) {
CompilerDirectives.transferToInterpreterAndInvalidate();
readNode = insert(Message.READ.createNode());
primitive = insert(ToPrimitiveNode.create());
isExecNode = insert(Message.IS_EXECUTABLE.createNode());
}
Object val = ForeignAccess.sendRead(readNode, obj, name);
Object primitiveVal = primitive.toPrimitive(val, returnType.clazz);
if (primitiveVal != null) {
return primitiveVal;
}
TruffleObject attr = (TruffleObject) val;
if (!ForeignAccess.sendIsExecutable(isExecNode, attr)) {
if (args.length == 0) {
return attr;
}
CompilerDirectives.transferToInterpreter();
throw ArityException.raise(0, args.length);
}
if (execNode == null) {
CompilerDirectives.transferToInterpreterAndInvalidate();
execNode = insert(Message.createExecute(args.length).createNode());
}
return ForeignAccess.sendExecute(execNode, attr, args);
} catch (ArityException | UnsupportedTypeException | UnknownIdentifierException ex) {
throw reraise(ex);
}
}
@Specialization
@CompilerDirectives.TruffleBoundary
Object doBoth(TruffleObject obj, String name, Object[] args) {
try {
return doInvoke(obj, name, args);
} catch (InteropException retry) {
try {
return doReadExec(obj, name, args);
} catch (UnsupportedMessageException error) {
throw reraise(error);
}
}
}
}
private static Message findMessage(Method method, MethodMessage mm, int arity) {
CompilerAsserts.neverPartOfCompilation();
if (mm == null) {
return null;
}
Message message = Message.valueOf(mm.message());
if (message == Message.WRITE && arity != 1) {
throw new IllegalStateException("Method needs to have a single argument to handle WRITE message " + method);
}
return message;
}
private static Object toJava(Object ret, Method method) {
return ToJavaNode.toJava(ret, TypeAndClass.forReturnType(method));
}
private static String jniName(Method m) {
StringBuilder sb = new StringBuilder();
noUnderscore(sb, m.getName()).append("__");
appendType(sb, m.getReturnType());
Class<?>[] arr = m.getParameterTypes();
for (int i = 0; i < arr.length; i++) {
appendType(sb, arr[i]);
}
return sb.toString();
}
private static StringBuilder noUnderscore(StringBuilder sb, String name) {
return sb.append(name.replace("_", "_1").replace('.', '_'));
}
private static void appendType(StringBuilder sb, Class<?> type) {
if (type == Integer.TYPE) {
sb.append('I');
return;
}
if (type == Long.TYPE) {
sb.append('J');
return;
}
if (type == Double.TYPE) {
sb.append('D');
return;
}
if (type == Float.TYPE) {
sb.append('F');
return;
}
if (type == Byte.TYPE) {
sb.append('B');
return;
}
if (type == Boolean.TYPE) {
sb.append('Z');
return;
}
if (type == Short.TYPE) {
sb.append('S');
return;
}
if (type == Void.TYPE) {
sb.append('V');
return;
}
if (type == Character.TYPE) {
sb.append('C');
return;
}
if (type.isArray()) {
sb.append("_3");
appendType(sb, type.getComponentType());
return;
}
noUnderscore(sb.append('L'), type.getName());
sb.append("_2");
}
}