# HG changeset patch # User attila # Date 1381747271 -7200 # Node ID d155c4a7703cec44d49ad6fb7dcfbbe70d07e17a # Parent 8c617a092d6841fa5fc4cdc9bae8f0971d5fbf25 8026113: Nashorn arrays should automatically convert to Java arrays Reviewed-by: jlaskey, sundar diff -r 8c617a092d68 -r d155c4a7703c src/jdk/nashorn/internal/runtime/JSType.java --- a/src/jdk/nashorn/internal/runtime/JSType.java Mon Oct 14 11:45:15 2013 +0200 +++ b/src/jdk/nashorn/internal/runtime/JSType.java Mon Oct 14 12:41:11 2013 +0200 @@ -31,6 +31,8 @@ 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 @@ /** 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 @@ res[idx++] = itr.next(); } return convertArray(res, componentType); + } else if(obj == null) { + return null; } else { throw new IllegalArgumentException("not a script object"); } @@ -919,6 +932,24 @@ } /** + * 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 * * @param obj object to check diff -r 8c617a092d68 -r d155c4a7703c src/jdk/nashorn/internal/runtime/linker/JavaArgumentConverters.java --- a/src/jdk/nashorn/internal/runtime/linker/JavaArgumentConverters.java Mon Oct 14 11:45:15 2013 +0200 +++ b/src/jdk/nashorn/internal/runtime/linker/JavaArgumentConverters.java Mon Oct 14 12:41:11 2013 +0200 @@ -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 @@ } 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 @@ 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 @@ return MH.findStatic(MethodHandles.lookup(), JavaArgumentConverters.class, name, MH.type(rtype, types)); } - private static final Map, MethodHandle> CONVERTERS = new HashMap<>(); + private static final ConcurrentMap, 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); diff -r 8c617a092d68 -r d155c4a7703c test/src/jdk/nashorn/api/javaaccess/ArrayConversionTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/src/jdk/nashorn/api/javaaccess/ArrayConversionTest.java Mon Oct 14 12:41:11 2013 +0200 @@ -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]); + } +}