package com.oracle.truffle.api.test.host;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import java.util.ArrayList;
import java.util.List;
import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.HostAccess;
import org.junit.Before;
import org.junit.Test;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.interop.InteropException;
import com.oracle.truffle.api.interop.InteropLibrary;
import com.oracle.truffle.api.interop.InvalidArrayIndexException;
import com.oracle.truffle.api.interop.TruffleObject;
import com.oracle.truffle.api.interop.UnsupportedMessageException;
import com.oracle.truffle.api.interop.UnsupportedTypeException;
import com.oracle.truffle.api.library.CachedLibrary;
import com.oracle.truffle.api.library.ExportLibrary;
import com.oracle.truffle.api.library.ExportMessage;
import com.oracle.truffle.api.test.examples.TargetMappings;
import com.oracle.truffle.api.test.polyglot.AbstractPolyglotTest;
import com.oracle.truffle.api.test.polyglot.ProxyLanguage;
public class JavaStringCoercionTest extends AbstractPolyglotTest {
private static final InteropLibrary INTEROP = InteropLibrary.getFactory().getUncached();
@Before
public void before() {
HostAccess access = TargetMappings.enableStringCoercions(HostAccess.newBuilder().allowPublicAccess(true)).build();
setupEnv(Context.newBuilder().allowHostAccess(access).allowAllAccess(true).build(),
ProxyLanguage.setDelegate(new ProxyLanguage() {
@Override
protected boolean isObjectOfLanguage(Object object) {
if (object instanceof UnboxableArrayObject) {
return true;
}
return super.isObjectOfLanguage(object);
}
@Override
protected String toString(LanguageContext c, Object value) {
if (value instanceof UnboxableArrayObject) {
return "UnboxableArray";
}
return super.toString(c, value);
}
}));
}
public static class StringConsumer1 {
public Object call(String arg) {
return arg;
}
}
public static class StringConsumer2 {
public Object call(List<?> arg) {
return arg.toString() + "(" + arg.size() + "):" + new ArrayList<>(arg).toString();
}
public Object call(String arg) {
return arg;
}
}
@Test
public void testStringCoercionSingleMethod() throws InteropException {
TruffleObject api = (TruffleObject) languageEnv.asGuestValue(new StringConsumer1());
testStringCoercion(api);
}
@Test
public void testStringCoercionOverloadedMethod() throws InteropException {
TruffleObject api = (TruffleObject) languageEnv.asGuestValue(new StringConsumer2());
testStringCoercion(api);
}
@Test
public void testPreferWrappingToStringCoercion() throws InteropException {
TruffleObject api = (TruffleObject) languageEnv.asGuestValue(new StringConsumer2());
Object list = call(api, new UnboxableArrayObject(4));
assertEquals("4", list);
}
private static void testStringCoercion(TruffleObject api) throws InteropException {
assertEquals("ok", call(api, "ok"));
assertEquals("42", call(api, 42));
assertEquals("true", call(api, true));
assertEquals("-128", call(api, Byte.MIN_VALUE));
assertEquals("-32768", call(api, Short.MIN_VALUE));
assertEquals("9223372036854775807", call(api, Long.MAX_VALUE));
assertEquals("3.14", call(api, 3.14));
assertEquals("3.14", call(api, 3.14f));
assertEquals("NaN", call(api, Double.NaN));
assertEquals("Infinity", call(api, Double.POSITIVE_INFINITY));
assertEquals("-Infinity", call(api, Double.NEGATIVE_INFINITY));
assertEquals("-0.0", call(api, -0.0));
assertEquals("\uffff", call(api, Character.MAX_VALUE));
assertEquals("42", call(api, new UnboxableToInt(42)));
callUnsupported(api, new NotCoercibleObject());
}
private static Object call(TruffleObject obj, Object value) throws InteropException {
try {
return INTEROP.invokeMember(obj, "call", value);
} catch (UnsupportedTypeException e) {
throw new AssertionError("String coercion failed for: " + value + " (" + (value == null ? null : value.getClass().getName()) + ")", e);
}
}
private static void callUnsupported(TruffleObject obj, Object value) throws InteropException {
try {
INTEROP.invokeMember(obj, "call", value);
fail("Expected coercion to fail");
} catch (UnsupportedTypeException e) {
}
}
public static class IntConsumer {
public Object call(int arg) {
return arg;
}
}
public static class IntegerConsumer {
public Object call(Integer arg) {
return arg;
}
}
public static class PrimitiveConsumer {
public Object call(int arg) {
return arg;
}
public Object call(long arg) {
return arg;
}
public Object call(double arg) {
return arg;
}
public Object call(boolean arg) {
return arg;
}
}
public static class BoxedPrimitiveConsumer {
public Object call(Integer arg) {
return arg;
}
public Object call(Long arg) {
return arg;
}
public Object call(Double arg) {
return arg;
}
public Object call(Boolean arg) {
return arg;
}
}
public static class ObjectOrIntConsumer {
public Object call(int arg) {
return arg;
}
public Object call(Integer arg) {
return arg;
}
public Object call(Object arg) {
return arg;
}
}
@Test
public void testStringToPrimitiveSingleMethod() throws InteropException {
for (Object consumer : new Object[]{new IntConsumer(), new IntegerConsumer()}) {
TruffleObject api = (TruffleObject) languageEnv.asGuestValue(consumer);
assertEquals(42, call(api, "42"));
assertEquals(42, call(api, "+42"));
assertEquals(-42, call(api, "-42"));
callUnsupported(api, "42garbage");
callUnsupported(api, "2147483648");
callUnsupported(api, "42.0");
callUnsupported(api, " 42");
callUnsupported(api, "42 ");
}
}
@Test
public void testStringToPrimitiveOverloadedMethod() throws InteropException {
for (Object consumer : new Object[]{new PrimitiveConsumer(), new BoxedPrimitiveConsumer()}) {
TruffleObject api = (TruffleObject) languageEnv.asGuestValue(consumer);
assertEquals(2147483648L, call(api, "2147483648"));
assertEquals(42, call(api, "42"));
assertEquals(42, call(api, "+42"));
assertEquals(-42, call(api, "-42"));
assertEquals(4.2, call(api, "4.2"));
assertEquals(true, call(api, "true"));
assertEquals(false, call(api, "false"));
assertEquals(42.0, call(api, "42.0"));
assertEquals(42, call(api, "42"));
assertEquals(true, call(api, "true"));
assertEquals(false, call(api, "false"));
assertEquals(42, call(api, "42"));
callUnsupported(api, "42garbage");
callUnsupported(api, "0x42");
callUnsupported(api, "True");
callUnsupported(api, " 42");
callUnsupported(api, "42 ");
}
}
@Test
public void testStringToPrimitiveLowPriority() throws InteropException {
TruffleObject api = (TruffleObject) languageEnv.asGuestValue(new ObjectOrIntConsumer());
assertEquals(42, call(api, "42"));
}
static final class NotCoercibleObject implements TruffleObject {
}
@SuppressWarnings("unused")
@ExportLibrary(InteropLibrary.class)
static final class UnboxableArrayObject implements TruffleObject {
final int value;
UnboxableArrayObject(int size) {
this.value = size;
}
@SuppressWarnings("static-method")
@ExportMessage
boolean hasArrayElements() {
return true;
}
@ExportMessage
Object readArrayElement(long index) throws UnsupportedMessageException, InvalidArrayIndexException {
if (index < 0 || index >= value) {
CompilerDirectives.transferToInterpreter();
throw InvalidArrayIndexException.create(index);
}
return index;
}
@ExportMessage
long getArraySize() throws UnsupportedMessageException {
return value;
}
@ExportMessage
boolean isArrayElementReadable(long index) {
return index >= 0 && index < value;
}
@SuppressWarnings("static-method")
@ExportMessage
public boolean isNumber() {
return true;
}
@ExportMessage
boolean fitsInByte(@CachedLibrary("this.value") InteropLibrary delegate) {
return delegate.fitsInByte(value);
}
@ExportMessage
boolean fitsInShort(@CachedLibrary("this.value") InteropLibrary delegate) {
return delegate.fitsInShort(value);
}
@ExportMessage
boolean fitsInInt(@CachedLibrary("this.value") InteropLibrary delegate) {
return delegate.fitsInInt(value);
}
@ExportMessage
boolean fitsInLong(@CachedLibrary("this.value") InteropLibrary delegate) {
return delegate.fitsInLong(value);
}
@ExportMessage
boolean fitsInFloat(@CachedLibrary("this.value") InteropLibrary delegate) {
return delegate.fitsInFloat(value);
}
@ExportMessage
boolean fitsInDouble(@CachedLibrary("this.value") InteropLibrary delegate) {
return delegate.fitsInDouble(value);
}
@ExportMessage
byte asByte(@CachedLibrary("this.value") InteropLibrary delegate) throws UnsupportedMessageException {
return delegate.asByte(value);
}
@ExportMessage
short asShort(@CachedLibrary("this.value") InteropLibrary delegate) throws UnsupportedMessageException {
return delegate.asShort(value);
}
@ExportMessage
int asInt(@CachedLibrary("this.value") InteropLibrary delegate) throws UnsupportedMessageException {
return delegate.asInt(value);
}
@ExportMessage
long asLong(@CachedLibrary("this.value") InteropLibrary delegate) throws UnsupportedMessageException {
return delegate.asLong(value);
}
@ExportMessage
float asFloat(@CachedLibrary("this.value") InteropLibrary delegate) throws UnsupportedMessageException {
return delegate.asFloat(value);
}
@ExportMessage
double asDouble(@CachedLibrary("this.value") InteropLibrary delegate) throws UnsupportedMessageException {
return delegate.asDouble(value);
}
}
}