src/jdk/internal/dynalink/beans/BeanLinker.java

changeset 90
5a820fb11814
child 101
f8221ce53c2e
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/src/jdk/internal/dynalink/beans/BeanLinker.java	Thu Feb 14 13:22:26 2013 +0100
     1.3 @@ -0,0 +1,493 @@
     1.4 +/*
     1.5 + * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
     1.6 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
     1.7 + *
     1.8 + * This code is free software; you can redistribute it and/or modify it
     1.9 + * under the terms of the GNU General Public License version 2 only, as
    1.10 + * published by the Free Software Foundation.  Oracle designates this
    1.11 + * particular file as subject to the "Classpath" exception as provided
    1.12 + * by Oracle in the LICENSE file that accompanied this code.
    1.13 + *
    1.14 + * This code is distributed in the hope that it will be useful, but WITHOUT
    1.15 + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
    1.16 + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
    1.17 + * version 2 for more details (a copy is included in the LICENSE file that
    1.18 + * accompanied this code).
    1.19 + *
    1.20 + * You should have received a copy of the GNU General Public License version
    1.21 + * 2 along with this work; if not, write to the Free Software Foundation,
    1.22 + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
    1.23 + *
    1.24 + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
    1.25 + * or visit www.oracle.com if you need additional information or have any
    1.26 + * questions.
    1.27 + */
    1.28 +
    1.29 +/*
    1.30 + * This file is available under and governed by the GNU General Public
    1.31 + * License version 2 only, as published by the Free Software Foundation.
    1.32 + * However, the following notice accompanied the original version of this
    1.33 + * file, and Oracle licenses the original version of this file under the BSD
    1.34 + * license:
    1.35 + */
    1.36 +/*
    1.37 +   Copyright 2009-2013 Attila Szegedi
    1.38 +
    1.39 +   Licensed under both the Apache License, Version 2.0 (the "Apache License")
    1.40 +   and the BSD License (the "BSD License"), with licensee being free to
    1.41 +   choose either of the two at their discretion.
    1.42 +
    1.43 +   You may not use this file except in compliance with either the Apache
    1.44 +   License or the BSD License.
    1.45 +
    1.46 +   If you choose to use this file in compliance with the Apache License, the
    1.47 +   following notice applies to you:
    1.48 +
    1.49 +       You may obtain a copy of the Apache License at
    1.50 +
    1.51 +           http://www.apache.org/licenses/LICENSE-2.0
    1.52 +
    1.53 +       Unless required by applicable law or agreed to in writing, software
    1.54 +       distributed under the License is distributed on an "AS IS" BASIS,
    1.55 +       WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
    1.56 +       implied. See the License for the specific language governing
    1.57 +       permissions and limitations under the License.
    1.58 +
    1.59 +   If you choose to use this file in compliance with the BSD License, the
    1.60 +   following notice applies to you:
    1.61 +
    1.62 +       Redistribution and use in source and binary forms, with or without
    1.63 +       modification, are permitted provided that the following conditions are
    1.64 +       met:
    1.65 +       * Redistributions of source code must retain the above copyright
    1.66 +         notice, this list of conditions and the following disclaimer.
    1.67 +       * Redistributions in binary form must reproduce the above copyright
    1.68 +         notice, this list of conditions and the following disclaimer in the
    1.69 +         documentation and/or other materials provided with the distribution.
    1.70 +       * Neither the name of the copyright holder nor the names of
    1.71 +         contributors may be used to endorse or promote products derived from
    1.72 +         this software without specific prior written permission.
    1.73 +
    1.74 +       THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
    1.75 +       IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
    1.76 +       TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
    1.77 +       PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL COPYRIGHT HOLDER
    1.78 +       BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
    1.79 +       CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
    1.80 +       SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
    1.81 +       BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
    1.82 +       WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
    1.83 +       OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
    1.84 +       ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    1.85 +*/
    1.86 +
    1.87 +package jdk.internal.dynalink.beans;
    1.88 +
    1.89 +import java.lang.invoke.MethodHandle;
    1.90 +import java.lang.invoke.MethodHandles;
    1.91 +import java.lang.invoke.MethodType;
    1.92 +import java.lang.reflect.Array;
    1.93 +import java.util.Collection;
    1.94 +import java.util.List;
    1.95 +import java.util.Map;
    1.96 +import jdk.internal.dynalink.CallSiteDescriptor;
    1.97 +import jdk.internal.dynalink.beans.GuardedInvocationComponent.ValidationType;
    1.98 +import jdk.internal.dynalink.linker.GuardedInvocation;
    1.99 +import jdk.internal.dynalink.linker.LinkerServices;
   1.100 +import jdk.internal.dynalink.linker.TypeBasedGuardingDynamicLinker;
   1.101 +import jdk.internal.dynalink.support.Guards;
   1.102 +import jdk.internal.dynalink.support.Lookup;
   1.103 +import jdk.internal.dynalink.support.TypeUtilities;
   1.104 +
   1.105 +
   1.106 +/**
   1.107 + * A class that provides linking capabilities for a single POJO class. Normally not used directly, but managed by
   1.108 + * {@link BeansLinker}.
   1.109 + *
   1.110 + * @author Attila Szegedi
   1.111 + */
   1.112 +class BeanLinker extends AbstractJavaLinker implements TypeBasedGuardingDynamicLinker {
   1.113 +    BeanLinker(Class<?> clazz) {
   1.114 +        super(clazz, Guards.getClassGuard(clazz), Guards.getInstanceOfGuard(clazz));
   1.115 +        if(clazz.isArray()) {
   1.116 +            // Some languages won't have a notion of manipulating collections. Exposing "length" on arrays as an
   1.117 +            // explicit property is beneficial for them.
   1.118 +            // REVISIT: is it maybe a code smell that "dyn:getLength" is not needed?
   1.119 +            setPropertyGetter("length", GET_ARRAY_LENGTH, ValidationType.IS_ARRAY);
   1.120 +        }
   1.121 +    }
   1.122 +
   1.123 +    @Override
   1.124 +    public boolean canLinkType(Class<?> type) {
   1.125 +        return type == clazz;
   1.126 +    }
   1.127 +
   1.128 +    @Override
   1.129 +    FacetIntrospector createFacetIntrospector() {
   1.130 +        return new BeanIntrospector(clazz);
   1.131 +    }
   1.132 +
   1.133 +    @Override
   1.134 +    protected GuardedInvocationComponent getGuardedInvocationComponent(CallSiteDescriptor callSiteDescriptor,
   1.135 +            LinkerServices linkerServices, List<String> operations) throws Exception {
   1.136 +        final GuardedInvocationComponent superGic = super.getGuardedInvocationComponent(callSiteDescriptor,
   1.137 +                linkerServices, operations);
   1.138 +        if(superGic != null) {
   1.139 +            return superGic;
   1.140 +        }
   1.141 +        if(operations.isEmpty()) {
   1.142 +            return null;
   1.143 +        }
   1.144 +        final String op = operations.get(0);
   1.145 +        // dyn:getElem(this, id)
   1.146 +        // id is typically either an int (for arrays and lists) or an object (for maps). linkerServices can provide
   1.147 +        // conversion from call site argument type though.
   1.148 +        if("getElem".equals(op)) {
   1.149 +            return getElementGetter(callSiteDescriptor, linkerServices, pop(operations));
   1.150 +        }
   1.151 +        if("setElem".equals(op)) {
   1.152 +            return getElementSetter(callSiteDescriptor, linkerServices, pop(operations));
   1.153 +        }
   1.154 +        // dyn:getLength(this) (works on Java arrays, collections, and maps)
   1.155 +        if("getLength".equals(op)) {
   1.156 +            return getLengthGetter(callSiteDescriptor);
   1.157 +        }
   1.158 +        return null;
   1.159 +    }
   1.160 +
   1.161 +    private static MethodHandle GET_LIST_ELEMENT = Lookup.PUBLIC.findVirtual(List.class, "get",
   1.162 +            MethodType.methodType(Object.class, int.class));
   1.163 +
   1.164 +    private static MethodHandle GET_MAP_ELEMENT = Lookup.PUBLIC.findVirtual(Map.class, "get",
   1.165 +            MethodType.methodType(Object.class, Object.class));
   1.166 +
   1.167 +    private static MethodHandle LIST_GUARD = Guards.getInstanceOfGuard(List.class);
   1.168 +    private static MethodHandle MAP_GUARD = Guards.getInstanceOfGuard(Map.class);
   1.169 +
   1.170 +    private GuardedInvocationComponent getElementGetter(final CallSiteDescriptor callSiteDescriptor,
   1.171 +            final LinkerServices linkerServices, List<String> operations) throws Exception {
   1.172 +        final MethodType callSiteType = callSiteDescriptor.getMethodType();
   1.173 +        final Class<?> declaredType = callSiteType.parameterType(0);
   1.174 +        final GuardedInvocationComponent nextComponent = getGuardedInvocationComponent(callSiteDescriptor,
   1.175 +                linkerServices, operations);
   1.176 +
   1.177 +        // If declared type of receiver at the call site is already an array, a list or map, bind without guard. Thing
   1.178 +        // is, it'd be quite stupid of a call site creator to go though invokedynamic when it knows in advance they're
   1.179 +        // dealing with an array, or a list or map, but hey...
   1.180 +        // Note that for arrays and lists, using LinkerServices.asType() will ensure that any language specific linkers
   1.181 +        // in use will get a chance to perform any (if there's any) implicit conversion to integer for the indices.
   1.182 +        final GuardedInvocationComponent gic;;
   1.183 +        final boolean isMap;
   1.184 +        if(declaredType.isArray()) {
   1.185 +            gic = new GuardedInvocationComponent(MethodHandles.arrayElementGetter(declaredType));
   1.186 +            isMap = false;
   1.187 +        } else if(List.class.isAssignableFrom(declaredType)) {
   1.188 +            gic = new GuardedInvocationComponent(GET_LIST_ELEMENT);
   1.189 +            isMap = false;
   1.190 +        } else if(Map.class.isAssignableFrom(declaredType)) {
   1.191 +            gic = new GuardedInvocationComponent(GET_MAP_ELEMENT);
   1.192 +            isMap = true;
   1.193 +        } else if(clazz.isArray()) {
   1.194 +            gic = getClassGuardedInvocationComponent(MethodHandles.arrayElementGetter(clazz), callSiteType);
   1.195 +            isMap = false;
   1.196 +        } else if(List.class.isAssignableFrom(clazz)) {
   1.197 +            gic = new GuardedInvocationComponent(GET_LIST_ELEMENT, Guards.asType(LIST_GUARD, callSiteType), List.class,
   1.198 +                    ValidationType.INSTANCE_OF);
   1.199 +            isMap = false;
   1.200 +        } else if(Map.class.isAssignableFrom(clazz)) {
   1.201 +            gic = new GuardedInvocationComponent(GET_MAP_ELEMENT, Guards.asType(MAP_GUARD, callSiteType), Map.class,
   1.202 +                    ValidationType.INSTANCE_OF);
   1.203 +            isMap = true;
   1.204 +        } else {
   1.205 +            // Can't retrieve elements for objects that are neither arrays, nor list, nor maps.
   1.206 +            return nextComponent;
   1.207 +        }
   1.208 +
   1.209 +        // We can have "dyn:getElem:foo", especially in composites, i.e. "dyn:getElem|getProp|getMethod:foo"
   1.210 +        final String fixedKey = getFixedKey(callSiteDescriptor);
   1.211 +        // Convert the key to a number if we're working with a list or array
   1.212 +        final Object typedFixedKey;
   1.213 +        if(!isMap && fixedKey != null) {
   1.214 +            typedFixedKey = convertKeyToInteger(fixedKey, linkerServices);
   1.215 +            if(typedFixedKey == null) {
   1.216 +                // key is not numeric, it can never succeed
   1.217 +                return nextComponent;
   1.218 +            }
   1.219 +        } else {
   1.220 +            typedFixedKey = fixedKey;
   1.221 +        }
   1.222 +
   1.223 +        final GuardedInvocation gi = gic.getGuardedInvocation();
   1.224 +        final Binder binder = new Binder(linkerServices, callSiteType, typedFixedKey);
   1.225 +        final MethodHandle invocation = gi.getInvocation();
   1.226 +
   1.227 +        if(nextComponent == null) {
   1.228 +            return gic.replaceInvocation(binder.bind(invocation));
   1.229 +        } else {
   1.230 +            final MethodHandle checkGuard;
   1.231 +            if(invocation == GET_LIST_ELEMENT) {
   1.232 +                checkGuard = convertArgToInt(RANGE_CHECK_LIST, linkerServices, callSiteDescriptor);
   1.233 +            } else if(invocation == GET_MAP_ELEMENT) {
   1.234 +                // TODO: A more complex solution could be devised for maps, one where we do a get() first, and fold it
   1.235 +                // into a GWT that tests if it returned null, and if it did, do another GWT with containsKey()
   1.236 +                // that returns constant null (on true), or falls back to next component (on false)
   1.237 +                checkGuard = CONTAINS_MAP;
   1.238 +            } else {
   1.239 +                checkGuard = convertArgToInt(RANGE_CHECK_ARRAY, linkerServices, callSiteDescriptor);
   1.240 +            }
   1.241 +            return nextComponent.compose(MethodHandles.guardWithTest(binder.bindTest(checkGuard),
   1.242 +                    binder.bind(invocation), nextComponent.getGuardedInvocation().getInvocation()), gi.getGuard(),
   1.243 +                    gic.getValidatorClass(), gic.getValidationType());
   1.244 +        }
   1.245 +    }
   1.246 +
   1.247 +    private static String getFixedKey(final CallSiteDescriptor callSiteDescriptor) {
   1.248 +        return callSiteDescriptor.getNameTokenCount() == 2 ? null : callSiteDescriptor.getNameToken(
   1.249 +                CallSiteDescriptor.NAME_OPERAND);
   1.250 +    }
   1.251 +
   1.252 +    private static Object convertKeyToInteger(String fixedKey, LinkerServices linkerServices) throws Exception {
   1.253 +        try {
   1.254 +            if(linkerServices.canConvert(String.class, Number.class)) {
   1.255 +                try {
   1.256 +                    final Object val = linkerServices.getTypeConverter(String.class, Number.class).invoke(fixedKey);
   1.257 +                    if(!(val instanceof Number)) {
   1.258 +                        return null; // not a number
   1.259 +                    }
   1.260 +                    final Number n = (Number)val;
   1.261 +                    if(n instanceof Integer) {
   1.262 +                        return n;
   1.263 +                    }
   1.264 +                    final int intIndex = n.intValue();
   1.265 +                    final double doubleValue = n.doubleValue();
   1.266 +                    if(intIndex != doubleValue && !Double.isInfinite(doubleValue)) { // let infinites trigger IOOBE
   1.267 +                        return null; // not an exact integer
   1.268 +                    }
   1.269 +                    return Integer.valueOf(intIndex);
   1.270 +                } catch(Exception|Error e) {
   1.271 +                    throw e;
   1.272 +                } catch(Throwable t) {
   1.273 +                    throw new RuntimeException(t);
   1.274 +                }
   1.275 +            }
   1.276 +            return Integer.valueOf(fixedKey);
   1.277 +        } catch(NumberFormatException e) {
   1.278 +            // key is not a number
   1.279 +            return null;
   1.280 +        }
   1.281 +    }
   1.282 +
   1.283 +    private static MethodHandle convertArgToInt(MethodHandle mh, LinkerServices ls, CallSiteDescriptor desc) {
   1.284 +        final Class<?> sourceType = desc.getMethodType().parameterType(1);
   1.285 +        if(TypeUtilities.isMethodInvocationConvertible(sourceType, Number.class)) {
   1.286 +            return mh;
   1.287 +        } else if(ls.canConvert(sourceType, Number.class)) {
   1.288 +            final MethodHandle converter = ls.getTypeConverter(sourceType, Number.class);
   1.289 +            return MethodHandles.filterArguments(mh, 1, converter.asType(converter.type().changeReturnType(
   1.290 +                    mh.type().parameterType(1))));
   1.291 +        }
   1.292 +        return mh;
   1.293 +    }
   1.294 +
   1.295 +    /**
   1.296 +     * Contains methods to adapt an item getter/setter method handle to the requested type, optionally binding it to a
   1.297 +     * fixed key first.
   1.298 +     * @author Attila Szegedi
   1.299 +     * @version $Id: $
   1.300 +     */
   1.301 +    private static class Binder {
   1.302 +        private final LinkerServices linkerServices;
   1.303 +        private final MethodType methodType;
   1.304 +        private final Object fixedKey;
   1.305 +
   1.306 +        Binder(LinkerServices linkerServices, MethodType methodType, Object fixedKey) {
   1.307 +            this.linkerServices = linkerServices;
   1.308 +            this.methodType = fixedKey == null ? methodType : methodType.insertParameterTypes(1, fixedKey.getClass());
   1.309 +            this.fixedKey = fixedKey;
   1.310 +        }
   1.311 +
   1.312 +        /*private*/ MethodHandle bind(MethodHandle handle) {
   1.313 +            return bindToFixedKey(linkerServices.asType(handle, methodType));
   1.314 +        }
   1.315 +
   1.316 +        /*private*/ MethodHandle bindTest(MethodHandle handle) {
   1.317 +            return bindToFixedKey(Guards.asType(handle, methodType));
   1.318 +        }
   1.319 +
   1.320 +        private MethodHandle bindToFixedKey(MethodHandle handle) {
   1.321 +            return fixedKey == null ? handle : MethodHandles.insertArguments(handle, 1, fixedKey);
   1.322 +        }
   1.323 +    }
   1.324 +
   1.325 +    private static MethodHandle RANGE_CHECK_ARRAY = findRangeCheck(Object.class);
   1.326 +    private static MethodHandle RANGE_CHECK_LIST = findRangeCheck(List.class);
   1.327 +    private static MethodHandle CONTAINS_MAP = Lookup.PUBLIC.findVirtual(Map.class, "containsKey",
   1.328 +            MethodType.methodType(boolean.class, Object.class));
   1.329 +
   1.330 +    private static MethodHandle findRangeCheck(Class<?> collectionType) {
   1.331 +        return Lookup.findOwnStatic(MethodHandles.lookup(), "rangeCheck", boolean.class, collectionType, Object.class);
   1.332 +    }
   1.333 +
   1.334 +    @SuppressWarnings("unused")
   1.335 +    private static final boolean rangeCheck(Object array, Object index) {
   1.336 +        if(!(index instanceof Number)) {
   1.337 +            return false;
   1.338 +        }
   1.339 +        final Number n = (Number)index;
   1.340 +        final int intIndex = n.intValue();
   1.341 +        final double doubleValue = n.doubleValue();
   1.342 +        if(intIndex != doubleValue && !Double.isInfinite(doubleValue)) { // let infinite trigger IOOBE
   1.343 +            return false;
   1.344 +        }
   1.345 +        if(0 <= intIndex && intIndex < Array.getLength(array)) {
   1.346 +            return true;
   1.347 +        }
   1.348 +        throw new ArrayIndexOutOfBoundsException("Array index out of range: " + n);
   1.349 +    }
   1.350 +
   1.351 +    @SuppressWarnings("unused")
   1.352 +    private static final boolean rangeCheck(List<?> list, Object index) {
   1.353 +        if(!(index instanceof Number)) {
   1.354 +            return false;
   1.355 +        }
   1.356 +        final Number n = (Number)index;
   1.357 +        final int intIndex = n.intValue();
   1.358 +        final double doubleValue = n.doubleValue();
   1.359 +        if(intIndex != doubleValue && !Double.isInfinite(doubleValue)) { // let infinite trigger IOOBE
   1.360 +            return false;
   1.361 +        }
   1.362 +        if(0 <= intIndex && intIndex < list.size()) {
   1.363 +            return true;
   1.364 +        }
   1.365 +        throw new IndexOutOfBoundsException("Index: " + n + ", Size: " + list.size());
   1.366 +    }
   1.367 +
   1.368 +    private static MethodHandle SET_LIST_ELEMENT = Lookup.PUBLIC.findVirtual(List.class, "set",
   1.369 +            MethodType.methodType(Object.class, int.class, Object.class));
   1.370 +
   1.371 +    private static MethodHandle PUT_MAP_ELEMENT = Lookup.PUBLIC.findVirtual(Map.class, "put",
   1.372 +            MethodType.methodType(Object.class, Object.class, Object.class));
   1.373 +
   1.374 +    private GuardedInvocationComponent getElementSetter(CallSiteDescriptor callSiteDescriptor,
   1.375 +            LinkerServices linkerServices, List<String> operations) throws Exception {
   1.376 +        final MethodType callSiteType = callSiteDescriptor.getMethodType();
   1.377 +        final Class<?> declaredType = callSiteType.parameterType(0);
   1.378 +
   1.379 +        final GuardedInvocationComponent gic;
   1.380 +        // If declared type of receiver at the call site is already an array, a list or map, bind without guard. Thing
   1.381 +        // is, it'd be quite stupid of a call site creator to go though invokedynamic when it knows in advance they're
   1.382 +        // dealing with an array, or a list or map, but hey...
   1.383 +        // Note that for arrays and lists, using LinkerServices.asType() will ensure that any language specific linkers
   1.384 +        // in use will get a chance to perform any (if there's any) implicit conversion to integer for the indices.
   1.385 +        final boolean isMap;
   1.386 +        if(declaredType.isArray()) {
   1.387 +            gic = new GuardedInvocationComponent(MethodHandles.arrayElementSetter(declaredType));
   1.388 +            isMap = false;
   1.389 +        } else if(List.class.isAssignableFrom(declaredType)) {
   1.390 +            gic = new GuardedInvocationComponent(SET_LIST_ELEMENT);
   1.391 +            isMap = false;
   1.392 +        } else if(Map.class.isAssignableFrom(declaredType)) {
   1.393 +            gic = new GuardedInvocationComponent(PUT_MAP_ELEMENT);
   1.394 +            isMap = true;
   1.395 +        } else if(clazz.isArray()) {
   1.396 +            gic = getClassGuardedInvocationComponent(MethodHandles.arrayElementSetter(clazz), callSiteType);
   1.397 +            isMap = false;
   1.398 +        } else if(List.class.isAssignableFrom(clazz)) {
   1.399 +            gic = new GuardedInvocationComponent(SET_LIST_ELEMENT, Guards.asType(LIST_GUARD, callSiteType), List.class,
   1.400 +                    ValidationType.INSTANCE_OF);
   1.401 +            isMap = false;
   1.402 +        } else if(Map.class.isAssignableFrom(clazz)) {
   1.403 +            gic = new GuardedInvocationComponent(PUT_MAP_ELEMENT, Guards.asType(MAP_GUARD, callSiteType), Map.class,
   1.404 +                    ValidationType.INSTANCE_OF);
   1.405 +            isMap = true;
   1.406 +        } else {
   1.407 +            // Can't set elements for objects that are neither arrays, nor list, nor maps.
   1.408 +            gic = null;
   1.409 +            isMap = false;
   1.410 +        }
   1.411 +
   1.412 +        // In contrast to, say, getElementGetter, we only compute the nextComponent if the target object is not a map,
   1.413 +        // as maps will always succeed in setting the element and will never need to fall back to the next component
   1.414 +        // operation.
   1.415 +        final GuardedInvocationComponent nextComponent = isMap ? null : getGuardedInvocationComponent(
   1.416 +                callSiteDescriptor, linkerServices, operations);
   1.417 +        if(gic == null) {
   1.418 +            return nextComponent;
   1.419 +        }
   1.420 +
   1.421 +        // We can have "dyn:setElem:foo", especially in composites, i.e. "dyn:setElem|setProp:foo"
   1.422 +        final String fixedKey = getFixedKey(callSiteDescriptor);
   1.423 +        // Convert the key to a number if we're working with a list or array
   1.424 +        final Object typedFixedKey;
   1.425 +        if(!isMap && fixedKey != null) {
   1.426 +            typedFixedKey = convertKeyToInteger(fixedKey, linkerServices);
   1.427 +            if(typedFixedKey == null) {
   1.428 +                // key is not numeric, it can never succeed
   1.429 +                return nextComponent;
   1.430 +            }
   1.431 +        } else {
   1.432 +            typedFixedKey = fixedKey;
   1.433 +        }
   1.434 +
   1.435 +        final GuardedInvocation gi = gic.getGuardedInvocation();
   1.436 +        final Binder binder = new Binder(linkerServices, callSiteType, typedFixedKey);
   1.437 +        final MethodHandle invocation = gi.getInvocation();
   1.438 +
   1.439 +        if(nextComponent == null) {
   1.440 +            return gic.replaceInvocation(binder.bind(invocation));
   1.441 +        } else {
   1.442 +            final MethodHandle checkGuard = convertArgToInt(invocation == SET_LIST_ELEMENT ? RANGE_CHECK_LIST :
   1.443 +                RANGE_CHECK_ARRAY, linkerServices, callSiteDescriptor);
   1.444 +            return nextComponent.compose(MethodHandles.guardWithTest(binder.bindTest(checkGuard),
   1.445 +                    binder.bind(invocation), nextComponent.getGuardedInvocation().getInvocation()), gi.getGuard(),
   1.446 +                    gic.getValidatorClass(), gic.getValidationType());
   1.447 +        }
   1.448 +    }
   1.449 +
   1.450 +    private static MethodHandle GET_ARRAY_LENGTH = Lookup.PUBLIC.findStatic(Array.class, "getLength",
   1.451 +            MethodType.methodType(int.class, Object.class));
   1.452 +
   1.453 +    private static MethodHandle GET_COLLECTION_LENGTH = Lookup.PUBLIC.findVirtual(Collection.class, "size",
   1.454 +            MethodType.methodType(int.class));
   1.455 +
   1.456 +    private static MethodHandle GET_MAP_LENGTH = Lookup.PUBLIC.findVirtual(Map.class, "size",
   1.457 +            MethodType.methodType(int.class));
   1.458 +
   1.459 +    private static MethodHandle COLLECTION_GUARD = Guards.getInstanceOfGuard(Collection.class);
   1.460 +
   1.461 +    private GuardedInvocationComponent getLengthGetter(CallSiteDescriptor callSiteDescriptor) {
   1.462 +        assertParameterCount(callSiteDescriptor, 1);
   1.463 +        final MethodType callSiteType = callSiteDescriptor.getMethodType();
   1.464 +        final Class<?> declaredType = callSiteType.parameterType(0);
   1.465 +        // If declared type of receiver at the call site is already an array, collection, or map, bind without guard.
   1.466 +        // Thing is, it'd be quite stupid of a call site creator to go though invokedynamic when it knows in advance
   1.467 +        // they're dealing with an array, collection, or map, but hey...
   1.468 +        if(declaredType.isArray()) {
   1.469 +            return new GuardedInvocationComponent(GET_ARRAY_LENGTH.asType(callSiteType));
   1.470 +        } else if(Collection.class.isAssignableFrom(declaredType)) {
   1.471 +            return new GuardedInvocationComponent(GET_COLLECTION_LENGTH.asType(callSiteType));
   1.472 +        } else if(Map.class.isAssignableFrom(declaredType)) {
   1.473 +            return new GuardedInvocationComponent(GET_MAP_LENGTH.asType(callSiteType));
   1.474 +        }
   1.475 +
   1.476 +        // Otherwise, create a binding based on the actual type of the argument with an appropriate guard.
   1.477 +        if(clazz.isArray()) {
   1.478 +            return new GuardedInvocationComponent(GET_ARRAY_LENGTH.asType(callSiteType), Guards.isArray(0,
   1.479 +                    callSiteType), ValidationType.IS_ARRAY);
   1.480 +        } if(Collection.class.isAssignableFrom(clazz)) {
   1.481 +            return new GuardedInvocationComponent(GET_COLLECTION_LENGTH.asType(callSiteType), Guards.asType(
   1.482 +                    COLLECTION_GUARD, callSiteType), Collection.class, ValidationType.INSTANCE_OF);
   1.483 +        } if(Map.class.isAssignableFrom(clazz)) {
   1.484 +            return new GuardedInvocationComponent(GET_MAP_LENGTH.asType(callSiteType), Guards.asType(MAP_GUARD,
   1.485 +                    callSiteType), Map.class, ValidationType.INSTANCE_OF);
   1.486 +        }
   1.487 +        // Can't retrieve length for objects that are neither arrays, nor collections, nor maps.
   1.488 +        return null;
   1.489 +    }
   1.490 +
   1.491 +    private static void assertParameterCount(CallSiteDescriptor descriptor, int paramCount) {
   1.492 +        if(descriptor.getMethodType().parameterCount() != paramCount) {
   1.493 +            throw new BootstrapMethodError(descriptor.getName() + " must have exactly " + paramCount + " parameters.");
   1.494 +        }
   1.495 +    }
   1.496 +}
   1.497 \ No newline at end of file

mercurial