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