BeanLinker.java revision 1551:f3b883bec2d0
1/* 2 * Copyright (c) 2010, 2013, 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.List; 92import java.util.Map; 93import jdk.dynalink.CallSiteDescriptor; 94import jdk.dynalink.Operation; 95import jdk.dynalink.StandardOperation; 96import jdk.dynalink.beans.GuardedInvocationComponent.ValidationType; 97import jdk.dynalink.linker.GuardedInvocation; 98import jdk.dynalink.linker.LinkerServices; 99import jdk.dynalink.linker.TypeBasedGuardingDynamicLinker; 100import jdk.dynalink.linker.support.Guards; 101import jdk.dynalink.linker.support.Lookup; 102import jdk.dynalink.linker.support.TypeUtilities; 103 104/** 105 * A class that provides linking capabilities for a single POJO class. Normally not used directly, but managed by 106 * {@link BeansLinker}. 107 */ 108class BeanLinker extends AbstractJavaLinker implements TypeBasedGuardingDynamicLinker { 109 BeanLinker(final Class<?> clazz) { 110 super(clazz, Guards.getClassGuard(clazz), Guards.getInstanceOfGuard(clazz)); 111 if(clazz.isArray()) { 112 // Some languages won't have a notion of manipulating collections. Exposing "length" on arrays as an 113 // explicit property is beneficial for them. 114 // REVISIT: is it maybe a code smell that StandardOperation.GET_LENGTH is not needed? 115 setPropertyGetter("length", GET_ARRAY_LENGTH, ValidationType.IS_ARRAY); 116 } else if(List.class.isAssignableFrom(clazz)) { 117 setPropertyGetter("length", GET_COLLECTION_LENGTH, ValidationType.INSTANCE_OF); 118 } 119 } 120 121 @Override 122 public boolean canLinkType(final Class<?> type) { 123 return type == clazz; 124 } 125 126 @Override 127 FacetIntrospector createFacetIntrospector() { 128 return new BeanIntrospector(clazz); 129 } 130 131 @Override 132 protected GuardedInvocationComponent getGuardedInvocationComponent(final CallSiteDescriptor callSiteDescriptor, 133 final LinkerServices linkerServices, final List<Operation> operations, final Object name) throws Exception { 134 final GuardedInvocationComponent superGic = super.getGuardedInvocationComponent(callSiteDescriptor, 135 linkerServices, operations, name); 136 if(superGic != null) { 137 return superGic; 138 } 139 if(operations.isEmpty()) { 140 return null; 141 } 142 final Operation op = operations.get(0); 143 if(op == StandardOperation.GET_ELEMENT) { 144 return getElementGetter(callSiteDescriptor, linkerServices, pop(operations), name); 145 } 146 if(op == StandardOperation.SET_ELEMENT) { 147 return getElementSetter(callSiteDescriptor, linkerServices, pop(operations), name); 148 } 149 if(op == StandardOperation.GET_LENGTH) { 150 return getLengthGetter(callSiteDescriptor); 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 enum CollectionType { 170 ARRAY, LIST, MAP 171 }; 172 173 private GuardedInvocationComponent getElementGetter(final CallSiteDescriptor callSiteDescriptor, 174 final LinkerServices linkerServices, final List<Operation> operations, final Object name) throws Exception { 175 final MethodType callSiteType = callSiteDescriptor.getMethodType(); 176 final Class<?> declaredType = callSiteType.parameterType(0); 177 final GuardedInvocationComponent nextComponent = getGuardedInvocationComponent(callSiteDescriptor, 178 linkerServices, operations, name); 179 180 // If declared type of receiver at the call site is already an array, a list or map, bind without guard. Thing 181 // is, it'd be quite stupid of a call site creator to go though invokedynamic when it knows in advance they're 182 // dealing with an array, or a list or map, but hey... 183 // Note that for arrays and lists, using LinkerServices.asType() will ensure that any language specific linkers 184 // in use will get a chance to perform any (if there's any) implicit conversion to integer for the indices. 185 final GuardedInvocationComponent gic; 186 final CollectionType collectionType; 187 if(declaredType.isArray()) { 188 gic = createInternalFilteredGuardedInvocationComponent(MethodHandles.arrayElementGetter(declaredType), linkerServices); 189 collectionType = CollectionType.ARRAY; 190 } else if(List.class.isAssignableFrom(declaredType)) { 191 gic = createInternalFilteredGuardedInvocationComponent(GET_LIST_ELEMENT, linkerServices); 192 collectionType = CollectionType.LIST; 193 } else if(Map.class.isAssignableFrom(declaredType)) { 194 gic = createInternalFilteredGuardedInvocationComponent(GET_MAP_ELEMENT, linkerServices); 195 collectionType = CollectionType.MAP; 196 } else if(clazz.isArray()) { 197 gic = getClassGuardedInvocationComponent(linkerServices.filterInternalObjects(MethodHandles.arrayElementGetter(clazz)), callSiteType); 198 collectionType = CollectionType.ARRAY; 199 } else if(List.class.isAssignableFrom(clazz)) { 200 gic = createInternalFilteredGuardedInvocationComponent(GET_LIST_ELEMENT, Guards.asType(LIST_GUARD, callSiteType), List.class, ValidationType.INSTANCE_OF, 201 linkerServices); 202 collectionType = CollectionType.LIST; 203 } else if(Map.class.isAssignableFrom(clazz)) { 204 gic = createInternalFilteredGuardedInvocationComponent(GET_MAP_ELEMENT, Guards.asType(MAP_GUARD, callSiteType), Map.class, ValidationType.INSTANCE_OF, 205 linkerServices); 206 collectionType = CollectionType.MAP; 207 } else { 208 // Can't retrieve elements for objects that are neither arrays, nor list, nor maps. 209 return nextComponent; 210 } 211 212 // Convert the key to a number if we're working with a list or array 213 final Object typedName; 214 if(collectionType != CollectionType.MAP && name != null) { 215 typedName = convertKeyToInteger(name, linkerServices); 216 if(typedName == null) { 217 // key is not numeric, it can never succeed 218 return nextComponent; 219 } 220 } else { 221 typedName = name; 222 } 223 224 final GuardedInvocation gi = gic.getGuardedInvocation(); 225 final Binder binder = new Binder(linkerServices, callSiteType, typedName); 226 final MethodHandle invocation = gi.getInvocation(); 227 228 if(nextComponent == null) { 229 return gic.replaceInvocation(binder.bind(invocation)); 230 } 231 232 final MethodHandle checkGuard; 233 switch(collectionType) { 234 case LIST: 235 checkGuard = convertArgToInt(RANGE_CHECK_LIST, linkerServices, callSiteDescriptor); 236 break; 237 case MAP: 238 // TODO: A more complex solution could be devised for maps, one where we do a get() first, and fold it 239 // 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) 241 checkGuard = linkerServices.filterInternalObjects(CONTAINS_MAP); 242 break; 243 case ARRAY: 244 checkGuard = convertArgToInt(RANGE_CHECK_ARRAY, linkerServices, callSiteDescriptor); 245 break; 246 default: 247 throw new AssertionError(); 248 } 249 final MethodPair matchedInvocations = matchReturnTypes(binder.bind(invocation), 250 nextComponent.getGuardedInvocation().getInvocation()); 251 return nextComponent.compose(matchedInvocations.guardWithTest(binder.bindTest(checkGuard)), gi.getGuard(), 252 gic.getValidatorClass(), gic.getValidationType()); 253 } 254 255 private static GuardedInvocationComponent createInternalFilteredGuardedInvocationComponent( 256 final MethodHandle invocation, final LinkerServices linkerServices) { 257 return new GuardedInvocationComponent(linkerServices.filterInternalObjects(invocation)); 258 } 259 260 private static GuardedInvocationComponent createInternalFilteredGuardedInvocationComponent( 261 final MethodHandle invocation, final MethodHandle guard, final Class<?> validatorClass, 262 final ValidationType validationType, final LinkerServices linkerServices) { 263 return new GuardedInvocationComponent(linkerServices.filterInternalObjects(invocation), guard, 264 validatorClass, validationType); 265 } 266 267 private static Integer convertKeyToInteger(final Object fixedKey, final LinkerServices linkerServices) throws Exception { 268 if (fixedKey instanceof Integer) { 269 return (Integer)fixedKey; 270 } 271 272 final Number n; 273 if (fixedKey instanceof Number) { 274 n = (Number)fixedKey; 275 } else { 276 final Class<?> keyClass = fixedKey.getClass(); 277 if(linkerServices.canConvert(keyClass, Number.class)) { 278 final Object val; 279 try { 280 val = linkerServices.getTypeConverter(keyClass, Number.class).invoke(fixedKey); 281 } catch(Exception|Error e) { 282 throw e; 283 } catch(final Throwable t) { 284 throw new RuntimeException(t); 285 } 286 if(!(val instanceof Number)) { 287 return null; // not a number 288 } 289 n = (Number)val; 290 } else if (fixedKey instanceof String){ 291 try { 292 return Integer.valueOf((String)fixedKey); 293 } catch(final NumberFormatException e) { 294 // key is not a number 295 return null; 296 } 297 } else { 298 return null; 299 } 300 } 301 302 if(n instanceof Integer) { 303 return (Integer)n; 304 } 305 final int intIndex = n.intValue(); 306 final double doubleValue = n.doubleValue(); 307 if(intIndex != doubleValue && !Double.isInfinite(doubleValue)) { // let infinites trigger IOOBE 308 return null; // not an exact integer 309 } 310 return intIndex; 311 } 312 313 private static MethodHandle convertArgToInt(final MethodHandle mh, final LinkerServices ls, final CallSiteDescriptor desc) { 314 final Class<?> sourceType = desc.getMethodType().parameterType(1); 315 if(TypeUtilities.isMethodInvocationConvertible(sourceType, Number.class)) { 316 return mh; 317 } else if(ls.canConvert(sourceType, Number.class)) { 318 final MethodHandle converter = ls.getTypeConverter(sourceType, Number.class); 319 return MethodHandles.filterArguments(mh, 1, converter.asType(converter.type().changeReturnType( 320 mh.type().parameterType(1)))); 321 } 322 return mh; 323 } 324 325 /** 326 * Contains methods to adapt an item getter/setter method handle to the requested type, optionally binding it to a 327 * fixed key first. 328 */ 329 private static class Binder { 330 private final LinkerServices linkerServices; 331 private final MethodType methodType; 332 private final Object fixedKey; 333 334 Binder(final LinkerServices linkerServices, final MethodType methodType, final Object fixedKey) { 335 this.linkerServices = linkerServices; 336 this.methodType = fixedKey == null ? methodType : methodType.insertParameterTypes(1, fixedKey.getClass()); 337 this.fixedKey = fixedKey; 338 } 339 340 /*private*/ MethodHandle bind(final MethodHandle handle) { 341 return bindToFixedKey(linkerServices.asTypeLosslessReturn(handle, methodType)); 342 } 343 344 /*private*/ MethodHandle bindTest(final MethodHandle handle) { 345 return bindToFixedKey(Guards.asType(handle, methodType)); 346 } 347 348 private MethodHandle bindToFixedKey(final MethodHandle handle) { 349 return fixedKey == null ? handle : MethodHandles.insertArguments(handle, 1, fixedKey); 350 } 351 } 352 353 private static final MethodHandle RANGE_CHECK_ARRAY = findRangeCheck(Object.class); 354 private static final MethodHandle RANGE_CHECK_LIST = findRangeCheck(List.class); 355 private static final MethodHandle CONTAINS_MAP = Lookup.PUBLIC.findVirtual(Map.class, "containsKey", 356 MethodType.methodType(boolean.class, Object.class)); 357 358 private static MethodHandle findRangeCheck(final Class<?> collectionType) { 359 return Lookup.findOwnStatic(MethodHandles.lookup(), "rangeCheck", boolean.class, collectionType, Object.class); 360 } 361 362 @SuppressWarnings("unused") 363 private static boolean rangeCheck(final Object array, final Object index) { 364 if(!(index instanceof Number)) { 365 return false; 366 } 367 final Number n = (Number)index; 368 final int intIndex = n.intValue(); 369 final double doubleValue = n.doubleValue(); 370 if(intIndex != doubleValue && !Double.isInfinite(doubleValue)) { // let infinite trigger IOOBE 371 return false; 372 } 373 if(0 <= intIndex && intIndex < Array.getLength(array)) { 374 return true; 375 } 376 throw new ArrayIndexOutOfBoundsException("Array index out of range: " + n); 377 } 378 379 @SuppressWarnings("unused") 380 private static boolean rangeCheck(final List<?> list, final Object index) { 381 if(!(index instanceof Number)) { 382 return false; 383 } 384 final Number n = (Number)index; 385 final int intIndex = n.intValue(); 386 final double doubleValue = n.doubleValue(); 387 if(intIndex != doubleValue && !Double.isInfinite(doubleValue)) { // let infinite trigger IOOBE 388 return false; 389 } 390 if(0 <= intIndex && intIndex < list.size()) { 391 return true; 392 } 393 throw new IndexOutOfBoundsException("Index: " + n + ", Size: " + list.size()); 394 } 395 396 private static final MethodHandle SET_LIST_ELEMENT = Lookup.PUBLIC.findVirtual(List.class, "set", 397 MethodType.methodType(Object.class, int.class, Object.class)); 398 399 private static final MethodHandle PUT_MAP_ELEMENT = Lookup.PUBLIC.findVirtual(Map.class, "put", 400 MethodType.methodType(Object.class, Object.class, Object.class)); 401 402 private GuardedInvocationComponent getElementSetter(final CallSiteDescriptor callSiteDescriptor, 403 final LinkerServices linkerServices, final List<Operation> operations, final Object name) throws Exception { 404 final MethodType callSiteType = callSiteDescriptor.getMethodType(); 405 final Class<?> declaredType = callSiteType.parameterType(0); 406 407 final GuardedInvocationComponent gic; 408 // If declared type of receiver at the call site is already an array, a list or map, bind without guard. Thing 409 // is, it'd be quite stupid of a call site creator to go though invokedynamic when it knows in advance they're 410 // dealing with an array, or a list or map, but hey... 411 // Note that for arrays and lists, using LinkerServices.asType() will ensure that any language specific linkers 412 // in use will get a chance to perform any (if there's any) implicit conversion to integer for the indices. 413 final CollectionType collectionType; 414 if(declaredType.isArray()) { 415 gic = createInternalFilteredGuardedInvocationComponent(MethodHandles.arrayElementSetter(declaredType), linkerServices); 416 collectionType = CollectionType.ARRAY; 417 } else if(List.class.isAssignableFrom(declaredType)) { 418 gic = createInternalFilteredGuardedInvocationComponent(SET_LIST_ELEMENT, linkerServices); 419 collectionType = CollectionType.LIST; 420 } else if(Map.class.isAssignableFrom(declaredType)) { 421 gic = createInternalFilteredGuardedInvocationComponent(PUT_MAP_ELEMENT, linkerServices); 422 collectionType = CollectionType.MAP; 423 } else if(clazz.isArray()) { 424 gic = getClassGuardedInvocationComponent(linkerServices.filterInternalObjects( 425 MethodHandles.arrayElementSetter(clazz)), callSiteType); 426 collectionType = CollectionType.ARRAY; 427 } else if(List.class.isAssignableFrom(clazz)) { 428 gic = createInternalFilteredGuardedInvocationComponent(SET_LIST_ELEMENT, Guards.asType(LIST_GUARD, callSiteType), List.class, ValidationType.INSTANCE_OF, 429 linkerServices); 430 collectionType = CollectionType.LIST; 431 } else if(Map.class.isAssignableFrom(clazz)) { 432 gic = createInternalFilteredGuardedInvocationComponent(PUT_MAP_ELEMENT, Guards.asType(MAP_GUARD, callSiteType), 433 Map.class, ValidationType.INSTANCE_OF, linkerServices); 434 collectionType = CollectionType.MAP; 435 } else { 436 // Can't set elements for objects that are neither arrays, nor list, nor maps. 437 gic = null; 438 collectionType = null; 439 } 440 441 // In contrast to, say, getElementGetter, we only compute the nextComponent if the target object is not a map, 442 // as maps will always succeed in setting the element and will never need to fall back to the next component 443 // operation. 444 final GuardedInvocationComponent nextComponent = collectionType == CollectionType.MAP ? null : getGuardedInvocationComponent( 445 callSiteDescriptor, linkerServices, operations, name); 446 if(gic == null) { 447 return nextComponent; 448 } 449 450 // Convert the key to a number if we're working with a list or array 451 final Object typedName; 452 if(collectionType != CollectionType.MAP && name != null) { 453 typedName = convertKeyToInteger(name, linkerServices); 454 if(typedName == null) { 455 // key is not numeric, it can never succeed 456 return nextComponent; 457 } 458 } else { 459 typedName = name; 460 } 461 462 final GuardedInvocation gi = gic.getGuardedInvocation(); 463 final Binder binder = new Binder(linkerServices, callSiteType, typedName); 464 final MethodHandle invocation = gi.getInvocation(); 465 466 if(nextComponent == null) { 467 return gic.replaceInvocation(binder.bind(invocation)); 468 } 469 470 assert collectionType == CollectionType.LIST || collectionType == CollectionType.ARRAY; 471 final MethodHandle checkGuard = convertArgToInt(collectionType == CollectionType.LIST ? RANGE_CHECK_LIST : 472 RANGE_CHECK_ARRAY, linkerServices, callSiteDescriptor); 473 final MethodPair matchedInvocations = matchReturnTypes(binder.bind(invocation), 474 nextComponent.getGuardedInvocation().getInvocation()); 475 return nextComponent.compose(matchedInvocations.guardWithTest(binder.bindTest(checkGuard)), gi.getGuard(), 476 gic.getValidatorClass(), gic.getValidationType()); 477 } 478 479 private static final MethodHandle GET_ARRAY_LENGTH = Lookup.PUBLIC.findStatic(Array.class, "getLength", 480 MethodType.methodType(int.class, Object.class)); 481 482 private static final MethodHandle GET_COLLECTION_LENGTH = Lookup.PUBLIC.findVirtual(Collection.class, "size", 483 MethodType.methodType(int.class)); 484 485 private static final MethodHandle GET_MAP_LENGTH = Lookup.PUBLIC.findVirtual(Map.class, "size", 486 MethodType.methodType(int.class)); 487 488 private static final MethodHandle COLLECTION_GUARD = Guards.getInstanceOfGuard(Collection.class); 489 490 private GuardedInvocationComponent getLengthGetter(final CallSiteDescriptor callSiteDescriptor) { 491 assertParameterCount(callSiteDescriptor, 1); 492 final MethodType callSiteType = callSiteDescriptor.getMethodType(); 493 final Class<?> declaredType = callSiteType.parameterType(0); 494 // If declared type of receiver at the call site is already an array, collection, or map, bind without guard. 495 // Thing is, it'd be quite stupid of a call site creator to go though invokedynamic when it knows in advance 496 // they're dealing with an array, collection, or map, but hey... 497 if(declaredType.isArray()) { 498 return new GuardedInvocationComponent(GET_ARRAY_LENGTH.asType(callSiteType)); 499 } else if(Collection.class.isAssignableFrom(declaredType)) { 500 return new GuardedInvocationComponent(GET_COLLECTION_LENGTH.asType(callSiteType)); 501 } else if(Map.class.isAssignableFrom(declaredType)) { 502 return new GuardedInvocationComponent(GET_MAP_LENGTH.asType(callSiteType)); 503 } 504 505 // Otherwise, create a binding based on the actual type of the argument with an appropriate guard. 506 if(clazz.isArray()) { 507 return new GuardedInvocationComponent(GET_ARRAY_LENGTH.asType(callSiteType), Guards.isArray(0, 508 callSiteType), ValidationType.IS_ARRAY); 509 } if(Collection.class.isAssignableFrom(clazz)) { 510 return new GuardedInvocationComponent(GET_COLLECTION_LENGTH.asType(callSiteType), Guards.asType( 511 COLLECTION_GUARD, callSiteType), Collection.class, ValidationType.INSTANCE_OF); 512 } if(Map.class.isAssignableFrom(clazz)) { 513 return new GuardedInvocationComponent(GET_MAP_LENGTH.asType(callSiteType), Guards.asType(MAP_GUARD, 514 callSiteType), Map.class, ValidationType.INSTANCE_OF); 515 } 516 // Can't retrieve length for objects that are neither arrays, nor collections, nor maps. 517 return null; 518 } 519 520 private static void assertParameterCount(final CallSiteDescriptor descriptor, final int paramCount) { 521 if(descriptor.getMethodType().parameterCount() != paramCount) { 522 throw new BootstrapMethodError(descriptor.getOperation() + " must have exactly " + paramCount + " parameters."); 523 } 524 } 525} 526