1 /*
   2  * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
   3  *
   4  * Redistribution and use in source and binary forms, with or without
   5  * modification, are permitted provided that the following conditions
   6  * are met:
   7  *
   8  *   - Redistributions of source code must retain the above copyright
   9  *     notice, this list of conditions and the following disclaimer.
  10  *
  11  *   - Redistributions in binary form must reproduce the above copyright
  12  *     notice, this list of conditions and the following disclaimer in the
  13  *     documentation and/or other materials provided with the distribution.
  14  *
  15  *   - Neither the name of Oracle nor the names of its
  16  *     contributors may be used to endorse or promote products derived
  17  *     from this software without specific prior written permission.
  18  *
  19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
  20  * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
  21  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
  22  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  23  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  24  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  25  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  26  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  27  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  28  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  29  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  30  */
  31 
  32 import java.lang.invoke.MethodHandle;
  33 import java.lang.invoke.MethodHandles;
  34 import java.lang.invoke.MethodType;
  35 import java.util.ArrayList;
  36 import java.util.List;
  37 import jdk.dynalink.CallSiteDescriptor;
  38 import jdk.dynalink.NamedOperation;
  39 import jdk.dynalink.NamespaceOperation;
  40 import jdk.dynalink.Operation;
  41 import jdk.dynalink.StandardNamespace;
  42 import jdk.dynalink.StandardOperation;
  43 import jdk.dynalink.beans.BeansLinker;
  44 import jdk.dynalink.linker.GuardedInvocation;
  45 import jdk.dynalink.linker.GuardingDynamicLinker;
  46 import jdk.dynalink.linker.GuardingDynamicLinkerExporter;
  47 import jdk.dynalink.linker.LinkRequest;
  48 import jdk.dynalink.linker.LinkerServices;
  49 import jdk.dynalink.linker.TypeBasedGuardingDynamicLinker;
  50 import jdk.dynalink.linker.support.Guards;
  51 import jdk.dynalink.linker.support.Lookup;
  52 
  53 /**
  54  * This is a dynalink pluggable linker (see http://openjdk.java.net/jeps/276).
  55  * This linker routes missing methods to Smalltalk-style doesNotUnderstand method.
  56  * Object of any Java class that implements MissingMethodHandler is handled by this linker.
  57  * For any method call, if a matching Java method is found, it is called. If there is no
  58  * method by that name, then MissingMethodHandler.doesNotUnderstand is called.
  59  */
  60 public final class MissingMethodLinkerExporter extends GuardingDynamicLinkerExporter {
  61     static {
  62         System.out.println("pluggable dynalink missing method linker loaded");
  63     }
  64 
  65     // represents a MissingMethod - just stores as name and also serves a guard type
  66     public static class MissingMethod {
  67         private final String name;
  68 
  69         public MissingMethod(final String name) {
  70             this.name = name;
  71         }
  72 
  73         public String getName() {
  74             return name;
  75         }
  76     }
  77 
  78     // MissingMethodHandler.doesNotUnderstand method
  79     private static final MethodHandle DOES_NOT_UNDERSTAND;
  80 
  81     // type of MissingMethodHandler - but "this" and String args are flipped
  82     private static final MethodType FLIPPED_DOES_NOT_UNDERSTAND_TYPE;
  83 
  84     // "is this a MissingMethod?" guard
  85     private static final MethodHandle IS_MISSING_METHOD;
  86 
  87     // MissingMethod object->it's name filter
  88     private static final MethodHandle MISSING_METHOD_TO_NAME;
  89 
  90     static {
  91         DOES_NOT_UNDERSTAND = Lookup.PUBLIC.findVirtual(
  92             MissingMethodHandler.class,
  93             "doesNotUnderstand",
  94             MethodType.methodType(Object.class, String.class, Object[].class));
  95         FLIPPED_DOES_NOT_UNDERSTAND_TYPE =
  96             MethodType.methodType(Object.class, String.class, MissingMethodHandler.class, Object[].class);
  97         IS_MISSING_METHOD = Guards.isOfClass(MissingMethod.class,
  98             MethodType.methodType(Boolean.TYPE, Object.class));
  99         MISSING_METHOD_TO_NAME = Lookup.PUBLIC.findVirtual(MissingMethod.class,
 100             "getName", MethodType.methodType(String.class));
 101     }
 102 
 103     @Override
 104     public List<GuardingDynamicLinker> get() {
 105         final ArrayList<GuardingDynamicLinker> linkers = new ArrayList<>();
 106         final BeansLinker beansLinker = new BeansLinker();
 107         linkers.add(new TypeBasedGuardingDynamicLinker() {
 108             // only handles MissingMethodHandler and MissingMethod objects
 109             @Override
 110             public boolean canLinkType(final Class<?> type) {
 111                 return
 112                     MissingMethodHandler.class.isAssignableFrom(type) ||
 113                     type == MissingMethod.class;
 114             }
 115 
 116             @Override
 117             public GuardedInvocation getGuardedInvocation(final LinkRequest request,
 118                 final LinkerServices linkerServices) throws Exception {
 119                 final Object self = request.getReceiver();
 120                 final CallSiteDescriptor desc = request.getCallSiteDescriptor();
 121 
 122                 // any method call is done by two steps. Step (1) GET_METHOD and (2) is CALL
 123                 // For step (1), we check if GET_METHOD can succeed by Java linker, if so
 124                 // we return that method object. If not, we return a MissingMethod object.
 125                 if (self instanceof MissingMethodHandler) {
 126                     // Check if this is a named GET_METHOD first.
 127                     final Operation namedOp = desc.getOperation();
 128                     final Operation namespaceOp = NamedOperation.getBaseOperation(namedOp);
 129                     final Operation op = NamespaceOperation.getBaseOperation(namespaceOp);
 130 
 131                     final boolean isGetMethod = op == StandardOperation.GET && StandardNamespace.findFirst(namespaceOp) == StandardNamespace.METHOD;
 132                     final Object name = NamedOperation.getName(namedOp);
 133                     if (isGetMethod && name instanceof String) {
 134                         final GuardingDynamicLinker javaLinker = beansLinker.getLinkerForClass(self.getClass());
 135                         GuardedInvocation inv;
 136                         try {
 137                             inv = javaLinker.getGuardedInvocation(request, linkerServices);
 138                         } catch (final Throwable th) {
 139                             inv = null;
 140                         }
 141 
 142                         final String nameStr = name.toString();
 143                         if (inv == null) {
 144                             // use "this" for just guard and drop it -- return a constant Method handle
 145                             // that returns a newly created MissingMethod object
 146                             final MethodHandle mh = MethodHandles.constant(Object.class, new MissingMethod(nameStr));
 147                             inv = new GuardedInvocation(
 148                                 MethodHandles.dropArguments(mh, 0, Object.class),
 149                                 Guards.isOfClass(self.getClass(), MethodType.methodType(Boolean.TYPE, Object.class)));
 150                         }
 151 
 152                         return inv;
 153                     }
 154                 } else if (self instanceof MissingMethod) {
 155                     // This is step (2). We call MissingMethodHandler.doesNotUnderstand here
 156                     // Check if this is this a CALL first.
 157                     final boolean isCall = NamedOperation.getBaseOperation(desc.getOperation()) == StandardOperation.CALL;
 158                     if (isCall) {
 159                         MethodHandle mh = DOES_NOT_UNDERSTAND;
 160 
 161                         // flip "this" and method name (String)
 162                         mh = MethodHandles.permuteArguments(mh, FLIPPED_DOES_NOT_UNDERSTAND_TYPE, 1, 0, 2);
 163 
 164                         // collect rest of the arguments as vararg
 165                         mh = mh.asCollector(Object[].class, desc.getMethodType().parameterCount() - 2);
 166 
 167                         // convert MissingMethod object to it's name
 168                         mh = MethodHandles.filterArguments(mh, 0, MISSING_METHOD_TO_NAME);
 169                         return new GuardedInvocation(mh, IS_MISSING_METHOD);
 170                     }
 171                 }
 172 
 173                 return null;
 174             }
 175         });
 176         return linkers;
 177     }
 178 }