1/*
2 * Copyright (c) 2010, 2016, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26/*
27 * This file is available under and governed by the GNU General Public
28 * License version 2 only, as published by the Free Software Foundation.
29 * However, the following notice accompanied the original version of this
30 * file, and Oracle licenses the original version of this file under the BSD
31 * license:
32 */
33/*
34   Copyright 2009-2013 Attila Szegedi
35
36   Licensed under both the Apache License, Version 2.0 (the "Apache License")
37   and the BSD License (the "BSD License"), with licensee being free to
38   choose either of the two at their discretion.
39
40   You may not use this file except in compliance with either the Apache
41   License or the BSD License.
42
43   If you choose to use this file in compliance with the Apache License, the
44   following notice applies to you:
45
46       You may obtain a copy of the Apache License at
47
48           http://www.apache.org/licenses/LICENSE-2.0
49
50       Unless required by applicable law or agreed to in writing, software
51       distributed under the License is distributed on an "AS IS" BASIS,
52       WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
53       implied. See the License for the specific language governing
54       permissions and limitations under the License.
55
56   If you choose to use this file in compliance with the BSD License, the
57   following notice applies to you:
58
59       Redistribution and use in source and binary forms, with or without
60       modification, are permitted provided that the following conditions are
61       met:
62       * Redistributions of source code must retain the above copyright
63         notice, this list of conditions and the following disclaimer.
64       * Redistributions in binary form must reproduce the above copyright
65         notice, this list of conditions and the following disclaimer in the
66         documentation and/or other materials provided with the distribution.
67       * Neither the name of the copyright holder nor the names of
68         contributors may be used to endorse or promote products derived from
69         this software without specific prior written permission.
70
71       THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
72       IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
73       TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
74       PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL COPYRIGHT HOLDER
75       BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
76       CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
77       SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
78       BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
79       WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
80       OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
81       ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
82*/
83
84package jdk.dynalink.beans;
85
86import java.lang.invoke.MethodHandle;
87import java.lang.invoke.MethodHandles;
88import java.lang.invoke.MethodType;
89import java.lang.reflect.Array;
90import java.util.Collection;
91import java.util.Collections;
92import java.util.List;
93import java.util.Map;
94import jdk.dynalink.CallSiteDescriptor;
95import jdk.dynalink.Namespace;
96import jdk.dynalink.Operation;
97import jdk.dynalink.StandardNamespace;
98import jdk.dynalink.StandardOperation;
99import jdk.dynalink.beans.GuardedInvocationComponent.ValidationType;
100import jdk.dynalink.linker.GuardedInvocation;
101import jdk.dynalink.linker.LinkerServices;
102import jdk.dynalink.linker.TypeBasedGuardingDynamicLinker;
103import jdk.dynalink.linker.support.Guards;
104import jdk.dynalink.linker.support.Lookup;
105import jdk.dynalink.linker.support.TypeUtilities;
106
107/**
108 * A class that provides linking capabilities for a single POJO class. Normally not used directly, but managed by
109 * {@link BeansLinker}.
110 */
111class BeanLinker extends AbstractJavaLinker implements TypeBasedGuardingDynamicLinker {
112    BeanLinker(final Class<?> clazz) {
113        super(clazz, Guards.getClassGuard(clazz), Guards.getInstanceOfGuard(clazz));
114        if(clazz.isArray()) {
115            // Some languages won't have a notion of manipulating collections. Exposing "length" on arrays as an
116            // explicit property is beneficial for them.
117            setPropertyGetter("length", MethodHandles.arrayLength(clazz), ValidationType.EXACT_CLASS);
118        } else if(Collection.class.isAssignableFrom(clazz)) {
119            setPropertyGetter("length", GET_COLLECTION_LENGTH, ValidationType.INSTANCE_OF);
120        } else if(Map.class.isAssignableFrom(clazz)) {
121            setPropertyGetter("length", GET_MAP_LENGTH, ValidationType.INSTANCE_OF);
122        }
123    }
124
125    @Override
126    public boolean canLinkType(final Class<?> type) {
127        return type == clazz;
128    }
129
130    @Override
131    FacetIntrospector createFacetIntrospector() {
132        return new BeanIntrospector(clazz);
133    }
134
135    @Override
136    protected GuardedInvocationComponent getGuardedInvocationComponent(final ComponentLinkRequest req) throws Exception {
137        final GuardedInvocationComponent superGic = super.getGuardedInvocationComponent(req);
138        if(superGic != null) {
139            return superGic;
140        }
141        if (!req.namespaces.isEmpty()) {
142            final Operation op = req.baseOperation;
143            final Namespace ns = req.namespaces.get(0);
144            if (ns == StandardNamespace.ELEMENT) {
145                if (op == StandardOperation.GET) {
146                    return getElementGetter(req.popNamespace());
147                } else if (op == StandardOperation.SET) {
148                    return getElementSetter(req.popNamespace());
149                }
150            }
151        }
152        return null;
153    }
154
155    @Override
156    SingleDynamicMethod getConstructorMethod(final String signature) {
157        return null;
158    }
159
160    private static final MethodHandle GET_LIST_ELEMENT = Lookup.PUBLIC.findVirtual(List.class, "get",
161            MethodType.methodType(Object.class, int.class));
162
163    private static final MethodHandle GET_MAP_ELEMENT = Lookup.PUBLIC.findVirtual(Map.class, "get",
164            MethodType.methodType(Object.class, Object.class));
165
166    private static final MethodHandle LIST_GUARD = Guards.getInstanceOfGuard(List.class);
167    private static final MethodHandle MAP_GUARD = Guards.getInstanceOfGuard(Map.class);
168
169    private static final MethodHandle NULL_GETTER_1;
170    private static final MethodHandle NULL_GETTER_2;
171    static {
172        final MethodHandle constantNull = MethodHandles.constant(Object.class, null);
173        NULL_GETTER_1 = dropObjectArguments(constantNull, 1);
174        NULL_GETTER_2 = dropObjectArguments(constantNull, 2);
175    }
176
177    private static MethodHandle dropObjectArguments(final MethodHandle m, final int n) {
178        return MethodHandles.dropArguments(m, 0, Collections.nCopies(n, Object.class));
179    }
180
181    private enum CollectionType {
182        ARRAY, LIST, MAP
183    };
184
185    private GuardedInvocationComponent getElementGetter(final ComponentLinkRequest req) throws Exception {
186        final CallSiteDescriptor callSiteDescriptor = req.getDescriptor();
187        final Object name = req.name;
188        final boolean isFixedKey = name != null;
189        assertParameterCount(callSiteDescriptor, isFixedKey ? 1 : 2);
190        final LinkerServices linkerServices = req.linkerServices;
191        final MethodType callSiteType = callSiteDescriptor.getMethodType();
192        final Class<?> declaredType = callSiteType.parameterType(0);
193        final GuardedInvocationComponent nextComponent = getNextComponent(req);
194
195        // If declared type of receiver at the call site is already an array, a list or map, bind without guard. Thing
196        // is, it'd be quite stupid of a call site creator to go though invokedynamic when it knows in advance they're
197        // dealing with an array, or a list or map, but hey...
198        // Note that for arrays and lists, using LinkerServices.asType() will ensure that any language specific linkers
199        // in use will get a chance to perform any (if there's any) implicit conversion to integer for the indices.
200        final GuardedInvocationComponent gic;
201        final CollectionType collectionType;
202        if(declaredType.isArray()) {
203            gic = createInternalFilteredGuardedInvocationComponent(MethodHandles.arrayElementGetter(declaredType), linkerServices);
204            collectionType = CollectionType.ARRAY;
205        } else if(List.class.isAssignableFrom(declaredType)) {
206            gic = createInternalFilteredGuardedInvocationComponent(GET_LIST_ELEMENT, linkerServices);
207            collectionType = CollectionType.LIST;
208        } else if(Map.class.isAssignableFrom(declaredType)) {
209            gic = createInternalFilteredGuardedInvocationComponent(GET_MAP_ELEMENT, linkerServices);
210            collectionType = CollectionType.MAP;
211        } else if(clazz.isArray()) {
212            gic = getClassGuardedInvocationComponent(linkerServices.filterInternalObjects(MethodHandles.arrayElementGetter(clazz)), callSiteType);
213            collectionType = CollectionType.ARRAY;
214        } else if(List.class.isAssignableFrom(clazz)) {
215            gic = createInternalFilteredGuardedInvocationComponent(GET_LIST_ELEMENT, Guards.asType(LIST_GUARD, callSiteType), List.class, ValidationType.INSTANCE_OF,
216                    linkerServices);
217            collectionType = CollectionType.LIST;
218        } else if(Map.class.isAssignableFrom(clazz)) {
219            gic = createInternalFilteredGuardedInvocationComponent(GET_MAP_ELEMENT, Guards.asType(MAP_GUARD, callSiteType), Map.class, ValidationType.INSTANCE_OF,
220                    linkerServices);
221            collectionType = CollectionType.MAP;
222        } else {
223            // Can't retrieve elements for objects that are neither arrays, nor list, nor maps.
224            return nextComponent;
225        }
226
227        // Convert the key to a number if we're working with a list or array
228        final Object typedName;
229        if (collectionType != CollectionType.MAP && isFixedKey) {
230            final Integer integer = convertKeyToInteger(name, linkerServices);
231            if (integer == null || integer.intValue() < 0) {
232                // key is not a non-negative integer, it can never address an
233                // array or list element
234                return nextComponent;
235            }
236            typedName = integer;
237        } else {
238            typedName = name;
239        }
240
241        final GuardedInvocation gi = gic.getGuardedInvocation();
242        final Binder binder = new Binder(linkerServices, callSiteType, typedName);
243        final MethodHandle invocation = gi.getInvocation();
244
245        final MethodHandle checkGuard;
246        switch(collectionType) {
247        case LIST:
248            checkGuard = convertArgToNumber(RANGE_CHECK_LIST, linkerServices, callSiteDescriptor);
249            break;
250        case MAP:
251            checkGuard = linkerServices.filterInternalObjects(CONTAINS_MAP);
252            break;
253        case ARRAY:
254            checkGuard = convertArgToNumber(RANGE_CHECK_ARRAY, linkerServices, callSiteDescriptor);
255            break;
256        default:
257            throw new AssertionError();
258        }
259
260        // If there's no next component, produce a fixed null-returning one
261        final GuardedInvocationComponent finalNextComponent;
262        if (nextComponent != null) {
263            finalNextComponent = nextComponent;
264        } else {
265            final MethodHandle nullGetterHandle = isFixedKey ? NULL_GETTER_1 : NULL_GETTER_2;
266            finalNextComponent = createGuardedInvocationComponentAsType(nullGetterHandle, callSiteType, linkerServices);
267        }
268
269        final MethodPair matchedInvocations = matchReturnTypes(binder.bind(invocation),
270                finalNextComponent.getGuardedInvocation().getInvocation());
271        return finalNextComponent.compose(matchedInvocations.guardWithTest(binder.bindTest(checkGuard)), gi.getGuard(),
272                gic.getValidatorClass(), gic.getValidationType());
273    }
274
275    private static GuardedInvocationComponent createInternalFilteredGuardedInvocationComponent(
276            final MethodHandle invocation, final LinkerServices linkerServices) {
277        return new GuardedInvocationComponent(linkerServices.filterInternalObjects(invocation));
278    }
279
280    private static GuardedInvocationComponent createGuardedInvocationComponentAsType(
281            final MethodHandle invocation, final MethodType fromType, final LinkerServices linkerServices) {
282        return new GuardedInvocationComponent(linkerServices.asType(invocation, fromType));
283    }
284
285    private static GuardedInvocationComponent createInternalFilteredGuardedInvocationComponent(
286            final MethodHandle invocation, final MethodHandle guard, final Class<?> validatorClass,
287            final ValidationType validationType, final LinkerServices linkerServices) {
288        return new GuardedInvocationComponent(linkerServices.filterInternalObjects(invocation), guard,
289                validatorClass, validationType);
290    }
291
292    private static Integer convertKeyToInteger(final Object fixedKey, final LinkerServices linkerServices) throws Exception {
293        if (fixedKey instanceof Integer) {
294            return (Integer)fixedKey;
295        }
296
297        final Number n;
298        if (fixedKey instanceof Number) {
299            n = (Number)fixedKey;
300        } else {
301            final Class<?> keyClass = fixedKey.getClass();
302            if(linkerServices.canConvert(keyClass, Number.class)) {
303                final Object val;
304                try {
305                    val = linkerServices.getTypeConverter(keyClass, Number.class).invoke(fixedKey);
306                } catch(Exception|Error e) {
307                    throw e;
308                } catch(final Throwable t) {
309                    throw new RuntimeException(t);
310                }
311                if(!(val instanceof Number)) {
312                    return null; // not a number
313                }
314                n = (Number)val;
315            } else if (fixedKey instanceof String){
316                try {
317                    return Integer.valueOf((String)fixedKey);
318                } catch(final NumberFormatException e) {
319                    // key is not a number
320                    return null;
321                }
322            } else {
323                return null;
324            }
325        }
326
327        if(n instanceof Integer) {
328            return (Integer)n;
329        }
330        final int intIndex = n.intValue();
331        final double doubleValue = n.doubleValue();
332        if(intIndex != doubleValue && !Double.isInfinite(doubleValue)) { // let infinites trigger IOOBE
333            return null; // not an exact integer
334        }
335        return intIndex;
336    }
337
338    private static MethodHandle convertArgToNumber(final MethodHandle mh, final LinkerServices ls, final CallSiteDescriptor desc) {
339        final Class<?> sourceType = desc.getMethodType().parameterType(1);
340        if(TypeUtilities.isMethodInvocationConvertible(sourceType, Number.class)) {
341            return mh;
342        } else if(ls.canConvert(sourceType, Number.class)) {
343            final MethodHandle converter = ls.getTypeConverter(sourceType, Number.class);
344            return MethodHandles.filterArguments(mh, 1, converter.asType(converter.type().changeReturnType(
345                    mh.type().parameterType(1))));
346        }
347        return mh;
348    }
349
350    /**
351     * Contains methods to adapt an item getter/setter method handle to the requested type, optionally binding it to a
352     * fixed key first.
353     */
354    private static class Binder {
355        private final LinkerServices linkerServices;
356        private final MethodType methodType;
357        private final Object fixedKey;
358
359        Binder(final LinkerServices linkerServices, final MethodType methodType, final Object fixedKey) {
360            this.linkerServices = linkerServices;
361            this.methodType = fixedKey == null ? methodType : methodType.insertParameterTypes(1, fixedKey.getClass());
362            this.fixedKey = fixedKey;
363        }
364
365        /*private*/ MethodHandle bind(final MethodHandle handle) {
366            return bindToFixedKey(linkerServices.asTypeLosslessReturn(handle, methodType));
367        }
368
369        /*private*/ MethodHandle bindTest(final MethodHandle handle) {
370            return bindToFixedKey(Guards.asType(handle, methodType));
371        }
372
373        private MethodHandle bindToFixedKey(final MethodHandle handle) {
374            return fixedKey == null ? handle : MethodHandles.insertArguments(handle, 1, fixedKey);
375        }
376    }
377
378    private static final MethodHandle RANGE_CHECK_ARRAY = findRangeCheck(Object.class);
379    private static final MethodHandle RANGE_CHECK_LIST = findRangeCheck(List.class);
380    private static final MethodHandle CONTAINS_MAP = Lookup.PUBLIC.findVirtual(Map.class, "containsKey",
381            MethodType.methodType(boolean.class, Object.class));
382
383    private static MethodHandle findRangeCheck(final Class<?> collectionType) {
384        return Lookup.findOwnStatic(MethodHandles.lookup(), "rangeCheck", boolean.class, collectionType, Object.class);
385    }
386
387    @SuppressWarnings("unused")
388    private static boolean rangeCheck(final Object array, final Object index) {
389        if(!(index instanceof Number)) {
390            return false;
391        }
392        final Number n = (Number)index;
393        final int intIndex = n.intValue();
394        if (intIndex != n.doubleValue()) {
395            return false;
396        }
397        return 0 <= intIndex && intIndex < Array.getLength(array);
398    }
399
400    @SuppressWarnings("unused")
401    private static boolean rangeCheck(final List<?> list, final Object index) {
402        if(!(index instanceof Number)) {
403            return false;
404        }
405        final Number n = (Number)index;
406        final int intIndex = n.intValue();
407        if (intIndex != n.doubleValue()) {
408            return false;
409        }
410        return 0 <= intIndex && intIndex < list.size();
411    }
412
413    @SuppressWarnings("unused")
414    private static void noOpSetter() {
415    }
416
417    private static final MethodHandle SET_LIST_ELEMENT = Lookup.PUBLIC.findVirtual(List.class, "set",
418            MethodType.methodType(Object.class, int.class, Object.class));
419
420    private static final MethodHandle PUT_MAP_ELEMENT = Lookup.PUBLIC.findVirtual(Map.class, "put",
421            MethodType.methodType(Object.class, Object.class, Object.class));
422
423    private static final MethodHandle NO_OP_SETTER_2;
424    private static final MethodHandle NO_OP_SETTER_3;
425    static {
426        final MethodHandle noOpSetter = Lookup.findOwnStatic(MethodHandles.lookup(), "noOpSetter", void.class);
427        NO_OP_SETTER_2 = dropObjectArguments(noOpSetter, 2);
428        NO_OP_SETTER_3 = dropObjectArguments(noOpSetter, 3);
429    }
430
431    private GuardedInvocationComponent getElementSetter(final ComponentLinkRequest req) throws Exception {
432        final CallSiteDescriptor callSiteDescriptor = req.getDescriptor();
433        final Object name = req.name;
434        final boolean isFixedKey = name != null;
435        assertParameterCount(callSiteDescriptor, isFixedKey ? 2 : 3);
436        final LinkerServices linkerServices = req.linkerServices;
437        final MethodType callSiteType = callSiteDescriptor.getMethodType();
438        final Class<?> declaredType = callSiteType.parameterType(0);
439
440        final GuardedInvocationComponent gic;
441        // If declared type of receiver at the call site is already an array, a list or map, bind without guard. Thing
442        // is, it'd be quite stupid of a call site creator to go though invokedynamic when it knows in advance they're
443        // dealing with an array, or a list or map, but hey...
444        // Note that for arrays and lists, using LinkerServices.asType() will ensure that any language specific linkers
445        // in use will get a chance to perform any (if there's any) implicit conversion to integer for the indices.
446        final CollectionType collectionType;
447        if(declaredType.isArray()) {
448            gic = createInternalFilteredGuardedInvocationComponent(MethodHandles.arrayElementSetter(declaredType), linkerServices);
449            collectionType = CollectionType.ARRAY;
450        } else if(List.class.isAssignableFrom(declaredType)) {
451            gic = createInternalFilteredGuardedInvocationComponent(SET_LIST_ELEMENT, linkerServices);
452            collectionType = CollectionType.LIST;
453        } else if(Map.class.isAssignableFrom(declaredType)) {
454            gic = createInternalFilteredGuardedInvocationComponent(PUT_MAP_ELEMENT, linkerServices);
455            collectionType = CollectionType.MAP;
456        } else if(clazz.isArray()) {
457            gic = getClassGuardedInvocationComponent(linkerServices.filterInternalObjects(
458                    MethodHandles.arrayElementSetter(clazz)), callSiteType);
459            collectionType = CollectionType.ARRAY;
460        } else if(List.class.isAssignableFrom(clazz)) {
461            gic = createInternalFilteredGuardedInvocationComponent(SET_LIST_ELEMENT, Guards.asType(LIST_GUARD, callSiteType), List.class, ValidationType.INSTANCE_OF,
462                    linkerServices);
463            collectionType = CollectionType.LIST;
464        } else if(Map.class.isAssignableFrom(clazz)) {
465            gic = createInternalFilteredGuardedInvocationComponent(PUT_MAP_ELEMENT, Guards.asType(MAP_GUARD, callSiteType),
466                    Map.class, ValidationType.INSTANCE_OF, linkerServices);
467            collectionType = CollectionType.MAP;
468        } else {
469            // Can't set elements for objects that are neither arrays, nor list, nor maps.
470            gic = null;
471            collectionType = null;
472        }
473
474        // In contrast to, say, getElementGetter, we only compute the nextComponent if the target object is not a map,
475        // as maps will always succeed in setting the element and will never need to fall back to the next component
476        // operation.
477        final GuardedInvocationComponent nextComponent = collectionType == CollectionType.MAP ? null : getNextComponent(req);
478        if(gic == null) {
479            return nextComponent;
480        }
481
482        // Convert the key to a number if we're working with a list or array
483        final Object typedName;
484        if (collectionType != CollectionType.MAP && isFixedKey) {
485            final Integer integer = convertKeyToInteger(name, linkerServices);
486            if (integer == null || integer.intValue() < 0) {
487                // key is not a non-negative integer, it can never address an
488                // array or list element
489                return nextComponent;
490            }
491            typedName = integer;
492        } else {
493            typedName = name;
494        }
495
496        final GuardedInvocation gi = gic.getGuardedInvocation();
497        final Binder binder = new Binder(linkerServices, callSiteType, typedName);
498        final MethodHandle invocation = gi.getInvocation();
499
500        if (collectionType == CollectionType.MAP) {
501            assert nextComponent == null;
502            return gic.replaceInvocation(binder.bind(invocation));
503        }
504
505        assert collectionType == CollectionType.LIST || collectionType == CollectionType.ARRAY;
506        final MethodHandle checkGuard = convertArgToNumber(collectionType == CollectionType.LIST ? RANGE_CHECK_LIST :
507            RANGE_CHECK_ARRAY, linkerServices, callSiteDescriptor);
508
509        // If there's no next component, produce a no-op one.
510        final GuardedInvocationComponent finalNextComponent;
511        if (nextComponent != null) {
512            finalNextComponent = nextComponent;
513        } else {
514            final MethodHandle noOpSetterHandle = isFixedKey ? NO_OP_SETTER_2 : NO_OP_SETTER_3;
515            finalNextComponent = createGuardedInvocationComponentAsType(noOpSetterHandle, callSiteType, linkerServices);
516        }
517
518        final MethodPair matchedInvocations = matchReturnTypes(binder.bind(invocation),
519                finalNextComponent.getGuardedInvocation().getInvocation());
520        return finalNextComponent.compose(matchedInvocations.guardWithTest(binder.bindTest(checkGuard)), gi.getGuard(),
521                gic.getValidatorClass(), gic.getValidationType());
522    }
523
524    private static final MethodHandle GET_COLLECTION_LENGTH = Lookup.PUBLIC.findVirtual(Collection.class, "size",
525            MethodType.methodType(int.class));
526
527    private static final MethodHandle GET_MAP_LENGTH = Lookup.PUBLIC.findVirtual(Map.class, "size",
528            MethodType.methodType(int.class));
529
530    private static void assertParameterCount(final CallSiteDescriptor descriptor, final int paramCount) {
531        if(descriptor.getMethodType().parameterCount() != paramCount) {
532            throw new BootstrapMethodError(descriptor.getOperation() + " must have exactly " + paramCount + " parameters.");
533        }
534    }
535}
536