All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.oracle.truffle.api.interop.java.ToJavaNode Maven / Gradle / Ivy

Go to download

Truffle is a multi-language framework for executing dynamic languages that achieves high performance when combined with Graal.

There is a newer version: 1.0.0-rc7
Show newest version
/*
 * 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.Array;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;

import org.graalvm.polyglot.Value;

import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.TruffleOptions;
import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.impl.Accessor.EngineSupport;
import com.oracle.truffle.api.interop.ForeignAccess;
import com.oracle.truffle.api.interop.Message;
import com.oracle.truffle.api.interop.TruffleObject;
import com.oracle.truffle.api.nodes.Node;

@SuppressWarnings("deprecation")
abstract class ToJavaNode extends Node {
    static final int LIMIT = 3;

    /** Subtype or lossless conversion to primitive type (incl. unboxing). */
    static final int STRICT = 0;
    /** Wrapping or array conversion; int to char. */
    static final int LOOSE = 1;
    /** Lossy conversion to String. */
    static final int COERCE = 2;
    /** Host object to interface proxy conversion. */
    static final int HOST_PROXY = 3;
    static final int[] PRIORITIES = {STRICT, LOOSE, COERCE, HOST_PROXY};

    @Child private Node isExecutable = Message.IS_EXECUTABLE.createNode();
    @Child private Node isInstantiable = Message.IS_INSTANTIABLE.createNode();
    @Child private Node isNull = Message.IS_NULL.createNode();
    @Child private Node hasKeysNode = Message.HAS_KEYS.createNode();
    @Child private ToPrimitiveNode primitive = ToPrimitiveNode.create();

    public abstract Object execute(Object value, Class targetType, Type genericType, Object languageContext);

    @SuppressWarnings("unused")
    @Specialization(guards = "operand == null")
    protected Object doNull(Object operand, Class targetType, Type genericType, Object languageContext) {
        return null;
    }

    @SuppressWarnings("unused")
    @Specialization(guards = {"operand != null", "operand.getClass() == cachedOperandType", "targetType == cachedTargetType"}, limit = "LIMIT")
    protected Object doCached(Object operand, Class targetType, Type genericType, Object languageContext,
                    @Cached("operand.getClass()") Class cachedOperandType,
                    @Cached("targetType") Class cachedTargetType) {
        return convertImpl(cachedOperandType.cast(operand), cachedTargetType, genericType, languageContext);
    }

    @Specialization(guards = "operand != null", replaces = "doCached")
    @TruffleBoundary
    protected Object doGeneric(Object operand, Class targetType, Type genericType, Object languageContext) {
        return convertImpl(operand, targetType, genericType, languageContext);
    }

    private Object convertImpl(Object value, Class targetType, Type genericType, Object languageContext) {
        Object convertedValue;
        if (isAssignableFromTrufflePrimitiveType(targetType)) {
            Object unboxed = primitive.unbox(value);
            convertedValue = primitive.toPrimitive(unboxed, targetType);
            if (convertedValue != null) {
                return convertedValue;
            } else if (targetType == char.class || targetType == Character.class) {
                Integer safeChar = primitive.toInteger(unboxed);
                if (safeChar != null) {
                    int v = safeChar;
                    if (v >= 0 && v < 65536) {
                        return (char) v;
                    }
                }
            } else if (targetType == String.class && JavaInterop.isPrimitive(unboxed)) {
                return convertToString(unboxed);
            }
        }
        if (targetType == Value.class && languageContext != null) {
            convertedValue = value instanceof Value ? value : JavaInterop.toHostValue(value, languageContext);
        } else if (value instanceof TruffleObject) {
            convertedValue = asJavaObject((TruffleObject) value, targetType, genericType, languageContext);
        } else if (targetType.isAssignableFrom(value.getClass())) {
            convertedValue = value;
        } else {
            CompilerDirectives.transferToInterpreter();
            String reason;
            if (isAssignableFromTrufflePrimitiveType(targetType)) {
                reason = "Invalid or lossy primitive coercion.";
            } else {
                reason = "Unsupported target type.";
            }
            throw JavaInteropErrors.cannotConvert(languageContext, value, targetType, reason);
        }
        return convertedValue;
    }

    boolean canConvertToPrimitive(Object value, Class targetType, int priority) {
        if (JavaObject.isJavaInstance(targetType, value)) {
            return true;
        }
        if (!isAssignableFromTrufflePrimitiveType(targetType)) {
            return false;
        }
        Object unboxed = primitive.unbox(value);
        Object convertedValue = primitive.toPrimitive(unboxed, targetType);
        if (convertedValue != null) {
            return true;
        }
        if (priority <= STRICT) {
            return false;
        }
        if (targetType == char.class || targetType == Character.class) {
            Integer safeChar = primitive.toInteger(unboxed);
            if (safeChar != null) {
                int v = safeChar;
                if (v >= 0 && v < 65536) {
                    return true;
                }
            }
        } else if (priority >= COERCE && targetType == String.class && JavaInterop.isPrimitive(unboxed)) {
            return true;
        }
        return false;
    }

    boolean canConvert(Object value, Class targetType, Type genericType, Object languageContext, int priority) {
        return canConvert(value, targetType, genericType, languageContext != null, priority);
    }

    boolean canConvert(Object value, Class targetType, int priority) {
        return canConvert(value, targetType, null, true, priority);
    }

    @SuppressWarnings({"unused"})
    private boolean canConvert(Object value, Class targetType, Type genericType, boolean allowValue, int priority) {
        if (canConvertToPrimitive(value, targetType, priority)) {
            return true;
        }
        if (priority <= STRICT) {
            return false;
        }
        if (targetType == Value.class && allowValue) {
            return true;
        } else if (value instanceof TruffleObject) {
            TruffleObject tValue = (TruffleObject) value;
            if (ForeignAccess.sendIsNull(isNull, tValue)) {
                if (targetType.isPrimitive()) {
                    return false;
                }
                return true;
            } else if (targetType == Object.class) {
                return true;
            } else if (JavaObject.isJavaInstance(targetType, tValue)) {
                return true;
            } else if (targetType == List.class) {
                return primitive.hasSize(tValue);
            } else if (targetType == Map.class) {
                return primitive.hasKeys(tValue);
            } else if (targetType == Function.class) {
                return isExecutable(tValue) || isInstantiable(tValue) || (TruffleOptions.AOT && ForeignAccess.sendHasKeys(hasKeysNode, tValue));
            } else if (targetType.isArray()) {
                return primitive.hasKeys(tValue);
            } else if (priority < HOST_PROXY && JavaObject.isInstance(tValue)) {
                return false;
            } else {
                if (TruffleOptions.AOT) {
                    // support Function also with AOT
                    if (targetType == Function.class) {
                        return isExecutable(tValue) || isInstantiable(tValue);
                    } else {
                        return false;
                    }
                } else {
                    if (JavaInteropReflect.isFunctionalInterface(targetType) && (isExecutable(tValue) || isInstantiable(tValue))) {
                        return true;
                    } else if (targetType.isInterface() && ForeignAccess.sendHasKeys(hasKeysNode, tValue)) {
                        return true;
                    } else {
                        return false;
                    }
                }
            }
        } else {
            assert !(value instanceof TruffleObject);
            return targetType.isInstance(value);
        }
    }

    static boolean isAssignableFromTrufflePrimitiveType(Class clazz) {
        return clazz == int.class || clazz == Integer.class ||
                        clazz == boolean.class || clazz == Boolean.class ||
                        clazz == byte.class || clazz == Byte.class ||
                        clazz == short.class || clazz == Short.class ||
                        clazz == long.class || clazz == Long.class ||
                        clazz == float.class || clazz == Float.class ||
                        clazz == double.class || clazz == Double.class ||
                        clazz == char.class || clazz == Character.class ||
                        clazz == Number.class ||
                        CharSequence.class.isAssignableFrom(clazz);
    }

    private boolean isExecutable(TruffleObject object) {
        return ForeignAccess.sendIsExecutable(isExecutable, object);
    }

    private boolean isInstantiable(TruffleObject object) {
        return ForeignAccess.sendIsInstantiable(isInstantiable, object);
    }

    private Object convertToObject(TruffleObject truffleObject, Object languageContext) {
        Object primitiveValue = primitive.unbox(truffleObject);
        if (primitiveValue != null) {
            return primitiveValue;
        } else if (languageContext == null) {
            // for legacy support
            return truffleObject;
        } else if (primitive.hasKeys(truffleObject)) {
            return asJavaObject(truffleObject, Map.class, null, languageContext);
        } else if (primitive.hasSize(truffleObject)) {
            return asJavaObject(truffleObject, List.class, null, languageContext);
        } else if (isExecutable(truffleObject) || isInstantiable(truffleObject)) {
            return asJavaObject(truffleObject, Function.class, null, languageContext);
        } else {
            return JavaInterop.toHostValue(truffleObject, languageContext);
        }
    }

    @TruffleBoundary
    private  T asJavaObject(TruffleObject truffleObject, Class targetType, Type genericType, Object languageContext) {
        Objects.requireNonNull(truffleObject);
        Object obj;
        if (primitive.isNull(truffleObject)) {
            if (targetType.isPrimitive()) {
                throw JavaInteropErrors.nullCoercion(languageContext, truffleObject, targetType);
            }
            return null;
        } else if (JavaObject.isJavaInstance(targetType, truffleObject)) {
            obj = JavaObject.valueOf(truffleObject);
        } else if (targetType == Object.class) {
            obj = convertToObject(truffleObject, languageContext);
        } else if (languageContext == null && targetType.isInstance(truffleObject)) {
            // legacy support for cast rather than wrap
            return targetType.cast(truffleObject);
        } else if (targetType == List.class) {
            if (primitive.hasSize(truffleObject)) {
                boolean implementsFunction = shouldImplementFunction(truffleObject);
                TypeAndClass elementType = getGenericParameterType(genericType, 0);
                obj = TruffleList.create(languageContext, truffleObject, implementsFunction, elementType.clazz, elementType.type);
            } else {
                throw JavaInteropErrors.cannotConvert(languageContext, truffleObject, targetType, "Value must have array elements.");
            }
        } else if (targetType == Map.class) {
            Class keyClazz = getGenericParameterType(genericType, 0).clazz;
            TypeAndClass valueType = getGenericParameterType(genericType, 1);
            if (!isSupportedMapKeyType(keyClazz)) {
                throw newInvalidKeyTypeException(keyClazz);
            }
            boolean hasSize = (Number.class.isAssignableFrom(keyClazz)) && primitive.hasSize(truffleObject);
            boolean hasKeys = (keyClazz == Object.class || keyClazz == String.class) && primitive.hasKeys(truffleObject);
            if (hasKeys || hasSize) {
                boolean implementsFunction = shouldImplementFunction(truffleObject);
                obj = TruffleMap.create(languageContext, truffleObject, implementsFunction, keyClazz, valueType.clazz, valueType.type);
            } else {
                throw JavaInteropErrors.cannotConvert(languageContext, truffleObject, targetType, "Value must have members or array elements.");
            }
        } else if (targetType == Function.class) {
            TypeAndClass returnType = getGenericParameterType(genericType, 1);
            if (isExecutable(truffleObject) || isInstantiable(truffleObject)) {
                obj = TruffleFunction.create(languageContext, truffleObject, returnType.clazz, returnType.type);
            } else if (!TruffleOptions.AOT && ForeignAccess.sendHasKeys(hasKeysNode, truffleObject)) {
                obj = JavaInteropReflect.newProxyInstance(targetType, truffleObject, languageContext);
            } else {
                throw JavaInteropErrors.cannotConvert(languageContext, truffleObject, targetType, "Value must be executable or instantiable.");
            }
        } else if (targetType.isArray()) {
            if (primitive.hasSize(truffleObject)) {
                obj = truffleObjectToArray(truffleObject, targetType, genericType, languageContext);
            } else {
                throw JavaInteropErrors.cannotConvert(languageContext, truffleObject, targetType, "Value must have array elements.");
            }
        } else if (!TruffleOptions.AOT && targetType.isInterface()) {
            if (JavaInteropReflect.isFunctionalInterface(targetType) && (isExecutable(truffleObject) || isInstantiable(truffleObject))) {
                obj = JavaInteropReflect.asJavaFunction(targetType, truffleObject, languageContext);
            } else if (ForeignAccess.sendHasKeys(hasKeysNode, truffleObject)) {
                obj = JavaInteropReflect.newProxyInstance(targetType, truffleObject, languageContext);
            } else {
                if (languageContext == null) {
                    // legacy support
                    obj = JavaInteropReflect.newProxyInstance(targetType, truffleObject, languageContext);
                } else {
                    throw JavaInteropErrors.cannotConvert(languageContext, truffleObject, targetType, "Value must have members.");
                }
            }
        } else {
            throw JavaInteropErrors.cannotConvert(languageContext, truffleObject, targetType, "Unsupported target type.");
        }

        assert targetType.isInstance(obj);
        return targetType.cast(obj);
    }

    private boolean shouldImplementFunction(TruffleObject truffleObject) {
        boolean executable = isExecutable(truffleObject);
        boolean instantiable = false;
        if (!executable) {
            instantiable = isInstantiable(truffleObject);
        }
        boolean implementsFunction = executable || instantiable;
        return implementsFunction;
    }

    private static boolean isSupportedMapKeyType(Class keyType) {
        return keyType == Object.class || keyType == String.class || keyType == Long.class || keyType == Integer.class || keyType == Number.class;
    }

    @TruffleBoundary
    private static ClassCastException newInvalidKeyTypeException(Type targetType) {
        String message = "Unsupported Map key type: " + targetType;
        return newClassCastException(message);
    }

    @TruffleBoundary
    private static ClassCastException newClassCastException(String message) {
        EngineSupport engine = JavaInteropAccessor.ACCESSOR.engine();
        return engine != null ? engine.newClassCastException(message, null) : new ClassCastException(message);
    }

    private static TypeAndClass getGenericParameterType(Type genericType, int index) {
        if (!TruffleOptions.AOT && genericType instanceof ParameterizedType) {
            ParameterizedType parametrizedType = (ParameterizedType) genericType;
            final Type[] typeArguments = parametrizedType.getActualTypeArguments();
            Class elementClass = Object.class;
            if (index < typeArguments.length) {
                Type elementType = typeArguments[index];
                if (elementType instanceof ParameterizedType) {
                    elementType = ((ParameterizedType) elementType).getRawType();
                }
                if (elementType instanceof Class) {
                    elementClass = (Class) elementType;
                }
                return new TypeAndClass<>(typeArguments[index], elementClass);
            }
        }
        return TypeAndClass.ANY;
    }

    private static Type getGenericArrayComponentType(Type genericType) {
        Type genericComponentType = null;
        if (!TruffleOptions.AOT && genericType instanceof GenericArrayType) {
            GenericArrayType genericArrayType = (GenericArrayType) genericType;
            genericComponentType = genericArrayType.getGenericComponentType();
        }
        return genericComponentType;
    }

    private static Object truffleObjectToArray(TruffleObject foreignObject, Class arrayType, Type genericArrayType, Object languageContext) {
        Class componentType = arrayType.getComponentType();
        List list = TruffleList.create(languageContext, foreignObject, false, componentType, getGenericArrayComponentType(genericArrayType));
        Object array = Array.newInstance(componentType, list.size());
        for (int i = 0; i < list.size(); i++) {
            Array.set(array, i, list.get(i));
        }
        return array;
    }

    @TruffleBoundary
    private static String convertToString(Object value) {
        return value.toString();
    }

    public static ToJavaNode create() {
        return ToJavaNodeGen.create();
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy