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.CompositeOperation;
  39 import jdk.dynalink.NamedOperation;
  40 import jdk.dynalink.Operation;
  41 import jdk.dynalink.StandardOperation;
  42 import jdk.dynalink.beans.BeansLinker;
  43 import jdk.dynalink.linker.GuardingDynamicLinker;
  44 import jdk.dynalink.linker.GuardingDynamicLinkerExporter;
  45 import jdk.dynalink.linker.GuardedInvocation;
  46 import jdk.dynalink.linker.TypeBasedGuardingDynamicLinker;
  47 import jdk.dynalink.linker.LinkRequest;
  48 import jdk.dynalink.linker.LinkerServices;
  49 import jdk.dynalink.linker.support.Guards;
  50 import jdk.dynalink.linker.support.Lookup;
  51 
  52 /**
  53  * This is a dynalink pluggable linker (see http://openjdk.java.net/jeps/276).
  54  * This linker routes missing methods to Smalltalk-style doesNotUnderstand method.
  55  * Object of any Java class that implements MissingMethodHandler is handled by this linker.
  56  * For any method call, if a matching Java method is found, it is called. If there is no
  57  * method by that name, then MissingMethodHandler.doesNotUnderstand is called.
  58  */
  59 public final class MissingMethodLinkerExporter extends GuardingDynamicLinkerExporter {
  60     static {
  61         System.out.println("pluggable dynalink missing method linker loaded");
  62     }
  63 
  64     // represents a MissingMethod - just stores as name and also serves a guard type
  65     public static class MissingMethod {
  66         private final String name;
  67 
  68         public MissingMethod(String name) {
  69             this.name = name;
  70         }
  71 
  72         public String getName() {
  73             return name;
  74         }
  75     }
  76 
  77     // MissingMethodHandler.doesNotUnderstand method
  78     private static final MethodHandle DOES_NOT_UNDERSTAND;
  79 
  80     // type of MissingMethodHandler - but "this" and String args are flipped
  81     private static final MethodType FLIPPED_DOES_NOT_UNDERSTAND_TYPE;
  82 
  83     // "is this a MissingMethod?" guard
  84     private static final MethodHandle IS_MISSING_METHOD;
  85 
  86     // MissingMethod object->it's name filter
  87     private static final MethodHandle MISSING_METHOD_TO_NAME;
  88 
  89     static {
  90         DOES_NOT_UNDERSTAND = Lookup.PUBLIC.findVirtual(
  91             MissingMethodHandler.class,
  92             "doesNotUnderstand",
  93             MethodType.methodType(Object.class, String.class, Object[].class));
  94         FLIPPED_DOES_NOT_UNDERSTAND_TYPE =
  95             MethodType.methodType(Object.class, String.class, MissingMethodHandler.class, Object[].class);
  96         IS_MISSING_METHOD = Guards.isOfClass(MissingMethod.class,
  97             MethodType.methodType(Boolean.TYPE, Object.class));
  98         MISSING_METHOD_TO_NAME = Lookup.PUBLIC.findVirtual(MissingMethod.class,
  99             "getName", MethodType.methodType(String.class));
 100     }
 101 
 102     // locate the first standard operation from the call descriptor
 103     private static StandardOperation getFirstStandardOperation(final CallSiteDescriptor desc) {
 104         final Operation base = NamedOperation.getBaseOperation(desc.getOperation());
 105         if (base instanceof StandardOperation) {
 106             return (StandardOperation)base;
 107         } else if (base instanceof CompositeOperation) {
 108             final CompositeOperation cop = (CompositeOperation)base;
 109             for(int i = 0; i < cop.getOperationCount(); ++i) {
 110                 final Operation op = cop.getOperation(i);
 111                 if (op instanceof StandardOperation) {
 112                     return (StandardOperation)op;
 113                 }
 114             }
 115         }
 116         return null;
 117     }
 118 
 119     @Override
 120     public List<GuardingDynamicLinker> get() {
 121         final ArrayList<GuardingDynamicLinker> linkers = new ArrayList<>();
 122         final BeansLinker beansLinker = new BeansLinker();
 123         linkers.add(new TypeBasedGuardingDynamicLinker() {
 124             // only handles MissingMethodHandler and MissingMethod objects
 125             @Override
 126             public boolean canLinkType(final Class<?> type) {
 127                 return
 128                     MissingMethodHandler.class.isAssignableFrom(type) ||
 129                     type == MissingMethod.class;
 130             }
 131 
 132             @Override
 133             public GuardedInvocation getGuardedInvocation(LinkRequest request,
 134                 LinkerServices linkerServices) throws Exception {
 135                 final Object self = request.getReceiver();
 136                 CallSiteDescriptor desc = request.getCallSiteDescriptor();
 137 
 138                 // any method call is done by two steps. Step (1) GET_METHOD and (2) is CALL
 139                 // For step (1), we check if GET_METHOD can succeed by Java linker, if so
 140                 // we return that method object. If not, we return a MissingMethod object.
 141                 if (self instanceof MissingMethodHandler) {
 142                     // Check if this is a named GET_METHOD first.
 143                     boolean isGetMethod = getFirstStandardOperation(desc) == StandardOperation.GET_METHOD;
 144                     Object name = NamedOperation.getName(desc.getOperation());
 145                     if (isGetMethod && name instanceof String) {
 146                         GuardingDynamicLinker javaLinker = beansLinker.getLinkerForClass(self.getClass());
 147                         GuardedInvocation inv;
 148                         try {
 149                             inv = javaLinker.getGuardedInvocation(request, linkerServices);
 150                         } catch (final Throwable th) {
 151                             inv = null;
 152                         }
 153 
 154                         String nameStr = name.toString();
 155                         if (inv == null) {
 156                             // use "this" for just guard and drop it -- return a constant Method handle
 157                             // that returns a newly created MissingMethod object
 158                             MethodHandle mh = MethodHandles.constant(Object.class, new MissingMethod(nameStr));
 159                             inv = new GuardedInvocation(
 160                                 MethodHandles.dropArguments(mh, 0, Object.class),
 161                                 Guards.isOfClass(self.getClass(), MethodType.methodType(Boolean.TYPE, Object.class)));
 162                         }
 163 
 164                         return inv;
 165                     }
 166                 } else if (self instanceof MissingMethod) {
 167                     // This is step (2). We call MissingMethodHandler.doesNotUnderstand here
 168                     // Check if this is this a CALL first.
 169                     boolean isCall = getFirstStandardOperation(desc) == StandardOperation.CALL;
 170                     if (isCall) {
 171                         MethodHandle mh = DOES_NOT_UNDERSTAND;
 172 
 173                         // flip "this" and method name (String)
 174                         mh = MethodHandles.permuteArguments(mh, FLIPPED_DOES_NOT_UNDERSTAND_TYPE, 1, 0, 2);
 175 
 176                         // collect rest of the arguments as vararg
 177                         mh = mh.asCollector(Object[].class, desc.getMethodType().parameterCount() - 2);
 178 
 179                         // convert MissingMethod object to it's name
 180                         mh = MethodHandles.filterArguments(mh, 0, MISSING_METHOD_TO_NAME);
 181                         return new GuardedInvocation(mh, IS_MISSING_METHOD);
 182                     }
 183                 }
 184 
 185                 return null;
 186             }
 187         });
 188         return linkers;
 189     }
 190 }