nashorn/src/jdk/internal/dynalink/beans/AbstractJavaLinker.java
changeset 16234 86cb162cec6c
child 16245 6a1c6c8bc113
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/nashorn/src/jdk/internal/dynalink/beans/AbstractJavaLinker.java	Thu Feb 14 13:22:26 2013 +0100
@@ -0,0 +1,690 @@
+/*
+ * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * This file is available under and governed by the GNU General Public
+ * License version 2 only, as published by the Free Software Foundation.
+ * However, the following notice accompanied the original version of this
+ * file, and Oracle licenses the original version of this file under the BSD
+ * license:
+ */
+/*
+   Copyright 2009-2013 Attila Szegedi
+
+   Licensed under both the Apache License, Version 2.0 (the "Apache License")
+   and the BSD License (the "BSD License"), with licensee being free to
+   choose either of the two at their discretion.
+
+   You may not use this file except in compliance with either the Apache
+   License or the BSD License.
+
+   If you choose to use this file in compliance with the Apache License, the
+   following notice applies to you:
+
+       You may obtain a copy of the Apache License at
+
+           http://www.apache.org/licenses/LICENSE-2.0
+
+       Unless required by applicable law or agreed to in writing, software
+       distributed under the License is distributed on an "AS IS" BASIS,
+       WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+       implied. See the License for the specific language governing
+       permissions and limitations under the License.
+
+   If you choose to use this file in compliance with the BSD License, the
+   following notice applies to you:
+
+       Redistribution and use in source and binary forms, with or without
+       modification, are permitted provided that the following conditions are
+       met:
+       * Redistributions of source code must retain the above copyright
+         notice, this list of conditions and the following disclaimer.
+       * Redistributions in binary form must reproduce the above copyright
+         notice, this list of conditions and the following disclaimer in the
+         documentation and/or other materials provided with the distribution.
+       * Neither the name of the copyright holder nor the names of
+         contributors may be used to endorse or promote products derived from
+         this software without specific prior written permission.
+
+       THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+       IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+       TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+       PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL COPYRIGHT HOLDER
+       BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+       CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+       SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+       BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+       WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+       OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+       ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+package jdk.internal.dynalink.beans;
+
+import java.beans.Introspector;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import jdk.internal.dynalink.CallSiteDescriptor;
+import jdk.internal.dynalink.beans.GuardedInvocationComponent.ValidationType;
+import jdk.internal.dynalink.linker.GuardedInvocation;
+import jdk.internal.dynalink.linker.GuardingDynamicLinker;
+import jdk.internal.dynalink.linker.LinkRequest;
+import jdk.internal.dynalink.linker.LinkerServices;
+import jdk.internal.dynalink.support.CallSiteDescriptorFactory;
+import jdk.internal.dynalink.support.Guards;
+import jdk.internal.dynalink.support.Lookup;
+
+
+/**
+ * A base class for both {@link StaticClassLinker} and {@link BeanLinker}. Deals with common aspects of property
+ * exposure and method calls for both static and instance facets of a class.
+ *
+ * @author Attila Szegedi
+ */
+abstract class AbstractJavaLinker implements GuardingDynamicLinker {
+    final Class<?> clazz;
+    private final MethodHandle classGuard;
+    private final MethodHandle assignableGuard;
+    private final Map<String, AnnotatedMethodHandle> propertyGetters = new HashMap<>();
+    private final Map<String, DynamicMethod> propertySetters = new HashMap<>();
+    private final Map<String, DynamicMethod> methods = new HashMap<>();
+
+    AbstractJavaLinker(Class<?> clazz, MethodHandle classGuard) {
+        this(clazz, classGuard, classGuard);
+    }
+
+    AbstractJavaLinker(Class<?> clazz, MethodHandle classGuard, MethodHandle assignableGuard) {
+        this.clazz = clazz;
+        this.classGuard = classGuard;
+        this.assignableGuard = assignableGuard;
+
+        final FacetIntrospector introspector = createFacetIntrospector();
+        try {
+            // Add methods and properties
+            for(Method method: introspector.getMethods()) {
+                final String name = method.getName();
+                final MethodHandle methodHandle = introspector.unreflect(method);
+                // Add method
+                addMember(name, methodHandle, methods);
+                // Add the method as a property getter and/or setter
+                if(name.startsWith("get") && name.length() > 3 && method.getParameterTypes().length == 0) {
+                    // Property getter
+                    setPropertyGetter(Introspector.decapitalize(name.substring(3)), introspector.unreflect(
+                            getMostGenericGetter(method)), ValidationType.INSTANCE_OF);
+                } else if(name.startsWith("is") && name.length() > 2 && method.getParameterTypes().length == 0 &&
+                        method.getReturnType() == boolean.class) {
+                    // Boolean property getter
+                    setPropertyGetter(Introspector.decapitalize(name.substring(2)), introspector.unreflect(
+                            getMostGenericGetter(method)), ValidationType.INSTANCE_OF);
+                } else if(name.startsWith("set") && name.length() > 3 && method.getParameterTypes().length == 1) {
+                    // Property setter
+                    addMember(Introspector.decapitalize(name.substring(3)), methodHandle, propertySetters);
+                }
+            }
+
+            // Add field getter/setters as property getters/setters.
+            for(Field field: introspector.getFields()) {
+                final String name = field.getName();
+                // Only add a property getter when one is not defined already as a getXxx()/isXxx() method.
+                if(!propertyGetters.containsKey(name)) {
+                    setPropertyGetter(name, introspector.unreflectGetter(field), ValidationType.EXACT_CLASS);
+                }
+                if(!(Modifier.isFinal(field.getModifiers()) || propertySetters.containsKey(name))) {
+                    addMember(name, introspector.unreflectSetter(field), propertySetters);
+                }
+            }
+
+            // Add inner classes, but only those for which we don't hide a property with it
+            for(Map.Entry<String, MethodHandle> innerClassSpec: introspector.getInnerClassGetters().entrySet()) {
+                final String name = innerClassSpec.getKey();
+                if(!propertyGetters.containsKey(name)) {
+                    setPropertyGetter(name, innerClassSpec.getValue(), ValidationType.EXACT_CLASS);
+                }
+            }
+        } finally {
+            introspector.close();
+        }
+    }
+
+    abstract FacetIntrospector createFacetIntrospector();
+
+    void setPropertyGetter(String name, MethodHandle handle, ValidationType validationType) {
+        propertyGetters.put(name, new AnnotatedMethodHandle(handle, validationType));
+    }
+
+    private void addMember(String name, MethodHandle mh, Map<String, DynamicMethod> methodMap) {
+        final DynamicMethod existingMethod = methodMap.get(name);
+        final DynamicMethod newMethod = addMember(mh, existingMethod, clazz, name);
+        if(newMethod != existingMethod) {
+            methodMap.put(name, newMethod);
+        }
+    }
+
+    static DynamicMethod createDynamicMethod(Iterable<MethodHandle> methodHandles, Class<?> clazz, String name) {
+        DynamicMethod dynMethod = null;
+        for(MethodHandle methodHandle: methodHandles) {
+            dynMethod = addMember(methodHandle, dynMethod, clazz, name);
+        }
+        return dynMethod;
+    }
+
+    private static DynamicMethod addMember(MethodHandle mh, DynamicMethod existing, Class<?> clazz, String name) {
+        if(existing == null) {
+            return new SimpleDynamicMethod(mh, clazz, name);
+        } else if(existing.contains(mh)) {
+            return existing;
+        } else if(existing instanceof SimpleDynamicMethod) {
+            final OverloadedDynamicMethod odm = new OverloadedDynamicMethod(clazz, name);
+            odm.addMethod(((SimpleDynamicMethod)existing));
+            odm.addMethod(mh);
+            return odm;
+        } else if(existing instanceof OverloadedDynamicMethod) {
+            ((OverloadedDynamicMethod)existing).addMethod(mh);
+            return existing;
+        }
+        throw new AssertionError();
+    }
+
+    @Override
+    public GuardedInvocation getGuardedInvocation(LinkRequest request, final LinkerServices linkerServices)
+            throws Exception {
+        final LinkRequest ncrequest = request.withoutRuntimeContext();
+        // BeansLinker already checked that the name is at least 2 elements long and the first element is "dyn".
+        final CallSiteDescriptor callSiteDescriptor = ncrequest.getCallSiteDescriptor();
+        final String op = callSiteDescriptor.getNameToken(CallSiteDescriptor.OPERATOR);
+        // Either dyn:callMethod:name(this[,args]) or dyn:callMethod(this,name[,args]).
+        if("callMethod" == op) {
+            return getCallPropWithThis(callSiteDescriptor, linkerServices);
+        }
+        List<String> operations = CallSiteDescriptorFactory.tokenizeOperators(callSiteDescriptor);
+        while(!operations.isEmpty()) {
+            final GuardedInvocationComponent gic = getGuardedInvocationComponent(callSiteDescriptor, linkerServices,
+                    operations);
+            if(gic != null) {
+                return gic.getGuardedInvocation();
+            }
+            operations = pop(operations);
+        }
+        return null;
+    }
+
+    protected GuardedInvocationComponent getGuardedInvocationComponent(CallSiteDescriptor callSiteDescriptor,
+            LinkerServices linkerServices, List<String> operations) throws Exception {
+        if(operations.isEmpty()) {
+            return null;
+        }
+        final String op = operations.get(0);
+        // Either dyn:getProp:name(this) or dyn:getProp(this, name)
+        if("getProp".equals(op)) {
+            return getPropertyGetter(callSiteDescriptor, linkerServices, pop(operations));
+        }
+        // Either dyn:setProp:name(this, value) or dyn:setProp(this, name, value)
+        if("setProp".equals(op)) {
+            return getPropertySetter(callSiteDescriptor, linkerServices, pop(operations));
+        }
+        // Either dyn:getMethod:name(this), or dyn:getMethod(this, name)
+        if("getMethod".equals(op)) {
+            return getMethodGetter(callSiteDescriptor, linkerServices, pop(operations));
+        }
+        return null;
+    }
+
+    static final <T> List<T> pop(List<T> l) {
+        return l.subList(1, l.size());
+    }
+
+    MethodHandle getClassGuard(CallSiteDescriptor desc) {
+        return getClassGuard(desc.getMethodType());
+    }
+
+    MethodHandle getClassGuard(MethodType type) {
+        return Guards.asType(classGuard, type);
+    }
+
+    GuardedInvocationComponent getClassGuardedInvocationComponent(MethodHandle invocation, MethodType type) {
+        return new GuardedInvocationComponent(invocation, getClassGuard(type), clazz, ValidationType.EXACT_CLASS);
+    }
+
+    private MethodHandle getAssignableGuard(MethodType type) {
+        return Guards.asType(assignableGuard, type);
+    }
+
+    private GuardedInvocation getCallPropWithThis(CallSiteDescriptor callSiteDescriptor, LinkerServices linkerServices) {
+        switch(callSiteDescriptor.getNameTokenCount()) {
+            case 3: {
+                return createGuardedDynamicMethodInvocation(callSiteDescriptor.getMethodType(), linkerServices,
+                        callSiteDescriptor.getNameToken(CallSiteDescriptor.NAME_OPERAND), methods);
+            }
+            default: {
+                return null;
+            }
+        }
+    }
+
+    private GuardedInvocation createGuardedDynamicMethodInvocation(MethodType callSiteType,
+            LinkerServices linkerServices, String methodName, Map<String, DynamicMethod> methodMap){
+        final MethodHandle inv = getDynamicMethodInvocation(callSiteType, linkerServices, methodName, methodMap);
+        return inv == null ? null : new GuardedInvocation(inv, getClassGuard(callSiteType));
+    }
+
+    private static MethodHandle getDynamicMethodInvocation(MethodType callSiteType, LinkerServices linkerServices,
+            String methodName, Map<String, DynamicMethod> methodMap) {
+        final DynamicMethod dynaMethod = getDynamicMethod(methodName, methodMap);
+        return dynaMethod != null ? dynaMethod.getInvocation(callSiteType, linkerServices) : null;
+    }
+
+    private static DynamicMethod getDynamicMethod(String methodName, Map<String, DynamicMethod> methodMap) {
+        final DynamicMethod dynaMethod = methodMap.get(methodName);
+        return dynaMethod != null ? dynaMethod : getExplicitSignatureDynamicMethod(methodName, methodMap);
+    }
+
+    private static SimpleDynamicMethod getExplicitSignatureDynamicMethod(String methodName,
+            Map<String, DynamicMethod> methodsMap) {
+        // What's below is meant to support the "name(type, type, ...)" syntax that programmers can use in a method name
+        // to manually pin down an exact overloaded variant. This is not usually required, as the overloaded method
+        // resolution works correctly in almost every situation. However, in presence of many language-specific
+        // conversions with a radically dynamic language, most overloaded methods will end up being constantly selected
+        // at invocation time, so a programmer knowledgable of the situation might choose to pin down an exact overload
+        // for performance reasons.
+
+        // Is the method name lexically of the form "name(types)"?
+        final int lastChar = methodName.length() - 1;
+        if(methodName.charAt(lastChar) != ')') {
+            return null;
+        }
+        final int openBrace = methodName.indexOf('(');
+        if(openBrace == -1) {
+            return null;
+        }
+
+        // Find an existing method for the "name" part
+        final DynamicMethod simpleNamedMethod = methodsMap.get(methodName.substring(0, openBrace));
+        if(simpleNamedMethod == null) {
+            return null;
+        }
+
+        // Try to get a narrowed dynamic method for the explicit parameter types.
+        return simpleNamedMethod.getMethodForExactParamTypes(methodName.substring(openBrace + 1, lastChar));
+    }
+
+    private static final MethodHandle IS_METHOD_HANDLE_NOT_NULL = Guards.isNotNull().asType(MethodType.methodType(
+            boolean.class, MethodHandle.class));
+    private static final MethodHandle CONSTANT_NULL_DROP_METHOD_HANDLE = MethodHandles.dropArguments(
+            MethodHandles.constant(Object.class, null), 0, MethodHandle.class);
+
+    private GuardedInvocationComponent getPropertySetter(CallSiteDescriptor callSiteDescriptor,
+            LinkerServices linkerServices, List<String> operations) throws Exception {
+        final MethodType type = callSiteDescriptor.getMethodType();
+        switch(callSiteDescriptor.getNameTokenCount()) {
+            case 2: {
+                // Must have three arguments: target object, property name, and property value.
+                assertParameterCount(callSiteDescriptor, 3);
+
+                // What's below is basically:
+                //   foldArguments(guardWithTest(isNotNull, invoke, null|nextComponent.invocation),
+                //     get_setter_handle(type, linkerServices))
+                // only with a bunch of method signature adjustments. Basically, retrieve method setter
+                // MethodHandle; if it is non-null, invoke it, otherwise either return null, or delegate to next
+                // component's invocation.
+
+                // Call site type is "ret_type(object_type,property_name_type,property_value_type)", which we'll
+                // abbreviate to R(O, N, V) going forward.
+                // We want setters that conform to "R(O, V)"
+                final MethodType setterType = type.dropParameterTypes(1, 2);
+                // Bind property setter handle to the expected setter type and linker services. Type is
+                // MethodHandle(Object, String, Object)
+                final MethodHandle boundGetter = MethodHandles.insertArguments(getPropertySetterHandle, 0, setterType,
+                        linkerServices);
+
+                // Cast getter to MethodHandle(O, N, V)
+                final MethodHandle typedGetter = linkerServices.asType(boundGetter, type.changeReturnType(
+                        MethodHandle.class));
+
+                // Handle to invoke the setter R(MethodHandle, O, V)
+                final MethodHandle invokeHandle = MethodHandles.exactInvoker(setterType);
+                // Handle to invoke the setter, dropping unnecessary fold arguments R(MethodHandle, O, N, V)
+                final MethodHandle invokeHandleFolded = MethodHandles.dropArguments(invokeHandle, 2, type.parameterType(
+                        1));
+                final GuardedInvocationComponent nextComponent = getGuardedInvocationComponent(callSiteDescriptor,
+                        linkerServices, operations);
+
+                final MethodHandle fallbackFolded;
+                if(nextComponent == null) {
+                    // Object(MethodHandle)->R(MethodHandle, O, N, V); returns constant null
+                    fallbackFolded = MethodHandles.dropArguments(CONSTANT_NULL_DROP_METHOD_HANDLE, 1,
+                            type.parameterList()).asType(type.insertParameterTypes(0, MethodHandle.class));
+                } else {
+                    // R(O, N, V)->R(MethodHandle, O, N, V); adapts the next component's invocation to drop the
+                    // extra argument resulting from fold
+                    fallbackFolded = MethodHandles.dropArguments(nextComponent.getGuardedInvocation().getInvocation(),
+                            0, MethodHandle.class);
+                }
+
+                // fold(R(MethodHandle, O, N, V), MethodHandle(O, N, V))
+                final MethodHandle compositeSetter = MethodHandles.foldArguments(MethodHandles.guardWithTest(
+                            IS_METHOD_HANDLE_NOT_NULL, invokeHandleFolded, fallbackFolded), typedGetter);
+                if(nextComponent == null) {
+                    return getClassGuardedInvocationComponent(compositeSetter, type);
+                } else {
+                    return nextComponent.compose(compositeSetter, getClassGuard(type), clazz,
+                            ValidationType.EXACT_CLASS);
+                }
+            }
+            case 3: {
+                // Must have two arguments: target object and property value
+                assertParameterCount(callSiteDescriptor, 2);
+                final GuardedInvocation gi = createGuardedDynamicMethodInvocation(callSiteDescriptor.getMethodType(),
+                        linkerServices, callSiteDescriptor.getNameToken(CallSiteDescriptor.NAME_OPERAND),
+                        propertySetters);
+                // If we have a property setter with this name, this composite operation will always stop here
+                if(gi != null) {
+                    return new GuardedInvocationComponent(gi, clazz, ValidationType.EXACT_CLASS);
+                }
+                // If we don't have a property setter with this name, always fall back to the next operation in the
+                // composite (if any)
+                return getGuardedInvocationComponent(callSiteDescriptor, linkerServices, operations);
+            }
+            default: {
+                // More than two name components; don't know what to do with it.
+                return null;
+            }
+        }
+    }
+
+    private static final Lookup privateLookup = new Lookup(MethodHandles.lookup());
+
+    private static final MethodHandle IS_ANNOTATED_HANDLE_NOT_NULL = Guards.isNotNull().asType(MethodType.methodType(
+            boolean.class, AnnotatedMethodHandle.class));
+    private static final MethodHandle CONSTANT_NULL_DROP_ANNOTATED_HANDLE = MethodHandles.dropArguments(
+            MethodHandles.constant(Object.class, null), 0, AnnotatedMethodHandle.class);
+    private static final MethodHandle GET_ANNOTATED_HANDLE = privateLookup.findGetter(AnnotatedMethodHandle.class,
+            "handle", MethodHandle.class);
+    private static final MethodHandle GENERIC_PROPERTY_GETTER_HANDLER_INVOKER = MethodHandles.filterArguments(
+            MethodHandles.invoker(MethodType.methodType(Object.class, Object.class)), 0, GET_ANNOTATED_HANDLE);
+
+    private GuardedInvocationComponent getPropertyGetter(CallSiteDescriptor callSiteDescriptor,
+            LinkerServices linkerServices, List<String> ops) throws Exception {
+        final MethodType type = callSiteDescriptor.getMethodType();
+        switch(callSiteDescriptor.getNameTokenCount()) {
+            case 2: {
+                // Must have exactly two arguments: receiver and name
+                assertParameterCount(callSiteDescriptor, 2);
+
+                // What's below is basically:
+                //   foldArguments(guardWithTest(isNotNull, invoke(get_handle), null|nextComponent.invocation), get_getter_handle)
+                // only with a bunch of method signature adjustments. Basically, retrieve method getter
+                // AnnotatedMethodHandle; if it is non-null, invoke its "handle" field, otherwise either return null,
+                // or delegate to next component's invocation.
+
+                final MethodHandle typedGetter = linkerServices.asType(getPropertyGetterHandle, type.changeReturnType(
+                        AnnotatedMethodHandle.class));
+                // Object(AnnotatedMethodHandle, Object)->R(AnnotatedMethodHandle, T0)
+                final MethodHandle invokeHandleTyped = linkerServices.asType(GENERIC_PROPERTY_GETTER_HANDLER_INVOKER,
+                        MethodType.methodType(type.returnType(), AnnotatedMethodHandle.class, type.parameterType(0)));
+                // Since it's in the target of a fold, drop the unnecessary second argument
+                // R(AnnotatedMethodHandle, T0)->R(AnnotatedMethodHandle, T0, T1)
+                final MethodHandle invokeHandleFolded = MethodHandles.dropArguments(invokeHandleTyped, 2,
+                        type.parameterType(1));
+                final GuardedInvocationComponent nextComponent = getGuardedInvocationComponent(callSiteDescriptor,
+                        linkerServices, ops);
+
+                final MethodHandle fallbackFolded;
+                if(nextComponent == null) {
+                    // Object(AnnotatedMethodHandle)->R(AnnotatedMethodHandle, T0, T1); returns constant null
+                    fallbackFolded = MethodHandles.dropArguments(CONSTANT_NULL_DROP_ANNOTATED_HANDLE, 1,
+                            type.parameterList()).asType(type.insertParameterTypes(0, AnnotatedMethodHandle.class));
+                } else {
+                    // R(T0, T1)->R(AnnotatedMethodHAndle, T0, T1); adapts the next component's invocation to drop the
+                    // extra argument resulting from fold
+                    fallbackFolded = MethodHandles.dropArguments(nextComponent.getGuardedInvocation().getInvocation(),
+                            0, AnnotatedMethodHandle.class);
+                }
+
+                // fold(R(AnnotatedMethodHandle, T0, T1), AnnotatedMethodHandle(T0, T1))
+                final MethodHandle compositeGetter = MethodHandles.foldArguments(MethodHandles.guardWithTest(
+                            IS_ANNOTATED_HANDLE_NOT_NULL, invokeHandleFolded, fallbackFolded), typedGetter);
+                if(nextComponent == null) {
+                    return getClassGuardedInvocationComponent(compositeGetter, type);
+                } else {
+                    return nextComponent.compose(compositeGetter, getClassGuard(type), clazz,
+                            ValidationType.EXACT_CLASS);
+                }
+            }
+            case 3: {
+                // Must have exactly one argument: receiver
+                assertParameterCount(callSiteDescriptor, 1);
+                // Fixed name
+                final AnnotatedMethodHandle annGetter = propertyGetters.get(callSiteDescriptor.getNameToken(
+                        CallSiteDescriptor.NAME_OPERAND));
+                if(annGetter == null) {
+                    // We have no such property, always delegate to the next component operation
+                    return getGuardedInvocationComponent(callSiteDescriptor, linkerServices, ops);
+                }
+                final MethodHandle getter = annGetter.handle;
+                // NOTE: since property getters (not field getters!) are no-arg, we don't have to worry about them being
+                // overloaded in a subclass. Therefore, we can discover the most abstract superclass that has the
+                // method, and use that as the guard with Guards.isInstance() for a more stably linked call site. If
+                // we're linking against a field getter, don't make the assumption.
+                // NOTE: No delegation to the next component operation if we have a property with this name, even if its
+                // value is null.
+                final ValidationType validationType = annGetter.validationType;
+                return new GuardedInvocationComponent(linkerServices.asType(getter, type), getGuard(validationType,
+                        type), clazz, validationType);
+            }
+            default: {
+                // Can't do anything with more than 3 name components
+                return null;
+            }
+        }
+    }
+
+    private MethodHandle getGuard(ValidationType validationType, MethodType methodType) {
+        switch(validationType) {
+            case EXACT_CLASS: {
+                return getClassGuard(methodType);
+            }
+            case INSTANCE_OF: {
+                return getAssignableGuard(methodType);
+            }
+            case IS_ARRAY: {
+                return Guards.isArray(0, methodType);
+            }
+            case NONE: {
+                return null;
+            }
+        }
+        throw new AssertionError();
+    }
+
+    private static final MethodHandle IS_DYNAMIC_METHOD_NOT_NULL = Guards.asType(Guards.isNotNull(),
+            MethodType.methodType(boolean.class, DynamicMethod.class));
+    private static final MethodHandle DYNAMIC_METHOD_IDENTITY = MethodHandles.identity(DynamicMethod.class);
+
+    private GuardedInvocationComponent getMethodGetter(CallSiteDescriptor callSiteDescriptor,
+            LinkerServices linkerServices, List<String> ops) throws Exception {
+        final MethodType type = callSiteDescriptor.getMethodType();
+        switch(callSiteDescriptor.getNameTokenCount()) {
+            case 2: {
+                // Must have exactly two arguments: receiver and name
+                assertParameterCount(callSiteDescriptor, 2);
+                final GuardedInvocationComponent nextComponent = getGuardedInvocationComponent(callSiteDescriptor,
+                        linkerServices, ops);
+                if(nextComponent == null) {
+                    // No next component operation; just return a component for this operation.
+                    return getClassGuardedInvocationComponent(linkerServices.asType(getDynamicMethod, type), type);
+                } else {
+                    // What's below is basically:
+                    //   foldArguments(guardWithTest(isNotNull, identity, nextComponent.invocation), getter)
+                    // only with a bunch of method signature adjustments. Basically, execute method getter; if
+                    // it returns a non-null DynamicMethod, use identity to return it, otherwise delegate to
+                    // nextComponent's invocation.
+
+                    final MethodHandle typedGetter = linkerServices.asType(getDynamicMethod, type.changeReturnType(
+                            DynamicMethod.class));
+                    // Since it is part of the foldArgument() target, it will have extra args that we need to drop.
+                    final MethodHandle returnMethodHandle = linkerServices.asType(MethodHandles.dropArguments(
+                            DYNAMIC_METHOD_IDENTITY, 1, type.parameterList()), type.insertParameterTypes(0,
+                                    DynamicMethod.class));
+                    final MethodHandle nextComponentInvocation = nextComponent.getGuardedInvocation().getInvocation();
+                    // The assumption is that getGuardedInvocationComponent() already asType()'d it correctly
+                    assert nextComponentInvocation.type().equals(type);
+                    // Since it is part of the foldArgument() target, we have to drop an extra arg it receives.
+                    final MethodHandle nextCombinedInvocation = MethodHandles.dropArguments(nextComponentInvocation, 0,
+                            DynamicMethod.class);
+                    // Assemble it all into a fold(guard(isNotNull, identity, nextInvocation), get)
+                    final MethodHandle compositeGetter = MethodHandles.foldArguments(MethodHandles.guardWithTest(
+                            IS_DYNAMIC_METHOD_NOT_NULL, returnMethodHandle, nextCombinedInvocation), typedGetter);
+
+                    return nextComponent.compose(compositeGetter, getClassGuard(type), clazz,
+                            ValidationType.EXACT_CLASS);
+                }
+            }
+            case 3: {
+                // Must have exactly one argument: receiver
+                assertParameterCount(callSiteDescriptor, 1);
+                final DynamicMethod method = getDynamicMethod(callSiteDescriptor.getNameToken(
+                        CallSiteDescriptor.NAME_OPERAND));
+                if(method == null) {
+                    // We have no such method, always delegate to the next component
+                    return getGuardedInvocationComponent(callSiteDescriptor, linkerServices, ops);
+                }
+                // No delegation to the next component of the composite operation; if we have a method with that name,
+                // we'll always return it at this point.
+                return getClassGuardedInvocationComponent(linkerServices.asType(MethodHandles.dropArguments(
+                        MethodHandles.constant(DynamicMethod.class, method), 0, type.parameterType(0)), type), type);
+            }
+            default: {
+                // Can't do anything with more than 3 name components
+                return null;
+            }
+        }
+    }
+
+    private static void assertParameterCount(CallSiteDescriptor descriptor, int paramCount) {
+        if(descriptor.getMethodType().parameterCount() != paramCount) {
+            throw new BootstrapMethodError(descriptor.getName() + " must have exactly " + paramCount + " parameters.");
+        }
+    }
+
+    private static MethodHandle GET_PROPERTY_GETTER_HANDLE = MethodHandles.dropArguments(privateLookup.findOwnSpecial(
+            "getPropertyGetterHandle", Object.class, Object.class), 1, Object.class);
+    private final MethodHandle getPropertyGetterHandle = GET_PROPERTY_GETTER_HANDLE.bindTo(this);
+
+    /**
+     * @param id the property ID
+     * @return the method handle for retrieving the property, or null if the property does not exist
+     */
+    @SuppressWarnings("unused")
+    private Object getPropertyGetterHandle(Object id) {
+        return propertyGetters.get(id);
+    }
+
+    // Type is MethodHandle(BeanLinker, MethodType, LinkerServices, Object, String, Object), of which the two "Object"
+    // args are dropped; this makes handles with first three args conform to "Object, String, Object" though, which is
+    // a typical property setter with variable name signature (target, name, value).
+    private static final MethodHandle GET_PROPERTY_SETTER_HANDLE = MethodHandles.dropArguments(MethodHandles.dropArguments(
+            privateLookup.findOwnSpecial("getPropertySetterHandle", MethodHandle.class, MethodType.class,
+                    LinkerServices.class, Object.class), 3, Object.class), 5, Object.class);
+    // Type is MethodHandle(MethodType, LinkerServices, Object, String, Object)
+    private final MethodHandle getPropertySetterHandle = GET_PROPERTY_SETTER_HANDLE.bindTo(this);
+
+    @SuppressWarnings("unused")
+    private MethodHandle getPropertySetterHandle(MethodType setterType, LinkerServices linkerServices, Object id) {
+        return getDynamicMethodInvocation(setterType, linkerServices, String.valueOf(id), propertySetters);
+    }
+
+    private static MethodHandle GET_DYNAMIC_METHOD = MethodHandles.dropArguments(privateLookup.findOwnSpecial(
+            "getDynamicMethod", DynamicMethod.class, Object.class), 1, Object.class);
+    private final MethodHandle getDynamicMethod = GET_DYNAMIC_METHOD.bindTo(this);
+
+    @SuppressWarnings("unused")
+    private DynamicMethod getDynamicMethod(Object name) {
+        return getDynamicMethod(String.valueOf(name), methods);
+    }
+
+    /**
+     * Returns a dynamic method of the specified name.
+     *
+     * @param name name of the method
+     * @return the dynamic method (either {@link SimpleDynamicMethod} or {@link OverloadedDynamicMethod}, or null if the
+     * method with the specified name does not exist.
+     */
+    public DynamicMethod getDynamicMethod(String name) {
+        return getDynamicMethod(name, methods);
+    }
+
+    /**
+     * Find the most generic superclass that declares this getter. Since getters have zero args (aside from the
+     * receiver), they can't be overloaded, so we're free to link with an instanceof guard for the most generic one,
+     * creating more stable call sites.
+     * @param getter the getter
+     * @return getter with same name, declared on the most generic superclass/interface of the declaring class
+     */
+    private static Method getMostGenericGetter(Method getter) {
+        return getMostGenericGetter(getter.getName(), getter.getReturnType(), getter.getDeclaringClass());
+    }
+
+    private static Method getMostGenericGetter(String name, Class<?> returnType, Class<?> declaringClass) {
+        if(declaringClass == null) {
+            return null;
+        }
+        // Prefer interfaces
+        for(Class<?> itf: declaringClass.getInterfaces()) {
+            final Method itfGetter = getMostGenericGetter(name, returnType, itf);
+            if(itfGetter != null) {
+                return itfGetter;
+            }
+        }
+        final Method superGetter = getMostGenericGetter(name, returnType, declaringClass.getSuperclass());
+        if(superGetter != null) {
+            return superGetter;
+        }
+        if(!CheckRestrictedPackage.isRestrictedClass(declaringClass)) {
+            try {
+                return declaringClass.getMethod(name);
+            } catch(NoSuchMethodException e) {
+                // Intentionally ignored, meant to fall through
+            }
+        }
+        return null;
+    }
+
+    private static final class AnnotatedMethodHandle {
+        final MethodHandle handle;
+        /*private*/ final ValidationType validationType;
+
+        AnnotatedMethodHandle(MethodHandle handle, ValidationType validationType) {
+            this.handle = handle;
+            this.validationType = validationType;
+        }
+    }
+}
\ No newline at end of file