Path: blob/aarch64-shenandoah-jdk8u272-b10/nashorn/src/jdk/internal/dynalink/beans/BeanLinker.java
48549 views
/*1* Copyright (c) 2010, 2013, 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 Szegedi3435Licensed under both the Apache License, Version 2.0 (the "Apache License")36and the BSD License (the "BSD License"), with licensee being free to37choose either of the two at their discretion.3839You may not use this file except in compliance with either the Apache40License or the BSD License.4142If you choose to use this file in compliance with the Apache License, the43following notice applies to you:4445You may obtain a copy of the Apache License at4647http://www.apache.org/licenses/LICENSE-2.04849Unless required by applicable law or agreed to in writing, software50distributed under the License is distributed on an "AS IS" BASIS,51WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or52implied. See the License for the specific language governing53permissions and limitations under the License.5455If you choose to use this file in compliance with the BSD License, the56following notice applies to you:5758Redistribution and use in source and binary forms, with or without59modification, are permitted provided that the following conditions are60met:61* Redistributions of source code must retain the above copyright62notice, this list of conditions and the following disclaimer.63* Redistributions in binary form must reproduce the above copyright64notice, this list of conditions and the following disclaimer in the65documentation and/or other materials provided with the distribution.66* Neither the name of the copyright holder nor the names of67contributors may be used to endorse or promote products derived from68this software without specific prior written permission.6970THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS71IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED72TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A73PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL COPYRIGHT HOLDER74BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR75CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF76SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR77BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,78WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR79OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF80ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.81*/8283package jdk.internal.dynalink.beans;8485import java.lang.invoke.MethodHandle;86import java.lang.invoke.MethodHandles;87import java.lang.invoke.MethodType;88import java.lang.reflect.Array;89import java.util.Collection;90import java.util.List;91import java.util.Map;92import jdk.internal.dynalink.CallSiteDescriptor;93import jdk.internal.dynalink.beans.GuardedInvocationComponent.ValidationType;94import jdk.internal.dynalink.linker.GuardedInvocation;95import jdk.internal.dynalink.linker.LinkerServices;96import jdk.internal.dynalink.linker.TypeBasedGuardingDynamicLinker;97import jdk.internal.dynalink.support.Guards;98import jdk.internal.dynalink.support.Lookup;99import jdk.internal.dynalink.support.TypeUtilities;100101/**102* A class that provides linking capabilities for a single POJO class. Normally not used directly, but managed by103* {@link BeansLinker}.104*105* @author Attila Szegedi106*/107class BeanLinker extends AbstractJavaLinker implements TypeBasedGuardingDynamicLinker {108BeanLinker(final Class<?> clazz) {109super(clazz, Guards.getClassGuard(clazz), Guards.getInstanceOfGuard(clazz));110if(clazz.isArray()) {111// Some languages won't have a notion of manipulating collections. Exposing "length" on arrays as an112// explicit property is beneficial for them.113// REVISIT: is it maybe a code smell that "dyn:getLength" is not needed?114setPropertyGetter("length", GET_ARRAY_LENGTH, ValidationType.IS_ARRAY);115} else if(List.class.isAssignableFrom(clazz)) {116setPropertyGetter("length", GET_COLLECTION_LENGTH, ValidationType.INSTANCE_OF);117}118}119120@Override121public boolean canLinkType(final Class<?> type) {122return type == clazz;123}124125@Override126FacetIntrospector createFacetIntrospector() {127return new BeanIntrospector(clazz);128}129130@Override131protected GuardedInvocationComponent getGuardedInvocationComponent(final CallSiteDescriptor callSiteDescriptor,132final LinkerServices linkerServices, final List<String> operations) throws Exception {133final GuardedInvocationComponent superGic = super.getGuardedInvocationComponent(callSiteDescriptor,134linkerServices, operations);135if(superGic != null) {136return superGic;137}138if(operations.isEmpty()) {139return null;140}141final String op = operations.get(0);142// dyn:getElem(this, id)143// id is typically either an int (for arrays and lists) or an object (for maps). linkerServices can provide144// conversion from call site argument type though.145if("getElem".equals(op)) {146return getElementGetter(callSiteDescriptor, linkerServices, pop(operations));147}148if("setElem".equals(op)) {149return getElementSetter(callSiteDescriptor, linkerServices, pop(operations));150}151// dyn:getLength(this) (works on Java arrays, collections, and maps)152if("getLength".equals(op)) {153return getLengthGetter(callSiteDescriptor);154}155return null;156}157158private static MethodHandle GET_LIST_ELEMENT = Lookup.PUBLIC.findVirtual(List.class, "get",159MethodType.methodType(Object.class, int.class));160161private static MethodHandle GET_MAP_ELEMENT = Lookup.PUBLIC.findVirtual(Map.class, "get",162MethodType.methodType(Object.class, Object.class));163164private static MethodHandle LIST_GUARD = Guards.getInstanceOfGuard(List.class);165private static MethodHandle MAP_GUARD = Guards.getInstanceOfGuard(Map.class);166167private enum CollectionType {168ARRAY, LIST, MAP169};170171private GuardedInvocationComponent getElementGetter(final CallSiteDescriptor callSiteDescriptor,172final LinkerServices linkerServices, final List<String> operations) throws Exception {173final MethodType callSiteType = callSiteDescriptor.getMethodType();174final Class<?> declaredType = callSiteType.parameterType(0);175final GuardedInvocationComponent nextComponent = getGuardedInvocationComponent(callSiteDescriptor,176linkerServices, operations);177178// If declared type of receiver at the call site is already an array, a list or map, bind without guard. Thing179// is, it'd be quite stupid of a call site creator to go though invokedynamic when it knows in advance they're180// dealing with an array, or a list or map, but hey...181// Note that for arrays and lists, using LinkerServices.asType() will ensure that any language specific linkers182// in use will get a chance to perform any (if there's any) implicit conversion to integer for the indices.183final GuardedInvocationComponent gic;184final CollectionType collectionType;185if(declaredType.isArray()) {186gic = createInternalFilteredGuardedInvocationComponent(MethodHandles.arrayElementGetter(declaredType), linkerServices);187collectionType = CollectionType.ARRAY;188} else if(List.class.isAssignableFrom(declaredType)) {189gic = createInternalFilteredGuardedInvocationComponent(GET_LIST_ELEMENT, linkerServices);190collectionType = CollectionType.LIST;191} else if(Map.class.isAssignableFrom(declaredType)) {192gic = createInternalFilteredGuardedInvocationComponent(GET_MAP_ELEMENT, linkerServices);193collectionType = CollectionType.MAP;194} else if(clazz.isArray()) {195gic = getClassGuardedInvocationComponent(linkerServices.filterInternalObjects(MethodHandles.arrayElementGetter(clazz)), callSiteType);196collectionType = CollectionType.ARRAY;197} else if(List.class.isAssignableFrom(clazz)) {198gic = createInternalFilteredGuardedInvocationComponent(GET_LIST_ELEMENT, Guards.asType(LIST_GUARD, callSiteType), List.class, ValidationType.INSTANCE_OF,199linkerServices);200collectionType = CollectionType.LIST;201} else if(Map.class.isAssignableFrom(clazz)) {202gic = createInternalFilteredGuardedInvocationComponent(GET_MAP_ELEMENT, Guards.asType(MAP_GUARD, callSiteType), Map.class, ValidationType.INSTANCE_OF,203linkerServices);204collectionType = CollectionType.MAP;205} else {206// Can't retrieve elements for objects that are neither arrays, nor list, nor maps.207return nextComponent;208}209210// We can have "dyn:getElem:foo", especially in composites, i.e. "dyn:getElem|getProp|getMethod:foo"211final String fixedKey = getFixedKey(callSiteDescriptor);212// Convert the key to a number if we're working with a list or array213final Object typedFixedKey;214if(collectionType != CollectionType.MAP && fixedKey != null) {215typedFixedKey = convertKeyToInteger(fixedKey, linkerServices);216if(typedFixedKey == null) {217// key is not numeric, it can never succeed218return nextComponent;219}220} else {221typedFixedKey = fixedKey;222}223224final GuardedInvocation gi = gic.getGuardedInvocation();225final Binder binder = new Binder(linkerServices, callSiteType, typedFixedKey);226final MethodHandle invocation = gi.getInvocation();227228if(nextComponent == null) {229return gic.replaceInvocation(binder.bind(invocation));230}231232final MethodHandle checkGuard;233switch(collectionType) {234case LIST:235checkGuard = convertArgToInt(RANGE_CHECK_LIST, linkerServices, callSiteDescriptor);236break;237case MAP:238// TODO: A more complex solution could be devised for maps, one where we do a get() first, and fold it239// into a GWT that tests if it returned null, and if it did, do another GWT with containsKey()240// that returns constant null (on true), or falls back to next component (on false)241checkGuard = linkerServices.filterInternalObjects(CONTAINS_MAP);242break;243case ARRAY:244checkGuard = convertArgToInt(RANGE_CHECK_ARRAY, linkerServices, callSiteDescriptor);245break;246default:247throw new AssertionError();248}249final MethodPair matchedInvocations = matchReturnTypes(binder.bind(invocation),250nextComponent.getGuardedInvocation().getInvocation());251return nextComponent.compose(matchedInvocations.guardWithTest(binder.bindTest(checkGuard)), gi.getGuard(),252gic.getValidatorClass(), gic.getValidationType());253}254255private static GuardedInvocationComponent createInternalFilteredGuardedInvocationComponent(256final MethodHandle invocation, final LinkerServices linkerServices) {257return new GuardedInvocationComponent(linkerServices.filterInternalObjects(invocation));258}259260private static GuardedInvocationComponent createInternalFilteredGuardedInvocationComponent(261final MethodHandle invocation, final MethodHandle guard, final Class<?> validatorClass,262final ValidationType validationType, final LinkerServices linkerServices) {263return new GuardedInvocationComponent(linkerServices.filterInternalObjects(invocation), guard,264validatorClass, validationType);265}266267private static String getFixedKey(final CallSiteDescriptor callSiteDescriptor) {268return callSiteDescriptor.getNameTokenCount() == 2 ? null : callSiteDescriptor.getNameToken(269CallSiteDescriptor.NAME_OPERAND);270}271272private static Object convertKeyToInteger(final String fixedKey, final LinkerServices linkerServices) throws Exception {273try {274if(linkerServices.canConvert(String.class, Number.class)) {275try {276final Object val = linkerServices.getTypeConverter(String.class, Number.class).invoke(fixedKey);277if(!(val instanceof Number)) {278return null; // not a number279}280final Number n = (Number)val;281if(n instanceof Integer) {282return n;283}284final int intIndex = n.intValue();285final double doubleValue = n.doubleValue();286if(intIndex != doubleValue && !Double.isInfinite(doubleValue)) { // let infinites trigger IOOBE287return null; // not an exact integer288}289return Integer.valueOf(intIndex);290} catch(Exception|Error e) {291throw e;292} catch(final Throwable t) {293throw new RuntimeException(t);294}295}296return Integer.valueOf(fixedKey);297} catch(final NumberFormatException e) {298// key is not a number299return null;300}301}302303private static MethodHandle convertArgToInt(final MethodHandle mh, final LinkerServices ls, final CallSiteDescriptor desc) {304final Class<?> sourceType = desc.getMethodType().parameterType(1);305if(TypeUtilities.isMethodInvocationConvertible(sourceType, Number.class)) {306return mh;307} else if(ls.canConvert(sourceType, Number.class)) {308final MethodHandle converter = ls.getTypeConverter(sourceType, Number.class);309return MethodHandles.filterArguments(mh, 1, converter.asType(converter.type().changeReturnType(310mh.type().parameterType(1))));311}312return mh;313}314315/**316* Contains methods to adapt an item getter/setter method handle to the requested type, optionally binding it to a317* fixed key first.318* @author Attila Szegedi319* @version $Id: $320*/321private static class Binder {322private final LinkerServices linkerServices;323private final MethodType methodType;324private final Object fixedKey;325326Binder(final LinkerServices linkerServices, final MethodType methodType, final Object fixedKey) {327this.linkerServices = linkerServices;328this.methodType = fixedKey == null ? methodType : methodType.insertParameterTypes(1, fixedKey.getClass());329this.fixedKey = fixedKey;330}331332/*private*/ MethodHandle bind(final MethodHandle handle) {333return bindToFixedKey(linkerServices.asTypeLosslessReturn(handle, methodType));334}335336/*private*/ MethodHandle bindTest(final MethodHandle handle) {337return bindToFixedKey(Guards.asType(handle, methodType));338}339340private MethodHandle bindToFixedKey(final MethodHandle handle) {341return fixedKey == null ? handle : MethodHandles.insertArguments(handle, 1, fixedKey);342}343}344345private static MethodHandle RANGE_CHECK_ARRAY = findRangeCheck(Object.class);346private static MethodHandle RANGE_CHECK_LIST = findRangeCheck(List.class);347private static MethodHandle CONTAINS_MAP = Lookup.PUBLIC.findVirtual(Map.class, "containsKey",348MethodType.methodType(boolean.class, Object.class));349350private static MethodHandle findRangeCheck(final Class<?> collectionType) {351return Lookup.findOwnStatic(MethodHandles.lookup(), "rangeCheck", boolean.class, collectionType, Object.class);352}353354@SuppressWarnings("unused")355private static final boolean rangeCheck(final Object array, final Object index) {356if(!(index instanceof Number)) {357return false;358}359final Number n = (Number)index;360final int intIndex = n.intValue();361final double doubleValue = n.doubleValue();362if(intIndex != doubleValue && !Double.isInfinite(doubleValue)) { // let infinite trigger IOOBE363return false;364}365if(0 <= intIndex && intIndex < Array.getLength(array)) {366return true;367}368throw new ArrayIndexOutOfBoundsException("Array index out of range: " + n);369}370371@SuppressWarnings("unused")372private static final boolean rangeCheck(final List<?> list, final Object index) {373if(!(index instanceof Number)) {374return false;375}376final Number n = (Number)index;377final int intIndex = n.intValue();378final double doubleValue = n.doubleValue();379if(intIndex != doubleValue && !Double.isInfinite(doubleValue)) { // let infinite trigger IOOBE380return false;381}382if(0 <= intIndex && intIndex < list.size()) {383return true;384}385throw new IndexOutOfBoundsException("Index: " + n + ", Size: " + list.size());386}387388private static MethodHandle SET_LIST_ELEMENT = Lookup.PUBLIC.findVirtual(List.class, "set",389MethodType.methodType(Object.class, int.class, Object.class));390391private static MethodHandle PUT_MAP_ELEMENT = Lookup.PUBLIC.findVirtual(Map.class, "put",392MethodType.methodType(Object.class, Object.class, Object.class));393394private GuardedInvocationComponent getElementSetter(final CallSiteDescriptor callSiteDescriptor,395final LinkerServices linkerServices, final List<String> operations) throws Exception {396final MethodType callSiteType = callSiteDescriptor.getMethodType();397final Class<?> declaredType = callSiteType.parameterType(0);398399final GuardedInvocationComponent gic;400// If declared type of receiver at the call site is already an array, a list or map, bind without guard. Thing401// is, it'd be quite stupid of a call site creator to go though invokedynamic when it knows in advance they're402// dealing with an array, or a list or map, but hey...403// Note that for arrays and lists, using LinkerServices.asType() will ensure that any language specific linkers404// in use will get a chance to perform any (if there's any) implicit conversion to integer for the indices.405final CollectionType collectionType;406if(declaredType.isArray()) {407gic = createInternalFilteredGuardedInvocationComponent(MethodHandles.arrayElementSetter(declaredType), linkerServices);408collectionType = CollectionType.ARRAY;409} else if(List.class.isAssignableFrom(declaredType)) {410gic = createInternalFilteredGuardedInvocationComponent(SET_LIST_ELEMENT, linkerServices);411collectionType = CollectionType.LIST;412} else if(Map.class.isAssignableFrom(declaredType)) {413gic = createInternalFilteredGuardedInvocationComponent(PUT_MAP_ELEMENT, linkerServices);414collectionType = CollectionType.MAP;415} else if(clazz.isArray()) {416gic = getClassGuardedInvocationComponent(linkerServices.filterInternalObjects(417MethodHandles.arrayElementSetter(clazz)), callSiteType);418collectionType = CollectionType.ARRAY;419} else if(List.class.isAssignableFrom(clazz)) {420gic = createInternalFilteredGuardedInvocationComponent(SET_LIST_ELEMENT, Guards.asType(LIST_GUARD, callSiteType), List.class, ValidationType.INSTANCE_OF,421linkerServices);422collectionType = CollectionType.LIST;423} else if(Map.class.isAssignableFrom(clazz)) {424gic = createInternalFilteredGuardedInvocationComponent(PUT_MAP_ELEMENT, Guards.asType(MAP_GUARD, callSiteType),425Map.class, ValidationType.INSTANCE_OF, linkerServices);426collectionType = CollectionType.MAP;427} else {428// Can't set elements for objects that are neither arrays, nor list, nor maps.429gic = null;430collectionType = null;431}432433// In contrast to, say, getElementGetter, we only compute the nextComponent if the target object is not a map,434// as maps will always succeed in setting the element and will never need to fall back to the next component435// operation.436final GuardedInvocationComponent nextComponent = collectionType == CollectionType.MAP ? null : getGuardedInvocationComponent(437callSiteDescriptor, linkerServices, operations);438if(gic == null) {439return nextComponent;440}441442// We can have "dyn:setElem:foo", especially in composites, i.e. "dyn:setElem|setProp:foo"443final String fixedKey = getFixedKey(callSiteDescriptor);444// Convert the key to a number if we're working with a list or array445final Object typedFixedKey;446if(collectionType != CollectionType.MAP && fixedKey != null) {447typedFixedKey = convertKeyToInteger(fixedKey, linkerServices);448if(typedFixedKey == null) {449// key is not numeric, it can never succeed450return nextComponent;451}452} else {453typedFixedKey = fixedKey;454}455456final GuardedInvocation gi = gic.getGuardedInvocation();457final Binder binder = new Binder(linkerServices, callSiteType, typedFixedKey);458final MethodHandle invocation = gi.getInvocation();459460if(nextComponent == null) {461return gic.replaceInvocation(binder.bind(invocation));462}463464assert collectionType == CollectionType.LIST || collectionType == CollectionType.ARRAY;465final MethodHandle checkGuard = convertArgToInt(collectionType == CollectionType.LIST ? RANGE_CHECK_LIST :466RANGE_CHECK_ARRAY, linkerServices, callSiteDescriptor);467final MethodPair matchedInvocations = matchReturnTypes(binder.bind(invocation),468nextComponent.getGuardedInvocation().getInvocation());469return nextComponent.compose(matchedInvocations.guardWithTest(binder.bindTest(checkGuard)), gi.getGuard(),470gic.getValidatorClass(), gic.getValidationType());471}472473private static MethodHandle GET_ARRAY_LENGTH = Lookup.PUBLIC.findStatic(Array.class, "getLength",474MethodType.methodType(int.class, Object.class));475476private static MethodHandle GET_COLLECTION_LENGTH = Lookup.PUBLIC.findVirtual(Collection.class, "size",477MethodType.methodType(int.class));478479private static MethodHandle GET_MAP_LENGTH = Lookup.PUBLIC.findVirtual(Map.class, "size",480MethodType.methodType(int.class));481482private static MethodHandle COLLECTION_GUARD = Guards.getInstanceOfGuard(Collection.class);483484private GuardedInvocationComponent getLengthGetter(final CallSiteDescriptor callSiteDescriptor) {485assertParameterCount(callSiteDescriptor, 1);486final MethodType callSiteType = callSiteDescriptor.getMethodType();487final Class<?> declaredType = callSiteType.parameterType(0);488// If declared type of receiver at the call site is already an array, collection, or map, bind without guard.489// Thing is, it'd be quite stupid of a call site creator to go though invokedynamic when it knows in advance490// they're dealing with an array, collection, or map, but hey...491if(declaredType.isArray()) {492return new GuardedInvocationComponent(GET_ARRAY_LENGTH.asType(callSiteType));493} else if(Collection.class.isAssignableFrom(declaredType)) {494return new GuardedInvocationComponent(GET_COLLECTION_LENGTH.asType(callSiteType));495} else if(Map.class.isAssignableFrom(declaredType)) {496return new GuardedInvocationComponent(GET_MAP_LENGTH.asType(callSiteType));497}498499// Otherwise, create a binding based on the actual type of the argument with an appropriate guard.500if(clazz.isArray()) {501return new GuardedInvocationComponent(GET_ARRAY_LENGTH.asType(callSiteType), Guards.isArray(0,502callSiteType), ValidationType.IS_ARRAY);503} if(Collection.class.isAssignableFrom(clazz)) {504return new GuardedInvocationComponent(GET_COLLECTION_LENGTH.asType(callSiteType), Guards.asType(505COLLECTION_GUARD, callSiteType), Collection.class, ValidationType.INSTANCE_OF);506} if(Map.class.isAssignableFrom(clazz)) {507return new GuardedInvocationComponent(GET_MAP_LENGTH.asType(callSiteType), Guards.asType(MAP_GUARD,508callSiteType), Map.class, ValidationType.INSTANCE_OF);509}510// Can't retrieve length for objects that are neither arrays, nor collections, nor maps.511return null;512}513514private static void assertParameterCount(final CallSiteDescriptor descriptor, final int paramCount) {515if(descriptor.getMethodType().parameterCount() != paramCount) {516throw new BootstrapMethodError(descriptor.getName() + " must have exactly " + paramCount + " parameters.");517}518}519}520521522