8191905: Add a REMOVE StandardOperation to Dynalink
Reviewed-by: hannesw, sundar
This commit is contained in:
parent
64f569ad1b
commit
9d93886076
src/jdk.dynalink/share/classes/jdk/dynalink
test/nashorn/src/jdk/dynalink/beans/test
@ -110,6 +110,15 @@ public enum StandardOperation implements Operation {
|
|||||||
* or reference). This operation must always be used as part of a {@link NamespaceOperation}.
|
* or reference). This operation must always be used as part of a {@link NamespaceOperation}.
|
||||||
*/
|
*/
|
||||||
SET,
|
SET,
|
||||||
|
/**
|
||||||
|
* Removes the value from a namespace defined on an object. Call sites with this
|
||||||
|
* operation should have a signature of
|
||||||
|
* <code>(receiver, name)→void</code> or
|
||||||
|
* <code>(receiver)→void</code> when used with {@link NamedOperation},
|
||||||
|
* with all parameters being of any type (either primitive
|
||||||
|
* or reference). This operation must always be used as part of a {@link NamespaceOperation}.
|
||||||
|
*/
|
||||||
|
REMOVE,
|
||||||
/**
|
/**
|
||||||
* Call a callable object. Call sites with this operation should have a
|
* Call a callable object. Call sites with this operation should have a
|
||||||
* signature of <code>(callable, receiver, arguments...)→value</code>,
|
* signature of <code>(callable, receiver, arguments...)→value</code>,
|
||||||
|
@ -107,7 +107,8 @@ import jdk.dynalink.linker.support.TypeUtilities;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* A class that provides linking capabilities for a single POJO class. Normally not used directly, but managed by
|
* A class that provides linking capabilities for a single POJO class. Normally not used directly, but managed by
|
||||||
* {@link BeansLinker}.
|
* {@link BeansLinker}. Most of the functionality is provided by the {@link AbstractJavaLinker} superclass; this
|
||||||
|
* class adds length and element operations for arrays and collections.
|
||||||
*/
|
*/
|
||||||
class BeanLinker extends AbstractJavaLinker implements TypeBasedGuardingDynamicLinker {
|
class BeanLinker extends AbstractJavaLinker implements TypeBasedGuardingDynamicLinker {
|
||||||
BeanLinker(final Class<?> clazz) {
|
BeanLinker(final Class<?> clazz) {
|
||||||
@ -147,6 +148,8 @@ class BeanLinker extends AbstractJavaLinker implements TypeBasedGuardingDynamicL
|
|||||||
return getElementGetter(req.popNamespace());
|
return getElementGetter(req.popNamespace());
|
||||||
} else if (op == StandardOperation.SET) {
|
} else if (op == StandardOperation.SET) {
|
||||||
return getElementSetter(req.popNamespace());
|
return getElementSetter(req.popNamespace());
|
||||||
|
} else if (op == StandardOperation.REMOVE) {
|
||||||
|
return getElementRemover(req.popNamespace());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -228,7 +231,7 @@ class BeanLinker extends AbstractJavaLinker implements TypeBasedGuardingDynamicL
|
|||||||
// dealing with an array, or a list or map, but hey...
|
// dealing with an array, or a list or map, but hey...
|
||||||
// Note that for arrays and lists, using LinkerServices.asType() will ensure that any language specific linkers
|
// Note that for arrays and lists, using LinkerServices.asType() will ensure that any language specific linkers
|
||||||
// in use will get a chance to perform any (if there's any) implicit conversion to integer for the indices.
|
// in use will get a chance to perform any (if there's any) implicit conversion to integer for the indices.
|
||||||
if(declaredType.isArray()) {
|
if(declaredType.isArray() && arrayMethod != null) {
|
||||||
return new GuardedInvocationComponentAndCollectionType(
|
return new GuardedInvocationComponentAndCollectionType(
|
||||||
createInternalFilteredGuardedInvocationComponent(arrayMethod.apply(declaredType), linkerServices),
|
createInternalFilteredGuardedInvocationComponent(arrayMethod.apply(declaredType), linkerServices),
|
||||||
CollectionType.ARRAY);
|
CollectionType.ARRAY);
|
||||||
@ -240,7 +243,7 @@ class BeanLinker extends AbstractJavaLinker implements TypeBasedGuardingDynamicL
|
|||||||
return new GuardedInvocationComponentAndCollectionType(
|
return new GuardedInvocationComponentAndCollectionType(
|
||||||
createInternalFilteredGuardedInvocationComponent(mapMethod, linkerServices),
|
createInternalFilteredGuardedInvocationComponent(mapMethod, linkerServices),
|
||||||
CollectionType.MAP);
|
CollectionType.MAP);
|
||||||
} else if(clazz.isArray()) {
|
} else if(clazz.isArray() && arrayMethod != null) {
|
||||||
return new GuardedInvocationComponentAndCollectionType(
|
return new GuardedInvocationComponentAndCollectionType(
|
||||||
getClassGuardedInvocationComponent(linkerServices.filterInternalObjects(arrayMethod.apply(clazz)), callSiteType),
|
getClassGuardedInvocationComponent(linkerServices.filterInternalObjects(arrayMethod.apply(clazz)), callSiteType),
|
||||||
CollectionType.ARRAY);
|
CollectionType.ARRAY);
|
||||||
@ -450,7 +453,7 @@ class BeanLinker extends AbstractJavaLinker implements TypeBasedGuardingDynamicL
|
|||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
private static void noOpSetter() {
|
private static void noOp() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final MethodHandle SET_LIST_ELEMENT = Lookup.PUBLIC.findVirtual(List.class, "set",
|
private static final MethodHandle SET_LIST_ELEMENT = Lookup.PUBLIC.findVirtual(List.class, "set",
|
||||||
@ -459,12 +462,14 @@ class BeanLinker extends AbstractJavaLinker implements TypeBasedGuardingDynamicL
|
|||||||
private static final MethodHandle PUT_MAP_ELEMENT = Lookup.PUBLIC.findVirtual(Map.class, "put",
|
private static final MethodHandle PUT_MAP_ELEMENT = Lookup.PUBLIC.findVirtual(Map.class, "put",
|
||||||
MethodType.methodType(Object.class, Object.class, Object.class));
|
MethodType.methodType(Object.class, Object.class, Object.class));
|
||||||
|
|
||||||
private static final MethodHandle NO_OP_SETTER_2;
|
private static final MethodHandle NO_OP_1;
|
||||||
private static final MethodHandle NO_OP_SETTER_3;
|
private static final MethodHandle NO_OP_2;
|
||||||
|
private static final MethodHandle NO_OP_3;
|
||||||
static {
|
static {
|
||||||
final MethodHandle noOpSetter = Lookup.findOwnStatic(MethodHandles.lookup(), "noOpSetter", void.class);
|
final MethodHandle noOp = Lookup.findOwnStatic(MethodHandles.lookup(), "noOp", void.class);
|
||||||
NO_OP_SETTER_2 = dropObjectArguments(noOpSetter, 2);
|
NO_OP_1 = dropObjectArguments(noOp, 1);
|
||||||
NO_OP_SETTER_3 = dropObjectArguments(noOpSetter, 3);
|
NO_OP_2 = dropObjectArguments(noOp, 2);
|
||||||
|
NO_OP_3 = dropObjectArguments(noOp, 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
private GuardedInvocationComponent getElementSetter(final ComponentLinkRequest req) throws Exception {
|
private GuardedInvocationComponent getElementSetter(final ComponentLinkRequest req) throws Exception {
|
||||||
@ -503,7 +508,39 @@ class BeanLinker extends AbstractJavaLinker implements TypeBasedGuardingDynamicL
|
|||||||
return gic.replaceInvocation(binder.bind(invocation));
|
return gic.replaceInvocation(binder.bind(invocation));
|
||||||
}
|
}
|
||||||
|
|
||||||
return guardComponentWithRangeCheck(gicact, callSiteType, nextComponent, binder, isFixedKey ? NO_OP_SETTER_2 : NO_OP_SETTER_3);
|
return guardComponentWithRangeCheck(gicact, callSiteType, nextComponent, binder, isFixedKey ? NO_OP_2 : NO_OP_3);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final MethodHandle REMOVE_LIST_ELEMENT = Lookup.PUBLIC.findVirtual(List.class, "remove",
|
||||||
|
MethodType.methodType(Object.class, int.class));
|
||||||
|
|
||||||
|
private static final MethodHandle REMOVE_MAP_ELEMENT = Lookup.PUBLIC.findVirtual(Map.class, "remove",
|
||||||
|
MethodType.methodType(Object.class, Object.class));
|
||||||
|
|
||||||
|
private GuardedInvocationComponent getElementRemover(final ComponentLinkRequest req) throws Exception {
|
||||||
|
final CallSiteDescriptor callSiteDescriptor = req.getDescriptor();
|
||||||
|
final Object name = req.name;
|
||||||
|
final boolean isFixedKey = name != null;
|
||||||
|
assertParameterCount(callSiteDescriptor, isFixedKey ? 1 : 2);
|
||||||
|
final LinkerServices linkerServices = req.linkerServices;
|
||||||
|
final MethodType callSiteType = callSiteDescriptor.getMethodType();
|
||||||
|
final GuardedInvocationComponent nextComponent = getNextComponent(req);
|
||||||
|
|
||||||
|
final GuardedInvocationComponentAndCollectionType gicact = guardedInvocationComponentAndCollectionType(
|
||||||
|
callSiteType, linkerServices, null, REMOVE_LIST_ELEMENT, REMOVE_MAP_ELEMENT);
|
||||||
|
|
||||||
|
if (gicact == null) {
|
||||||
|
// Can't remove elements for objects that are neither lists, nor maps.
|
||||||
|
return nextComponent;
|
||||||
|
}
|
||||||
|
|
||||||
|
final Object typedName = getTypedName(name, gicact.collectionType == CollectionType.MAP, linkerServices);
|
||||||
|
if (typedName == INVALID_NAME) {
|
||||||
|
return nextComponent;
|
||||||
|
}
|
||||||
|
|
||||||
|
return guardComponentWithRangeCheck(gicact, callSiteType, nextComponent,
|
||||||
|
new Binder(linkerServices, callSiteType, typedName), isFixedKey ? NO_OP_1: NO_OP_2);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final MethodHandle GET_COLLECTION_LENGTH = Lookup.PUBLIC.findVirtual(Collection.class, "size",
|
private static final MethodHandle GET_COLLECTION_LENGTH = Lookup.PUBLIC.findVirtual(Collection.class, "size",
|
||||||
|
@ -113,6 +113,8 @@ import jdk.dynalink.linker.TypeBasedGuardingDynamicLinker;
|
|||||||
* <li> expose elements of native Java arrays, {@link java.util.List} and {@link java.util.Map} objects as
|
* <li> expose elements of native Java arrays, {@link java.util.List} and {@link java.util.Map} objects as
|
||||||
* {@link StandardOperation#GET} and {@link StandardOperation#SET} operations in the
|
* {@link StandardOperation#GET} and {@link StandardOperation#SET} operations in the
|
||||||
* {@link StandardNamespace#ELEMENT} namespace;</li>
|
* {@link StandardNamespace#ELEMENT} namespace;</li>
|
||||||
|
* <li> expose removal of elements of {@link java.util.List} and {@link java.util.Map} objects as
|
||||||
|
* {@link StandardOperation#REMOVE} operation in the {@link StandardNamespace#ELEMENT} namespace;</li>
|
||||||
* <li>expose a virtual property named {@code length} on Java arrays, {@link java.util.Collection} and
|
* <li>expose a virtual property named {@code length} on Java arrays, {@link java.util.Collection} and
|
||||||
* {@link java.util.Map} objects;</li>
|
* {@link java.util.Map} objects;</li>
|
||||||
* <li>expose {@link StandardOperation#NEW} on instances of {@link StaticClass}
|
* <li>expose {@link StandardOperation#NEW} on instances of {@link StaticClass}
|
||||||
|
@ -30,6 +30,7 @@ import static jdk.dynalink.StandardNamespace.PROPERTY;
|
|||||||
import static jdk.dynalink.StandardOperation.CALL;
|
import static jdk.dynalink.StandardOperation.CALL;
|
||||||
import static jdk.dynalink.StandardOperation.GET;
|
import static jdk.dynalink.StandardOperation.GET;
|
||||||
import static jdk.dynalink.StandardOperation.NEW;
|
import static jdk.dynalink.StandardOperation.NEW;
|
||||||
|
import static jdk.dynalink.StandardOperation.REMOVE;
|
||||||
import static jdk.dynalink.StandardOperation.SET;
|
import static jdk.dynalink.StandardOperation.SET;
|
||||||
|
|
||||||
import java.lang.invoke.CallSite;
|
import java.lang.invoke.CallSite;
|
||||||
@ -39,7 +40,9 @@ import java.lang.invoke.MethodType;
|
|||||||
import java.security.AccessControlException;
|
import java.security.AccessControlException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import jdk.dynalink.CallSiteDescriptor;
|
import jdk.dynalink.CallSiteDescriptor;
|
||||||
import jdk.dynalink.DynamicLinker;
|
import jdk.dynalink.DynamicLinker;
|
||||||
import jdk.dynalink.DynamicLinkerFactory;
|
import jdk.dynalink.DynamicLinkerFactory;
|
||||||
@ -90,6 +93,7 @@ public class BeanLinkerTest {
|
|||||||
private static final Operation GET_ELEMENT = GET.withNamespace(ELEMENT);
|
private static final Operation GET_ELEMENT = GET.withNamespace(ELEMENT);
|
||||||
private static final Operation GET_METHOD = GET.withNamespace(METHOD);
|
private static final Operation GET_METHOD = GET.withNamespace(METHOD);
|
||||||
private static final Operation SET_ELEMENT = SET.withNamespace(ELEMENT);
|
private static final Operation SET_ELEMENT = SET.withNamespace(ELEMENT);
|
||||||
|
private static final Operation REMOVE_ELEMENT = REMOVE.withNamespace(ELEMENT);
|
||||||
|
|
||||||
private static final MethodHandle findThrower(final String name) {
|
private static final MethodHandle findThrower(final String name) {
|
||||||
try {
|
try {
|
||||||
@ -121,8 +125,8 @@ public class BeanLinkerTest {
|
|||||||
final CallSiteDescriptor desc = req.getCallSiteDescriptor();
|
final CallSiteDescriptor desc = req.getCallSiteDescriptor();
|
||||||
final Operation op = desc.getOperation();
|
final Operation op = desc.getOperation();
|
||||||
final Operation baseOp = NamedOperation.getBaseOperation(op);
|
final Operation baseOp = NamedOperation.getBaseOperation(op);
|
||||||
if (baseOp != GET_ELEMENT && baseOp != SET_ELEMENT) {
|
if (baseOp != GET_ELEMENT && baseOp != SET_ELEMENT && baseOp != REMOVE_ELEMENT) {
|
||||||
// We only handle GET_ELEMENT and SET_ELEMENT.
|
// We only handle GET_ELEMENT, SET_ELEMENT and REMOVE_ELEMENT.
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -538,4 +542,85 @@ public class BeanLinkerTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test(dataProvider = "flags")
|
||||||
|
public void removeElementFromListTest(final boolean publicLookup) throws Throwable {
|
||||||
|
final MethodType mt = MethodType.methodType(void.class, Object.class, int.class);
|
||||||
|
final CallSite cs = createCallSite(publicLookup, REMOVE_ELEMENT, mt);
|
||||||
|
|
||||||
|
final List<Integer> list = new ArrayList<>(List.of(23, 430, -4354));
|
||||||
|
|
||||||
|
cs.getTarget().invoke(list, 1);
|
||||||
|
Assert.assertEquals(list, List.of(23, -4354));
|
||||||
|
cs.getTarget().invoke(list, 1);
|
||||||
|
Assert.assertEquals(list, List.of(23));
|
||||||
|
cs.getTarget().invoke(list, 0);
|
||||||
|
Assert.assertEquals(list, List.of());
|
||||||
|
try {
|
||||||
|
cs.getTarget().invoke(list, -1);
|
||||||
|
throw new RuntimeException("expected IndexOutOfBoundsException");
|
||||||
|
} catch (final IndexOutOfBoundsException ex) {
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
cs.getTarget().invoke(list, list.size());
|
||||||
|
throw new RuntimeException("expected IndexOutOfBoundsException");
|
||||||
|
} catch (final IndexOutOfBoundsException ex) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(dataProvider = "flags")
|
||||||
|
public void removeElementFromListWithFixedKeyTest(final boolean publicLookup) throws Throwable {
|
||||||
|
final MethodType mt = MethodType.methodType(void.class, Object.class);
|
||||||
|
|
||||||
|
final List<Integer> list = new ArrayList<>(List.of(23, 430, -4354));
|
||||||
|
|
||||||
|
createCallSite(publicLookup, REMOVE_ELEMENT.named(1), mt).getTarget().invoke(list);
|
||||||
|
Assert.assertEquals(list, List.of(23, -4354));
|
||||||
|
createCallSite(publicLookup, REMOVE_ELEMENT.named(1), mt).getTarget().invoke(list);
|
||||||
|
Assert.assertEquals(list, List.of(23));
|
||||||
|
createCallSite(publicLookup, REMOVE_ELEMENT.named(0), mt).getTarget().invoke(list);
|
||||||
|
Assert.assertEquals(list, List.of());
|
||||||
|
try {
|
||||||
|
createCallSite(publicLookup, REMOVE_ELEMENT.named(-1), mt).getTarget().invoke(list);
|
||||||
|
throw new RuntimeException("expected IndexOutOfBoundsException");
|
||||||
|
} catch (final IndexOutOfBoundsException ex) {
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
createCallSite(publicLookup, REMOVE_ELEMENT.named(list.size()), mt).getTarget().invoke(list);
|
||||||
|
throw new RuntimeException("expected IndexOutOfBoundsException");
|
||||||
|
} catch (final IndexOutOfBoundsException ex) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(dataProvider = "flags")
|
||||||
|
public void removeElementFromMapTest(final boolean publicLookup) throws Throwable {
|
||||||
|
final MethodType mt = MethodType.methodType(void.class, Object.class, Object.class);
|
||||||
|
final CallSite cs = createCallSite(publicLookup, REMOVE_ELEMENT, mt);
|
||||||
|
|
||||||
|
final Map<String, String> map = new HashMap<>(Map.of("k1", "v1", "k2", "v2", "k3", "v3"));
|
||||||
|
|
||||||
|
cs.getTarget().invoke(map, "k2");
|
||||||
|
Assert.assertEquals(map, Map.of("k1", "v1", "k3", "v3"));
|
||||||
|
cs.getTarget().invoke(map, "k4");
|
||||||
|
Assert.assertEquals(map, Map.of("k1", "v1", "k3", "v3"));
|
||||||
|
cs.getTarget().invoke(map, "k1");
|
||||||
|
Assert.assertEquals(map, Map.of("k3", "v3"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test(dataProvider = "flags")
|
||||||
|
public void removeElementFromMapWithFixedKeyTest(final boolean publicLookup) throws Throwable {
|
||||||
|
final MethodType mt = MethodType.methodType(void.class, Object.class);
|
||||||
|
|
||||||
|
final Map<String, String> map = new HashMap<>(Map.of("k1", "v1", "k2", "v2", "k3", "v3"));
|
||||||
|
|
||||||
|
createCallSite(publicLookup, REMOVE_ELEMENT.named("k2"), mt).getTarget().invoke(map);
|
||||||
|
Assert.assertEquals(map, Map.of("k1", "v1", "k3", "v3"));
|
||||||
|
createCallSite(publicLookup, REMOVE_ELEMENT.named("k4"), mt).getTarget().invoke(map);
|
||||||
|
Assert.assertEquals(map, Map.of("k1", "v1", "k3", "v3"));
|
||||||
|
createCallSite(publicLookup, REMOVE_ELEMENT.named("k1"), mt).getTarget().invoke(map);
|
||||||
|
Assert.assertEquals(map, Map.of("k3", "v3"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user