Path: blob/master/src/jdk.dynalink/share/classes/jdk/dynalink/beans/BeanLinker.java
41161 views
/*1* Copyright (c) 2010, 2016, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425/*26* This file is available under and governed by the GNU General Public27* License version 2 only, as published by the Free Software Foundation.28* However, the following notice accompanied the original version of this29* file, and Oracle licenses the original version of this file under the BSD30* license:31*/32/*33Copyright 2009-2013 Attila Szegedi3435Redistribution and use in source and binary forms, with or without36modification, are permitted provided that the following conditions are37met:38* Redistributions of source code must retain the above copyright39notice, this list of conditions and the following disclaimer.40* Redistributions in binary form must reproduce the above copyright41notice, this list of conditions and the following disclaimer in the42documentation and/or other materials provided with the distribution.43* Neither the name of the copyright holder nor the names of44contributors may be used to endorse or promote products derived from45this software without specific prior written permission.4647THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS48IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED49TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A50PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL COPYRIGHT HOLDER51BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR52CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF53SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR54BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,55WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR56OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF57ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.58*/5960package jdk.dynalink.beans;6162import java.lang.invoke.MethodHandle;63import java.lang.invoke.MethodHandles;64import java.lang.invoke.MethodType;65import java.lang.reflect.Array;66import java.util.Collection;67import java.util.Collections;68import java.util.List;69import java.util.Map;70import java.util.function.Function;71import jdk.dynalink.CallSiteDescriptor;72import jdk.dynalink.Namespace;73import jdk.dynalink.Operation;74import jdk.dynalink.StandardNamespace;75import jdk.dynalink.StandardOperation;76import jdk.dynalink.beans.GuardedInvocationComponent.ValidationType;77import jdk.dynalink.linker.GuardedInvocation;78import jdk.dynalink.linker.LinkerServices;79import jdk.dynalink.linker.TypeBasedGuardingDynamicLinker;80import jdk.dynalink.linker.support.Guards;81import jdk.dynalink.linker.support.Lookup;82import jdk.dynalink.linker.support.TypeUtilities;8384/**85* A class that provides linking capabilities for a single POJO class. Normally not used directly, but managed by86* {@link BeansLinker}. Most of the functionality is provided by the {@link AbstractJavaLinker} superclass; this87* class adds length and element operations for arrays and collections.88*/89class BeanLinker extends AbstractJavaLinker implements TypeBasedGuardingDynamicLinker {90BeanLinker(final Class<?> clazz) {91super(clazz, Guards.getClassGuard(clazz), Guards.getInstanceOfGuard(clazz));92if(clazz.isArray()) {93// Some languages won't have a notion of manipulating collections. Exposing "length" on arrays as an94// explicit property is beneficial for them.95setPropertyGetter("length", GET_ARRAY_LENGTH, ValidationType.IS_ARRAY);96} else if(Collection.class.isAssignableFrom(clazz)) {97setPropertyGetter("length", GET_COLLECTION_LENGTH, ValidationType.INSTANCE_OF);98} else if(Map.class.isAssignableFrom(clazz)) {99setPropertyGetter("length", GET_MAP_LENGTH, ValidationType.INSTANCE_OF);100}101}102103@Override104public boolean canLinkType(final Class<?> type) {105return type == clazz;106}107108@Override109FacetIntrospector createFacetIntrospector() {110return new BeanIntrospector(clazz);111}112113@Override114protected GuardedInvocationComponent getGuardedInvocationComponent(final ComponentLinkRequest req) throws Exception {115if (req.namespaces.isEmpty()) {116return null;117}118final Namespace ns = req.namespaces.get(0);119if (ns == StandardNamespace.ELEMENT) {120final Operation op = req.baseOperation;121if (op == StandardOperation.GET) {122return getElementGetter(req.popNamespace());123} else if (op == StandardOperation.SET) {124return getElementSetter(req.popNamespace());125} else if (op == StandardOperation.REMOVE) {126return getElementRemover(req.popNamespace());127}128}129return super.getGuardedInvocationComponent(req);130}131132@Override133SingleDynamicMethod getConstructorMethod(final String signature) {134return null;135}136137private static final MethodHandle GET_LIST_ELEMENT = Lookup.PUBLIC.findVirtual(List.class, "get",138MethodType.methodType(Object.class, int.class));139140private static final MethodHandle GET_MAP_ELEMENT = Lookup.PUBLIC.findVirtual(Map.class, "get",141MethodType.methodType(Object.class, Object.class));142143private static final MethodHandle LIST_GUARD = Guards.getInstanceOfGuard(List.class);144private static final MethodHandle MAP_GUARD = Guards.getInstanceOfGuard(Map.class);145146private static final MethodHandle NULL_GETTER_1;147private static final MethodHandle NULL_GETTER_2;148static {149final MethodHandle constantNull = MethodHandles.constant(Object.class, null);150NULL_GETTER_1 = dropObjectArguments(constantNull, 1);151NULL_GETTER_2 = dropObjectArguments(constantNull, 2);152}153154private static MethodHandle dropObjectArguments(final MethodHandle m, final int n) {155return MethodHandles.dropArguments(m, 0, Collections.nCopies(n, Object.class));156}157158private enum CollectionType {159ARRAY, LIST, MAP160}161162private GuardedInvocationComponent getElementGetter(final ComponentLinkRequest req) throws Exception {163final CallSiteDescriptor callSiteDescriptor = req.getDescriptor();164final Object name = req.name;165final boolean isFixedKey = name != null;166assertParameterCount(callSiteDescriptor, isFixedKey ? 1 : 2);167final LinkerServices linkerServices = req.linkerServices;168final MethodType callSiteType = callSiteDescriptor.getMethodType();169final GuardedInvocationComponent nextComponent = getNextComponent(req);170171final GuardedInvocationComponentAndCollectionType gicact = guardedInvocationComponentAndCollectionType(172callSiteType, linkerServices, MethodHandles::arrayElementGetter, GET_LIST_ELEMENT, GET_MAP_ELEMENT);173174if (gicact == null) {175// Can't retrieve elements for objects that are neither arrays, nor list, nor maps.176return nextComponent;177}178179final Object typedName = getTypedName(name, gicact.collectionType == CollectionType.MAP, linkerServices);180if (typedName == INVALID_NAME) {181return nextComponent;182}183184return guardComponentWithRangeCheck(gicact, callSiteType, nextComponent,185new Binder(linkerServices, callSiteType, typedName), isFixedKey ? NULL_GETTER_1 : NULL_GETTER_2);186}187188private static class GuardedInvocationComponentAndCollectionType {189final GuardedInvocationComponent gic;190final CollectionType collectionType;191192GuardedInvocationComponentAndCollectionType(final GuardedInvocationComponent gic, final CollectionType collectionType) {193this.gic = gic;194this.collectionType = collectionType;195}196}197198private GuardedInvocationComponentAndCollectionType guardedInvocationComponentAndCollectionType(199final MethodType callSiteType, final LinkerServices linkerServices,200final Function<Class<?>, MethodHandle> arrayMethod, final MethodHandle listMethod, final MethodHandle mapMethod) {201final Class<?> declaredType = callSiteType.parameterType(0);202// If declared type of receiver at the call site is already an array, a list or map, bind without guard. Thing203// is, it'd be quite stupid of a call site creator to go though invokedynamic when it knows in advance they're204// dealing with an array, or a list or map, but hey...205// Note that for arrays and lists, using LinkerServices.asType() will ensure that any language specific linkers206// in use will get a chance to perform any (if there's any) implicit conversion to integer for the indices.207if(declaredType.isArray() && arrayMethod != null) {208return new GuardedInvocationComponentAndCollectionType(209createInternalFilteredGuardedInvocationComponent(arrayMethod.apply(declaredType), linkerServices),210CollectionType.ARRAY);211} else if(List.class.isAssignableFrom(declaredType)) {212return new GuardedInvocationComponentAndCollectionType(213createInternalFilteredGuardedInvocationComponent(listMethod, linkerServices),214CollectionType.LIST);215} else if(Map.class.isAssignableFrom(declaredType)) {216return new GuardedInvocationComponentAndCollectionType(217createInternalFilteredGuardedInvocationComponent(mapMethod, linkerServices),218CollectionType.MAP);219} else if(clazz.isArray() && arrayMethod != null) {220return new GuardedInvocationComponentAndCollectionType(221getClassGuardedInvocationComponent(linkerServices.filterInternalObjects(arrayMethod.apply(clazz)), callSiteType),222CollectionType.ARRAY);223} else if(List.class.isAssignableFrom(clazz)) {224return new GuardedInvocationComponentAndCollectionType(225createInternalFilteredGuardedInvocationComponent(listMethod, Guards.asType(LIST_GUARD, callSiteType),226List.class, ValidationType.INSTANCE_OF, linkerServices),227CollectionType.LIST);228} else if(Map.class.isAssignableFrom(clazz)) {229return new GuardedInvocationComponentAndCollectionType(230createInternalFilteredGuardedInvocationComponent(mapMethod, Guards.asType(MAP_GUARD, callSiteType),231Map.class, ValidationType.INSTANCE_OF, linkerServices),232CollectionType.MAP);233}234return null;235}236237private static final Object INVALID_NAME = new Object();238239private static Object getTypedName(final Object name, final boolean isMap, final LinkerServices linkerServices) throws Exception {240// Convert the key to a number if we're working with a list or array241if (!isMap && name != null) {242final Integer integer = convertKeyToInteger(name, linkerServices);243if (integer == null || integer < 0) {244// key is not a non-negative integer, it can never address an245// array or list element246return INVALID_NAME;247}248return integer;249}250return name;251}252253private static GuardedInvocationComponent guardComponentWithRangeCheck(254final GuardedInvocationComponentAndCollectionType gicact, final MethodType callSiteType,255final GuardedInvocationComponent nextComponent, final Binder binder, final MethodHandle noOp) {256257final MethodHandle checkGuard;258switch(gicact.collectionType) {259case LIST:260checkGuard = binder.convertArgToNumber(RANGE_CHECK_LIST);261break;262case MAP:263checkGuard = binder.linkerServices.filterInternalObjects(CONTAINS_MAP);264break;265case ARRAY:266checkGuard = binder.convertArgToNumber(RANGE_CHECK_ARRAY);267break;268default:269throw new AssertionError();270}271272// If there's no next component, produce a fixed no-op one273final GuardedInvocationComponent finalNextComponent;274if (nextComponent != null) {275finalNextComponent = nextComponent;276} else {277finalNextComponent = createGuardedInvocationComponentAsType(noOp, callSiteType, binder.linkerServices);278}279280final GuardedInvocationComponent gic = gicact.gic;281final GuardedInvocation gi = gic.getGuardedInvocation();282283final MethodPair matchedInvocations = matchReturnTypes(binder.bind(gi.getInvocation()),284finalNextComponent.getGuardedInvocation().getInvocation());285286return finalNextComponent.compose(matchedInvocations.guardWithTest(binder.bindTest(checkGuard)), gi.getGuard(),287gic.getValidatorClass(), gic.getValidationType());288}289290private static GuardedInvocationComponent createInternalFilteredGuardedInvocationComponent(291final MethodHandle invocation, final LinkerServices linkerServices) {292return new GuardedInvocationComponent(linkerServices.filterInternalObjects(invocation));293}294295private static GuardedInvocationComponent createGuardedInvocationComponentAsType(296final MethodHandle invocation, final MethodType fromType, final LinkerServices linkerServices) {297return new GuardedInvocationComponent(linkerServices.asType(invocation, fromType));298}299300private static GuardedInvocationComponent createInternalFilteredGuardedInvocationComponent(301final MethodHandle invocation, final MethodHandle guard, final Class<?> validatorClass,302final ValidationType validationType, final LinkerServices linkerServices) {303return new GuardedInvocationComponent(linkerServices.filterInternalObjects(invocation), guard,304validatorClass, validationType);305}306307private static Integer convertKeyToInteger(final Object fixedKey, final LinkerServices linkerServices) throws Exception {308if (fixedKey instanceof Integer) {309return (Integer)fixedKey;310}311312final Number n;313if (fixedKey instanceof Number) {314n = (Number)fixedKey;315} else {316final Class<?> keyClass = fixedKey.getClass();317if(linkerServices.canConvert(keyClass, Number.class)) {318final Object val;319try {320val = linkerServices.getTypeConverter(keyClass, Number.class).invoke(fixedKey);321} catch(Exception|Error e) {322throw e;323} catch(final Throwable t) {324throw new RuntimeException(t);325}326if(!(val instanceof Number)) {327return null; // not a number328}329n = (Number)val;330} else if (fixedKey instanceof String){331try {332return Integer.valueOf((String)fixedKey);333} catch(final NumberFormatException e) {334// key is not a number335return null;336}337} else {338return null;339}340}341342if(n instanceof Integer) {343return (Integer)n;344}345final int intIndex = n.intValue();346final double doubleValue = n.doubleValue();347if(intIndex != doubleValue && !Double.isInfinite(doubleValue)) { // let infinites trigger IOOBE348return null; // not an exact integer349}350return intIndex;351}352353/**354* Contains methods to adapt an item getter/setter method handle to the requested type, optionally binding it to a355* fixed key first.356*/357private static class Binder {358private final LinkerServices linkerServices;359private final MethodType methodType;360private final Object fixedKey;361362Binder(final LinkerServices linkerServices, final MethodType methodType, final Object fixedKey) {363this.linkerServices = linkerServices;364this.methodType = fixedKey == null ? methodType : methodType.insertParameterTypes(1, fixedKey.getClass());365this.fixedKey = fixedKey;366}367368/*private*/ MethodHandle bind(final MethodHandle handle) {369return bindToFixedKey(linkerServices.asTypeLosslessReturn(handle, methodType));370}371372/*private*/ MethodHandle bindTest(final MethodHandle handle) {373return bindToFixedKey(Guards.asType(handle, methodType));374}375376/*private*/ MethodHandle convertArgToNumber(final MethodHandle mh) {377final Class<?> sourceType = methodType.parameterType(1);378if(TypeUtilities.isMethodInvocationConvertible(sourceType, Number.class)) {379return mh;380} else if(linkerServices.canConvert(sourceType, Number.class)) {381final MethodHandle converter = linkerServices.getTypeConverter(sourceType, Number.class);382return MethodHandles.filterArguments(mh, 1, converter.asType(converter.type().changeReturnType(383mh.type().parameterType(1))));384}385return mh;386}387388private MethodHandle bindToFixedKey(final MethodHandle handle) {389return fixedKey == null ? handle : MethodHandles.insertArguments(handle, 1, fixedKey);390}391}392393private static final MethodHandle RANGE_CHECK_ARRAY = findRangeCheck(Object.class);394private static final MethodHandle RANGE_CHECK_LIST = findRangeCheck(List.class);395private static final MethodHandle CONTAINS_MAP = Lookup.PUBLIC.findVirtual(Map.class, "containsKey",396MethodType.methodType(boolean.class, Object.class));397398private static MethodHandle findRangeCheck(final Class<?> collectionType) {399return Lookup.findOwnStatic(MethodHandles.lookup(), "rangeCheck", boolean.class, collectionType, Object.class);400}401402@SuppressWarnings("unused")403private static boolean rangeCheck(final Object array, final Object index) {404if(!(index instanceof Number)) {405return false;406}407final Number n = (Number)index;408final int intIndex = n.intValue();409if (intIndex != n.doubleValue()) {410return false;411}412return 0 <= intIndex && intIndex < Array.getLength(array);413}414415@SuppressWarnings("unused")416private static boolean rangeCheck(final List<?> list, final Object index) {417if(!(index instanceof Number)) {418return false;419}420final Number n = (Number)index;421final int intIndex = n.intValue();422if (intIndex != n.doubleValue()) {423return false;424}425return 0 <= intIndex && intIndex < list.size();426}427428@SuppressWarnings("unused")429private static void noOp() {430}431432private static final MethodHandle SET_LIST_ELEMENT = Lookup.PUBLIC.findVirtual(List.class, "set",433MethodType.methodType(Object.class, int.class, Object.class));434435private static final MethodHandle PUT_MAP_ELEMENT = Lookup.PUBLIC.findVirtual(Map.class, "put",436MethodType.methodType(Object.class, Object.class, Object.class));437438private static final MethodHandle NO_OP_1;439private static final MethodHandle NO_OP_2;440private static final MethodHandle NO_OP_3;441static {442final MethodHandle noOp = Lookup.findOwnStatic(MethodHandles.lookup(), "noOp", void.class);443NO_OP_1 = dropObjectArguments(noOp, 1);444NO_OP_2 = dropObjectArguments(noOp, 2);445NO_OP_3 = dropObjectArguments(noOp, 3);446}447448private GuardedInvocationComponent getElementSetter(final ComponentLinkRequest req) throws Exception {449final CallSiteDescriptor callSiteDescriptor = req.getDescriptor();450final Object name = req.name;451final boolean isFixedKey = name != null;452assertParameterCount(callSiteDescriptor, isFixedKey ? 2 : 3);453final LinkerServices linkerServices = req.linkerServices;454final MethodType callSiteType = callSiteDescriptor.getMethodType();455456final GuardedInvocationComponentAndCollectionType gicact = guardedInvocationComponentAndCollectionType(457callSiteType, linkerServices, MethodHandles::arrayElementSetter, SET_LIST_ELEMENT, PUT_MAP_ELEMENT);458459if(gicact == null) {460return getNextComponent(req);461}462463final boolean isMap = gicact.collectionType == CollectionType.MAP;464465// In contrast to, say, getElementGetter, we only compute the nextComponent if the target object is not a map,466// as maps will always succeed in setting the element and will never need to fall back to the next component467// operation.468final GuardedInvocationComponent nextComponent = isMap ? null : getNextComponent(req);469470final Object typedName = getTypedName(name, isMap, linkerServices);471if (typedName == INVALID_NAME) {472return nextComponent;473}474475final GuardedInvocationComponent gic = gicact.gic;476final GuardedInvocation gi = gic.getGuardedInvocation();477final Binder binder = new Binder(linkerServices, callSiteType, typedName);478final MethodHandle invocation = gi.getInvocation();479480if (isMap) {481return gic.replaceInvocation(binder.bind(invocation));482}483484return guardComponentWithRangeCheck(gicact, callSiteType, nextComponent, binder, isFixedKey ? NO_OP_2 : NO_OP_3);485}486487private static final MethodHandle REMOVE_LIST_ELEMENT = Lookup.PUBLIC.findVirtual(List.class, "remove",488MethodType.methodType(Object.class, int.class));489490private static final MethodHandle REMOVE_MAP_ELEMENT = Lookup.PUBLIC.findVirtual(Map.class, "remove",491MethodType.methodType(Object.class, Object.class));492493private GuardedInvocationComponent getElementRemover(final ComponentLinkRequest req) throws Exception {494final CallSiteDescriptor callSiteDescriptor = req.getDescriptor();495final Object name = req.name;496final boolean isFixedKey = name != null;497assertParameterCount(callSiteDescriptor, isFixedKey ? 1 : 2);498final LinkerServices linkerServices = req.linkerServices;499final MethodType callSiteType = callSiteDescriptor.getMethodType();500final GuardedInvocationComponent nextComponent = getNextComponent(req);501502final GuardedInvocationComponentAndCollectionType gicact = guardedInvocationComponentAndCollectionType(503callSiteType, linkerServices, null, REMOVE_LIST_ELEMENT, REMOVE_MAP_ELEMENT);504505if (gicact == null) {506// Can't remove elements for objects that are neither lists, nor maps.507return nextComponent;508}509510final Object typedName = getTypedName(name, gicact.collectionType == CollectionType.MAP, linkerServices);511if (typedName == INVALID_NAME) {512return nextComponent;513}514515return guardComponentWithRangeCheck(gicact, callSiteType, nextComponent,516new Binder(linkerServices, callSiteType, typedName), isFixedKey ? NO_OP_1: NO_OP_2);517}518519private static final MethodHandle GET_COLLECTION_LENGTH = Lookup.PUBLIC.findVirtual(Collection.class, "size",520MethodType.methodType(int.class));521522private static final MethodHandle GET_MAP_LENGTH = Lookup.PUBLIC.findVirtual(Map.class, "size",523MethodType.methodType(int.class));524525private static final MethodHandle GET_ARRAY_LENGTH = Lookup.PUBLIC.findStatic(Array.class, "getLength",526MethodType.methodType(int.class, Object.class));527528private static void assertParameterCount(final CallSiteDescriptor descriptor, final int paramCount) {529if(descriptor.getMethodType().parameterCount() != paramCount) {530throw new BootstrapMethodError(descriptor.getOperation() + " must have exactly " + paramCount + " parameters.");531}532}533}534535536