提交 327ef678 编写于 作者: A attila

8015267: Allow conversion of JS arrays to Java List/Deque

Reviewed-by: lagergren, sundar
上级 e1cacfed
...@@ -42,8 +42,6 @@ ...@@ -42,8 +42,6 @@
<condition property="hg.executable" value="/usr/local/bin/hg" else="hg"> <condition property="hg.executable" value="/usr/local/bin/hg" else="hg">
<available file="/usr/local/bin/hg"/> <available file="/usr/local/bin/hg"/>
</condition> </condition>
<!-- check if JDK already has ASM classes -->
<available property="asm.available" classname="jdk.internal.org.objectweb.asm.Type"/>
<!-- check if testng.jar is avaiable --> <!-- check if testng.jar is avaiable -->
<available property="testng.available" file="${file.reference.testng.jar}"/> <available property="testng.available" file="${file.reference.testng.jar}"/>
...@@ -80,19 +78,7 @@ ...@@ -80,19 +78,7 @@
<delete dir="${dist.dir}"/> <delete dir="${dist.dir}"/>
</target> </target>
<!-- do it only if ASM is not available --> <target name="compile" depends="prepare" description="Compiles nashorn">
<target name="compile-asm" depends="prepare" unless="asm.available">
<javac srcdir="${jdk.asm.src.dir}"
destdir="${build.classes.dir}"
excludes="**/optimizer/* **/xml/* **/attrs/*"
source="${javac.source}"
target="${javac.target}"
debug="${javac.debug}"
encoding="${javac.encoding}"
includeantruntime="false"/>
</target>
<target name="compile" depends="compile-asm" description="Compiles nashorn">
<javac srcdir="${src.dir}" <javac srcdir="${src.dir}"
destdir="${build.classes.dir}" destdir="${build.classes.dir}"
classpath="${javac.classpath}" classpath="${javac.classpath}"
......
...@@ -30,6 +30,8 @@ import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED; ...@@ -30,6 +30,8 @@ import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED;
import java.lang.reflect.Array; import java.lang.reflect.Array;
import java.util.Collection; import java.util.Collection;
import java.util.Deque;
import java.util.List;
import jdk.internal.dynalink.beans.StaticClass; import jdk.internal.dynalink.beans.StaticClass;
import jdk.internal.dynalink.support.TypeUtilities; import jdk.internal.dynalink.support.TypeUtilities;
import jdk.nashorn.internal.objects.annotations.Attribute; import jdk.nashorn.internal.objects.annotations.Attribute;
...@@ -37,6 +39,7 @@ import jdk.nashorn.internal.objects.annotations.Function; ...@@ -37,6 +39,7 @@ import jdk.nashorn.internal.objects.annotations.Function;
import jdk.nashorn.internal.objects.annotations.ScriptClass; import jdk.nashorn.internal.objects.annotations.ScriptClass;
import jdk.nashorn.internal.objects.annotations.Where; import jdk.nashorn.internal.objects.annotations.Where;
import jdk.nashorn.internal.runtime.JSType; import jdk.nashorn.internal.runtime.JSType;
import jdk.nashorn.internal.runtime.ListAdapter;
import jdk.nashorn.internal.runtime.ScriptObject; import jdk.nashorn.internal.runtime.ScriptObject;
import jdk.nashorn.internal.runtime.linker.JavaAdapterFactory; import jdk.nashorn.internal.runtime.linker.JavaAdapterFactory;
...@@ -240,8 +243,8 @@ public final class NativeJava { ...@@ -240,8 +243,8 @@ public final class NativeJava {
} }
/** /**
* Given a script object and a Java type, converts the script object into the desired Java type. Currently it only * Given a script object and a Java type, converts the script object into the desired Java type. Currently it
* performs shallow creation of Java arrays, but might be extended for other types in the future. Example: * performs shallow creation of Java arrays, as well as wrapping of objects in Lists and Dequeues. Example:
* <pre> * <pre>
* var anArray = [1, "13", false] * var anArray = [1, "13", false]
* var javaIntArray = Java.to(anArray, "int[]") * var javaIntArray = Java.to(anArray, "int[]")
...@@ -250,42 +253,46 @@ public final class NativeJava { ...@@ -250,42 +253,46 @@ public final class NativeJava {
* print(javaIntArray[2]) // prints 0, as boolean false was converted to number 0 as per ECMAScript ToNumber conversion * print(javaIntArray[2]) // prints 0, as boolean false was converted to number 0 as per ECMAScript ToNumber conversion
* </pre> * </pre>
* @param self not used * @param self not used
* @param objArray the script object. Can be null. * @param obj the script object. Can be null.
* @param objType either a {@link #type(Object, Object) type object} or a String describing the type of the Java * @param objType either a {@link #type(Object, Object) type object} or a String describing the type of the Java
* object to create. Can not be null. If undefined, a "default" conversion is presumed (allowing the argument to be * object to create. Can not be null. If undefined, a "default" conversion is presumed (allowing the argument to be
* omitted). * omitted).
* @return a Java object whose value corresponds to the original script object's value. Specifically, for array * @return a Java object whose value corresponds to the original script object's value. Specifically, for array
* target types, returns a Java array of the same type with contents converted to the array's component type. Does * target types, returns a Java array of the same type with contents converted to the array's component type. Does
* not recursively convert for multidimensional arrays. * not recursively convert for multidimensional arrays. For {@link List} or {@link Deque}, returns a live wrapper
* type. Returns null if scriptObject is null. * around the object, see {@link ListAdapter} for details. Returns null if obj is null.
* @throws ClassNotFoundException if the class described by objType is not found * @throws ClassNotFoundException if the class described by objType is not found
*/ */
@Function(attributes = Attribute.NOT_ENUMERABLE, where = Where.CONSTRUCTOR) @Function(attributes = Attribute.NOT_ENUMERABLE, where = Where.CONSTRUCTOR)
public static Object to(final Object self, final Object objArray, final Object objType) throws ClassNotFoundException { public static Object to(final Object self, final Object obj, final Object objType) throws ClassNotFoundException {
if (objArray == null) { if (obj == null) {
return null; return null;
} }
final Class<?> componentType; Global.checkObject(obj);
final Class<?> targetClass;
if(objType == UNDEFINED) { if(objType == UNDEFINED) {
componentType = Object.class; targetClass = Object[].class;
} else { } else {
final StaticClass arrayType; final StaticClass targetType;
if(objType instanceof StaticClass) { if(objType instanceof StaticClass) {
arrayType = (StaticClass)objType; targetType = (StaticClass)objType;
} else { } else {
arrayType = type(objType); targetType = type(objType);
}
final Class<?> arrayClass = arrayType.getRepresentedClass();
if(!arrayClass.isArray()) {
throw typeError("to.expects.array.type", arrayClass.getName());
} }
componentType = arrayClass.getComponentType(); targetClass = targetType.getRepresentedClass();
} }
Global.checkObject(objArray); if(targetClass.isArray()) {
return ((ScriptObject)obj).getArray().asArrayOfType(targetClass.getComponentType());
}
if(targetClass == List.class || targetClass == Deque.class) {
return new ListAdapter((ScriptObject)obj);
}
return ((ScriptObject)objArray).getArray().asArrayOfType(componentType); throw typeError("unsupported.java.to.type", targetClass.getName());
} }
/** /**
......
package jdk.nashorn.internal.runtime;
import java.util.AbstractList;
import java.util.Deque;
import java.util.Iterator;
import java.util.ListIterator;
import java.util.NoSuchElementException;
import java.util.RandomAccess;
import jdk.nashorn.internal.runtime.linker.InvokeByName;
/**
* An adapter that can wrap any ECMAScript Array-like object (that adheres to the array rules for the property
* {@code length} and having conforming {@code push}, {@code pop}, {@code shift}, {@code unshift}, and {@code splice}
* methods) and expose it as both a Java list and double-ended queue. While script arrays aren't necessarily efficient
* as dequeues, it's still slightly more efficient to be able to translate dequeue operations into pushes, pops, shifts,
* and unshifts, than to blindly translate all list's add/remove operations into splices. Also, it is conceivable that a
* custom script object that implements an Array-like API can have a background data representation that is optimized
* for dequeue-like access. Note that with ECMAScript arrays, {@code push} and {@pop} operate at the end of the array,
* while in Java {@code Deque} they operate on the front of the queue and as such the Java dequeue {@link #push(Object)}
* and {@link #pop()} operations will translate to {@code unshift} and {@code shift} script operations respectively,
* while {@link #addLast(Object)} and {@link #removeLast()} will translate to {@code push} and {@code pop}.
*/
public class ListAdapter extends AbstractList<Object> implements RandomAccess, Deque<Object> {
// These add to the back and front of the list
private static final InvokeByName PUSH = new InvokeByName("push", ScriptObject.class, void.class, Object.class);
private static final InvokeByName UNSHIFT = new InvokeByName("unshift", ScriptObject.class, void.class, Object.class);
// These remove from the back and front of the list
private static final InvokeByName POP = new InvokeByName("pop", ScriptObject.class, Object.class);
private static final InvokeByName SHIFT = new InvokeByName("shift", ScriptObject.class, Object.class);
// These insert and remove in the middle of the list
private static final InvokeByName SPLICE_ADD = new InvokeByName("splice", ScriptObject.class, void.class, int.class, int.class, Object.class);
private static final InvokeByName SPLICE_REMOVE = new InvokeByName("splice", ScriptObject.class, void.class, int.class, int.class);
private final ScriptObject obj;
/**
* Creates a new list wrapper for the specified script object.
* @param obj script the object to wrap
*/
public ListAdapter(ScriptObject obj) {
this.obj = obj;
}
@Override
public int size() {
return JSType.toInt32(obj.getLength());
}
@Override
public Object get(int index) {
checkRange(index);
return obj.get(index);
}
@Override
public Object set(int index, Object element) {
checkRange(index);
final Object prevValue = get(index);
obj.set(index, element, false);
return prevValue;
}
private void checkRange(int index) {
if(index < 0 || index >= size()) {
throw invalidIndex(index);
}
}
@Override
public void push(Object e) {
addFirst(e);
}
@Override
public boolean add(Object e) {
addLast(e);
return true;
}
@Override
public void addFirst(Object e) {
try {
final Object fn = UNSHIFT.getGetter().invokeExact(obj);
checkFunction(fn, UNSHIFT);
UNSHIFT.getInvoker().invokeExact(fn, obj, e);
} catch(RuntimeException | Error ex) {
throw ex;
} catch(Throwable t) {
throw new RuntimeException(t);
}
}
@Override
public void addLast(Object e) {
try {
final Object fn = PUSH.getGetter().invokeExact(obj);
checkFunction(fn, PUSH);
PUSH.getInvoker().invokeExact(fn, obj, e);
} catch(RuntimeException | Error ex) {
throw ex;
} catch(Throwable t) {
throw new RuntimeException(t);
}
}
@Override
public void add(int index, Object e) {
try {
if(index < 0) {
throw invalidIndex(index);
} else if(index == 0) {
addFirst(e);
} else {
final int size = size();
if(index < size) {
final Object fn = SPLICE_ADD.getGetter().invokeExact(obj);
checkFunction(fn, SPLICE_ADD);
SPLICE_ADD.getInvoker().invokeExact(fn, obj, index, 0, e);
} else if(index == size) {
addLast(e);
} else {
throw invalidIndex(index);
}
}
} catch(RuntimeException | Error ex) {
throw ex;
} catch(Throwable t) {
throw new RuntimeException(t);
}
}
private static void checkFunction(Object fn, InvokeByName invoke) {
if(!(fn instanceof ScriptFunction)) {
throw new UnsupportedOperationException("The script object doesn't have a function named " + invoke.getName());
}
}
private static IndexOutOfBoundsException invalidIndex(int index) {
return new IndexOutOfBoundsException(String.valueOf(index));
}
@Override
public boolean offer(Object e) {
return offerLast(e);
}
@Override
public boolean offerFirst(Object e) {
addFirst(e);
return true;
}
@Override
public boolean offerLast(Object e) {
addLast(e);
return true;
}
@Override
public Object pop() {
return removeFirst();
}
@Override
public Object remove() {
return removeFirst();
}
@Override
public Object removeFirst() {
checkNonEmpty();
return invokeShift();
}
@Override
public Object removeLast() {
checkNonEmpty();
return invokePop();
}
private void checkNonEmpty() {
if(isEmpty()) {
throw new NoSuchElementException();
}
}
@Override
public Object remove(int index) {
if(index < 0) {
throw invalidIndex(index);
} else if (index == 0) {
return invokeShift();
} else {
final int maxIndex = size() - 1;
if(index < maxIndex) {
final Object prevValue = get(index);
invokeSpliceRemove(index, 1);
return prevValue;
} else if(index == maxIndex) {
return invokePop();
} else {
throw invalidIndex(index);
}
}
}
private Object invokeShift() {
try {
final Object fn = SHIFT.getGetter().invokeExact(obj);
checkFunction(fn, SHIFT);
return SHIFT.getInvoker().invokeExact(fn, obj);
} catch(RuntimeException | Error ex) {
throw ex;
} catch(Throwable t) {
throw new RuntimeException(t);
}
}
private Object invokePop() {
try {
final Object fn = POP.getGetter().invokeExact(obj);
checkFunction(fn, POP);
return POP.getInvoker().invokeExact(fn, obj);
} catch(RuntimeException | Error ex) {
throw ex;
} catch(Throwable t) {
throw new RuntimeException(t);
}
}
@Override
protected void removeRange(int fromIndex, int toIndex) {
invokeSpliceRemove(fromIndex, toIndex - fromIndex);
}
private void invokeSpliceRemove(int fromIndex, int count) {
try {
final Object fn = SPLICE_REMOVE.getGetter().invokeExact(obj);
checkFunction(fn, SPLICE_REMOVE);
SPLICE_REMOVE.getInvoker().invokeExact(fn, obj, fromIndex, count);
} catch(RuntimeException | Error ex) {
throw ex;
} catch(Throwable t) {
throw new RuntimeException(t);
}
}
@Override
public Object poll() {
return pollFirst();
}
@Override
public Object pollFirst() {
return isEmpty() ? null : invokeShift();
}
@Override
public Object pollLast() {
return isEmpty() ? null : invokePop();
}
@Override
public Object peek() {
return peekFirst();
}
@Override
public Object peekFirst() {
return isEmpty() ? null : get(0);
}
@Override
public Object peekLast() {
return isEmpty() ? null : get(size() - 1);
}
@Override
public Object element() {
return getFirst();
}
@Override
public Object getFirst() {
checkNonEmpty();
return get(0);
}
@Override
public Object getLast() {
checkNonEmpty();
return get(size() - 1);
}
@Override
public Iterator<Object> descendingIterator() {
final ListIterator<Object> it = listIterator(size());
return new Iterator<Object>() {
@Override
public boolean hasNext() {
return it.hasPrevious();
}
@Override
public Object next() {
return it.previous();
}
@Override
public void remove() {
it.remove();
}
};
}
@Override
public boolean removeFirstOccurrence(Object o) {
return removeOccurrence(o, iterator());
}
@Override
public boolean removeLastOccurrence(Object o) {
return removeOccurrence(o, descendingIterator());
}
private static boolean removeOccurrence(Object o, Iterator<Object> it) {
while(it.hasNext()) {
final Object e = it.next();
if(o == null ? e == null : o.equals(e)) {
it.remove();
return true;
}
}
return false;
}
}
...@@ -40,7 +40,7 @@ import java.lang.invoke.MethodHandle; ...@@ -40,7 +40,7 @@ import java.lang.invoke.MethodHandle;
* private static final InvokeByName TO_JSON = new InvokeByName("toJSON", Object.class, Object.class, Object.class); * private static final InvokeByName TO_JSON = new InvokeByName("toJSON", Object.class, Object.class, Object.class);
* ... * ...
* final Object toJSONFn = TO_JSON.getGetter().invokeExact(obj); * final Object toJSONFn = TO_JSON.getGetter().invokeExact(obj);
* value = TO_JSON.getInvoker().invokeExact(toJSON, obj, key); * value = TO_JSON.getInvoker().invokeExact(toJSONFn, obj, key);
* </pre> * </pre>
* In practice, you can have stronger type assumptions if it makes sense for your code, just remember that you must use * In practice, you can have stronger type assumptions if it makes sense for your code, just remember that you must use
* the same parameter types as the formal types of the arguments for {@code invokeExact} to work: * the same parameter types as the formal types of the arguments for {@code invokeExact} to work:
...@@ -50,7 +50,7 @@ import java.lang.invoke.MethodHandle; ...@@ -50,7 +50,7 @@ import java.lang.invoke.MethodHandle;
* final ScriptObject sobj = (ScriptObject)obj; * final ScriptObject sobj = (ScriptObject)obj;
* final Object toJSONFn = TO_JSON.getGetter().invokeExact(sobj); * final Object toJSONFn = TO_JSON.getGetter().invokeExact(sobj);
* if(toJSONFn instanceof ScriptFunction) { * if(toJSONFn instanceof ScriptFunction) {
* value = TO_JSON.getInvoker().invokeExact(toJSON, sobj, key); * value = TO_JSON.getInvoker().invokeExact(toJSONFn, sobj, key);
* } * }
* </pre> * </pre>
* Note that in general you will not want to reuse a single instance of this class for implementing more than one call * Note that in general you will not want to reuse a single instance of this class for implementing more than one call
...@@ -59,6 +59,7 @@ import java.lang.invoke.MethodHandle; ...@@ -59,6 +59,7 @@ import java.lang.invoke.MethodHandle;
* separate instance of this class for every place. * separate instance of this class for every place.
*/ */
public class InvokeByName { public class InvokeByName {
private final String name;
private final MethodHandle getter; private final MethodHandle getter;
private final MethodHandle invoker; private final MethodHandle invoker;
...@@ -81,6 +82,7 @@ public class InvokeByName { ...@@ -81,6 +82,7 @@ public class InvokeByName {
* @param ptypes the parameter types of the function. * @param ptypes the parameter types of the function.
*/ */
public InvokeByName(final String name, final Class<?> targetClass, final Class<?> rtype, final Class<?>... ptypes) { public InvokeByName(final String name, final Class<?> targetClass, final Class<?> rtype, final Class<?>... ptypes) {
this.name = name;
getter = Bootstrap.createDynamicInvoker("dyn:getMethod|getProp|getItem:" + name, Object.class, targetClass); getter = Bootstrap.createDynamicInvoker("dyn:getMethod|getProp|getItem:" + name, Object.class, targetClass);
final Class<?>[] finalPtypes; final Class<?>[] finalPtypes;
...@@ -96,6 +98,14 @@ public class InvokeByName { ...@@ -96,6 +98,14 @@ public class InvokeByName {
invoker = Bootstrap.createDynamicInvoker("dyn:call", rtype, finalPtypes); invoker = Bootstrap.createDynamicInvoker("dyn:call", rtype, finalPtypes);
} }
/**
* Returns the name of the function retrieved through this invoker.
* @return the name of the function retrieved through this invoker.
*/
public String getName() {
return name;
}
/** /**
* Returns the property getter that can be invoked on an object to retrieve the function object that will be * Returns the property getter that can be invoked on an object to retrieve the function object that will be
* subsequently invoked by the invoker returned by {@link #getInvoker()}. * subsequently invoked by the invoker returned by {@link #getInvoker()}.
......
...@@ -125,7 +125,7 @@ type.error.no.constructor.matches.args=Can not construct {0} with the passed arg ...@@ -125,7 +125,7 @@ type.error.no.constructor.matches.args=Can not construct {0} with the passed arg
type.error.no.method.matches.args=Can not invoke method {0} with the passed arguments; they do not match any of its method signatures. type.error.no.method.matches.args=Can not invoke method {0} with the passed arguments; they do not match any of its method signatures.
type.error.method.not.constructor=Java method {0} can't be used as a constructor. type.error.method.not.constructor=Java method {0} can't be used as a constructor.
type.error.env.not.object=$ENV must be an Object. type.error.env.not.object=$ENV must be an Object.
type.error.to.expects.array.type=Java.to() expects an array target type. {0} is not an array type. type.error.unsupported.java.to.type=Unsupported Java.to target type {0}.
range.error.inappropriate.array.length=inappropriate array length: {0} range.error.inappropriate.array.length=inappropriate array length: {0}
range.error.invalid.fraction.digits=fractionDigits argument to {0} must be in [0, 20] range.error.invalid.fraction.digits=fractionDigits argument to {0} must be in [0, 20]
range.error.invalid.precision=precision argument toPrecision() must be in [1, 21] range.error.invalid.precision=precision argument toPrecision() must be in [1, 21]
......
/*
* 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.
*
* 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.
*/
/**
* JDK-8015267: have a List/Deque adapter for JS array-like objects
*
* @test
* @run
*/
var a = ['a', 'b', 'c', 'd']
var l = Java.to(a, java.util.List)
print(l instanceof java.util.List)
print(l instanceof java.util.Deque)
print(l[0])
print(l[1])
print(l[2])
print(l[3])
print(l.size())
l.push('x')
print(a)
l.addLast('y')
print(a)
print(l.pop())
print(l.removeLast())
print(a)
l.add('e')
l.add(5, 'f')
print(a)
l.add(0, 'z')
print(a)
l.add(2, 'x')
print(a)
l[7] = 'g'
print(a)
try { l.add(15, '') } catch(e) { print(e.class) }
try { l.remove(15) } catch(e) { print(e.class) }
try { l.add(-1, '') } catch(e) { print(e.class) }
try { l.remove(-1) } catch(e) { print(e.class) }
l.remove(7)
l.remove(2)
l.remove(0)
print(a)
print(l.peek())
print(l.peekFirst())
print(l.peekLast())
print(l.element())
print(l.getFirst())
print(l.getLast())
l.offer('1')
l.offerFirst('2')
l.offerLast('3')
print(a)
a = ['1', '2', 'x', '3', '4', 'x', '5', '6', 'x', '7', '8']
print(a)
var l = Java.to(a, java.util.List)
l.removeFirstOccurrence('x')
print(a)
l.removeLastOccurrence('x')
print(a)
var empty = Java.to([], java.util.List)
try { empty.pop() } catch(e) { print(e.class) }
try { empty.removeFirst() } catch(e) { print(e.class) }
try { empty.removeLast() } catch(e) { print(e.class) }
try { empty.element() } catch(e) { print(e.class) }
try { empty.getFirst() } catch(e) { print(e.class) }
try { empty.getLast() } catch(e) { print(e.class) }
print(empty.peek())
print(empty.peekFirst())
print(empty.peekLast())
true
true
a
b
c
d
4
x,a,b,c,d
x,a,b,c,d,y
x
y
a,b,c,d
a,b,c,d,e,f
z,a,b,c,d,e,f
z,a,x,b,c,d,e,f
z,a,x,b,c,d,e,g
class java.lang.IndexOutOfBoundsException
class java.lang.IndexOutOfBoundsException
class java.lang.IndexOutOfBoundsException
class java.lang.IndexOutOfBoundsException
a,b,c,d,e
a
a
e
a
a
e
2,a,b,c,d,e,1,3
1,2,x,3,4,x,5,6,x,7,8
1,2,3,4,x,5,6,x,7,8
1,2,3,4,x,5,6,7,8
class java.util.NoSuchElementException
class java.util.NoSuchElementException
class java.util.NoSuchElementException
class java.util.NoSuchElementException
class java.util.NoSuchElementException
class java.util.NoSuchElementException
null
null
null
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册