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

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

mercurial