package com.oracle.truffle.api.test.polyglot;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import org.hamcrest.CoreMatchers;
import org.junit.Before;
import org.junit.Test;
import com.oracle.truffle.api.interop.InteropException;
import com.oracle.truffle.api.interop.InteropLibrary;
import com.oracle.truffle.api.interop.TruffleObject;
import com.oracle.truffle.api.interop.UnsupportedMessageException;
import com.oracle.truffle.api.test.polyglot.ValueHostInteropTest.Data;
public class LanguageSPIHostInteropTest extends AbstractPolyglotTest {
private static final InteropLibrary INTEROP = InteropLibrary.getFactory().getUncached();
@Before
public void before() {
setupEnv();
}
@Test
public void testSystemMethod() throws InteropException {
Object system = languageEnv.lookupHostSymbol(System.class.getName());
Object value = INTEROP.invokeMember(system, "getProperty", "file.separator");
assertThat(value, CoreMatchers.instanceOf(String.class));
assertThat(value, CoreMatchers.anyOf(CoreMatchers.equalTo("/"), CoreMatchers.equalTo("\\")));
Object getProperty = INTEROP.readMember(system, "getProperty");
assertThat(getProperty, CoreMatchers.instanceOf(TruffleObject.class));
assertTrue("IS_EXECUTABLE", INTEROP.isExecutable(getProperty));
value = INTEROP.execute(getProperty, "file.separator");
assertThat(value, CoreMatchers.instanceOf(String.class));
assertThat(value, CoreMatchers.anyOf(CoreMatchers.equalTo("/"), CoreMatchers.equalTo("\\")));
}
static class TestClass {
}
@Test
public void conversionToClassYieldsTheClass() {
Object javaValue = languageEnv.asHostObject(languageEnv.asGuestValue(TestClass.class));
Object javaSymbol = languageEnv.asHostObject(languageEnv.lookupHostSymbol(TestClass.class.getName()));
assertTrue(javaValue instanceof Class);
assertSame("Both class objects are the same", javaValue, javaSymbol);
}
@Test
public void conversionToClassNull() {
Object meta = languageEnv.findMetaObject(languageEnv.asGuestValue(null));
assertNull(meta);
}
@Test
public void nullAsJavaObject() {
Object nullObject = languageEnv.asGuestValue(null);
assertTrue(languageEnv.isHostObject(nullObject));
assertNull(languageEnv.asHostObject(nullObject));
}
@SuppressWarnings("unchecked")
@Test
public void assertKeysAndProperties() throws InteropException {
Data dataObj = new Data();
TruffleObject data = (TruffleObject) languageEnv.asGuestValue(dataObj);
Object keys = INTEROP.getMembers(data);
List<Object> list = context.asValue(keys).as(List.class);
assertThat(list, CoreMatchers.hasItems("x", "y", "arr", "value", "map", "dataMap", "data", "plus"));
Method[] objectMethods = Object.class.getMethods();
for (Method objectMethod : objectMethods) {
assertThat("No java.lang.Object methods", list, CoreMatchers.not(CoreMatchers.hasItem(objectMethod.getName())));
}
keys = INTEROP.getMembers(data, true);
list = context.asValue(keys).as(List.class);
for (Method objectMethod : objectMethods) {
assertThat("java.lang.Object methods", list, CoreMatchers.hasItem(objectMethod.getName()));
}
}
@Test
public void invokeJavaLangObjectFields() throws InteropException {
Data data = new Data();
TruffleObject obj = (TruffleObject) languageEnv.asGuestValue(data);
Object string = INTEROP.invokeMember(obj, "toString");
assertTrue(string instanceof String && ((String) string).startsWith(Data.class.getName() + "@"));
Object clazz = INTEROP.invokeMember(obj, "getClass");
assertTrue(clazz instanceof TruffleObject && languageEnv.asHostObject(clazz) == Data.class);
assertEquals(true, INTEROP.invokeMember(obj, "equals", obj));
assertTrue(INTEROP.invokeMember(obj, "hashCode") instanceof Integer);
for (String m : new String[]{"notify", "notifyAll", "wait"}) {
assertThrowsExceptionWithCause(() -> INTEROP.invokeMember(obj, m), IllegalMonitorStateException.class);
}
}
@Test
public void indexJavaArrayWithNumberTypes() throws Exception {
int[] a = new int[]{1, 2, 3};
TruffleObject truffleObject = (TruffleObject) languageEnv.asGuestValue(a);
assertEquals(2, INTEROP.readArrayElement(truffleObject, 1));
assertEquals(2, INTEROP.readArrayElement(truffleObject, 1L));
INTEROP.writeArrayElement(truffleObject, 1, 42);
INTEROP.writeArrayElement(truffleObject, 1L, 42);
assertEquals(42, INTEROP.readArrayElement(truffleObject, 1));
assertEquals(42, INTEROP.readArrayElement(truffleObject, 1L));
}
@Test
public void testAsGuestValue() {
Object object = new Object();
assertEquals(languageEnv.asGuestValue(object), languageEnv.asGuestValue(object));
Data data = new Data();
Object obj = languageEnv.asGuestValue(data);
assertEquals(obj, languageEnv.asGuestValue(data));
object = 42;
assertTrue(languageEnv.asGuestValue(object) == object);
object = (byte) 42;
assertTrue(languageEnv.asGuestValue(object) == object);
object = (short) 42;
assertTrue(languageEnv.asGuestValue(object) == object);
object = 424242424242L;
assertTrue(languageEnv.asGuestValue(object) == object);
object = 42.42;
assertTrue(languageEnv.asGuestValue(object) == object);
object = true;
assertTrue(languageEnv.asGuestValue(object) == object);
object = "42";
assertTrue(languageEnv.asGuestValue(object) == object);
object = '4';
assertTrue(languageEnv.asGuestValue(object) == object);
object = true;
assertTrue(languageEnv.asGuestValue(object) == object);
}
@Test
public void notUnboxable() {
assertError(() -> INTEROP.asInt(languageEnv.asGuestValue(null)), UnsupportedMessageException.class);
assertError(() -> INTEROP.asString(languageEnv.asGuestValue(null)), UnsupportedMessageException.class);
assertError(() -> INTEROP.asBoolean(languageEnv.asGuestValue(null)), UnsupportedMessageException.class);
}
@Test
public void testPrimitiveBoxing() throws InteropException {
assertNumberMembers((byte) 1);
assertNumberMembers((short) 1);
assertNumberMembers(1);
assertNumberMembers(1L);
assertNumberMembers(1F);
assertNumberMembers(1D);
assertStringMembers("foobarbaz", languageEnv.asBoxedGuestValue("foobarbaz"));
assertStringMembers("", languageEnv.asBoxedGuestValue(""));
assertBooleanMembers(false, languageEnv.asBoxedGuestValue(false));
assertBooleanMembers(true, languageEnv.asBoxedGuestValue(true));
assertCharacterMembers('a', languageEnv.asBoxedGuestValue('a'));
}
private void assertNumberMembers(Number testNumber) throws InteropException {
Object guestValue = languageEnv.asBoxedGuestValue(testNumber);
assertTrue(guestValue instanceof TruffleObject);
assertInvocable(testNumber.intValue(), guestValue, "intValue");
assertInvocable(testNumber.longValue(), guestValue, "longValue");
assertInvocable(testNumber.floatValue(), guestValue, "floatValue");
assertInvocable(testNumber.doubleValue(), guestValue, "doubleValue");
assertInvocable(testNumber.byteValue(), guestValue, "byteValue");
assertInvocable(testNumber.shortValue(), guestValue, "shortValue");
assertInvocable(true, guestValue, "equals", testNumber);
assertInvocable(testNumber.hashCode(), guestValue, "hashCode");
assertInvocable(testNumber.toString(), guestValue, "toString");
assertTrue(languageEnv.isHostObject(guestValue));
assertEquals(testNumber, languageEnv.asHostObject(guestValue));
}
private static void assertInvocable(Object expectedValue, Object receiver, String method, Object... args) throws InteropException {
TruffleObject o = (TruffleObject) receiver;
assertTrue(INTEROP.isMemberReadable(receiver, method));
assertTrue(INTEROP.isMemberInvocable(receiver, method));
assertFalse(INTEROP.isMemberWritable(receiver, method));
assertFalse(INTEROP.isMemberInsertable(receiver, method));
assertFalse(INTEROP.isMemberInternal(receiver, method));
assertFalse(INTEROP.isMemberRemovable(receiver, method));
Object methodObject = INTEROP.readMember(o, method);
assertEquals(expectedValue, INTEROP.execute(methodObject, args));
assertEquals(expectedValue, INTEROP.invokeMember(receiver, method, args));
assertHasKey(o, method);
}
private static void assertHasKey(Object receiver, String key) throws InteropException {
TruffleObject o = (TruffleObject) receiver;
Object keys = INTEROP.getMembers(o);
long size = INTEROP.getArraySize(keys);
List<String> allKeys = new ArrayList<>();
for (int i = 0; i < size; i++) {
String search = (String) INTEROP.readArrayElement(keys, i);
if (search.equals(key)) {
return;
}
allKeys.add(search);
}
throw new AssertionError("Key not found " + key + ". Keys are " + allKeys);
}
private void assertStringMembers(String unboxValue, Object stringObject) throws InteropException {
String string = INTEROP.asString(stringObject);
assertEquals(unboxValue, string);
assertInvocable(string.length(), stringObject, "length");
if (unboxValue.length() > 0) {
assertInvocable(string.charAt(0), stringObject, "charAt", 0);
}
assertInvocable(string.equals(unboxValue), stringObject, "equals", unboxValue);
assertInvocable(string.hashCode(), stringObject, "hashCode");
assertInvocable(string.toString(), stringObject, "toString");
assertTrue(languageEnv.isHostObject(stringObject));
assertEquals(string, languageEnv.asHostObject(stringObject));
}
private void assertBooleanMembers(Object unboxValue, Object booleanObject) throws InteropException {
Boolean b = INTEROP.asBoolean(booleanObject);
assertEquals(unboxValue, b);
assertInvocable(unboxValue, booleanObject, "booleanValue");
assertInvocable(b.equals(unboxValue), booleanObject, "equals", unboxValue);
assertInvocable(b.compareTo((Boolean) unboxValue), booleanObject, "compareTo", unboxValue);
assertInvocable(b.hashCode(), booleanObject, "hashCode");
assertInvocable(b.toString(), booleanObject, "toString");
assertTrue(languageEnv.isHostObject(booleanObject));
assertEquals(b, languageEnv.asHostObject(booleanObject));
}
private void assertCharacterMembers(Character unboxValue, Object charObject) throws InteropException {
String b = INTEROP.asString(charObject);
assertEquals(unboxValue.toString(), b);
assertInvocable(unboxValue, charObject, "charValue");
assertInvocable(b.equals(unboxValue.toString()), charObject, "equals", unboxValue);
assertInvocable(b.compareTo(unboxValue.toString()), charObject, "compareTo", unboxValue);
assertInvocable(b.hashCode(), charObject, "hashCode");
assertInvocable(b.toString(), charObject, "toString");
assertTrue(languageEnv.isHostObject(charObject));
assertEquals(unboxValue, languageEnv.asHostObject(charObject));
}
@Test
public void keyInfoJavaObject() {
Object d = languageEnv.asGuestValue(new TestJavaObject());
assertFalse(INTEROP.isMemberExisting(d, "nnoonnee"));
assertTrue(INTEROP.isMemberExisting(d, "aField"));
assertTrue(INTEROP.isMemberReadable(d, "aField"));
assertTrue(INTEROP.isMemberWritable(d, "aField"));
assertFalse(INTEROP.isMemberInvocable(d, "aField"));
assertFalse(INTEROP.isMemberRemovable(d, "aField"));
assertTrue(INTEROP.isMemberExisting(d, "toString"));
assertTrue(INTEROP.isMemberReadable(d, "toString"));
assertFalse(INTEROP.isMemberWritable(d, "toString"));
assertTrue(INTEROP.isMemberInvocable(d, "toString"));
assertFalse(INTEROP.isMemberRemovable(d, "toString"));
}
private static void assertError(Callable<?> callable, Class<? extends Exception> exception) {
try {
callable.call();
fail("Expected " + exception.getSimpleName() + " but no exception was thrown");
} catch (Exception e) {
assertTrue(exception.toString(), exception.isInstance(e));
}
}
private void assertThrowsExceptionWithCause(Callable<?> callable, Class<? extends Exception> exception) {
try {
callable.call();
fail("Expected " + exception.getSimpleName() + " but no exception was thrown");
} catch (Exception e) {
assertTrue(languageEnv.isHostException(e));
assertSame(exception, languageEnv.asHostException(e).getClass());
}
}
@Test
public void testIsHostFunction() throws InteropException {
TruffleObject system = (TruffleObject) languageEnv.lookupHostSymbol(System.class.getName());
Object exit = INTEROP.readMember(system, "exit");
assertTrue(exit instanceof TruffleObject);
assertFalse(languageEnv.isHostObject(exit));
assertTrue(languageEnv.isHostFunction(exit));
Object out = INTEROP.readMember(system, "out");
assertTrue(exit instanceof TruffleObject);
assertTrue(languageEnv.isHostObject(out));
assertFalse(languageEnv.isHostFunction(out));
assertFalse(languageEnv.isHostFunction(system));
assertFalse(languageEnv.isHostFunction(false));
}
public static final class TestJavaObject {
public int aField = 10;
}
}