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
26 package java.lang.runtime;
27
28 import java.lang.invoke.ConstantCallSite;
29 import java.lang.invoke.MethodHandle;
30 import java.lang.invoke.MethodHandles;
31 import java.lang.invoke.MethodType;
32 import java.lang.invoke.StringConcatFactory;
33 import java.lang.invoke.TypeDescriptor;
34 import java.security.AccessController;
35 import java.security.PrivilegedAction;
36 import java.util.ArrayList;
37 import java.util.Arrays;
38 import java.util.HashMap;
39 import java.util.List;
40 import java.util.Objects;
41
42 import static java.util.Objects.requireNonNull;
43
44 /**
45 * Bootstrap methods for state-driven implementations of core methods,
46 * including {@link Object#equals(Object)}, {@link Object#hashCode()}, and
47 * {@link Object#toString()}. These methods may be used, for example, by
55 private ObjectMethods() { }
56
57 private static final int MAX_STRING_CONCAT_SLOTS = 20;
58
59 private static final MethodType DESCRIPTOR_MT = MethodType.methodType(MethodType.class);
60 private static final MethodType NAMES_MT = MethodType.methodType(List.class);
61 private static final MethodHandle FALSE = MethodHandles.constant(boolean.class, false);
62 private static final MethodHandle TRUE = MethodHandles.constant(boolean.class, true);
63 private static final MethodHandle ZERO = MethodHandles.constant(int.class, 0);
64 private static final MethodHandle CLASS_IS_INSTANCE;
65 private static final MethodHandle OBJECT_EQUALS;
66 private static final MethodHandle OBJECTS_EQUALS;
67 private static final MethodHandle OBJECTS_HASHCODE;
68 private static final MethodHandle OBJECTS_TOSTRING;
69 private static final MethodHandle OBJECT_EQ;
70 private static final MethodHandle OBJECT_HASHCODE;
71 private static final MethodHandle OBJECT_TO_STRING;
72 private static final MethodHandle STRING_FORMAT;
73 private static final MethodHandle HASH_COMBINER;
74
75 private static final HashMap<Class<?>, MethodHandle> primitiveEquals = new HashMap<>();
76 private static final HashMap<Class<?>, MethodHandle> primitiveHashers = new HashMap<>();
77 private static final HashMap<Class<?>, MethodHandle> primitiveToString = new HashMap<>();
78
79 static {
80 try {
81 Class<ObjectMethods> OBJECT_METHODS_CLASS = ObjectMethods.class;
82 MethodHandles.Lookup publicLookup = MethodHandles.publicLookup();
83 MethodHandles.Lookup lookup = MethodHandles.lookup();
84
85 @SuppressWarnings("removal")
86 ClassLoader loader = AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() {
87 @Override public ClassLoader run() { return ClassLoader.getPlatformClassLoader(); }
88 });
89
90 CLASS_IS_INSTANCE = publicLookup.findVirtual(Class.class, "isInstance",
91 MethodType.methodType(boolean.class, Object.class));
92 OBJECT_EQUALS = publicLookup.findVirtual(Object.class, "equals",
93 MethodType.methodType(boolean.class, Object.class));
94 OBJECT_HASHCODE = publicLookup.findVirtual(Object.class, "hashCode",
95 MethodType.fromMethodDescriptorString("()I", loader));
234 * @return the method handle
235 */
236 private static MethodHandle makeHashCode(Class<?> receiverClass,
237 List<MethodHandle> getters) {
238 MethodHandle accumulator = MethodHandles.dropArguments(ZERO, 0, receiverClass); // (R)I
239
240 // @@@ Use loop combinator instead?
241 for (MethodHandle getter : getters) {
242 MethodHandle hasher = hasher(getter.type().returnType()); // (T)I
243 MethodHandle hashThisField = MethodHandles.filterArguments(hasher, 0, getter); // (R)I
244 MethodHandle combineHashes = MethodHandles.filterArguments(HASH_COMBINER, 0, accumulator, hashThisField); // (RR)I
245 accumulator = MethodHandles.permuteArguments(combineHashes, accumulator.type(), 0, 0); // adapt (R)I to (RR)I
246 }
247
248 return accumulator;
249 }
250
251 /**
252 * Generates a method handle for the {@code toString} method for a given data class
253 * @param receiverClass the data class
254 * @param getters the list of getters
255 * @param names the names
256 * @return the method handle
257 */
258 private static MethodHandle makeToString(MethodHandles.Lookup lookup,
259 Class<?> receiverClass,
260 MethodHandle[] getters,
261 List<String> names) {
262 assert getters.length == names.size();
263 if (getters.length == 0) {
264 // special case
265 MethodHandle emptyRecordCase = MethodHandles.constant(String.class, receiverClass.getSimpleName() + "[]");
266 emptyRecordCase = MethodHandles.dropArguments(emptyRecordCase, 0, receiverClass); // (R)S
267 return emptyRecordCase;
268 }
269
270 boolean firstTime = true;
271 MethodHandle[] mhs;
272 List<List<MethodHandle>> splits;
273 MethodHandle[] toSplit = getters;
274 int namesIndex = 0;
275 do {
276 /* StringConcatFactory::makeConcatWithConstants can only deal with 200 slots, longs and double occupy two
277 * the rest 1 slot, we need to chop the current `getters` into chunks, it could be that for records with
278 * a lot of components that we need to do a couple of iterations. The main difference between the first
279 * iteration and the rest would be on the recipe
280 */
281 splits = split(toSplit);
282 mhs = new MethodHandle[splits.size()];
283 for (int splitIndex = 0; splitIndex < splits.size(); splitIndex++) {
284 String recipe = "";
285 if (firstTime && splitIndex == 0) {
286 recipe = receiverClass.getSimpleName() + "[";
287 }
288 for (int i = 0; i < splits.get(splitIndex).size(); i++) {
289 recipe += firstTime ? names.get(namesIndex) + "=" + "\1" : "\1";
290 if (firstTime && namesIndex != names.size() - 1) {
291 recipe += ", ";
292 }
293 namesIndex++;
294 }
295 if (firstTime && splitIndex == splits.size() - 1) {
296 recipe += "]";
297 }
298 Class<?>[] concatTypeArgs = new Class<?>[splits.get(splitIndex).size()];
299 // special case: no need to create another getters if there is only one split
300 MethodHandle[] currentSplitGetters = new MethodHandle[splits.get(splitIndex).size()];
301 for (int j = 0; j < splits.get(splitIndex).size(); j++) {
302 concatTypeArgs[j] = splits.get(splitIndex).get(j).type().returnType();
303 currentSplitGetters[j] = splits.get(splitIndex).get(j);
304 }
305 MethodType concatMT = MethodType.methodType(String.class, concatTypeArgs);
306 try {
395 * @return a call site if invoked by indy, or a method handle
396 * if invoked by a condy
397 * @throws IllegalArgumentException if the bootstrap arguments are invalid
398 * or inconsistent
399 * @throws NullPointerException if any argument is {@code null} or if any element
400 * in the {@code getters} array is {@code null}
401 * @throws Throwable if any exception is thrown during call site construction
402 */
403 public static Object bootstrap(MethodHandles.Lookup lookup, String methodName, TypeDescriptor type,
404 Class<?> recordClass,
405 String names,
406 MethodHandle... getters) throws Throwable {
407 requireNonNull(lookup);
408 requireNonNull(methodName);
409 requireNonNull(type);
410 requireNonNull(recordClass);
411 requireNonNull(names);
412 requireNonNull(getters);
413 Arrays.stream(getters).forEach(Objects::requireNonNull);
414 MethodType methodType;
415 if (type instanceof MethodType mt)
416 methodType = mt;
417 else {
418 methodType = null;
419 if (!MethodHandle.class.equals(type))
420 throw new IllegalArgumentException(type.toString());
421 }
422 List<MethodHandle> getterList = List.of(getters);
423 MethodHandle handle = switch (methodName) {
424 case "equals" -> {
425 if (methodType != null && !methodType.equals(MethodType.methodType(boolean.class, recordClass, Object.class)))
426 throw new IllegalArgumentException("Bad method type: " + methodType);
427 yield makeEquals(recordClass, getterList);
428 }
429 case "hashCode" -> {
430 if (methodType != null && !methodType.equals(MethodType.methodType(int.class, recordClass)))
431 throw new IllegalArgumentException("Bad method type: " + methodType);
432 yield makeHashCode(recordClass, getterList);
433 }
434 case "toString" -> {
435 if (methodType != null && !methodType.equals(MethodType.methodType(String.class, recordClass)))
436 throw new IllegalArgumentException("Bad method type: " + methodType);
437 List<String> nameList = "".equals(names) ? List.of() : List.of(names.split(";"));
438 if (nameList.size() != getterList.size())
439 throw new IllegalArgumentException("Name list and accessor list do not match");
440 yield makeToString(lookup, recordClass, getters, nameList);
441 }
442 default -> throw new IllegalArgumentException(methodName);
443 };
444 return methodType != null ? new ConstantCallSite(handle) : handle;
445 }
446 }
|
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
26 package java.lang.runtime;
27
28 import jdk.internal.value.PrimitiveClass;
29
30 import java.lang.invoke.ConstantCallSite;
31 import java.lang.invoke.MethodHandle;
32 import java.lang.invoke.MethodHandles;
33 import java.lang.invoke.MethodType;
34 import java.lang.invoke.StringConcatFactory;
35 import java.lang.invoke.TypeDescriptor;
36 import java.security.AccessController;
37 import java.security.PrivilegedAction;
38 import java.util.ArrayList;
39 import java.util.Arrays;
40 import java.util.HashMap;
41 import java.util.List;
42 import java.util.Objects;
43
44 import static java.util.Objects.requireNonNull;
45
46 /**
47 * Bootstrap methods for state-driven implementations of core methods,
48 * including {@link Object#equals(Object)}, {@link Object#hashCode()}, and
49 * {@link Object#toString()}. These methods may be used, for example, by
57 private ObjectMethods() { }
58
59 private static final int MAX_STRING_CONCAT_SLOTS = 20;
60
61 private static final MethodType DESCRIPTOR_MT = MethodType.methodType(MethodType.class);
62 private static final MethodType NAMES_MT = MethodType.methodType(List.class);
63 private static final MethodHandle FALSE = MethodHandles.constant(boolean.class, false);
64 private static final MethodHandle TRUE = MethodHandles.constant(boolean.class, true);
65 private static final MethodHandle ZERO = MethodHandles.constant(int.class, 0);
66 private static final MethodHandle CLASS_IS_INSTANCE;
67 private static final MethodHandle OBJECT_EQUALS;
68 private static final MethodHandle OBJECTS_EQUALS;
69 private static final MethodHandle OBJECTS_HASHCODE;
70 private static final MethodHandle OBJECTS_TOSTRING;
71 private static final MethodHandle OBJECT_EQ;
72 private static final MethodHandle OBJECT_HASHCODE;
73 private static final MethodHandle OBJECT_TO_STRING;
74 private static final MethodHandle STRING_FORMAT;
75 private static final MethodHandle HASH_COMBINER;
76
77 /* package-private */
78 static final HashMap<Class<?>, MethodHandle> primitiveEquals = new HashMap<>();
79
80 private static final HashMap<Class<?>, MethodHandle> primitiveHashers = new HashMap<>();
81 private static final HashMap<Class<?>, MethodHandle> primitiveToString = new HashMap<>();
82
83 static {
84 try {
85 Class<ObjectMethods> OBJECT_METHODS_CLASS = ObjectMethods.class;
86 MethodHandles.Lookup publicLookup = MethodHandles.publicLookup();
87 MethodHandles.Lookup lookup = MethodHandles.lookup();
88
89 @SuppressWarnings("removal")
90 ClassLoader loader = AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() {
91 @Override public ClassLoader run() { return ClassLoader.getPlatformClassLoader(); }
92 });
93
94 CLASS_IS_INSTANCE = publicLookup.findVirtual(Class.class, "isInstance",
95 MethodType.methodType(boolean.class, Object.class));
96 OBJECT_EQUALS = publicLookup.findVirtual(Object.class, "equals",
97 MethodType.methodType(boolean.class, Object.class));
98 OBJECT_HASHCODE = publicLookup.findVirtual(Object.class, "hashCode",
99 MethodType.fromMethodDescriptorString("()I", loader));
238 * @return the method handle
239 */
240 private static MethodHandle makeHashCode(Class<?> receiverClass,
241 List<MethodHandle> getters) {
242 MethodHandle accumulator = MethodHandles.dropArguments(ZERO, 0, receiverClass); // (R)I
243
244 // @@@ Use loop combinator instead?
245 for (MethodHandle getter : getters) {
246 MethodHandle hasher = hasher(getter.type().returnType()); // (T)I
247 MethodHandle hashThisField = MethodHandles.filterArguments(hasher, 0, getter); // (R)I
248 MethodHandle combineHashes = MethodHandles.filterArguments(HASH_COMBINER, 0, accumulator, hashThisField); // (RR)I
249 accumulator = MethodHandles.permuteArguments(combineHashes, accumulator.type(), 0, 0); // adapt (R)I to (RR)I
250 }
251
252 return accumulator;
253 }
254
255 /**
256 * Generates a method handle for the {@code toString} method for a given data class
257 * @param receiverClass the data class
258 * @param simpleName the simple name of the record class
259 * @param getters the list of getters
260 * @param names the names
261 * @return the method handle
262 */
263 private static MethodHandle makeToString(MethodHandles.Lookup lookup,
264 Class<?> receiverClass,
265 String simpleName,
266 MethodHandle[] getters,
267 List<String> names) {
268 assert getters.length == names.size();
269 if (getters.length == 0) {
270 // special case
271 MethodHandle emptyRecordCase = MethodHandles.constant(String.class, simpleName + "[]");
272 emptyRecordCase = MethodHandles.dropArguments(emptyRecordCase, 0, receiverClass); // (R)S
273 return emptyRecordCase;
274 }
275
276 boolean firstTime = true;
277 MethodHandle[] mhs;
278 List<List<MethodHandle>> splits;
279 MethodHandle[] toSplit = getters;
280 int namesIndex = 0;
281 do {
282 /* StringConcatFactory::makeConcatWithConstants can only deal with 200 slots, longs and double occupy two
283 * the rest 1 slot, we need to chop the current `getters` into chunks, it could be that for records with
284 * a lot of components that we need to do a couple of iterations. The main difference between the first
285 * iteration and the rest would be on the recipe
286 */
287 splits = split(toSplit);
288 mhs = new MethodHandle[splits.size()];
289 for (int splitIndex = 0; splitIndex < splits.size(); splitIndex++) {
290 String recipe = "";
291 if (firstTime && splitIndex == 0) {
292 recipe = simpleName + "[";
293 }
294 for (int i = 0; i < splits.get(splitIndex).size(); i++) {
295 recipe += firstTime ? names.get(namesIndex) + "=" + "\1" : "\1";
296 if (firstTime && namesIndex != names.size() - 1) {
297 recipe += ", ";
298 }
299 namesIndex++;
300 }
301 if (firstTime && splitIndex == splits.size() - 1) {
302 recipe += "]";
303 }
304 Class<?>[] concatTypeArgs = new Class<?>[splits.get(splitIndex).size()];
305 // special case: no need to create another getters if there is only one split
306 MethodHandle[] currentSplitGetters = new MethodHandle[splits.get(splitIndex).size()];
307 for (int j = 0; j < splits.get(splitIndex).size(); j++) {
308 concatTypeArgs[j] = splits.get(splitIndex).get(j).type().returnType();
309 currentSplitGetters[j] = splits.get(splitIndex).get(j);
310 }
311 MethodType concatMT = MethodType.methodType(String.class, concatTypeArgs);
312 try {
401 * @return a call site if invoked by indy, or a method handle
402 * if invoked by a condy
403 * @throws IllegalArgumentException if the bootstrap arguments are invalid
404 * or inconsistent
405 * @throws NullPointerException if any argument is {@code null} or if any element
406 * in the {@code getters} array is {@code null}
407 * @throws Throwable if any exception is thrown during call site construction
408 */
409 public static Object bootstrap(MethodHandles.Lookup lookup, String methodName, TypeDescriptor type,
410 Class<?> recordClass,
411 String names,
412 MethodHandle... getters) throws Throwable {
413 requireNonNull(lookup);
414 requireNonNull(methodName);
415 requireNonNull(type);
416 requireNonNull(recordClass);
417 requireNonNull(names);
418 requireNonNull(getters);
419 Arrays.stream(getters).forEach(Objects::requireNonNull);
420 MethodType methodType;
421 Class<?> receiverType = PrimitiveClass.isPrimitiveClass(recordClass)
422 ? PrimitiveClass.asValueType(recordClass) : recordClass;
423 if (type instanceof MethodType mt) {
424 methodType = mt;
425 if (mt.parameterType(0) != receiverType) {
426 throw new IllegalArgumentException("Bad method type: " + mt);
427 }
428 } else {
429 methodType = null;
430 if (!MethodHandle.class.equals(type))
431 throw new IllegalArgumentException(type.toString());
432 }
433 List<MethodHandle> getterList = List.of(getters);
434 for (MethodHandle getter : getterList) {
435 if (getter.type().parameterType(0) != receiverType) {
436 throw new IllegalArgumentException("Bad receiver type: " + getter);
437 }
438 }
439 MethodHandle handle = switch (methodName) {
440 case "equals" -> {
441 if (methodType != null && !methodType.equals(MethodType.methodType(boolean.class, receiverType, Object.class)))
442 throw new IllegalArgumentException("Bad method type: " + methodType);
443 yield makeEquals(receiverType, getterList);
444 }
445 case "hashCode" -> {
446 if (methodType != null && !methodType.equals(MethodType.methodType(int.class, receiverType)))
447 throw new IllegalArgumentException("Bad method type: " + methodType);
448 yield makeHashCode(receiverType, getterList);
449 }
450 case "toString" -> {
451 if (methodType != null && !methodType.equals(MethodType.methodType(String.class, receiverType)))
452 throw new IllegalArgumentException("Bad method type: " + methodType);
453 List<String> nameList = "".equals(names) ? List.of() : List.of(names.split(";"));
454 if (nameList.size() != getterList.size())
455 throw new IllegalArgumentException("Name list and accessor list do not match");
456 yield makeToString(lookup, receiverType, recordClass.getSimpleName(), getters, nameList);
457 }
458 default -> throw new IllegalArgumentException(methodName);
459 };
460 return methodType != null ? new ConstantCallSite(handle) : handle;
461 }
462 }
|