1 /*
2  * Copyright (c) 2018, 2020, 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  */
25 package java.lang.constant;
26 
27 import java.lang.invoke.CallSite;
28 import java.lang.invoke.MethodHandle;
29 import java.lang.invoke.MethodHandles;
30 import java.util.Arrays;
31 import java.util.Objects;
32 import java.util.stream.Stream;
33 
34 import static java.lang.constant.ConstantDescs.CD_String;
35 import static java.lang.constant.ConstantUtils.EMPTY_CONSTANTDESC;
36 import static java.lang.constant.ConstantUtils.validateMemberName;
37 import static java.util.Objects.requireNonNull;
38 import static java.util.stream.Collectors.joining;
39 
40 /**
41  * A <a href="package-summary.html#nominal">nominal descriptor</a> for an
42  * {@code invokedynamic} call site.
43  *
44  * <p>Concrete subtypes of {@linkplain DynamicCallSiteDesc} should be immutable
45  * and their behavior should not rely on object identity.
46  *
47  * @since 12
48  */
49 public class DynamicCallSiteDesc {
50 
51     private final DirectMethodHandleDesc bootstrapMethod;
52     private final ConstantDesc[] bootstrapArgs;
53     private final String invocationName;
54     private final MethodTypeDesc invocationType;
55 
56     /**
57      * Creates a nominal descriptor for an {@code invokedynamic} call site.
58      *
59      * @param bootstrapMethod a {@link DirectMethodHandleDesc} describing the
60      *                        bootstrap method for the {@code invokedynamic}
61      * @param invocationName The unqualified name that would appear in the {@code NameAndType}
62      *                       operand of the {@code invokedynamic}
63      * @param invocationType a {@link MethodTypeDesc} describing the invocation
64      *                       type that would appear in the {@code NameAndType}
65      *                       operand of the {@code invokedynamic}
66      * @param bootstrapArgs {@link ConstantDesc}s describing the static arguments
67      *                      to the bootstrap, that would appear in the
68      *                      {@code BootstrapMethods} attribute
69      * @throws NullPointerException if any parameter or its contents are {@code null}
70      * @throws IllegalArgumentException if the invocation name has the incorrect
71      * format
72      * @jvms 4.2.2 Unqualified Names
73      */
DynamicCallSiteDesc(DirectMethodHandleDesc bootstrapMethod, String invocationName, MethodTypeDesc invocationType, ConstantDesc[] bootstrapArgs)74     private DynamicCallSiteDesc(DirectMethodHandleDesc bootstrapMethod,
75                                 String invocationName,
76                                 MethodTypeDesc invocationType,
77                                 ConstantDesc[] bootstrapArgs) {
78         this.invocationName = validateMemberName(requireNonNull(invocationName), true);
79         this.invocationType = requireNonNull(invocationType);
80         this.bootstrapMethod = requireNonNull(bootstrapMethod);
81         this.bootstrapArgs = requireNonNull(bootstrapArgs.clone());
82         for (int i = 0; i < this.bootstrapArgs.length; i++) {
83             requireNonNull(this.bootstrapArgs[i]);
84         }
85         if (invocationName.length() == 0)
86             throw new IllegalArgumentException("Illegal invocation name: " + invocationName);
87     }
88 
89     /**
90      * Creates a nominal descriptor for an {@code invokedynamic} call site.
91      *
92      * @param bootstrapMethod a {@link DirectMethodHandleDesc} describing the
93      *                        bootstrap method for the {@code invokedynamic}
94      * @param invocationName The unqualified name that would appear in the {@code NameAndType}
95      *                       operand of the {@code invokedynamic}
96      * @param invocationType a {@link MethodTypeDesc} describing the invocation
97      *                       type that would appear in the {@code NameAndType}
98      *                       operand of the {@code invokedynamic}
99      * @param bootstrapArgs {@link ConstantDesc}s describing the static arguments
100      *                      to the bootstrap, that would appear in the
101      *                      {@code BootstrapMethods} attribute
102      * @return the nominal descriptor
103      * @throws NullPointerException if any parameter or its contents are {@code null}
104      * @throws IllegalArgumentException if the invocation name has the incorrect
105      * format
106      * @jvms 4.2.2 Unqualified Names
107      */
of(DirectMethodHandleDesc bootstrapMethod, String invocationName, MethodTypeDesc invocationType, ConstantDesc... bootstrapArgs)108     public static DynamicCallSiteDesc of(DirectMethodHandleDesc bootstrapMethod,
109                                          String invocationName,
110                                          MethodTypeDesc invocationType,
111                                          ConstantDesc... bootstrapArgs) {
112         return new DynamicCallSiteDesc(bootstrapMethod, invocationName, invocationType, bootstrapArgs);
113     }
114 
115     /**
116      * Creates a nominal descriptor for an {@code invokedynamic} call site whose
117      * bootstrap method has no static arguments.
118      *
119      * @param bootstrapMethod The bootstrap method for the {@code invokedynamic}
120      * @param invocationName The invocationName that would appear in the
121      * {@code NameAndType} operand of the {@code invokedynamic}
122      * @param invocationType The invocation invocationType that would appear
123      * in the {@code NameAndType} operand of the {@code invokedynamic}
124      * @return the nominal descriptor
125      * @throws NullPointerException if any parameter is null
126      * @throws IllegalArgumentException if the invocation name has the incorrect
127      * format
128      */
of(DirectMethodHandleDesc bootstrapMethod, String invocationName, MethodTypeDesc invocationType)129     public static DynamicCallSiteDesc of(DirectMethodHandleDesc bootstrapMethod,
130                                          String invocationName,
131                                          MethodTypeDesc invocationType) {
132         return new DynamicCallSiteDesc(bootstrapMethod, invocationName, invocationType, EMPTY_CONSTANTDESC);
133     }
134 
135     /**
136      * Creates a nominal descriptor for an {@code invokedynamic} call site whose
137      * bootstrap method has no static arguments and for which the name parameter
138      * is {@link ConstantDescs#DEFAULT_NAME}.
139      *
140      * @param bootstrapMethod a {@link DirectMethodHandleDesc} describing the
141      *                        bootstrap method for the {@code invokedynamic}
142      * @param invocationType a {@link MethodTypeDesc} describing the invocation
143      *                       type that would appear in the {@code NameAndType}
144      *                       operand of the {@code invokedynamic}
145      * @return the nominal descriptor
146      * @throws NullPointerException if any parameter is null
147      */
of(DirectMethodHandleDesc bootstrapMethod, MethodTypeDesc invocationType)148     public static DynamicCallSiteDesc of(DirectMethodHandleDesc bootstrapMethod,
149                                          MethodTypeDesc invocationType) {
150         return of(bootstrapMethod, ConstantDescs.DEFAULT_NAME, invocationType);
151     }
152 
153     /**
154      * Returns a nominal descriptor for an {@code invokedynamic} call site whose
155      * bootstrap method, name, and invocation type are the same as this one, but
156      * with the specified bootstrap arguments.
157      *
158      * @param bootstrapArgs {@link ConstantDesc}s describing the static arguments
159      *                      to the bootstrap, that would appear in the
160      *                      {@code BootstrapMethods} attribute
161      * @return the nominal descriptor
162      * @throws NullPointerException if the argument or its contents are {@code null}
163      */
withArgs(ConstantDesc... bootstrapArgs)164     public DynamicCallSiteDesc withArgs(ConstantDesc... bootstrapArgs) {
165         return new DynamicCallSiteDesc(bootstrapMethod, invocationName, invocationType, bootstrapArgs);
166     }
167 
168     /**
169      * Returns a nominal descriptor for an {@code invokedynamic} call site whose
170      * bootstrap and bootstrap arguments are the same as this one, but with the
171      * specified invocationName and invocation invocationType
172      *
173      * @param invocationName The unqualified name that would appear in the {@code NameAndType}
174      *                       operand of the {@code invokedynamic}
175      * @param invocationType a {@link MethodTypeDesc} describing the invocation
176      *                       type that would appear in the {@code NameAndType}
177      *                       operand of the {@code invokedynamic}
178      * @return the nominal descriptor
179      * @throws NullPointerException if any parameter is null
180      * @throws IllegalArgumentException if the invocation name has the incorrect
181      * format
182      * @jvms 4.2.2 Unqualified Names
183      */
withNameAndType(String invocationName, MethodTypeDesc invocationType)184     public DynamicCallSiteDesc withNameAndType(String invocationName,
185                                                MethodTypeDesc invocationType) {
186         return new DynamicCallSiteDesc(bootstrapMethod, invocationName, invocationType, bootstrapArgs);
187     }
188 
189     /**
190      * Returns the invocation name that would appear in the {@code NameAndType}
191      * operand of the {@code invokedynamic}.
192      *
193      * @return the invocation name
194      */
invocationName()195     public String invocationName() {
196         return invocationName;
197     }
198 
199     /**
200      * Returns a {@link MethodTypeDesc} describing the invocation type that
201      * would appear in the {@code NameAndType} operand of the {@code invokedynamic}.
202      *
203      * @return the invocation type
204      */
invocationType()205     public MethodTypeDesc invocationType() {
206         return invocationType;
207     }
208 
209     /**
210      * Returns a {@link MethodHandleDesc} describing the bootstrap method for
211      * the {@code invokedynamic}.
212      *
213      * @return the bootstrap method for the {@code invokedynamic}
214      */
bootstrapMethod()215     public MethodHandleDesc bootstrapMethod() { return bootstrapMethod; }
216 
217     /**
218      * Returns {@link ConstantDesc}s describing the bootstrap arguments for the
219      * {@code invokedynamic}. The returned array is always non-null. A zero
220      * length array is returned if this {@linkplain DynamicCallSiteDesc} has no
221      * bootstrap arguments.
222      *
223      * @return the bootstrap arguments for the {@code invokedynamic}
224      */
bootstrapArgs()225     public ConstantDesc[] bootstrapArgs() { return bootstrapArgs.clone(); }
226 
227     /**
228      * Reflectively invokes the bootstrap method with the specified arguments,
229      * and return the resulting {@link CallSite}
230      *
231      * @param lookup The {@link MethodHandles.Lookup} used to resolve class names
232      * @return the {@link CallSite}
233      * @throws Throwable if any exception is thrown by the bootstrap method
234      */
resolveCallSiteDesc(MethodHandles.Lookup lookup)235     public CallSite resolveCallSiteDesc(MethodHandles.Lookup lookup) throws Throwable {
236         assert bootstrapMethod.invocationType().parameterType(1).equals(CD_String);
237         MethodHandle bsm = (MethodHandle) bootstrapMethod.resolveConstantDesc(lookup);
238         Object[] args = new Object[bootstrapArgs.length + 3];
239         args[0] = lookup;
240         args[1] = invocationName;
241         args[2] = invocationType.resolveConstantDesc(lookup);
242         System.arraycopy(bootstrapArgs, 0, args, 3, bootstrapArgs.length);
243         return (CallSite) bsm.invokeWithArguments(args);
244     }
245 
246     /**
247      * Compares the specified object with this descriptor for equality.  Returns
248      * {@code true} if and only if the specified object is also a
249      * {@linkplain DynamicCallSiteDesc}, and both descriptors have equal
250      * bootstrap methods, bootstrap argument lists, invocation name, and
251      * invocation type.
252      *
253      * @param o the {@code DynamicCallSiteDesc} to compare to this
254      *       {@code DynamicCallSiteDesc}
255      * @return {@code true} if the specified {@code DynamicCallSiteDesc}
256      *      is equal to this {@code DynamicCallSiteDesc}.
257      */
258     @Override
equals(Object o)259     public final boolean equals(Object o) {
260         if (this == o) return true;
261         if (o == null || getClass() != o.getClass()) return false;
262         DynamicCallSiteDesc specifier = (DynamicCallSiteDesc) o;
263         return Objects.equals(bootstrapMethod, specifier.bootstrapMethod) &&
264                Arrays.equals(bootstrapArgs, specifier.bootstrapArgs) &&
265                Objects.equals(invocationName, specifier.invocationName) &&
266                Objects.equals(invocationType, specifier.invocationType);
267     }
268 
269     @Override
hashCode()270     public final int hashCode() {
271         int result = Objects.hash(bootstrapMethod, invocationName, invocationType);
272         result = 31 * result + Arrays.hashCode(bootstrapArgs);
273         return result;
274     }
275 
276     /**
277      * Returns a compact textual description of this call site description,
278      * including the bootstrap method, the invocation name and type, and
279      * the static bootstrap arguments.
280      *
281      * @return A compact textual description of this call site descriptor
282      */
283     @Override
toString()284     public String toString() {
285         return String.format("DynamicCallSiteDesc[%s::%s(%s%s):%s]",
286                              bootstrapMethod.owner().displayName(),
287                              bootstrapMethod.methodName(),
288                              invocationName.equals(ConstantDescs.DEFAULT_NAME) ? "" : invocationName + "/",
289                              Stream.of(bootstrapArgs).map(Object::toString).collect(joining(",")),
290                              invocationType.displayDescriptor());
291     }
292 }
293