/*
* 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.test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.junit.Before;
import org.junit.Test;
import com.oracle.truffle.api.CallTarget;
import com.oracle.truffle.api.Truffle;
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.KeyInfo;
import com.oracle.truffle.api.interop.Message;
import com.oracle.truffle.api.interop.MessageResolution;
import com.oracle.truffle.api.interop.Resolve;
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.JavaInterop;
import com.oracle.truffle.api.interop.java.MethodMessage;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.nodes.RootNode;
import com.oracle.truffle.api.test.ReflectionUtils;
public class JavaInteropTest {
public 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);
assertThisCalled = true;
return this;
}
}
private TruffleObject obj;
private Data data;
private XYPlus xyp;
private boolean assertThisCalled;
@Before
public void initObjects() {
data = new Data();
obj = JavaInterop.asTruffleObject(data);
xyp = JavaInterop.asJavaObject(XYPlus.class, obj);
}
@Test
public void conversionToClassYieldsTheClass() {
TruffleObject expected = JavaInterop.asTruffleObject(Data.class);
TruffleObject computed = JavaInterop.toJavaClass(obj);
assertEquals("Both class objects are the same", expected, computed);
}
@Test
public void doubleWrap() {
data.x = 32;
data.y = 10.1;
assertEquals("Assume delegated", 42.1d, xyp.plus(xyp.x(), xyp.y()), 0.05);
}
@Test
public void writeX() {
xyp.x(10);
assertEquals("Changed", 10, data.x);
}
@Test
public void assertThisIsSame() {
assertThisCalled = false;
XYPlus anotherThis = xyp.assertThis(data);
assertTrue(assertThisCalled);
data.x = 44;
assertEquals(44, anotherThis.x());
assertEquals("The two proxies are equal", anotherThis, xyp);
}
@Test
public void assertKeysAndProperties() {
CallTarget callTarget = sendKeys();
final TruffleObject ret = (TruffleObject) callTarget.call(obj);
List<?> list = JavaInterop.asJavaObject(List.class, ret);
assertTrue("Contains x " + list, list.contains("x"));
assertTrue("Contains y " + list, list.contains("y"));
assertTrue("Contains arr " + list, list.contains("arr"));
assertTrue("Contains value " + list, list.contains("value"));
assertTrue("Contains map " + list, list.contains("map"));
assertFalse("No object fields " + list, list.contains("notifyAll"));
assertFalse("No object fields " + list, list.contains("notify"));
assertFalse("No object fields " + list, list.contains("wait"));
assertFalse("No object fields " + list, list.contains("hashCode"));
assertFalse("No object fields " + list, list.contains("equals"));
assertFalse("No object fields " + list, list.contains("toString"));
assertFalse("No object fields " + list, list.contains("getClass"));
}
@Test
public void assertKeysFromAMap() {
Map<String, Integer> map = new HashMap<>();
map.put("one", 1);
map.put("null", null);
map.put("three", 3);
TruffleObject truffleMap = JavaInterop.asTruffleObject(map);
TruffleObject ret = (TruffleObject) sendKeys().call(truffleMap);
List<?> list = JavaInterop.asJavaObject(List.class, ret);
assertTrue("Contains one " + list, list.contains("one"));
assertTrue("Contains null " + list, list.contains("null"));
assertTrue("Contains three " + list, list.contains("three"));
}
@Test
public void readUnknownField() throws Exception {
assertNoRead("unknown");
}
private void assertNoRead(final String name) throws UnsupportedMessageException {
try {
ForeignAccess.sendRead(Message.READ.createNode(), obj, name);
fail("Exception thrown when reading " + name + " field");
} catch (UnknownIdentifierException ex) {
assertEquals(name, ex.getUnknownIdentifier());
}
}
private void assertNoInvoke(final String name) throws UnsupportedMessageException {
for (int arity = 0;; arity++) {
try {
ForeignAccess.sendInvoke(Message.createInvoke(arity).createNode(), obj, name);
fail("Exception thrown when reading " + name + " field");
} catch (UnknownIdentifierException ex) {
assertEquals(name, ex.getUnknownIdentifier());
break;
} catch (UnsupportedTypeException ex) {
fail("Types are OK");
} catch (ArityException ex) {
assertEquals(arity, ex.getExpectedArity());
}
}
}
@Test
public void readJavaLangObjectFields() throws Exception {
assertNoRead("notify");
assertNoRead("notifyAll");
assertNoRead("wait");
assertNoRead("hashCode");
assertNoRead("equals");
assertNoRead("toString");
assertNoRead("getClass");
}
@Test
public void invokeJavaLangObjectFields() throws Exception {
assertNoInvoke("notify");
assertNoInvoke("notifyAll");
assertNoInvoke("wait");
assertNoInvoke("hashCode");
assertNoInvoke("equals");
assertNoInvoke("toString");
assertNoInvoke("getClass");
}
static CallTarget sendKeys() {
final Node keysNode = Message.KEYS.createNode();
class SendKeys extends RootNode {
SendKeys() {
super(null);
}
@Override
public Object execute(VirtualFrame frame) {
try {
final TruffleObject receiver = (TruffleObject) frame.getArguments()[0];
return ForeignAccess.sendKeys(keysNode, receiver);
} catch (InteropException ex) {
throw ex.raise();
}
}
}
CallTarget callTarget = Truffle.getRuntime().createCallTarget(new SendKeys());
return callTarget;
}
class PrivatePOJO {
public int x;
}
@Test
public void accessAllProperties() {
TruffleObject pojo = JavaInterop.asTruffleObject(new PrivatePOJO());
Map<?, ?> map = JavaInterop.asJavaObject(Map.class, pojo);
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() {
TruffleObject pojo = JavaInterop.asTruffleObject(new PrivatePOJO());
CallTarget callKeys = sendKeys();
TruffleObject result = (TruffleObject) callKeys.call(pojo);
List<?> propertyNames = JavaInterop.asJavaObject(List.class, result);
assertEquals("No props, class isn't public", 0, propertyNames.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 TruffleObject pojo = JavaInterop.asTruffleObject(orig);
CallTarget callKeys = sendKeys();
TruffleObject result = (TruffleObject) callKeys.call(pojo);
List<?> propertyNames = JavaInterop.asJavaObject(List.class, result);
assertEquals("One instance field and one method", 2, propertyNames.size());
assertEquals("One field x", "x", propertyNames.get(0));
assertEquals("One method to access x", "readX", propertyNames.get(1));
TruffleObject readX = (TruffleObject) message(Message.READ, pojo, "readX");
Boolean isExecutable = (Boolean) message(Message.IS_EXECUTABLE, readX);
assertTrue("Method can be executed " + readX, isExecutable);
orig.writeX(10);
final Object value = message(Message.createExecute(0), readX);
assertEquals(10, value);
}
@Test
public void noNonStaticPropertiesForAClass() {
TruffleObject pojo = JavaInterop.asTruffleObject(PublicPOJO.class);
CallTarget callKeys = sendKeys();
TruffleObject result = (TruffleObject) callKeys.call(pojo);
List<?> propertyNames = JavaInterop.asJavaObject(List.class, result);
assertEquals("One static field and one method", 2, propertyNames.size());
assertEquals("One field y", "y", propertyNames.get(0));
assertEquals("One method to read y", "readY", propertyNames.get(1));
}
@Test
public void javaObjectsWrappedForTruffle() {
Object ret = message(Message.createInvoke(1), obj, "assertThis", obj);
assertTrue("Expecting truffle wrapper: " + ret, ret instanceof TruffleObject);
assertEquals("Same as this obj", ret, obj);
}
@Test
public void arrayHasSize() {
data.arr = new String[]{"Hello", "World", "!"};
Object arrObj = message(Message.READ, obj, "arr");
assertTrue("It's obj: " + arrObj, arrObj instanceof TruffleObject);
TruffleObject truffleArr = (TruffleObject) arrObj;
assertEquals("It has size", Boolean.TRUE, message(Message.HAS_SIZE, truffleArr));
assertEquals("Three elements", 3, message(Message.GET_SIZE, truffleArr));
assertEquals("Hello", message(Message.READ, truffleArr, 0));
assertEquals("World", message(Message.READ, truffleArr, 1));
assertEquals("!", message(Message.READ, truffleArr, 2));
}
@Test
public void arrayAsList() {
data.arr = new String[]{"Hello", "World", "!"};
List<String> list = xyp.arr();
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", data.arr[1]);
}
@Test
public void objectsAsMap() {
data.x = 10;
data.y = 33.3;
data.map = data;
Map<String, Object> map = xyp.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() {
assertNull(xyp.value());
}
@Test
public void integerCanBeConvertedFromAnObjectField() {
data.value = 42;
assertEquals((Integer) 42, xyp.value());
}
@Test
public void indexJavaArrayWithNumberTypes() throws Exception {
int[] a = new int[]{1, 2, 3};
TruffleObject truffleObject = JavaInterop.asTruffleObject(a);
assertEquals(2, ForeignAccess.sendRead(Message.READ.createNode(), truffleObject, 1));
assertEquals(2, ForeignAccess.sendRead(Message.READ.createNode(), truffleObject, 1.0));
assertEquals(2, ForeignAccess.sendRead(Message.READ.createNode(), truffleObject, 1L));
ForeignAccess.sendWrite(Message.WRITE.createNode(), truffleObject, 1, 42);
ForeignAccess.sendWrite(Message.WRITE.createNode(), truffleObject, 1.0, 42);
ForeignAccess.sendWrite(Message.WRITE.createNode(), truffleObject, 1L, 42);
assertEquals(42, ForeignAccess.sendRead(Message.READ.createNode(), truffleObject, 1));
assertEquals(42, ForeignAccess.sendRead(Message.READ.createNode(), truffleObject, 1.0));
assertEquals(42, ForeignAccess.sendRead(Message.READ.createNode(), truffleObject, 1L));
}
@Test
public void isPrimitive() {
assertFalse(JavaInterop.isPrimitive(null));
assertFalse(JavaInterop.isPrimitive(new Object()));
assertFalse(JavaInterop.isPrimitive(this));
assertTrue(JavaInterop.isPrimitive(42));
assertTrue(JavaInterop.isPrimitive((byte) 42));
assertTrue(JavaInterop.isPrimitive((short) 42));
assertTrue(JavaInterop.isPrimitive(424242424242L));
assertTrue(JavaInterop.isPrimitive(42.42f));
assertTrue(JavaInterop.isPrimitive(42e42));
assertTrue(JavaInterop.isPrimitive("42"));
assertTrue(JavaInterop.isPrimitive('4'));
assertTrue(JavaInterop.isPrimitive(true));
assertTrue(JavaInterop.isPrimitive(false));
}
@Test
public void isJavaObject() {
// obj == JavaInterop.asJavaObject(new Data())
assertFalse(JavaInterop.isJavaObject(XYPlus.class, obj));
assertTrue(JavaInterop.isJavaObject(Data.class, obj));
assertTrue(JavaInterop.isJavaObject(Object.class, obj));
// assert that asJavaObject unwraps the object if isJavaObject returns true
assertTrue(JavaInterop.asJavaObject(Data.class, obj) == data);
assertTrue(JavaInterop.asJavaObject(Object.class, obj) == data);
}
@Test
public void truffleValue() {
Object object = new Object();
// Test that asTruffleValue() returns the same as asTruffleObject() for non-primitive types:
assertEquals(JavaInterop.asTruffleObject(object), JavaInterop.asTruffleValue(object));
assertEquals(this.obj, JavaInterop.asTruffleValue(this.data));
// Test that asTruffleValue() returns non-wraped primitives:
object = 42;
assertTrue(JavaInterop.asTruffleValue(object) == object);
object = (byte) 42;
assertTrue(JavaInterop.asTruffleValue(object) == object);
object = (short) 42;
assertTrue(JavaInterop.asTruffleValue(object) == object);
object = 424242424242L;
assertTrue(JavaInterop.asTruffleValue(object) == object);
object = 42.42;
assertTrue(JavaInterop.asTruffleValue(object) == object);
object = true;
assertTrue(JavaInterop.asTruffleValue(object) == object);
object = "42";
assertTrue(JavaInterop.asTruffleValue(object) == object);
object = '4';
assertTrue(JavaInterop.asTruffleValue(object) == object);
object = true;
assertTrue(JavaInterop.asTruffleValue(object) == object);
}
@Test
public void isNull() {
assertTrue(JavaInterop.isNull(null));
assertFalse(JavaInterop.isNull(JavaInterop.asTruffleObject(new Object())));
assertTrue(JavaInterop.isNull(JavaInterop.asTruffleObject(null)));
}
@Test
public void isArray() {
assertFalse(JavaInterop.isArray(null));
assertFalse(JavaInterop.isNull(JavaInterop.asTruffleObject(new Object())));
int[] a = new int[]{1, 2, 3};
TruffleObject truffleArray = JavaInterop.asTruffleObject(a);
assertTrue(JavaInterop.isArray(truffleArray));
}
@Test
public void truffleObjectIsntFunctionalInterface() throws Exception {
final boolean is = isJavaFunctionalInterface(TruffleObject.class);
assertFalse("TruffleObject isn't functional interface", is);
}
private static boolean isJavaFunctionalInterface(final Class<?> clazz) throws Exception {
Method isFunctionaInterface = JavaInterop.class.getDeclaredMethod("isJavaFunctionInterface", Class.class);
ReflectionUtils.setAccessible(isFunctionaInterface, true);
return (boolean) isFunctionaInterface.invoke(null, clazz);
}
@Test
public void functionalInterfaceWithDefaultMethods() throws Exception {
final boolean is = isJavaFunctionalInterface(FunctionalWithDefaults.class);
assertTrue("yes, it is", is);
}
@FunctionalInterface
interface FunctionalWithDefaults {
Object call(Object... args);
default int call(int a, int b) {
return (int) call(new Object[]{a, b});
}
}
@Test
public void listUnwrapsTruffleObject() {
data.data = new Data[]{new Data()};
Data value = xyp.data().get(0);
assertSame(data.data[0], value);
}
@Test
public void mapUnwrapsTruffleObject() {
data.dataMap = data;
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.dataMap = data;
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 isBoxed() {
assertFalse(JavaInterop.isBoxed(null));
assertFalse(JavaInterop.isBoxed(JavaInterop.asTruffleObject(new Object())));
assertTrue(JavaInterop.isBoxed(JavaInterop.asTruffleObject(42)));
assertTrue(JavaInterop.isBoxed(JavaInterop.asTruffleObject((byte) 0x42)));
assertTrue(JavaInterop.isBoxed(JavaInterop.asTruffleObject((short) 42)));
assertTrue(JavaInterop.isBoxed(JavaInterop.asTruffleObject(4242424242424242L)));
assertTrue(JavaInterop.isBoxed(JavaInterop.asTruffleObject(42.42f)));
assertTrue(JavaInterop.isBoxed(JavaInterop.asTruffleObject(42.42)));
assertTrue(JavaInterop.isBoxed(JavaInterop.asTruffleObject("42")));
assertTrue(JavaInterop.isBoxed(JavaInterop.asTruffleObject('4')));
assertTrue(JavaInterop.isBoxed(JavaInterop.asTruffleObject(true)));
assertTrue(JavaInterop.isBoxed(JavaInterop.asTruffleObject(false)));
}
@Test
public void unbox() {
assertNull(JavaInterop.unbox(null));
assertNull(JavaInterop.unbox(JavaInterop.asTruffleObject(new Object())));
assertEquals(42, JavaInterop.unbox(JavaInterop.asTruffleObject(42)));
assertEquals((byte) 42, JavaInterop.unbox(JavaInterop.asTruffleObject((byte) 42)));
assertEquals((short) 42, JavaInterop.unbox(JavaInterop.asTruffleObject((short) 42)));
assertEquals(4242424242424242L, JavaInterop.unbox(JavaInterop.asTruffleObject(4242424242424242L)));
assertEquals(42.42f, JavaInterop.unbox(JavaInterop.asTruffleObject(42.42f)));
assertEquals(42.42, JavaInterop.unbox(JavaInterop.asTruffleObject(42.42)));
assertEquals("42", JavaInterop.unbox(JavaInterop.asTruffleObject("42")));
assertEquals('4', JavaInterop.unbox(JavaInterop.asTruffleObject('4')));
assertEquals(true, JavaInterop.unbox(JavaInterop.asTruffleObject(true)));
assertEquals(false, JavaInterop.unbox(JavaInterop.asTruffleObject(false)));
}
@Test
public void keyInfoDefaults() {
TruffleObject noKeys = new NoKeysObject();
int keyInfo = JavaInterop.getKeyInfo(noKeys, "p1");
assertEquals(0, keyInfo);
TruffleObject nkio = new NoKeyInfoObject();
keyInfo = JavaInterop.getKeyInfo(nkio, "p1");
assertEquals(0b111, keyInfo);
keyInfo = JavaInterop.getKeyInfo(nkio, "p6");
assertEquals(0b111, keyInfo);
keyInfo = JavaInterop.getKeyInfo(nkio, "p7");
assertEquals(0, keyInfo);
}
@Test
public void keyInfo() {
TruffleObject ipobj = new InternalPropertiesObject(-1, -1, 0, 0);
int keyInfo = JavaInterop.getKeyInfo(ipobj, "p1");
assertTrue(KeyInfo.isReadable(keyInfo));
assertTrue(KeyInfo.isWritable(keyInfo));
assertFalse(KeyInfo.isInvocable(keyInfo));
assertFalse(KeyInfo.isInternal(keyInfo));
keyInfo = JavaInterop.getKeyInfo(ipobj, "p6");
assertTrue(KeyInfo.isReadable(keyInfo));
assertTrue(KeyInfo.isWritable(keyInfo));
assertFalse(KeyInfo.isInvocable(keyInfo));
assertFalse(KeyInfo.isInternal(keyInfo));
keyInfo = JavaInterop.getKeyInfo(ipobj, "p7");
assertEquals(0, keyInfo);
ipobj = new InternalPropertiesObject(0b0100010, 0b0100100, 0b0011000, 0);
keyInfo = JavaInterop.getKeyInfo(ipobj, "p1");
assertTrue(KeyInfo.isReadable(keyInfo));
assertFalse(KeyInfo.isWritable(keyInfo));
assertFalse(KeyInfo.isInvocable(keyInfo));
keyInfo = JavaInterop.getKeyInfo(ipobj, "p2");
assertFalse(KeyInfo.isReadable(keyInfo));
assertTrue(KeyInfo.isWritable(keyInfo));
assertFalse(KeyInfo.isInvocable(keyInfo));
keyInfo = JavaInterop.getKeyInfo(ipobj, "p3");
assertFalse(KeyInfo.isReadable(keyInfo));
assertFalse(KeyInfo.isWritable(keyInfo));
assertTrue(KeyInfo.isInvocable(keyInfo));
keyInfo = JavaInterop.getKeyInfo(ipobj, "p4");
assertFalse(KeyInfo.isReadable(keyInfo));
assertFalse(KeyInfo.isWritable(keyInfo));
assertTrue(KeyInfo.isInvocable(keyInfo));
keyInfo = JavaInterop.getKeyInfo(ipobj, "p5");
assertTrue(KeyInfo.isReadable(keyInfo));
assertTrue(KeyInfo.isWritable(keyInfo));
assertFalse(KeyInfo.isInvocable(keyInfo));
keyInfo = JavaInterop.getKeyInfo(ipobj, "p6");
assertFalse(KeyInfo.isReadable(keyInfo));
assertFalse(KeyInfo.isWritable(keyInfo));
assertFalse(KeyInfo.isInvocable(keyInfo));
keyInfo = JavaInterop.getKeyInfo(ipobj, "p7");
assertEquals(0, keyInfo);
TruffleObject aobj = new ArrayTruffleObject(100);
testArrayObject(aobj, 100);
aobj = JavaInterop.asTruffleObject(new String[]{"A", "B", "C", "D"});
testArrayObject(aobj, 4);
}
private static void testArrayObject(TruffleObject array, int length) {
int keyInfo;
for (int i = 0; i < length; i++) {
keyInfo = JavaInterop.getKeyInfo(array, i);
assertTrue(KeyInfo.isReadable(keyInfo));
assertTrue(KeyInfo.isWritable(keyInfo));
assertFalse(KeyInfo.isInvocable(keyInfo));
assertFalse(KeyInfo.isInternal(keyInfo));
keyInfo = JavaInterop.getKeyInfo(array, (long) i);
assertTrue(KeyInfo.isReadable(keyInfo));
keyInfo = JavaInterop.getKeyInfo(array, (double) i);
assertTrue(KeyInfo.isReadable(keyInfo));
}
assertEquals(0, JavaInterop.getKeyInfo(array, length));
assertEquals(0, JavaInterop.getKeyInfo(array, 1.12));
assertEquals(0, JavaInterop.getKeyInfo(array, -1));
assertEquals(0, JavaInterop.getKeyInfo(array, 10L * length));
assertEquals(0, JavaInterop.getKeyInfo(array, Double.NEGATIVE_INFINITY));
assertEquals(0, JavaInterop.getKeyInfo(array, Double.NaN));
assertEquals(0, JavaInterop.getKeyInfo(array, Double.POSITIVE_INFINITY));
}
@Test
@SuppressWarnings("rawtypes")
public void internalKeys() {
// All non-internal
InternalPropertiesObject ipobj = new InternalPropertiesObject(0);
Map map = JavaInterop.asJavaObject(Map.class, ipobj);
checkInternalKeys(map, "[p1, p2, p3, p4, p5, p6]");
assertFalse(KeyInfo.isInternal(JavaInterop.getKeyInfo(ipobj, "p1")));
assertFalse(KeyInfo.isInternal(JavaInterop.getKeyInfo(ipobj, "p6")));
// All internal
ipobj = new InternalPropertiesObject(-1);
map = JavaInterop.asJavaObject(Map.class, ipobj);
checkInternalKeys(map, "[]");
assertTrue(KeyInfo.isInternal(JavaInterop.getKeyInfo(ipobj, "p1")));
assertTrue(KeyInfo.isInternal(JavaInterop.getKeyInfo(ipobj, "p6")));
// Combinations:
ipobj = new InternalPropertiesObject(0b1101000);
map = JavaInterop.asJavaObject(Map.class, ipobj);
checkInternalKeys(map, "[p1, p2, p4]");
assertFalse(KeyInfo.isInternal(JavaInterop.getKeyInfo(ipobj, "p1")));
assertFalse(KeyInfo.isInternal(JavaInterop.getKeyInfo(ipobj, "p2")));
assertTrue(KeyInfo.isInternal(JavaInterop.getKeyInfo(ipobj, "p3")));
assertFalse(KeyInfo.isInternal(JavaInterop.getKeyInfo(ipobj, "p4")));
assertTrue(KeyInfo.isInternal(JavaInterop.getKeyInfo(ipobj, "p5")));
assertTrue(KeyInfo.isInternal(JavaInterop.getKeyInfo(ipobj, "p6")));
ipobj = new InternalPropertiesObject(0b1001110);
map = JavaInterop.asJavaObject(Map.class, ipobj);
checkInternalKeys(map, "[p4, p5]");
assertTrue(KeyInfo.isInternal(JavaInterop.getKeyInfo(ipobj, "p1")));
assertTrue(KeyInfo.isInternal(JavaInterop.getKeyInfo(ipobj, "p3")));
assertFalse(KeyInfo.isInternal(JavaInterop.getKeyInfo(ipobj, "p4")));
assertFalse(KeyInfo.isInternal(JavaInterop.getKeyInfo(ipobj, "p5")));
assertTrue(KeyInfo.isInternal(JavaInterop.getKeyInfo(ipobj, "p6")));
ipobj = new InternalPropertiesObject(0b0101010);
map = JavaInterop.asJavaObject(Map.class, ipobj);
checkInternalKeys(map, "[p2, p4, p6]");
assertTrue(KeyInfo.isInternal(JavaInterop.getKeyInfo(ipobj, "p1")));
assertFalse(KeyInfo.isInternal(JavaInterop.getKeyInfo(ipobj, "p2")));
assertTrue(KeyInfo.isInternal(JavaInterop.getKeyInfo(ipobj, "p3")));
assertFalse(KeyInfo.isInternal(JavaInterop.getKeyInfo(ipobj, "p4")));
assertTrue(KeyInfo.isInternal(JavaInterop.getKeyInfo(ipobj, "p5")));
assertFalse(KeyInfo.isInternal(JavaInterop.getKeyInfo(ipobj, "p6")));
}
@Test
public void keyInfoJavaObject() {
TruffleObject d = JavaInterop.asTruffleObject(new TestJavaObject());
int keyInfo = JavaInterop.getKeyInfo(d, "nnoonnee");
assertFalse(KeyInfo.isExisting(keyInfo));
keyInfo = JavaInterop.getKeyInfo(d, "aField");
assertTrue(KeyInfo.isExisting(keyInfo));
assertTrue(KeyInfo.isReadable(keyInfo));
assertTrue(KeyInfo.isWritable(keyInfo));
assertFalse(KeyInfo.isInvocable(keyInfo));
keyInfo = JavaInterop.getKeyInfo(d, "toString");
assertTrue(KeyInfo.isExisting(keyInfo));
assertTrue(KeyInfo.isReadable(keyInfo));
assertTrue(KeyInfo.isWritable(keyInfo));
assertTrue(KeyInfo.isInvocable(keyInfo));
}
static final class TestJavaObject {
public int aField = 10;
}
@SuppressWarnings({"rawtypes", "unchecked"})
private static void checkInternalKeys(Map map, String nonInternalKeys) {
Map mapWithInternalKeys = JavaInterop.getMapView(map, true);
Map mapWithoutInternalKeys = JavaInterop.getMapView(map, false);
assertEquals(nonInternalKeys, map.keySet().toString());
assertEquals(nonInternalKeys, mapWithoutInternalKeys.keySet().toString());
assertEquals("[p1, p2, p3, p4, p5, p6]", mapWithInternalKeys.keySet().toString());
}
public interface XYPlus {
List<String> arr();
Map<String, Object> map();
Map<String, Data> dataMap();
int x();
@MethodMessage(message = "WRITE")
void x(int v);
double y();
double plus(double a, double b);
Integer value();
XYPlus assertThis(Object obj);
List<Data> data();
}
static Object message(final Message m, TruffleObject receiver, Object... arr) {
Node n = m.createNode();
CallTarget callTarget = Truffle.getRuntime().createCallTarget(new TemporaryRoot(n, receiver, arr));
return callTarget.call();
}
private static class TemporaryRoot extends RootNode {
@Node.Child private Node foreignAccess;
private final TruffleObject function;
private final Object[] args;
TemporaryRoot(Node foreignAccess, TruffleObject function, Object... args) {
super(null);
this.foreignAccess = foreignAccess;
this.function = function;
this.args = args;
}
@Override
public Object execute(VirtualFrame frame) {
try {
return ForeignAccess.send(foreignAccess, function, args);
} catch (InteropException e) {
throw e.raise();
}
}
} // end of TemporaryRoot
static final class NoKeysObject implements TruffleObject {
@Override
public ForeignAccess getForeignAccess() {
return NoKeysObjectMessageResolutionForeign.ACCESS;
}
public static boolean isInstance(TruffleObject obj) {
return obj instanceof NoKeysObject;
}
@MessageResolution(receiverType = NoKeysObject.class)
static final class NoKeysObjectMessageResolution {
// no messages defined, defaults only
}
}
static final class NoKeyInfoObject implements TruffleObject {
@Override
public ForeignAccess getForeignAccess() {
return NoKeyInfoObjectMessageResolutionForeign.ACCESS;
}
public static boolean isInstance(TruffleObject obj) {
return obj instanceof NoKeyInfoObject;
}
@MessageResolution(receiverType = NoKeyInfoObject.class)
static final class NoKeyInfoObjectMessageResolution {
// KEYS defined only, using default KEY_INFO
@Resolve(message = "KEYS")
public abstract static class PropertiesKeysOnlyNode extends Node {
public Object access(NoKeyInfoObject receiver) {
assert receiver != null;
return JavaInterop.asTruffleObject(new String[]{"p1", "p2", "p3", "p4", "p5", "p6"});
}
}
}
}
static final class ArrayTruffleObject implements TruffleObject {
private final int size;
ArrayTruffleObject(int size) {
this.size = size;
}
@Override
public ForeignAccess getForeignAccess() {
return ArrayTruffleObjectMessageResolutionForeign.ACCESS;
}
public static boolean isInstance(TruffleObject obj) {
return obj instanceof ArrayTruffleObject;
}
@MessageResolution(receiverType = ArrayTruffleObject.class)
static final class ArrayTruffleObjectMessageResolution {
@Resolve(message = "HAS_SIZE")
public abstract static class ArrayHasSizeNode extends Node {
public Object access(ArrayTruffleObject receiver) {
assert receiver != null;
return true;
}
}
@Resolve(message = "GET_SIZE")
public abstract static class ArrayGetSizeNode extends Node {
public Object access(ArrayTruffleObject receiver) {
return receiver.size;
}
}
@Resolve(message = "READ")
public abstract static class ArrayReadSizeNode extends Node {
public Object access(ArrayTruffleObject receiver, int index) {
if (index < 0 || index >= receiver.size) {
throw new ArrayIndexOutOfBoundsException(index);
}
return receiver.size - index;
}
}
}
}
static final class InternalPropertiesObject implements TruffleObject {
private final int rBits; // readable
private final int wBits; // writable
private final int iBits; // invocable
private final int nBits; // internal
/**
* @param iBits bits at property number indexes, where '1' means internal, '0' means
* non-internal.
*/
InternalPropertiesObject(int iBits) {
this(-1, -1, -1, iBits);
}
InternalPropertiesObject(int rBits, int wBits, int iBits, int nBits) {
this.rBits = rBits;
this.wBits = wBits;
this.iBits = iBits;
this.nBits = nBits;
}
@Override
public ForeignAccess getForeignAccess() {
return PropertiesVisibilityObjectMessageResolutionForeign.ACCESS;
}
public static boolean isInstance(TruffleObject obj) {
return obj instanceof InternalPropertiesObject;
}
@MessageResolution(receiverType = InternalPropertiesObject.class)
static final class PropertiesVisibilityObjectMessageResolution {
@Resolve(message = "KEYS")
public abstract static class PropertiesKeysNode extends Node {
public Object access(InternalPropertiesObject receiver, boolean includeInternal) {
assert receiver != null;
if (includeInternal) {
return JavaInterop.asTruffleObject(new String[]{"p1", "p2", "p3", "p4", "p5", "p6"});
} else {
List<String> propertyNames = new ArrayList<>();
for (int i = 1; i <= 6; i++) {
if ((receiver.nBits & (1 << i)) == 0) {
propertyNames.add("p" + i);
}
}
return JavaInterop.asTruffleObject(propertyNames.toArray());
}
}
}
@Resolve(message = "KEY_INFO")
public abstract static class IsReadableNode extends Node {
public int access(InternalPropertiesObject receiver, String propertyName) {
if (propertyName.length() != 2 || propertyName.charAt(0) != 'p' || !Character.isDigit(propertyName.charAt(1))) {
return 0;
}
int d = Character.digit(propertyName.charAt(1), 10);
if (d > 6) {
return 0;
}
boolean readable = (receiver.rBits & (1 << d)) > 0;
boolean writable = (receiver.wBits & (1 << d)) > 0;
boolean invocable = (receiver.iBits & (1 << d)) > 0;
boolean internal = (receiver.nBits & (1 << d)) > 0;
return KeyInfo.newBuilder().setReadable(readable).setWritable(writable).setInvocable(invocable).setInternal(internal).build();
}
}
}
}
}