8026113: Nashorn arrays should automatically convert to Java arrays
Reviewed-by: jlaskey, sundar
This commit is contained in:
parent
3c50f75173
commit
3538d0af55
@ -31,6 +31,8 @@ import static jdk.nashorn.internal.runtime.ECMAErrors.typeError;
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.Deque;
|
||||
import java.util.List;
|
||||
import jdk.internal.dynalink.beans.StaticClass;
|
||||
import jdk.nashorn.api.scripting.JSObject;
|
||||
import jdk.nashorn.internal.codegen.CompilerConstants.Call;
|
||||
@ -110,6 +112,15 @@ public enum JSType {
|
||||
/** Combined call to toPrimitive followed by toString. */
|
||||
public static final Call TO_PRIMITIVE_TO_STRING = staticCall(myLookup, JSType.class, "toPrimitiveToString", String.class, Object.class);
|
||||
|
||||
/** Method handle to convert a JS Object to a Java array. */
|
||||
public static final Call TO_JAVA_ARRAY = staticCall(myLookup, JSType.class, "toJavaArray", Object.class, Object.class, Class.class);
|
||||
|
||||
/** Method handle to convert a JS Object to a Java List. */
|
||||
public static final Call TO_JAVA_LIST = staticCall(myLookup, JSType.class, "toJavaList", List.class, Object.class);
|
||||
|
||||
/** Method handle to convert a JS Object to a Java deque. */
|
||||
public static final Call TO_JAVA_DEQUE = staticCall(myLookup, JSType.class, "toJavaDeque", Deque.class, Object.class);
|
||||
|
||||
private static final double INT32_LIMIT = 4294967296.0;
|
||||
|
||||
/**
|
||||
@ -890,6 +901,8 @@ public enum JSType {
|
||||
res[idx++] = itr.next();
|
||||
}
|
||||
return convertArray(res, componentType);
|
||||
} else if(obj == null) {
|
||||
return null;
|
||||
} else {
|
||||
throw new IllegalArgumentException("not a script object");
|
||||
}
|
||||
@ -918,6 +931,24 @@ public enum JSType {
|
||||
return dst;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a JavaScript object to a Java List. See {@link ListAdapter} for details.
|
||||
* @param obj the object to convert. Can be any array-like object.
|
||||
* @return a List that is live-backed by the JavaScript object.
|
||||
*/
|
||||
public static List<?> toJavaList(final Object obj) {
|
||||
return ListAdapter.create(obj);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a JavaScript object to a Java Deque. See {@link ListAdapter} for details.
|
||||
* @param obj the object to convert. Can be any array-like object.
|
||||
* @return a Deque that is live-backed by the JavaScript object.
|
||||
*/
|
||||
public static Deque<?> toJavaDeque(final Object obj) {
|
||||
return ListAdapter.create(obj);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an object is null or undefined
|
||||
*
|
||||
|
@ -25,14 +25,16 @@
|
||||
|
||||
package jdk.nashorn.internal.runtime.linker;
|
||||
|
||||
import static jdk.nashorn.internal.lookup.Lookup.MH;
|
||||
import static jdk.nashorn.internal.runtime.ECMAErrors.typeError;
|
||||
import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED;
|
||||
import static jdk.nashorn.internal.lookup.Lookup.MH;
|
||||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Deque;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import jdk.internal.dynalink.support.TypeUtilities;
|
||||
import jdk.nashorn.internal.runtime.ConsString;
|
||||
import jdk.nashorn.internal.runtime.JSType;
|
||||
@ -57,7 +59,13 @@ final class JavaArgumentConverters {
|
||||
}
|
||||
|
||||
static MethodHandle getConverter(final Class<?> targetType) {
|
||||
return CONVERTERS.get(targetType);
|
||||
MethodHandle converter = CONVERTERS.get(targetType);
|
||||
if(converter == null && targetType.isArray()) {
|
||||
converter = MH.insertArguments(JSType.TO_JAVA_ARRAY.methodHandle(), 1, targetType.getComponentType());
|
||||
converter = MH.asType(converter, converter.type().changeReturnType(targetType));
|
||||
CONVERTERS.putIfAbsent(targetType, converter);
|
||||
}
|
||||
return converter;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@ -211,6 +219,20 @@ final class JavaArgumentConverters {
|
||||
return null;
|
||||
} else if (obj instanceof Long) {
|
||||
return (Long) obj;
|
||||
} else if (obj instanceof Integer) {
|
||||
return ((Integer)obj).longValue();
|
||||
} else if (obj instanceof Double) {
|
||||
final Double d = (Double)obj;
|
||||
if(Double.isInfinite(d.doubleValue())) {
|
||||
return 0L;
|
||||
}
|
||||
return d.longValue();
|
||||
} else if (obj instanceof Float) {
|
||||
final Float f = (Float)obj;
|
||||
if(Float.isInfinite(f.floatValue())) {
|
||||
return 0L;
|
||||
}
|
||||
return f.longValue();
|
||||
} else if (obj instanceof Number) {
|
||||
return ((Number)obj).longValue();
|
||||
} else if (obj instanceof String || obj instanceof ConsString) {
|
||||
@ -241,9 +263,12 @@ final class JavaArgumentConverters {
|
||||
return MH.findStatic(MethodHandles.lookup(), JavaArgumentConverters.class, name, MH.type(rtype, types));
|
||||
}
|
||||
|
||||
private static final Map<Class<?>, MethodHandle> CONVERTERS = new HashMap<>();
|
||||
private static final ConcurrentMap<Class<?>, MethodHandle> CONVERTERS = new ConcurrentHashMap<>();
|
||||
|
||||
static {
|
||||
CONVERTERS.put(List.class, JSType.TO_JAVA_LIST.methodHandle());
|
||||
CONVERTERS.put(Deque.class, JSType.TO_JAVA_DEQUE.methodHandle());
|
||||
|
||||
CONVERTERS.put(Number.class, TO_NUMBER);
|
||||
CONVERTERS.put(String.class, TO_STRING);
|
||||
|
||||
|
@ -0,0 +1,191 @@
|
||||
/*
|
||||
* Copyright (c) 2010, 2013, 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 jdk.nashorn.api.javaaccess;
|
||||
|
||||
import static org.testng.AssertJUnit.assertEquals;
|
||||
import static org.testng.AssertJUnit.assertFalse;
|
||||
import static org.testng.AssertJUnit.assertNull;
|
||||
import static org.testng.AssertJUnit.assertTrue;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import javax.script.ScriptContext;
|
||||
import javax.script.ScriptEngine;
|
||||
import javax.script.ScriptEngineManager;
|
||||
import javax.script.ScriptException;
|
||||
import org.testng.TestNG;
|
||||
import org.testng.annotations.AfterClass;
|
||||
import org.testng.annotations.BeforeClass;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
public class ArrayConversionTest {
|
||||
private static ScriptEngine e = null;
|
||||
|
||||
public static void main(final String[] args) {
|
||||
TestNG.main(args);
|
||||
}
|
||||
|
||||
@BeforeClass
|
||||
public static void setUpClass() throws ScriptException {
|
||||
e = new ScriptEngineManager().getEngineByName("nashorn");
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void tearDownClass() {
|
||||
e = null;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIntArrays() throws ScriptException {
|
||||
runTest("assertNullIntArray", "null");
|
||||
runTest("assertEmptyIntArray", "[]");
|
||||
runTest("assertSingle42IntArray", "[42]");
|
||||
runTest("assertSingle42IntArray", "['42']");
|
||||
runTest("assertIntArrayConversions", "[false, true, NaN, Infinity, -Infinity, 0.4, 0.6, null, undefined, [], {}, [1], [1, 2]]");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIntIntArrays() throws ScriptException {
|
||||
runTest("assertNullIntIntArray", "null");
|
||||
runTest("assertEmptyIntIntArray", "[]");
|
||||
runTest("assertSingleEmptyIntIntArray", "[[]]");
|
||||
runTest("assertSingleNullIntIntArray", "[null]");
|
||||
runTest("assertLargeIntIntArray", "[[false], [1], [2, 3], [4, 5, 6], ['7', {valueOf: function() { return 8 }}]]");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testObjectObjectArrays() throws ScriptException {
|
||||
runTest("assertLargeObjectObjectArray", "[[false], [1], ['foo', 42.3], [{x: 17}]]");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBooleanArrays() throws ScriptException {
|
||||
runTest("assertBooleanArrayConversions", "[false, true, '', 'false', 0, 1, 0.4, 0.6, {}, [], [false], [true], NaN, Infinity, null, undefined]");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testListArrays() throws ScriptException {
|
||||
runTest("assertListArray", "[['foo', 'bar'], ['apple', 'orange']]");
|
||||
}
|
||||
|
||||
private static void runTest(final String testMethodName, final String argument) throws ScriptException {
|
||||
e.eval("Java.type('" + ArrayConversionTest.class.getName() + "')." + testMethodName + "(" + argument + ")");
|
||||
}
|
||||
|
||||
public static void assertNullIntArray(int[] array) {
|
||||
assertNull(array);
|
||||
}
|
||||
|
||||
public static void assertNullIntIntArray(int[][] array) {
|
||||
assertNull(array);
|
||||
}
|
||||
|
||||
public static void assertEmptyIntArray(int[] array) {
|
||||
assertEquals(0, array.length);
|
||||
}
|
||||
|
||||
public static void assertSingle42IntArray(int[] array) {
|
||||
assertEquals(1, array.length);
|
||||
assertEquals(42, array[0]);
|
||||
}
|
||||
|
||||
|
||||
public static void assertIntArrayConversions(int[] array) {
|
||||
assertEquals(13, array.length);
|
||||
assertEquals(0, array[0]); // false
|
||||
assertEquals(1, array[1]); // true
|
||||
assertEquals(0, array[2]); // NaN
|
||||
assertEquals(0, array[3]); // Infinity
|
||||
assertEquals(0, array[4]); // -Infinity
|
||||
assertEquals(0, array[5]); // 0.4
|
||||
assertEquals(0, array[6]); // 0.6 - floor, not round
|
||||
assertEquals(0, array[7]); // null
|
||||
assertEquals(0, array[8]); // undefined
|
||||
assertEquals(0, array[9]); // []
|
||||
assertEquals(0, array[10]); // {}
|
||||
assertEquals(1, array[11]); // [1]
|
||||
assertEquals(0, array[12]); // [1, 2]
|
||||
}
|
||||
|
||||
public static void assertEmptyIntIntArray(int[][] array) {
|
||||
assertEquals(0, array.length);
|
||||
}
|
||||
|
||||
public static void assertSingleEmptyIntIntArray(int[][] array) {
|
||||
assertEquals(1, array.length);
|
||||
assertTrue(Arrays.equals(new int[0], array[0]));
|
||||
}
|
||||
|
||||
public static void assertSingleNullIntIntArray(int[][] array) {
|
||||
assertEquals(1, array.length);
|
||||
assertNull(null, array[0]);
|
||||
}
|
||||
|
||||
public static void assertLargeIntIntArray(int[][] array) {
|
||||
assertEquals(5, array.length);
|
||||
assertTrue(Arrays.equals(new int[] { 0 }, array[0]));
|
||||
assertTrue(Arrays.equals(new int[] { 1 }, array[1]));
|
||||
assertTrue(Arrays.equals(new int[] { 2, 3 }, array[2]));
|
||||
assertTrue(Arrays.equals(new int[] { 4, 5, 6 }, array[3]));
|
||||
assertTrue(Arrays.equals(new int[] { 7, 8 }, array[4]));
|
||||
}
|
||||
|
||||
public static void assertLargeObjectObjectArray(Object[][] array) throws ScriptException {
|
||||
assertEquals(4, array.length);
|
||||
assertTrue(Arrays.equals(new Object[] { Boolean.FALSE }, array[0]));
|
||||
assertTrue(Arrays.equals(new Object[] { 1 }, array[1]));
|
||||
assertTrue(Arrays.equals(new Object[] { "foo", 42.3d }, array[2]));
|
||||
assertEquals(1, array[3].length);
|
||||
e.getBindings(ScriptContext.ENGINE_SCOPE).put("obj", array[3][0]);
|
||||
assertEquals(17, e.eval("obj.x"));
|
||||
}
|
||||
|
||||
public static void assertBooleanArrayConversions(boolean[] array) {
|
||||
assertEquals(16, array.length);
|
||||
assertFalse(array[0]); // false
|
||||
assertTrue(array[1]); // true
|
||||
assertFalse(array[2]); // ''
|
||||
assertTrue(array[3]); // 'false' (yep, every non-empty string converts to true)
|
||||
assertFalse(array[4]); // 0
|
||||
assertTrue(array[5]); // 1
|
||||
assertTrue(array[6]); // 0.4
|
||||
assertTrue(array[7]); // 0.6
|
||||
assertTrue(array[8]); // {}
|
||||
assertTrue(array[9]); // []
|
||||
assertTrue(array[10]); // [false]
|
||||
assertTrue(array[11]); // [true]
|
||||
assertFalse(array[12]); // NaN
|
||||
assertTrue(array[13]); // Infinity
|
||||
assertFalse(array[14]); // null
|
||||
assertFalse(array[15]); // undefined
|
||||
}
|
||||
|
||||
public static void assertListArray(List<?>[] array) {
|
||||
assertEquals(2, array.length);
|
||||
assertEquals(Arrays.asList("foo", "bar"), array[0]);
|
||||
assertEquals(Arrays.asList("apple", "orange"), array[1]);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user