|
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 } |