package com.oracle.truffle.api.test.polyglot;
import static com.oracle.truffle.tck.tests.ValueAssert.assertValue;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNotSame;
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 static org.junit.Assume.assumeTrue;
import java.awt.Color;
import java.awt.image.BufferedImage;
import java.lang.reflect.Method;
import java.util.AbstractList;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
import org.graalvm.polyglot.PolyglotException;
import org.graalvm.polyglot.TypeLiteral;
import org.graalvm.polyglot.Value;
import org.graalvm.polyglot.proxy.ProxyObject;
import org.hamcrest.CoreMatchers;
import org.junit.Assume;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.TruffleOptions;
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.UnknownIdentifierException;
import com.oracle.truffle.api.interop.UnsupportedMessageException;
import com.oracle.truffle.api.library.ExportLibrary;
import com.oracle.truffle.api.library.ExportMessage;
public class ValueHostInteropTest extends AbstractPolyglotTest {
public static final boolean Java9OrLater = System.getProperty("java.specification.version").compareTo("1.9") >= 0;
public static class Data {
public int x;
public double y;
public String[] arr;
public Object value;
public Object map;
public Object dataMap;
public Data[] data;
public double plus(double a, double b) {
return a + b;
}
public Object assertThis(Object param) {
assertSame("When a Java object is passed into Truffle and back, it is again the same object", this, param);
return this;
}
}
@Before
public void initObjects() {
setupEnv();
}
@Test
public void testAccessInvisibleAPIVirtualCall() {
if (TruffleOptions.AOT) {
return;
}
Value imageClass = context.asValue(java.awt.image.BufferedImage.class);
Value image = imageClass.newInstance(450, 450, BufferedImage.TYPE_INT_RGB);
Value graphics = image.invokeMember("getGraphics");
graphics.invokeMember("setBackground", Color.white);
}
@Test
public void testAccessInvisibleAPIDirect() {
if (TruffleOptions.AOT) {
return;
}
try {
languageEnv.lookupHostSymbol("sun.awt.image.OffScreenImage");
if (Java9OrLater) {
fail("On >= Java9 sun.awt.image should not be visible.");
}
} catch (RuntimeException e) {
if (!Java9OrLater) {
fail("On < Java9 sun.awt.image should be visible.");
}
}
}
@Test
public void testRecursiveListMarshalling() {
List<Data> testList = Arrays.asList(new Data());
Value testListValue = context.asValue(testList);
assertTrue(testListValue.isHostObject());
Value data = testListValue.getArrayElement(0);
assertTrue(data.isHostObject());
assertValue(testListValue);
assertValue(data);
}
@Test
public void conversionToClassNull() {
assertNull(context.asValue(null).getMetaObject());
}
@Test
public void nullAsJavaObject() {
assertNull(context.asValue(null).asHostObject());
assertTrue(context.asValue(null).isHostObject());
}
@Test
public void doubleWrap() {
Data data = new Data();
data.x = 32;
data.y = 10.1;
XYPlus xyp = context.asValue(data).as(XYPlus.class);
assertEquals("Assume delegated", 42.1d, xyp.plus(xyp.x(), xyp.y()), 0.05);
}
@Test
public void assertThisIsSame() {
AtomicReference<Boolean> thisCalled = new AtomicReference<>(false);
Data data = new Data() {
@Override
public Object assertThis(Object param) {
thisCalled.set(true);
return super.assertThis(param);
}
};
XYPlus xyp = context.asValue(data).as(XYPlus.class);
XYPlus anotherThis = xyp.assertThis(data);
assertTrue(thisCalled.get());
data.x = 44;
assertEquals(44, anotherThis.x());
assertNotSame(anotherThis, xyp);
assertEquals(anotherThis, xyp);
assertEquals(anotherThis.hashCode(), xyp.hashCode());
}
@Test
public void assertKeysAndProperties() {
Data data = new Data();
Value dataValue = context.asValue(data);
assertThat(dataValue.getMemberKeys(), 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", dataValue.getMemberKeys(), CoreMatchers.not(CoreMatchers.hasItem(objectMethod.getName())));
}
}
@Test
public void assertKeysFromAMap() {
Map<String, Integer> map = new HashMap<>();
map.put("one", 1);
map.put("null", null);
map.put("three", 3);
Set<String> memberKeys = context.asValue(map).getMemberKeys();
assertFalse(memberKeys.contains("one"));
assertFalse(memberKeys.contains("null"));
assertFalse(memberKeys.contains("three"));
assertValue(context.asValue(map));
}
@Test
public void readUnknownField() throws Exception {
Value dataValue = context.asValue(new Data());
assertFalse(dataValue.hasMember("unknown"));
assertFalse(dataValue.getMemberKeys().contains("unknown"));
assertNull(dataValue.getMember("unknown"));
}
static void assertThrowsExceptionWithCause(Callable<?> callable, Class<? extends Exception> exception) throws Exception {
try {
callable.call();
fail("Expected " + exception.getSimpleName() + " but no exception was thrown");
} catch (PolyglotException e) {
assertTrue(e.isHostException());
assertEquals(exception, e.asHostException().getClass());
} catch (Exception e) {
throw e;
}
}
@Test
public void readJavaLangObjectFields() {
assertReadMethod("notify");
assertReadMethod("notifyAll");
assertReadMethod("wait");
assertReadMethod("hashCode");
assertReadMethod("equals");
assertReadMethod("toString");
assertReadMethod("getClass");
}
private void assertReadMethod(final String name) {
Value dataValue = context.asValue(new Data());
Value member = dataValue.getMember(name);
assertNotNull(member);
assertTrue(member.canExecute());
}
@Test
public void invokeJavaLangObjectFields() throws Exception {
Value obj = context.asValue(new Data());
String toStringValue = obj.getMember("toString").execute().asString();
String indirectToStringValue = obj.toString();
assertEquals(toStringValue, indirectToStringValue);
assertTrue(toStringValue.startsWith(Data.class.getName() + "@"));
assertSame(Data.class, obj.getMember("getClass").execute().asHostObject());
assertTrue(obj.getMember("equals").execute(obj).asBoolean());
assertEquals(obj.asHostObject().hashCode(), obj.getMember("hashCode").execute().asInt());
for (String m : new String[]{"notify", "notifyAll", "wait"}) {
assertThrowsExceptionWithCause(() -> obj.getMember(m).execute(), IllegalMonitorStateException.class);
}
}
class PrivatePOJO {
public int x;
}
@Test
public void accessAllProperties() {
Value pojo = context.asValue(new PrivatePOJO());
Map<?, ?> map = pojo.as(Map.class);
int cnt = 0;
for (Map.Entry<?, ?> entry : map.entrySet()) {
Object key = entry.getKey();
Object value = entry.getValue();
assertNotNull(key);
assertNotNull(value);
cnt++;
}
assertEquals("No properties", 0, cnt);
assertEquals("Empty: " + map, 0, map.size());
}
@Test
public void accessAllPropertiesDirectly() {
Value pojo = context.asValue(new PrivatePOJO());
assertEquals("No props, class isn't public", 0, pojo.getMemberKeys().size());
}
public static class PublicPOJO {
PublicPOJO() {
}
public int x;
public static int y;
public int readX() {
return x;
}
void writeX(int value) {
this.x = value;
}
public static int readY() {
return y;
}
static void writeY(int value) {
y = value;
}
}
@Test
public void accessAllPublicPropertiesDirectly() {
final PublicPOJO orig = new PublicPOJO();
final Value pojo = context.asValue(orig);
Object[] propertyNames = pojo.getMemberKeys().toArray();
assertEquals("One instance field and one method", 2, propertyNames.length);
assertEquals("One field x", "x", propertyNames[0]);
assertEquals("One method to access x", "readX", propertyNames[1]);
Value readX = pojo.getMember("readX");
assertTrue(readX.canExecute());
orig.writeX(10);
assertEquals(10, readX.execute().asInt());
}
@Test
public void arrayHasSize() {
Value arrObj = context.asValue(new String[]{"Hello", "World", "!"});
assertTrue(arrObj.hasArrayElements());
assertEquals("Three elements", 3L, arrObj.getArraySize());
assertEquals("Hello", arrObj.getArrayElement(0).asString());
assertEquals("World", arrObj.getArrayElement(1).asString());
assertEquals("!", arrObj.getArrayElement(2).asString());
}
@Test
public void emptyArrayHasSize() {
Value arrObj = context.asValue(new String[0]);
assertTrue(arrObj.hasArrayElements());
assertEquals(0L, arrObj.getArraySize());
}
private static final TypeLiteral<List<String>> LIST_STRING = new TypeLiteral<List<String>>() {
};
@Test
public void arrayAsList() {
String[] arr = new String[]{"Hello", "World", "!"};
Value arrObj = context.asValue(arr);
List<String> list = arrObj.as(LIST_STRING);
assertEquals("Three elements", 3, list.size());
assertEquals("Hello", list.get(0));
assertEquals("World", list.get(1));
assertEquals("!", list.get(2));
list.set(1, "there");
assertEquals("there", arr[1]);
}
@Test
public void objectsAsMap() {
Data data = new Data();
data.x = 10;
data.y = 33.3;
data.map = data;
Map<String, Object> map = context.asValue(data).as(XYPlus.class).map();
assertEquals("x", map.get("x"), 10);
assertEquals("y", map.get("y"), 33.3);
map.put("x", 13);
assertEquals("x changed", data.x, 13);
boolean foundX = false;
boolean foundY = false;
Iterator<Map.Entry<String, Object>> it = map.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<String, Object> entry = it.next();
if ("x".equals(entry.getKey())) {
assertEquals("x value found", data.x, entry.getValue());
foundX = true;
}
if ("y".equals(entry.getKey())) {
assertEquals("y value found", data.y, entry.getValue());
foundY = true;
}
}
assertTrue(foundX);
assertTrue(foundY);
}
@Test
public void nullCanBeReturned() {
Data data = new Data();
XYPlus xyp = context.asValue(data).as(XYPlus.class);
assertNull(xyp.value());
}
@Test
public void integerCanBeConvertedFromAnObjectField() {
Data data = new Data();
XYPlus xyp = context.asValue(data).as(XYPlus.class);
data.value = 42;
assertEquals((Integer) 42, xyp.value());
}
@Test
public void isNull() {
Value value = context.asValue(null);
assertTrue(value.isNull());
assertFalse(context.asValue(new Object()).isNull());
}
@Test
public void testClassStaticMembers() {
Value stringClass = context.asValue(String.class);
Value stringStatic = stringClass.getMember("static");
assertEquals("concatenated", stringStatic.getMember("join").execute("cat", "con", "enated").asString());
assertEquals(String.class, stringStatic.getMember("class").asHostObject());
}
@FunctionalInterface
public interface FunctionalWithDefaults {
Object call(Object... args);
default int call(int a, int b) {
return (int) call(new Object[]{a, b});
}
}
@Test
public void functionalInterfaceOverridingObjectMethods() throws Exception {
Assume.assumeFalse("Cannot get reflection data for a lambda", TruffleOptions.AOT);
Value object = context.asValue((FunctionalWithObjectMethodOverrides) (args) -> args.length >= 1 ? args[0] : null);
assertArrayEquals(new Object[]{"call"}, object.getMemberKeys().toArray());
assertEquals(42, object.execute(42).asInt());
}
@FunctionalInterface
public interface FunctionalWithObjectMethodOverrides {
@Override
boolean equals(Object obj);
@Override
int hashCode();
@Override
String toString();
Object call(Object... args);
}
@Test
@SuppressWarnings("unchecked")
public void executableAsFunction() throws Exception {
TruffleObject executable = new FunctionObject();
Function<Integer, Integer> f = context.asValue(executable).as(Function.class);
assertEquals(13, (int) f.apply(13));
assertTrue(f.equals(f));
}
@Test
public void executableAsFunctionalInterface1() throws Exception {
TruffleObject executable = new FunctionObject();
FunctionalWithDefaults f = context.asValue(executable).as(FunctionalWithDefaults.class);
assertEquals(50, f.call((Object) 13, (Object) 37));
f.hashCode();
f.equals(null);
assertTrue(f.equals(f));
assertEquals(f, context.asValue(executable).as(FunctionalWithDefaults.class));
f.toString();
}
@Test
public void executableAsFunctionalInterface2() throws Exception {
TruffleObject executable = new FunctionObject();
FunctionalWithObjectMethodOverrides f = context.asValue(executable).as(FunctionalWithObjectMethodOverrides.class);
assertEquals(50, f.call(13, 37));
f.hashCode();
f.equals(null);
assertTrue(f.equals(f));
assertEquals(f, context.asValue(executable).as(FunctionalWithObjectMethodOverrides.class));
f.toString();
}
@Ignore("Interface not accessible")
@Test
public void executableAsFunctionalInterface3() throws Exception {
assumeTrue("JDK 9 or later", System.getProperty("java.specification.version").compareTo("1.9") >= 0);
TruffleObject executable = new FunctionObject();
FunctionalWithDefaults f = context.asValue(executable).as(FunctionalWithDefaults.class);
assertEquals(42, f.call((Object) 13, (Object) 29));
assertEquals(50, f.call(13, 37));
f.hashCode();
f.equals(null);
f.toString();
}
@Test
public void listUnwrapsTruffleObject() {
Data data = new Data();
data.data = new Data[]{new Data()};
XYPlus xyp = context.asValue(data).as(XYPlus.class);
Data value = xyp.data().get(0);
assertSame(data.data[0], value);
}
@Test
public void mapUnwrapsTruffleObject() {
Data data = new Data();
data.dataMap = data;
XYPlus xyp = context.asValue(data).as(XYPlus.class);
Data value = xyp.dataMap().get("dataMap");
assertSame(data, value);
Data newValue = new Data();
Data previousValue = xyp.dataMap().put("dataMap", newValue);
assertSame(data, previousValue);
assertSame(newValue, data.dataMap);
}
@Test
public void mapEntrySetUnwrapsTruffleObject() {
Data data = new Data();
data.dataMap = data;
XYPlus xyp = context.asValue(data).as(XYPlus.class);
final Map<String, Data> map = xyp.dataMap();
Data value = map.get("dataMap");
assertSame(data, value);
for (Map.Entry<String, Data> entry : xyp.dataMap().entrySet()) {
if ("dataMap".equals(entry.getKey())) {
assertSame(value, entry.getValue());
Data newValue = new Data();
Data prev = entry.setValue(newValue);
assertSame(value, prev);
assertSame(newValue, map.get("dataMap"));
return;
}
}
fail("Entry dataMap not found");
}
@Test
public void testNewClass() {
Value hashMapClass = context.asValue(HashMap.class);
assertTrue(hashMapClass.canInstantiate());
Value hashMap = hashMapClass.newInstance();
assertTrue(hashMap.isHostObject());
assertTrue(hashMap.asHostObject() instanceof HashMap);
}
@Test
public void testNewObject() {
Value objectClass = context.asValue(Object.class);
Value object = objectClass.newInstance();
assertTrue(object.isHostObject());
}
@Test
public void testNewArray() {
Value objectClass = context.asValue(long[].class);
Value object = objectClass.newInstance(4);
assertTrue(object.isHostObject());
assertTrue(object.hasArrayElements());
assertEquals(4, object.getArraySize());
}
@Test
public void testMultiDimArray() {
long[][] matrix = {
{1, 2},
{3, 4},
{5, 6},
};
Value object = context.asValue(matrix);
assertTrue(object.isHostObject());
assertTrue(object.hasArrayElements());
assertEquals(3, object.getArraySize());
Value row = object.getArrayElement(1);
assertTrue(row.hasArrayElements());
assertEquals(2, row.getArraySize());
assertEquals(3, row.getArrayElement(0).asInt());
assertEquals(4, row.getArrayElement(1).asInt());
}
@Test
public void testNewMultiDimArray() {
Value objectClass = context.asValue(long[][].class);
assertFails(() -> objectClass.newInstance(3, 4), IllegalArgumentException.class);
Value object = objectClass.newInstance(4);
assertTrue(object.isHostObject());
assertTrue(object.hasArrayElements());
assertEquals(4, object.getArraySize());
Value row = object.getArrayElement(0);
assertTrue(row.isNull());
object.setArrayElement(0, new long[]{3, 4});
row = object.getArrayElement(0);
assertTrue(row.hasArrayElements());
assertEquals(2, row.getArraySize());
assertEquals(3, row.getArrayElement(0).asInt());
assertEquals(4, row.getArrayElement(1).asInt());
}
@Test
public void testException() {
Value iterator = context.asValue(Collections.emptyList().iterator());
try {
iterator.getMember("next").execute();
fail("expected an exception but none was thrown");
} catch (PolyglotException ex) {
assertTrue("expected HostException but was: " + ex.getClass(), ex.isHostException());
assertThat(ex.asHostException(), CoreMatchers.instanceOf(NoSuchElementException.class));
}
}
@Test
public void testException2() {
Value hashMapClass = context.asValue(HashMap.class);
try {
hashMapClass.newInstance(-1);
fail("expected an exception but none was thrown");
} catch (PolyglotException ex) {
assertTrue("expected HostException but was: " + ex.getClass(), ex.isHostException());
assertThat(ex.asHostException(), CoreMatchers.instanceOf(IllegalArgumentException.class));
}
try {
hashMapClass.newInstance("");
fail("expected an exception but none was thrown");
} catch (IllegalArgumentException ex) {
}
}
@Test
public void testRemoveMessage() {
Data data = new Data();
data.arr = new String[]{"Hello", "World", "!"};
Value truffleList = context.asValue(new ArrayList<>(Arrays.asList(data.arr)));
assertEquals(3L, truffleList.getArraySize());
assertEquals(true, truffleList.removeArrayElement(1));
assertEquals(2, truffleList.getArraySize());
try {
truffleList.removeArrayElement(10L);
fail("Out of bounds.");
} catch (ArrayIndexOutOfBoundsException e) {
}
Value arrObj = context.asValue(data.arr);
try {
arrObj.removeArrayElement(0);
fail("Remove of elements of an array is not supported.");
} catch (UnsupportedOperationException e) {
}
Map<String, Object> map = new HashMap<>();
map.put("a", "aa");
map.put("b", "bb");
Value truffleMap = context.asValue(ProxyObject.fromMap(map));
assertEquals(true, truffleMap.removeMember("a"));
assertEquals(1, map.size());
assertFalse(truffleMap.removeMember("a"));
}
@SuppressWarnings("unchecked")
@Test
public void testRemoveList() {
List<Integer> list = context.asValue(new ArrayTruffleObject(100)).as(List.class);
assertEquals(100, list.size());
Integer value = list.remove(10);
assertEquals(Integer.valueOf(90), value);
assertEquals(99, list.size());
boolean success = list.remove((Object) 20);
assertTrue(success);
assertEquals(98, list.size());
Iterator<Integer> liter = list.iterator();
try {
liter.remove();
fail("IllegalStateException");
} catch (IllegalStateException e) {
}
assertEquals(Integer.valueOf(98), liter.next());
assertEquals(Integer.valueOf(97), liter.next());
liter.remove();
assertEquals(97, list.size());
try {
liter.remove();
fail("IllegalStateException");
} catch (IllegalStateException e) {
}
assertEquals(Integer.valueOf(96), liter.next());
liter.remove();
assertEquals(96, list.size());
List<String> arr = context.asValue(new String[]{"Hello", "World", "!"}).as(LIST_STRING);
try {
assertEquals("World", arr.remove(1));
fail("Remove of elements of an array is not supported.");
} catch (UnsupportedOperationException e) {
}
}
private static final TypeLiteral<Map<String, String>> MAP_STRING_STRING = new TypeLiteral<Map<String, String>>() {
};
@SuppressWarnings("unchecked")
@Test
public void testRemoveMap() {
int size = 15;
Map<String, String> map = new LinkedHashMap<>();
for (int i = 0; i < size; i++) {
char c = (char) ('a' + i);
map.put(new String(new char[]{c}), new String(new char[]{c, c}));
}
Map<String, String> jmap = context.asValue(new RemoveKeysObject(map)).as(MAP_STRING_STRING);
assertEquals(size, jmap.size());
String value = jmap.remove("a");
assertEquals("aa", value);
assertEquals(size - 1, jmap.size());
boolean success = jmap.remove("b", "c");
assertFalse(success);
assertEquals(size - 1, jmap.size());
success = jmap.remove("b", "bb");
assertTrue(success);
assertEquals(size - 2, jmap.size());
Set<String> keySet = jmap.keySet();
success = keySet.remove("c");
assertTrue(success);
assertEquals(size - 3, jmap.size());
success = keySet.remove("xx");
assertFalse(success);
assertEquals(size - 3, jmap.size());
assertEquals(size - 3, keySet.size());
Iterator<String> siter = keySet.iterator();
try {
siter.remove();
fail("IllegalStateException");
} catch (IllegalStateException e) {
}
assertEquals("d", siter.next());
siter.remove();
assertEquals(size - 4, jmap.size());
try {
siter.remove();
fail("IllegalStateException");
} catch (IllegalStateException e) {
}
assertEquals("e", siter.next());
siter.remove();
assertEquals(size - 5, jmap.size());
Set<Map.Entry<String, String>> entrySet = jmap.entrySet();
success = entrySet.remove(new AbstractMap.SimpleEntry<>("f", "ff"));
assertTrue(success);
assertEquals(size - 6, jmap.size());
success = entrySet.remove(new AbstractMap.SimpleEntry<>("g", "xx"));
assertFalse(success);
success = entrySet.remove(new AbstractMap.SimpleEntry<>("xx", "gg"));
assertFalse(success);
assertEquals(size - 6, jmap.size());
success = entrySet.remove(new AbstractMap.SimpleEntry<>("g", "gg"));
assertTrue(success);
assertEquals(size - 7, jmap.size());
assertEquals(size - 7, entrySet.size());
Iterator<Map.Entry<String, String>> esiter = entrySet.iterator();
try {
esiter.remove();
fail("IllegalStateException");
} catch (IllegalStateException e) {
}
Map.Entry<String, String> nextEntry = esiter.next();
assertEquals("h", nextEntry.getKey());
assertEquals("hh", nextEntry.getValue());
esiter.remove();
assertEquals(size - 8, jmap.size());
Collection<String> values = jmap.values();
success = values.remove("ii");
assertTrue(success);
assertEquals(size - 9, jmap.size());
success = values.remove("xxx");
assertFalse(success);
assertEquals(size - 9, jmap.size());
Iterator<String> viter = values.iterator();
try {
viter.remove();
fail("IllegalStateException");
} catch (IllegalStateException e) {
}
assertEquals("jj", viter.next());
viter.remove();
assertEquals(size - 10, jmap.size());
assertEquals(size - 10, values.size());
Data data = new Data();
data.map = data;
Map<String, Object> dmap = context.asValue(data).as(Map.class);
try {
dmap.remove("x");
fail("Remove of object fields is not supported.");
} catch (UnsupportedOperationException e) {
}
}
public interface XYPlus {
List<String> arr();
Map<String, Object> map();
Map<String, Data> dataMap();
int x();
double y();
double plus(double a, double b);
Integer value();
XYPlus assertThis(Object obj);
List<Data> data();
}
@ExportLibrary(InteropLibrary.class)
static class ListArray implements TruffleObject {
private final List<String> array;
ListArray(List<String> array) {
this.array = array;
}
@ExportMessage
boolean hasArrayElements() {
return true;
}
@ExportMessage
@TruffleBoundary
final Object readArrayElement(long index) {
return array.get((int) index);
}
@ExportMessage
@TruffleBoundary
final void removeArrayElement(long index) {
array.remove((int) index);
}
@ExportMessage
@TruffleBoundary
final long getArraySize() {
return array.size();
}
@ExportMessage(name = "isArrayElementReadable")
@ExportMessage(name = "isArrayElementRemovable")
@TruffleBoundary
final boolean isArrayElementExisting(long index) {
return index < array.size() && index >= 0;
}
}
@ExportLibrary(InteropLibrary.class)
@SuppressWarnings({"static-method", "unused"})
static final class RemoveKeysObject implements TruffleObject {
private final Map<String, ?> keys;
RemoveKeysObject(Map<String, ?> keys) {
this.keys = keys;
}
@ExportMessage
boolean hasMembers() {
return true;
}
@ExportMessage
@TruffleBoundary
Object getMembers(boolean includeInternal) throws UnsupportedMessageException {
List<String> list = new AbstractList<String>() {
final Set<String> keys = RemoveKeysObject.this.keys.keySet();
@Override
public String get(int index) {
Iterator<String> iterator = keys.iterator();
for (int i = 0; i < index; i++) {
iterator.next();
}
return iterator.next();
}
@Override
public int size() {
return keys.size();
}
@Override
public String remove(int index) {
Iterator<String> iterator = keys.iterator();
for (int i = 0; i < index; i++) {
iterator.next();
}
String removed = iterator.next();
iterator.remove();
return removed;
}
};
return new ListArray(list);
}
@ExportMessage(name = "isMemberReadable")
@ExportMessage(name = "isMemberRemovable")
@TruffleBoundary
boolean isMemberExisting(String member) {
return keys.containsKey(member);
}
@ExportMessage
@TruffleBoundary
Object readMember(String member) throws UnsupportedMessageException, UnknownIdentifierException {
if (!keys.containsKey(member)) {
CompilerDirectives.transferToInterpreter();
throw UnknownIdentifierException.create(member);
}
return keys.get(member);
}
@ExportMessage
@TruffleBoundary
void removeMember(String member) throws UnsupportedMessageException, UnknownIdentifierException {
if (!keys.containsKey(member)) {
throw UnknownIdentifierException.create(member);
}
keys.remove(member);
}
}
@ExportLibrary(InteropLibrary.class)
@SuppressWarnings({"static-method", "unused"})
static final class ArrayTruffleObject implements TruffleObject {
private int size;
ArrayTruffleObject(int size) {
this.size = size;
}
@ExportMessage
boolean hasArrayElements() {
return true;
}
@ExportMessage
Object readArrayElement(long index) throws InvalidArrayIndexException {
if (!isArrayElementReadable(index)) {
throw InvalidArrayIndexException.create(index);
}
return (int) (size - index);
}
@ExportMessage
void removeArrayElement(long index) throws InvalidArrayIndexException {
if (!isArrayElementReadable(index)) {
throw InvalidArrayIndexException.create(index);
}
size--;
}
@ExportMessage
long getArraySize() {
return size;
}
@ExportMessage(name = "isArrayElementReadable")
@ExportMessage(name = "isArrayElementRemovable")
boolean isArrayElementReadable(long index) {
return index >= 0 && index < size;
}
}
@ExportLibrary(InteropLibrary.class)
@SuppressWarnings({"static-method", "unused"})
static final class FunctionObject implements TruffleObject {
@ExportMessage
boolean isExecutable() {
return true;
}
@ExportMessage
@TruffleBoundary
Object execute(Object[] arguments) {
return Arrays.stream(arguments).mapToInt(o -> (int) o).sum();
}
}
}