Wed, 27 Feb 2013 15:20:26 +0100
8009143: Eliminate Dynalink dependency on java.beans
Reviewed-by: jlaskey, lagergren, sundar
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 */
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
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.
40 You may not use this file except in compliance with either the Apache
41 License or the BSD License.
43 If you choose to use this file in compliance with the Apache License, the
44 following notice applies to you:
46 You may obtain a copy of the Apache License at
48 http://www.apache.org/licenses/LICENSE-2.0
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.
56 If you choose to use this file in compliance with the BSD License, the
57 following notice applies to you:
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.
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 */
84 package jdk.internal.dynalink.beans;
86 import java.lang.invoke.MethodHandle;
87 import java.lang.invoke.MethodHandles;
88 import java.lang.invoke.MethodType;
89 import java.lang.reflect.Field;
90 import java.lang.reflect.Method;
91 import java.lang.reflect.Modifier;
92 import java.util.HashMap;
93 import java.util.List;
94 import java.util.Map;
95 import jdk.internal.dynalink.CallSiteDescriptor;
96 import jdk.internal.dynalink.beans.GuardedInvocationComponent.ValidationType;
97 import jdk.internal.dynalink.linker.GuardedInvocation;
98 import jdk.internal.dynalink.linker.GuardingDynamicLinker;
99 import jdk.internal.dynalink.linker.LinkRequest;
100 import jdk.internal.dynalink.linker.LinkerServices;
101 import jdk.internal.dynalink.support.CallSiteDescriptorFactory;
102 import jdk.internal.dynalink.support.Guards;
103 import jdk.internal.dynalink.support.Lookup;
105 /**
106 * A base class for both {@link StaticClassLinker} and {@link BeanLinker}. Deals with common aspects of property
107 * exposure and method calls for both static and instance facets of a class.
108 *
109 * @author Attila Szegedi
110 */
111 abstract class AbstractJavaLinker implements GuardingDynamicLinker {
112 final Class<?> clazz;
113 private final MethodHandle classGuard;
114 private final MethodHandle assignableGuard;
115 private final Map<String, AnnotatedMethodHandle> propertyGetters = new HashMap<>();
116 private final Map<String, DynamicMethod> propertySetters = new HashMap<>();
117 private final Map<String, DynamicMethod> methods = new HashMap<>();
119 AbstractJavaLinker(Class<?> clazz, MethodHandle classGuard) {
120 this(clazz, classGuard, classGuard);
121 }
123 AbstractJavaLinker(Class<?> clazz, MethodHandle classGuard, MethodHandle assignableGuard) {
124 this.clazz = clazz;
125 this.classGuard = classGuard;
126 this.assignableGuard = assignableGuard;
128 final FacetIntrospector introspector = createFacetIntrospector();
129 // Add methods and properties
130 for(Method method: introspector.getMethods()) {
131 final String name = method.getName();
132 final MethodHandle methodHandle = introspector.unreflect(method);
133 // Add method
134 addMember(name, methodHandle, methods);
135 // Add the method as a property getter and/or setter
136 if(name.startsWith("get") && name.length() > 3 && method.getParameterTypes().length == 0) {
137 // Property getter
138 setPropertyGetter(decapitalize(name.substring(3)), introspector.unreflect(
139 getMostGenericGetter(method)), ValidationType.INSTANCE_OF);
140 } else if(name.startsWith("is") && name.length() > 2 && method.getParameterTypes().length == 0 &&
141 method.getReturnType() == boolean.class) {
142 // Boolean property getter
143 setPropertyGetter(decapitalize(name.substring(2)), introspector.unreflect(
144 getMostGenericGetter(method)), ValidationType.INSTANCE_OF);
145 } else if(name.startsWith("set") && name.length() > 3 && method.getParameterTypes().length == 1) {
146 // Property setter
147 addMember(decapitalize(name.substring(3)), methodHandle, propertySetters);
148 }
149 }
151 // Add field getter/setters as property getters/setters.
152 for(Field field: introspector.getFields()) {
153 final String name = field.getName();
154 // Only add a property getter when one is not defined already as a getXxx()/isXxx() method.
155 if(!propertyGetters.containsKey(name)) {
156 setPropertyGetter(name, introspector.unreflectGetter(field), ValidationType.EXACT_CLASS);
157 }
158 if(!(Modifier.isFinal(field.getModifiers()) || propertySetters.containsKey(name))) {
159 addMember(name, introspector.unreflectSetter(field), propertySetters);
160 }
161 }
163 // Add inner classes, but only those for which we don't hide a property with it
164 for(Map.Entry<String, MethodHandle> innerClassSpec: introspector.getInnerClassGetters().entrySet()) {
165 final String name = innerClassSpec.getKey();
166 if(!propertyGetters.containsKey(name)) {
167 setPropertyGetter(name, innerClassSpec.getValue(), ValidationType.EXACT_CLASS);
168 }
169 }
170 }
172 private static String decapitalize(String str) {
173 assert str != null;
174 if(str.isEmpty()) {
175 return str;
176 }
178 final char c0 = str.charAt(0);
179 if(Character.isLowerCase(c0)) {
180 return str;
181 }
183 // If it has two consecutive upper-case characters, i.e. "URL", don't decapitalize
184 if(str.length() > 1 && Character.isUpperCase(str.charAt(1))) {
185 return str;
186 }
188 final char c[] = str.toCharArray();
189 c[0] = Character.toLowerCase(c0);
190 return new String(c);
191 }
193 abstract FacetIntrospector createFacetIntrospector();
195 void setPropertyGetter(String name, MethodHandle handle, ValidationType validationType) {
196 propertyGetters.put(name, new AnnotatedMethodHandle(handle, validationType));
197 }
199 private void addMember(String name, MethodHandle mh, Map<String, DynamicMethod> methodMap) {
200 final DynamicMethod existingMethod = methodMap.get(name);
201 final DynamicMethod newMethod = addMember(mh, existingMethod, clazz, name);
202 if(newMethod != existingMethod) {
203 methodMap.put(name, newMethod);
204 }
205 }
207 static DynamicMethod createDynamicMethod(Iterable<MethodHandle> methodHandles, Class<?> clazz, String name) {
208 DynamicMethod dynMethod = null;
209 for(MethodHandle methodHandle: methodHandles) {
210 dynMethod = addMember(methodHandle, dynMethod, clazz, name);
211 }
212 return dynMethod;
213 }
215 private static DynamicMethod addMember(MethodHandle mh, DynamicMethod existing, Class<?> clazz, String name) {
216 if(existing == null) {
217 return new SimpleDynamicMethod(mh, clazz, name);
218 } else if(existing.contains(mh)) {
219 return existing;
220 } else if(existing instanceof SimpleDynamicMethod) {
221 final OverloadedDynamicMethod odm = new OverloadedDynamicMethod(clazz, name);
222 odm.addMethod(((SimpleDynamicMethod)existing));
223 odm.addMethod(mh);
224 return odm;
225 } else if(existing instanceof OverloadedDynamicMethod) {
226 ((OverloadedDynamicMethod)existing).addMethod(mh);
227 return existing;
228 }
229 throw new AssertionError();
230 }
232 @Override
233 public GuardedInvocation getGuardedInvocation(LinkRequest request, final LinkerServices linkerServices)
234 throws Exception {
235 final LinkRequest ncrequest = request.withoutRuntimeContext();
236 // BeansLinker already checked that the name is at least 2 elements long and the first element is "dyn".
237 final CallSiteDescriptor callSiteDescriptor = ncrequest.getCallSiteDescriptor();
238 final String op = callSiteDescriptor.getNameToken(CallSiteDescriptor.OPERATOR);
239 // Either dyn:callMethod:name(this[,args]) or dyn:callMethod(this,name[,args]).
240 if("callMethod" == op) {
241 return getCallPropWithThis(callSiteDescriptor, linkerServices);
242 }
243 List<String> operations = CallSiteDescriptorFactory.tokenizeOperators(callSiteDescriptor);
244 while(!operations.isEmpty()) {
245 final GuardedInvocationComponent gic = getGuardedInvocationComponent(callSiteDescriptor, linkerServices,
246 operations);
247 if(gic != null) {
248 return gic.getGuardedInvocation();
249 }
250 operations = pop(operations);
251 }
252 return null;
253 }
255 protected GuardedInvocationComponent getGuardedInvocationComponent(CallSiteDescriptor callSiteDescriptor,
256 LinkerServices linkerServices, List<String> operations) throws Exception {
257 if(operations.isEmpty()) {
258 return null;
259 }
260 final String op = operations.get(0);
261 // Either dyn:getProp:name(this) or dyn:getProp(this, name)
262 if("getProp".equals(op)) {
263 return getPropertyGetter(callSiteDescriptor, linkerServices, pop(operations));
264 }
265 // Either dyn:setProp:name(this, value) or dyn:setProp(this, name, value)
266 if("setProp".equals(op)) {
267 return getPropertySetter(callSiteDescriptor, linkerServices, pop(operations));
268 }
269 // Either dyn:getMethod:name(this), or dyn:getMethod(this, name)
270 if("getMethod".equals(op)) {
271 return getMethodGetter(callSiteDescriptor, linkerServices, pop(operations));
272 }
273 return null;
274 }
276 static final <T> List<T> pop(List<T> l) {
277 return l.subList(1, l.size());
278 }
280 MethodHandle getClassGuard(CallSiteDescriptor desc) {
281 return getClassGuard(desc.getMethodType());
282 }
284 MethodHandle getClassGuard(MethodType type) {
285 return Guards.asType(classGuard, type);
286 }
288 GuardedInvocationComponent getClassGuardedInvocationComponent(MethodHandle invocation, MethodType type) {
289 return new GuardedInvocationComponent(invocation, getClassGuard(type), clazz, ValidationType.EXACT_CLASS);
290 }
292 private MethodHandle getAssignableGuard(MethodType type) {
293 return Guards.asType(assignableGuard, type);
294 }
296 private GuardedInvocation getCallPropWithThis(CallSiteDescriptor callSiteDescriptor, LinkerServices linkerServices) {
297 switch(callSiteDescriptor.getNameTokenCount()) {
298 case 3: {
299 return createGuardedDynamicMethodInvocation(callSiteDescriptor.getMethodType(), linkerServices,
300 callSiteDescriptor.getNameToken(CallSiteDescriptor.NAME_OPERAND), methods);
301 }
302 default: {
303 return null;
304 }
305 }
306 }
308 private GuardedInvocation createGuardedDynamicMethodInvocation(MethodType callSiteType,
309 LinkerServices linkerServices, String methodName, Map<String, DynamicMethod> methodMap){
310 final MethodHandle inv = getDynamicMethodInvocation(callSiteType, linkerServices, methodName, methodMap);
311 return inv == null ? null : new GuardedInvocation(inv, getClassGuard(callSiteType));
312 }
314 private static MethodHandle getDynamicMethodInvocation(MethodType callSiteType, LinkerServices linkerServices,
315 String methodName, Map<String, DynamicMethod> methodMap) {
316 final DynamicMethod dynaMethod = getDynamicMethod(methodName, methodMap);
317 return dynaMethod != null ? dynaMethod.getInvocation(callSiteType, linkerServices) : null;
318 }
320 private static DynamicMethod getDynamicMethod(String methodName, Map<String, DynamicMethod> methodMap) {
321 final DynamicMethod dynaMethod = methodMap.get(methodName);
322 return dynaMethod != null ? dynaMethod : getExplicitSignatureDynamicMethod(methodName, methodMap);
323 }
325 private static SimpleDynamicMethod getExplicitSignatureDynamicMethod(String methodName,
326 Map<String, DynamicMethod> methodsMap) {
327 // What's below is meant to support the "name(type, type, ...)" syntax that programmers can use in a method name
328 // to manually pin down an exact overloaded variant. This is not usually required, as the overloaded method
329 // resolution works correctly in almost every situation. However, in presence of many language-specific
330 // conversions with a radically dynamic language, most overloaded methods will end up being constantly selected
331 // at invocation time, so a programmer knowledgable of the situation might choose to pin down an exact overload
332 // for performance reasons.
334 // Is the method name lexically of the form "name(types)"?
335 final int lastChar = methodName.length() - 1;
336 if(methodName.charAt(lastChar) != ')') {
337 return null;
338 }
339 final int openBrace = methodName.indexOf('(');
340 if(openBrace == -1) {
341 return null;
342 }
344 // Find an existing method for the "name" part
345 final DynamicMethod simpleNamedMethod = methodsMap.get(methodName.substring(0, openBrace));
346 if(simpleNamedMethod == null) {
347 return null;
348 }
350 // Try to get a narrowed dynamic method for the explicit parameter types.
351 return simpleNamedMethod.getMethodForExactParamTypes(methodName.substring(openBrace + 1, lastChar));
352 }
354 private static final MethodHandle IS_METHOD_HANDLE_NOT_NULL = Guards.isNotNull().asType(MethodType.methodType(
355 boolean.class, MethodHandle.class));
356 private static final MethodHandle CONSTANT_NULL_DROP_METHOD_HANDLE = MethodHandles.dropArguments(
357 MethodHandles.constant(Object.class, null), 0, MethodHandle.class);
359 private GuardedInvocationComponent getPropertySetter(CallSiteDescriptor callSiteDescriptor,
360 LinkerServices linkerServices, List<String> operations) throws Exception {
361 final MethodType type = callSiteDescriptor.getMethodType();
362 switch(callSiteDescriptor.getNameTokenCount()) {
363 case 2: {
364 // Must have three arguments: target object, property name, and property value.
365 assertParameterCount(callSiteDescriptor, 3);
367 // What's below is basically:
368 // foldArguments(guardWithTest(isNotNull, invoke, null|nextComponent.invocation),
369 // get_setter_handle(type, linkerServices))
370 // only with a bunch of method signature adjustments. Basically, retrieve method setter
371 // MethodHandle; if it is non-null, invoke it, otherwise either return null, or delegate to next
372 // component's invocation.
374 // Call site type is "ret_type(object_type,property_name_type,property_value_type)", which we'll
375 // abbreviate to R(O, N, V) going forward.
376 // We want setters that conform to "R(O, V)"
377 final MethodType setterType = type.dropParameterTypes(1, 2);
378 // Bind property setter handle to the expected setter type and linker services. Type is
379 // MethodHandle(Object, String, Object)
380 final MethodHandle boundGetter = MethodHandles.insertArguments(getPropertySetterHandle, 0, setterType,
381 linkerServices);
383 // Cast getter to MethodHandle(O, N, V)
384 final MethodHandle typedGetter = linkerServices.asType(boundGetter, type.changeReturnType(
385 MethodHandle.class));
387 // Handle to invoke the setter R(MethodHandle, O, V)
388 final MethodHandle invokeHandle = MethodHandles.exactInvoker(setterType);
389 // Handle to invoke the setter, dropping unnecessary fold arguments R(MethodHandle, O, N, V)
390 final MethodHandle invokeHandleFolded = MethodHandles.dropArguments(invokeHandle, 2, type.parameterType(
391 1));
392 final GuardedInvocationComponent nextComponent = getGuardedInvocationComponent(callSiteDescriptor,
393 linkerServices, operations);
395 final MethodHandle fallbackFolded;
396 if(nextComponent == null) {
397 // Object(MethodHandle)->R(MethodHandle, O, N, V); returns constant null
398 fallbackFolded = MethodHandles.dropArguments(CONSTANT_NULL_DROP_METHOD_HANDLE, 1,
399 type.parameterList()).asType(type.insertParameterTypes(0, MethodHandle.class));
400 } else {
401 // R(O, N, V)->R(MethodHandle, O, N, V); adapts the next component's invocation to drop the
402 // extra argument resulting from fold
403 fallbackFolded = MethodHandles.dropArguments(nextComponent.getGuardedInvocation().getInvocation(),
404 0, MethodHandle.class);
405 }
407 // fold(R(MethodHandle, O, N, V), MethodHandle(O, N, V))
408 final MethodHandle compositeSetter = MethodHandles.foldArguments(MethodHandles.guardWithTest(
409 IS_METHOD_HANDLE_NOT_NULL, invokeHandleFolded, fallbackFolded), typedGetter);
410 if(nextComponent == null) {
411 return getClassGuardedInvocationComponent(compositeSetter, type);
412 }
413 return nextComponent.compose(compositeSetter, getClassGuard(type), clazz, ValidationType.EXACT_CLASS);
414 }
415 case 3: {
416 // Must have two arguments: target object and property value
417 assertParameterCount(callSiteDescriptor, 2);
418 final GuardedInvocation gi = createGuardedDynamicMethodInvocation(callSiteDescriptor.getMethodType(),
419 linkerServices, callSiteDescriptor.getNameToken(CallSiteDescriptor.NAME_OPERAND),
420 propertySetters);
421 // If we have a property setter with this name, this composite operation will always stop here
422 if(gi != null) {
423 return new GuardedInvocationComponent(gi, clazz, ValidationType.EXACT_CLASS);
424 }
425 // If we don't have a property setter with this name, always fall back to the next operation in the
426 // composite (if any)
427 return getGuardedInvocationComponent(callSiteDescriptor, linkerServices, operations);
428 }
429 default: {
430 // More than two name components; don't know what to do with it.
431 return null;
432 }
433 }
434 }
436 private static final Lookup privateLookup = new Lookup(MethodHandles.lookup());
438 private static final MethodHandle IS_ANNOTATED_HANDLE_NOT_NULL = Guards.isNotNull().asType(MethodType.methodType(
439 boolean.class, AnnotatedMethodHandle.class));
440 private static final MethodHandle CONSTANT_NULL_DROP_ANNOTATED_HANDLE = MethodHandles.dropArguments(
441 MethodHandles.constant(Object.class, null), 0, AnnotatedMethodHandle.class);
442 private static final MethodHandle GET_ANNOTATED_HANDLE = privateLookup.findGetter(AnnotatedMethodHandle.class,
443 "handle", MethodHandle.class);
444 private static final MethodHandle GENERIC_PROPERTY_GETTER_HANDLER_INVOKER = MethodHandles.filterArguments(
445 MethodHandles.invoker(MethodType.methodType(Object.class, Object.class)), 0, GET_ANNOTATED_HANDLE);
447 private GuardedInvocationComponent getPropertyGetter(CallSiteDescriptor callSiteDescriptor,
448 LinkerServices linkerServices, List<String> ops) throws Exception {
449 final MethodType type = callSiteDescriptor.getMethodType();
450 switch(callSiteDescriptor.getNameTokenCount()) {
451 case 2: {
452 // Must have exactly two arguments: receiver and name
453 assertParameterCount(callSiteDescriptor, 2);
455 // What's below is basically:
456 // foldArguments(guardWithTest(isNotNull, invoke(get_handle), null|nextComponent.invocation), get_getter_handle)
457 // only with a bunch of method signature adjustments. Basically, retrieve method getter
458 // AnnotatedMethodHandle; if it is non-null, invoke its "handle" field, otherwise either return null,
459 // or delegate to next component's invocation.
461 final MethodHandle typedGetter = linkerServices.asType(getPropertyGetterHandle, type.changeReturnType(
462 AnnotatedMethodHandle.class));
463 // Object(AnnotatedMethodHandle, Object)->R(AnnotatedMethodHandle, T0)
464 final MethodHandle invokeHandleTyped = linkerServices.asType(GENERIC_PROPERTY_GETTER_HANDLER_INVOKER,
465 MethodType.methodType(type.returnType(), AnnotatedMethodHandle.class, type.parameterType(0)));
466 // Since it's in the target of a fold, drop the unnecessary second argument
467 // R(AnnotatedMethodHandle, T0)->R(AnnotatedMethodHandle, T0, T1)
468 final MethodHandle invokeHandleFolded = MethodHandles.dropArguments(invokeHandleTyped, 2,
469 type.parameterType(1));
470 final GuardedInvocationComponent nextComponent = getGuardedInvocationComponent(callSiteDescriptor,
471 linkerServices, ops);
473 final MethodHandle fallbackFolded;
474 if(nextComponent == null) {
475 // Object(AnnotatedMethodHandle)->R(AnnotatedMethodHandle, T0, T1); returns constant null
476 fallbackFolded = MethodHandles.dropArguments(CONSTANT_NULL_DROP_ANNOTATED_HANDLE, 1,
477 type.parameterList()).asType(type.insertParameterTypes(0, AnnotatedMethodHandle.class));
478 } else {
479 // R(T0, T1)->R(AnnotatedMethodHAndle, T0, T1); adapts the next component's invocation to drop the
480 // extra argument resulting from fold
481 fallbackFolded = MethodHandles.dropArguments(nextComponent.getGuardedInvocation().getInvocation(),
482 0, AnnotatedMethodHandle.class);
483 }
485 // fold(R(AnnotatedMethodHandle, T0, T1), AnnotatedMethodHandle(T0, T1))
486 final MethodHandle compositeGetter = MethodHandles.foldArguments(MethodHandles.guardWithTest(
487 IS_ANNOTATED_HANDLE_NOT_NULL, invokeHandleFolded, fallbackFolded), typedGetter);
488 if(nextComponent == null) {
489 return getClassGuardedInvocationComponent(compositeGetter, type);
490 }
491 return nextComponent.compose(compositeGetter, getClassGuard(type), clazz, ValidationType.EXACT_CLASS);
492 }
493 case 3: {
494 // Must have exactly one argument: receiver
495 assertParameterCount(callSiteDescriptor, 1);
496 // Fixed name
497 final AnnotatedMethodHandle annGetter = propertyGetters.get(callSiteDescriptor.getNameToken(
498 CallSiteDescriptor.NAME_OPERAND));
499 if(annGetter == null) {
500 // We have no such property, always delegate to the next component operation
501 return getGuardedInvocationComponent(callSiteDescriptor, linkerServices, ops);
502 }
503 final MethodHandle getter = annGetter.handle;
504 // NOTE: since property getters (not field getters!) are no-arg, we don't have to worry about them being
505 // overloaded in a subclass. Therefore, we can discover the most abstract superclass that has the
506 // method, and use that as the guard with Guards.isInstance() for a more stably linked call site. If
507 // we're linking against a field getter, don't make the assumption.
508 // NOTE: No delegation to the next component operation if we have a property with this name, even if its
509 // value is null.
510 final ValidationType validationType = annGetter.validationType;
511 return new GuardedInvocationComponent(linkerServices.asType(getter, type), getGuard(validationType,
512 type), clazz, validationType);
513 }
514 default: {
515 // Can't do anything with more than 3 name components
516 return null;
517 }
518 }
519 }
521 private MethodHandle getGuard(ValidationType validationType, MethodType methodType) {
522 switch(validationType) {
523 case EXACT_CLASS: {
524 return getClassGuard(methodType);
525 }
526 case INSTANCE_OF: {
527 return getAssignableGuard(methodType);
528 }
529 case IS_ARRAY: {
530 return Guards.isArray(0, methodType);
531 }
532 case NONE: {
533 return null;
534 }
535 default: {
536 throw new AssertionError();
537 }
538 }
539 }
541 private static final MethodHandle IS_DYNAMIC_METHOD_NOT_NULL = Guards.asType(Guards.isNotNull(),
542 MethodType.methodType(boolean.class, DynamicMethod.class));
543 private static final MethodHandle DYNAMIC_METHOD_IDENTITY = MethodHandles.identity(DynamicMethod.class);
545 private GuardedInvocationComponent getMethodGetter(CallSiteDescriptor callSiteDescriptor,
546 LinkerServices linkerServices, List<String> ops) throws Exception {
547 final MethodType type = callSiteDescriptor.getMethodType();
548 switch(callSiteDescriptor.getNameTokenCount()) {
549 case 2: {
550 // Must have exactly two arguments: receiver and name
551 assertParameterCount(callSiteDescriptor, 2);
552 final GuardedInvocationComponent nextComponent = getGuardedInvocationComponent(callSiteDescriptor,
553 linkerServices, ops);
554 if(nextComponent == null) {
555 // No next component operation; just return a component for this operation.
556 return getClassGuardedInvocationComponent(linkerServices.asType(getDynamicMethod, type), type);
557 }
559 // What's below is basically:
560 // foldArguments(guardWithTest(isNotNull, identity, nextComponent.invocation), getter) only with a
561 // bunch of method signature adjustments. Basically, execute method getter; if it returns a non-null
562 // DynamicMethod, use identity to return it, otherwise delegate to nextComponent's invocation.
564 final MethodHandle typedGetter = linkerServices.asType(getDynamicMethod, type.changeReturnType(
565 DynamicMethod.class));
566 // Since it is part of the foldArgument() target, it will have extra args that we need to drop.
567 final MethodHandle returnMethodHandle = linkerServices.asType(MethodHandles.dropArguments(
568 DYNAMIC_METHOD_IDENTITY, 1, type.parameterList()), type.insertParameterTypes(0,
569 DynamicMethod.class));
570 final MethodHandle nextComponentInvocation = nextComponent.getGuardedInvocation().getInvocation();
571 // The assumption is that getGuardedInvocationComponent() already asType()'d it correctly
572 assert nextComponentInvocation.type().equals(type);
573 // Since it is part of the foldArgument() target, we have to drop an extra arg it receives.
574 final MethodHandle nextCombinedInvocation = MethodHandles.dropArguments(nextComponentInvocation, 0,
575 DynamicMethod.class);
576 // Assemble it all into a fold(guard(isNotNull, identity, nextInvocation), get)
577 final MethodHandle compositeGetter = MethodHandles.foldArguments(MethodHandles.guardWithTest(
578 IS_DYNAMIC_METHOD_NOT_NULL, returnMethodHandle, nextCombinedInvocation), typedGetter);
580 return nextComponent.compose(compositeGetter, getClassGuard(type), clazz, ValidationType.EXACT_CLASS);
581 }
582 case 3: {
583 // Must have exactly one argument: receiver
584 assertParameterCount(callSiteDescriptor, 1);
585 final DynamicMethod method = getDynamicMethod(callSiteDescriptor.getNameToken(
586 CallSiteDescriptor.NAME_OPERAND));
587 if(method == null) {
588 // We have no such method, always delegate to the next component
589 return getGuardedInvocationComponent(callSiteDescriptor, linkerServices, ops);
590 }
591 // No delegation to the next component of the composite operation; if we have a method with that name,
592 // we'll always return it at this point.
593 return getClassGuardedInvocationComponent(linkerServices.asType(MethodHandles.dropArguments(
594 MethodHandles.constant(DynamicMethod.class, method), 0, type.parameterType(0)), type), type);
595 }
596 default: {
597 // Can't do anything with more than 3 name components
598 return null;
599 }
600 }
601 }
603 private static void assertParameterCount(CallSiteDescriptor descriptor, int paramCount) {
604 if(descriptor.getMethodType().parameterCount() != paramCount) {
605 throw new BootstrapMethodError(descriptor.getName() + " must have exactly " + paramCount + " parameters.");
606 }
607 }
609 private static MethodHandle GET_PROPERTY_GETTER_HANDLE = MethodHandles.dropArguments(privateLookup.findOwnSpecial(
610 "getPropertyGetterHandle", Object.class, Object.class), 1, Object.class);
611 private final MethodHandle getPropertyGetterHandle = GET_PROPERTY_GETTER_HANDLE.bindTo(this);
613 /**
614 * @param id the property ID
615 * @return the method handle for retrieving the property, or null if the property does not exist
616 */
617 @SuppressWarnings("unused")
618 private Object getPropertyGetterHandle(Object id) {
619 return propertyGetters.get(id);
620 }
622 // Type is MethodHandle(BeanLinker, MethodType, LinkerServices, Object, String, Object), of which the two "Object"
623 // args are dropped; this makes handles with first three args conform to "Object, String, Object" though, which is
624 // a typical property setter with variable name signature (target, name, value).
625 private static final MethodHandle GET_PROPERTY_SETTER_HANDLE = MethodHandles.dropArguments(MethodHandles.dropArguments(
626 privateLookup.findOwnSpecial("getPropertySetterHandle", MethodHandle.class, MethodType.class,
627 LinkerServices.class, Object.class), 3, Object.class), 5, Object.class);
628 // Type is MethodHandle(MethodType, LinkerServices, Object, String, Object)
629 private final MethodHandle getPropertySetterHandle = GET_PROPERTY_SETTER_HANDLE.bindTo(this);
631 @SuppressWarnings("unused")
632 private MethodHandle getPropertySetterHandle(MethodType setterType, LinkerServices linkerServices, Object id) {
633 return getDynamicMethodInvocation(setterType, linkerServices, String.valueOf(id), propertySetters);
634 }
636 private static MethodHandle GET_DYNAMIC_METHOD = MethodHandles.dropArguments(privateLookup.findOwnSpecial(
637 "getDynamicMethod", DynamicMethod.class, Object.class), 1, Object.class);
638 private final MethodHandle getDynamicMethod = GET_DYNAMIC_METHOD.bindTo(this);
640 @SuppressWarnings("unused")
641 private DynamicMethod getDynamicMethod(Object name) {
642 return getDynamicMethod(String.valueOf(name), methods);
643 }
645 /**
646 * Returns a dynamic method of the specified name.
647 *
648 * @param name name of the method
649 * @return the dynamic method (either {@link SimpleDynamicMethod} or {@link OverloadedDynamicMethod}, or null if the
650 * method with the specified name does not exist.
651 */
652 DynamicMethod getDynamicMethod(String name) {
653 return getDynamicMethod(name, methods);
654 }
656 /**
657 * Find the most generic superclass that declares this getter. Since getters have zero args (aside from the
658 * receiver), they can't be overloaded, so we're free to link with an instanceof guard for the most generic one,
659 * creating more stable call sites.
660 * @param getter the getter
661 * @return getter with same name, declared on the most generic superclass/interface of the declaring class
662 */
663 private static Method getMostGenericGetter(Method getter) {
664 return getMostGenericGetter(getter.getName(), getter.getReturnType(), getter.getDeclaringClass());
665 }
667 private static Method getMostGenericGetter(String name, Class<?> returnType, Class<?> declaringClass) {
668 if(declaringClass == null) {
669 return null;
670 }
671 // Prefer interfaces
672 for(Class<?> itf: declaringClass.getInterfaces()) {
673 final Method itfGetter = getMostGenericGetter(name, returnType, itf);
674 if(itfGetter != null) {
675 return itfGetter;
676 }
677 }
678 final Method superGetter = getMostGenericGetter(name, returnType, declaringClass.getSuperclass());
679 if(superGetter != null) {
680 return superGetter;
681 }
682 if(!CheckRestrictedPackage.isRestrictedClass(declaringClass)) {
683 try {
684 return declaringClass.getMethod(name);
685 } catch(NoSuchMethodException e) {
686 // Intentionally ignored, meant to fall through
687 }
688 }
689 return null;
690 }
692 private static final class AnnotatedMethodHandle {
693 final MethodHandle handle;
694 /*private*/ final ValidationType validationType;
696 AnnotatedMethodHandle(MethodHandle handle, ValidationType validationType) {
697 this.handle = handle;
698 this.validationType = validationType;
699 }
700 }
701 }