Mon, 14 Oct 2013 12:41:11 +0200
8026113: Nashorn arrays should automatically convert to Java arrays
Reviewed-by: jlaskey, sundar
1.1 --- a/src/jdk/nashorn/internal/runtime/JSType.java Mon Oct 14 11:45:15 2013 +0200 1.2 +++ b/src/jdk/nashorn/internal/runtime/JSType.java Mon Oct 14 12:41:11 2013 +0200 1.3 @@ -31,6 +31,8 @@ 1.4 import java.lang.invoke.MethodHandle; 1.5 import java.lang.invoke.MethodHandles; 1.6 import java.lang.reflect.Array; 1.7 +import java.util.Deque; 1.8 +import java.util.List; 1.9 import jdk.internal.dynalink.beans.StaticClass; 1.10 import jdk.nashorn.api.scripting.JSObject; 1.11 import jdk.nashorn.internal.codegen.CompilerConstants.Call; 1.12 @@ -110,6 +112,15 @@ 1.13 /** Combined call to toPrimitive followed by toString. */ 1.14 public static final Call TO_PRIMITIVE_TO_STRING = staticCall(myLookup, JSType.class, "toPrimitiveToString", String.class, Object.class); 1.15 1.16 + /** Method handle to convert a JS Object to a Java array. */ 1.17 + public static final Call TO_JAVA_ARRAY = staticCall(myLookup, JSType.class, "toJavaArray", Object.class, Object.class, Class.class); 1.18 + 1.19 + /** Method handle to convert a JS Object to a Java List. */ 1.20 + public static final Call TO_JAVA_LIST = staticCall(myLookup, JSType.class, "toJavaList", List.class, Object.class); 1.21 + 1.22 + /** Method handle to convert a JS Object to a Java deque. */ 1.23 + public static final Call TO_JAVA_DEQUE = staticCall(myLookup, JSType.class, "toJavaDeque", Deque.class, Object.class); 1.24 + 1.25 private static final double INT32_LIMIT = 4294967296.0; 1.26 1.27 /** 1.28 @@ -890,6 +901,8 @@ 1.29 res[idx++] = itr.next(); 1.30 } 1.31 return convertArray(res, componentType); 1.32 + } else if(obj == null) { 1.33 + return null; 1.34 } else { 1.35 throw new IllegalArgumentException("not a script object"); 1.36 } 1.37 @@ -919,6 +932,24 @@ 1.38 } 1.39 1.40 /** 1.41 + * Converts a JavaScript object to a Java List. See {@link ListAdapter} for details. 1.42 + * @param obj the object to convert. Can be any array-like object. 1.43 + * @return a List that is live-backed by the JavaScript object. 1.44 + */ 1.45 + public static List<?> toJavaList(final Object obj) { 1.46 + return ListAdapter.create(obj); 1.47 + } 1.48 + 1.49 + /** 1.50 + * Converts a JavaScript object to a Java Deque. See {@link ListAdapter} for details. 1.51 + * @param obj the object to convert. Can be any array-like object. 1.52 + * @return a Deque that is live-backed by the JavaScript object. 1.53 + */ 1.54 + public static Deque<?> toJavaDeque(final Object obj) { 1.55 + return ListAdapter.create(obj); 1.56 + } 1.57 + 1.58 + /** 1.59 * Check if an object is null or undefined 1.60 * 1.61 * @param obj object to check
2.1 --- a/src/jdk/nashorn/internal/runtime/linker/JavaArgumentConverters.java Mon Oct 14 11:45:15 2013 +0200 2.2 +++ b/src/jdk/nashorn/internal/runtime/linker/JavaArgumentConverters.java Mon Oct 14 12:41:11 2013 +0200 2.3 @@ -25,14 +25,16 @@ 2.4 2.5 package jdk.nashorn.internal.runtime.linker; 2.6 2.7 +import static jdk.nashorn.internal.lookup.Lookup.MH; 2.8 import static jdk.nashorn.internal.runtime.ECMAErrors.typeError; 2.9 import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED; 2.10 -import static jdk.nashorn.internal.lookup.Lookup.MH; 2.11 2.12 import java.lang.invoke.MethodHandle; 2.13 import java.lang.invoke.MethodHandles; 2.14 -import java.util.HashMap; 2.15 -import java.util.Map; 2.16 +import java.util.Deque; 2.17 +import java.util.List; 2.18 +import java.util.concurrent.ConcurrentHashMap; 2.19 +import java.util.concurrent.ConcurrentMap; 2.20 import jdk.internal.dynalink.support.TypeUtilities; 2.21 import jdk.nashorn.internal.runtime.ConsString; 2.22 import jdk.nashorn.internal.runtime.JSType; 2.23 @@ -57,7 +59,13 @@ 2.24 } 2.25 2.26 static MethodHandle getConverter(final Class<?> targetType) { 2.27 - return CONVERTERS.get(targetType); 2.28 + MethodHandle converter = CONVERTERS.get(targetType); 2.29 + if(converter == null && targetType.isArray()) { 2.30 + converter = MH.insertArguments(JSType.TO_JAVA_ARRAY.methodHandle(), 1, targetType.getComponentType()); 2.31 + converter = MH.asType(converter, converter.type().changeReturnType(targetType)); 2.32 + CONVERTERS.putIfAbsent(targetType, converter); 2.33 + } 2.34 + return converter; 2.35 } 2.36 2.37 @SuppressWarnings("unused") 2.38 @@ -211,6 +219,20 @@ 2.39 return null; 2.40 } else if (obj instanceof Long) { 2.41 return (Long) obj; 2.42 + } else if (obj instanceof Integer) { 2.43 + return ((Integer)obj).longValue(); 2.44 + } else if (obj instanceof Double) { 2.45 + final Double d = (Double)obj; 2.46 + if(Double.isInfinite(d.doubleValue())) { 2.47 + return 0L; 2.48 + } 2.49 + return d.longValue(); 2.50 + } else if (obj instanceof Float) { 2.51 + final Float f = (Float)obj; 2.52 + if(Float.isInfinite(f.floatValue())) { 2.53 + return 0L; 2.54 + } 2.55 + return f.longValue(); 2.56 } else if (obj instanceof Number) { 2.57 return ((Number)obj).longValue(); 2.58 } else if (obj instanceof String || obj instanceof ConsString) { 2.59 @@ -241,9 +263,12 @@ 2.60 return MH.findStatic(MethodHandles.lookup(), JavaArgumentConverters.class, name, MH.type(rtype, types)); 2.61 } 2.62 2.63 - private static final Map<Class<?>, MethodHandle> CONVERTERS = new HashMap<>(); 2.64 + private static final ConcurrentMap<Class<?>, MethodHandle> CONVERTERS = new ConcurrentHashMap<>(); 2.65 2.66 static { 2.67 + CONVERTERS.put(List.class, JSType.TO_JAVA_LIST.methodHandle()); 2.68 + CONVERTERS.put(Deque.class, JSType.TO_JAVA_DEQUE.methodHandle()); 2.69 + 2.70 CONVERTERS.put(Number.class, TO_NUMBER); 2.71 CONVERTERS.put(String.class, TO_STRING); 2.72
3.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 3.2 +++ b/test/src/jdk/nashorn/api/javaaccess/ArrayConversionTest.java Mon Oct 14 12:41:11 2013 +0200 3.3 @@ -0,0 +1,191 @@ 3.4 +/* 3.5 + * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. 3.6 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 3.7 + * 3.8 + * This code is free software; you can redistribute it and/or modify it 3.9 + * under the terms of the GNU General Public License version 2 only, as 3.10 + * published by the Free Software Foundation. Oracle designates this 3.11 + * particular file as subject to the "Classpath" exception as provided 3.12 + * by Oracle in the LICENSE file that accompanied this code. 3.13 + * 3.14 + * This code is distributed in the hope that it will be useful, but WITHOUT 3.15 + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 3.16 + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 3.17 + * version 2 for more details (a copy is included in the LICENSE file that 3.18 + * accompanied this code). 3.19 + * 3.20 + * You should have received a copy of the GNU General Public License version 3.21 + * 2 along with this work; if not, write to the Free Software Foundation, 3.22 + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 3.23 + * 3.24 + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 3.25 + * or visit www.oracle.com if you need additional information or have any 3.26 + * questions. 3.27 + */ 3.28 + 3.29 +package jdk.nashorn.api.javaaccess; 3.30 + 3.31 +import static org.testng.AssertJUnit.assertEquals; 3.32 +import static org.testng.AssertJUnit.assertFalse; 3.33 +import static org.testng.AssertJUnit.assertNull; 3.34 +import static org.testng.AssertJUnit.assertTrue; 3.35 + 3.36 +import java.util.Arrays; 3.37 +import java.util.List; 3.38 +import javax.script.ScriptContext; 3.39 +import javax.script.ScriptEngine; 3.40 +import javax.script.ScriptEngineManager; 3.41 +import javax.script.ScriptException; 3.42 +import org.testng.TestNG; 3.43 +import org.testng.annotations.AfterClass; 3.44 +import org.testng.annotations.BeforeClass; 3.45 +import org.testng.annotations.Test; 3.46 + 3.47 +public class ArrayConversionTest { 3.48 + private static ScriptEngine e = null; 3.49 + 3.50 + public static void main(final String[] args) { 3.51 + TestNG.main(args); 3.52 + } 3.53 + 3.54 + @BeforeClass 3.55 + public static void setUpClass() throws ScriptException { 3.56 + e = new ScriptEngineManager().getEngineByName("nashorn"); 3.57 + } 3.58 + 3.59 + @AfterClass 3.60 + public static void tearDownClass() { 3.61 + e = null; 3.62 + } 3.63 + 3.64 + @Test 3.65 + public void testIntArrays() throws ScriptException { 3.66 + runTest("assertNullIntArray", "null"); 3.67 + runTest("assertEmptyIntArray", "[]"); 3.68 + runTest("assertSingle42IntArray", "[42]"); 3.69 + runTest("assertSingle42IntArray", "['42']"); 3.70 + runTest("assertIntArrayConversions", "[false, true, NaN, Infinity, -Infinity, 0.4, 0.6, null, undefined, [], {}, [1], [1, 2]]"); 3.71 + } 3.72 + 3.73 + @Test 3.74 + public void testIntIntArrays() throws ScriptException { 3.75 + runTest("assertNullIntIntArray", "null"); 3.76 + runTest("assertEmptyIntIntArray", "[]"); 3.77 + runTest("assertSingleEmptyIntIntArray", "[[]]"); 3.78 + runTest("assertSingleNullIntIntArray", "[null]"); 3.79 + runTest("assertLargeIntIntArray", "[[false], [1], [2, 3], [4, 5, 6], ['7', {valueOf: function() { return 8 }}]]"); 3.80 + } 3.81 + 3.82 + @Test 3.83 + public void testObjectObjectArrays() throws ScriptException { 3.84 + runTest("assertLargeObjectObjectArray", "[[false], [1], ['foo', 42.3], [{x: 17}]]"); 3.85 + } 3.86 + 3.87 + @Test 3.88 + public void testBooleanArrays() throws ScriptException { 3.89 + runTest("assertBooleanArrayConversions", "[false, true, '', 'false', 0, 1, 0.4, 0.6, {}, [], [false], [true], NaN, Infinity, null, undefined]"); 3.90 + } 3.91 + 3.92 + @Test 3.93 + public void testListArrays() throws ScriptException { 3.94 + runTest("assertListArray", "[['foo', 'bar'], ['apple', 'orange']]"); 3.95 + } 3.96 + 3.97 + private static void runTest(final String testMethodName, final String argument) throws ScriptException { 3.98 + e.eval("Java.type('" + ArrayConversionTest.class.getName() + "')." + testMethodName + "(" + argument + ")"); 3.99 + } 3.100 + 3.101 + public static void assertNullIntArray(int[] array) { 3.102 + assertNull(array); 3.103 + } 3.104 + 3.105 + public static void assertNullIntIntArray(int[][] array) { 3.106 + assertNull(array); 3.107 + } 3.108 + 3.109 + public static void assertEmptyIntArray(int[] array) { 3.110 + assertEquals(0, array.length); 3.111 + } 3.112 + 3.113 + public static void assertSingle42IntArray(int[] array) { 3.114 + assertEquals(1, array.length); 3.115 + assertEquals(42, array[0]); 3.116 + } 3.117 + 3.118 + 3.119 + public static void assertIntArrayConversions(int[] array) { 3.120 + assertEquals(13, array.length); 3.121 + assertEquals(0, array[0]); // false 3.122 + assertEquals(1, array[1]); // true 3.123 + assertEquals(0, array[2]); // NaN 3.124 + assertEquals(0, array[3]); // Infinity 3.125 + assertEquals(0, array[4]); // -Infinity 3.126 + assertEquals(0, array[5]); // 0.4 3.127 + assertEquals(0, array[6]); // 0.6 - floor, not round 3.128 + assertEquals(0, array[7]); // null 3.129 + assertEquals(0, array[8]); // undefined 3.130 + assertEquals(0, array[9]); // [] 3.131 + assertEquals(0, array[10]); // {} 3.132 + assertEquals(1, array[11]); // [1] 3.133 + assertEquals(0, array[12]); // [1, 2] 3.134 + } 3.135 + 3.136 + public static void assertEmptyIntIntArray(int[][] array) { 3.137 + assertEquals(0, array.length); 3.138 + } 3.139 + 3.140 + public static void assertSingleEmptyIntIntArray(int[][] array) { 3.141 + assertEquals(1, array.length); 3.142 + assertTrue(Arrays.equals(new int[0], array[0])); 3.143 + } 3.144 + 3.145 + public static void assertSingleNullIntIntArray(int[][] array) { 3.146 + assertEquals(1, array.length); 3.147 + assertNull(null, array[0]); 3.148 + } 3.149 + 3.150 + public static void assertLargeIntIntArray(int[][] array) { 3.151 + assertEquals(5, array.length); 3.152 + assertTrue(Arrays.equals(new int[] { 0 }, array[0])); 3.153 + assertTrue(Arrays.equals(new int[] { 1 }, array[1])); 3.154 + assertTrue(Arrays.equals(new int[] { 2, 3 }, array[2])); 3.155 + assertTrue(Arrays.equals(new int[] { 4, 5, 6 }, array[3])); 3.156 + assertTrue(Arrays.equals(new int[] { 7, 8 }, array[4])); 3.157 + } 3.158 + 3.159 + public static void assertLargeObjectObjectArray(Object[][] array) throws ScriptException { 3.160 + assertEquals(4, array.length); 3.161 + assertTrue(Arrays.equals(new Object[] { Boolean.FALSE }, array[0])); 3.162 + assertTrue(Arrays.equals(new Object[] { 1 }, array[1])); 3.163 + assertTrue(Arrays.equals(new Object[] { "foo", 42.3d }, array[2])); 3.164 + assertEquals(1, array[3].length); 3.165 + e.getBindings(ScriptContext.ENGINE_SCOPE).put("obj", array[3][0]); 3.166 + assertEquals(17, e.eval("obj.x")); 3.167 + } 3.168 + 3.169 + public static void assertBooleanArrayConversions(boolean[] array) { 3.170 + assertEquals(16, array.length); 3.171 + assertFalse(array[0]); // false 3.172 + assertTrue(array[1]); // true 3.173 + assertFalse(array[2]); // '' 3.174 + assertTrue(array[3]); // 'false' (yep, every non-empty string converts to true) 3.175 + assertFalse(array[4]); // 0 3.176 + assertTrue(array[5]); // 1 3.177 + assertTrue(array[6]); // 0.4 3.178 + assertTrue(array[7]); // 0.6 3.179 + assertTrue(array[8]); // {} 3.180 + assertTrue(array[9]); // [] 3.181 + assertTrue(array[10]); // [false] 3.182 + assertTrue(array[11]); // [true] 3.183 + assertFalse(array[12]); // NaN 3.184 + assertTrue(array[13]); // Infinity 3.185 + assertFalse(array[14]); // null 3.186 + assertFalse(array[15]); // undefined 3.187 + } 3.188 + 3.189 + public static void assertListArray(List<?>[] array) { 3.190 + assertEquals(2, array.length); 3.191 + assertEquals(Arrays.asList("foo", "bar"), array[0]); 3.192 + assertEquals(Arrays.asList("apple", "orange"), array[1]); 3.193 + } 3.194 +}