001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*- 002 * 003 * Copyright © 2022–2023 microBean™. 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 006 * the License. You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 011 * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 012 * specific language governing permissions and limitations under the License. 013 */ 014package org.microbean.invoke; 015 016import java.lang.invoke.ConstantCallSite; 017import java.lang.invoke.MethodHandle; 018import java.lang.invoke.MethodHandles; 019import java.lang.invoke.MethodHandles.Lookup; 020import java.lang.invoke.MethodType; 021 022import java.util.Collection; 023import java.util.Collections; 024import java.util.Comparator; 025import java.util.List; 026import java.util.Map; 027import java.util.Map.Entry; 028import java.util.SortedMap; 029import java.util.SortedSet; 030import java.util.TreeMap; 031import java.util.TreeSet; 032 033/** 034 * Useful constant bootstrap methods and methods that make sense to invoke from {@link 035 * java.lang.invoke.ConstantBootstraps#invoke(Lookup, String, Class, MethodHandle, Object...)}. 036 * 037 * @author <a href="https://about.me/lairdnelson" target="_parent">Laird Nelson</a> 038 * 039 * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/invoke/package-summary.html" 040 * target="_parent"><code>java.lang.invoke</code></a> 041 */ 042public final class BootstrapMethods { 043 044 045 /* 046 * Constructors. 047 */ 048 049 050 private BootstrapMethods() { 051 super(); 052 } 053 054 055 /* 056 * public static methods. 057 */ 058 059 060 /** 061 * Returns a {@link ConstantCallSite} {@linkplain ConstantCallSite#getTarget() backed} by a {@linkplain 062 * Lookup#findStaticSetter(Class, String, Class) static setter <code>MethodHandle</code>}. 063 * 064 * @param lookup a {@link Lookup}; will not be {@code null} 065 * 066 * @param fieldName the name of the static field to find; will not be {@code null} 067 * 068 * @param methodType a {@link MethodType}; will not be {@code null} 069 * 070 * @param targetClass the {@link Class} whose static field will be sought; will not be {@code null} 071 * 072 * @return a {@link ConstantCallSite} {@linkplain ConstantCallSite#getTarget() backed} by a {@linkplain 073 * Lookup#findStaticSetter(Class, String, Class) static setter <code>MethodHandle</code>} 074 * 075 * @exception Throwable if an error occurs 076 * 077 * @nullability This method never returns {@code null}. 078 * 079 * @idempotency This method is idempotent and deterministic. 080 * 081 * @threadsafety This method is safe for concurrent use by multiple threads. 082 * 083 * @see Lookup#findStaticSetter(Class, String, Class) 084 */ 085 public static final ConstantCallSite findStaticSetterCallSite(final Lookup lookup, 086 final String fieldName, 087 final MethodType methodType, 088 final Class<?> targetClass) 089 throws Throwable { 090 return new ConstantCallSite(findStaticSetterMethodHandle(lookup, fieldName, methodType.parameterType(0), targetClass)); 091 } 092 093 /** 094 * Returns a {@link ConstantCallSite} {@linkplain ConstantCallSite#getTarget() backed} by a {@linkplain 095 * Lookup#findStatic(Class, String, MethodType) static <code>MethodHandle</code>} {@linkplain 096 * MethodHandle#asSpreader(Class, int) adapted to be an <em>array-spreading</em> <code>MethodHandle</code>}. 097 * 098 * @param lookup a {@link Lookup}; will not be {@code null} 099 * 100 * @param methodName the name of the static field to find; will not be {@code null} 101 * 102 * @param methodType a {@link MethodType}; will not be {@code null} 103 * 104 * @param targetClass the {@link Class} whose static method will be sought; will not be {@code null} 105 * 106 * @return a {@link ConstantCallSite} {@linkplain ConstantCallSite#getTarget() backed} by a {@linkplain 107 * Lookup#findStatic(Class, String, MethodType) static <code>MethodHandle</code>} {@linkplain 108 * MethodHandle#asSpreader(Class, int) adapted to be an <em>array-spreading</em> <code>MethodHandle</code>} 109 * 110 * @exception Throwable if an error occurs 111 * 112 * @nullability This method never returns {@code null}. 113 * 114 * @idempotency This method is idempotent and deterministic. 115 * 116 * @threadsafety This method is safe for concurrent use by multiple threads. 117 * 118 * @see Lookup#findStatic(Class, String, MethodType) 119 * 120 * @see MethodHandle#asSpreader(Class, int) 121 */ 122 public static final ConstantCallSite findStaticCallSiteAsSpreader(final Lookup lookup, 123 final String methodName, 124 final MethodType methodType, 125 final Class<?> targetClass) 126 throws Throwable { 127 return new ConstantCallSite(findStaticMethodHandle(lookup, methodName, methodType, targetClass) 128 .asSpreader(Object[].class, methodType.parameterCount())); 129 } 130 131 /** 132 * Returns a {@link ConstantCallSite} {@linkplain ConstantCallSite#getTarget() backed} by a {@linkplain 133 * Lookup#findSetter(Class, String, Class) setter <code>MethodHandle</code>}. 134 * 135 * @param lookup a {@link Lookup}; will not be {@code null} 136 * 137 * @param fieldName the name of the field to find; will not be {@code null} 138 * 139 * @param methodType a {@link MethodType}; will not be {@code null} 140 * 141 * @param targetClass the {@link Class} whose instance field will be sought; will not be {@code null} 142 * 143 * @return a {@link ConstantCallSite} {@linkplain ConstantCallSite#getTarget() backed} by a {@linkplain 144 * Lookup#findSetter(Class, String, Class) setter <code>MethodHandle</code>} 145 * 146 * @exception Throwable if an error occurs 147 * 148 * @nullability This method never returns {@code null}. 149 * 150 * @idempotency This method is idempotent and deterministic. 151 * 152 * @threadsafety This method is safe for concurrent use by multiple threads. 153 * 154 * @see Lookup#findSetter(Class, String, Class) 155 */ 156 public static final ConstantCallSite findSetterCallSite(final Lookup lookup, 157 final String fieldName, 158 final MethodType methodType, 159 final Class<?> targetClass) 160 throws Throwable { 161 return new ConstantCallSite(findSetterMethodHandle(lookup, fieldName, methodType.parameterType(0), targetClass)); 162 } 163 164 /** 165 * Returns a {@link ConstantCallSite} {@linkplain ConstantCallSite#getTarget() backed} by a {@linkplain 166 * Lookup#findVirtual(Class, String, MethodType) virtual <code>MethodHandle</code>}. 167 * 168 * @param lookup a {@link Lookup}; will not be {@code null} 169 * 170 * @param methodName the name of the method to find; will not be {@code null} 171 * 172 * @param methodType a {@link MethodType}; will not be {@code null} 173 * 174 * @param targetClass the {@link Class} whose virtual method will be sought; will not be {@code null} 175 * 176 * @return a {@link ConstantCallSite} {@linkplain ConstantCallSite#getTarget() backed} by a {@linkplain 177 * Lookup#findVirtual(Class, String, MethodType) virtual <code>MethodHandle</code>} 178 * 179 * @exception Throwable if an error occurs 180 * 181 * @nullability This method never returns {@code null}. 182 * 183 * @idempotency This method is idempotent and deterministic. 184 * 185 * @threadsafety This method is safe for concurrent use by multiple threads. 186 * 187 * @see Lookup#findVirtual(Class, String, MethodType) 188 */ 189 public static final ConstantCallSite findVirtualCallSite(final Lookup lookup, 190 final String methodName, 191 final MethodType methodType, 192 final Class<?> targetClass) 193 throws Throwable { 194 return new ConstantCallSite(findVirtualMethodHandle(lookup, methodName, methodType, targetClass)); 195 } 196 197 /** 198 * Returns a {@link ConstantCallSite} {@linkplain ConstantCallSite#getTarget() backed} by a {@linkplain 199 * Lookup#findConstructor(Class, MethodType) constructor <code>MethodHandle</code>}. 200 * 201 * @param lookup a {@link Lookup}; will not be {@code null} 202 * 203 * @param ignoredMethodName ignored 204 * 205 * @param methodType a {@link MethodType}; will not be {@code null} 206 * 207 * @param targetClass the {@link Class} whose constructor will be sought; will not be {@code null} 208 * 209 * @return a {@link ConstantCallSite} {@linkplain ConstantCallSite#getTarget() backed} by a {@linkplain 210 * Lookup#findConstructor(Class, MethodType) constructor <code>MethodHandle</code>} 211 * 212 * @exception Throwable if an error occurs 213 * 214 * @nullability This method never returns {@code null}. 215 * 216 * @idempotency This method is idempotent and deterministic. 217 * 218 * @threadsafety This method is safe for concurrent use by multiple threads. 219 * 220 * @see Lookup#findConstructor(Class, MethodType) 221 */ 222 public static final ConstantCallSite findConstructorCallSite(final Lookup lookup, 223 final String ignoredMethodName, 224 final MethodType methodType, 225 final Class<?> targetClass) 226 throws Throwable { 227 return new ConstantCallSite(findConstructorMethodHandle(lookup, methodType, targetClass)); 228 } 229 230 /** 231 * Returns an empty, immutable {@link SortedMap}. 232 * 233 * @param <K> the type borne by the supplied {@link Map}'s {@linkplain Map#keySet() keys} 234 * 235 * @param <V> the type borne by the supplied {@link Map}'s {@linkplain Map#values() values} 236 * 237 * @param comparator the {@link Comparator} to be returned by the returned {@link SortedMap}'s {@link 238 * SortedMap#comparator()} method; may be {@code null} 239 * 240 * @return an immutable, empty {@link SortedMap} 241 * 242 * @exception NullPointerException if {@code map} is {@code null} 243 * 244 * @nullability This method never returns {@code null}. 245 * 246 * @idempotency This method is idempotent and deterministic. 247 * 248 * @threadsafety This method is safe for concurrent use by multiple threads. 249 */ 250 public static final <K, V> SortedMap<K, V> immutableEmptySortedMap(final Comparator<? super K> comparator) { 251 return comparator == null ? Collections.emptySortedMap() : immutableSortedMapOf(Map.of(), comparator); 252 } 253 254 /** 255 * Given a {@link Collection} of {@link Entry} instances, returns a {@link SortedMap} representing it that is 256 * immutable. 257 * 258 * @param <K> the type borne by the supplied entries' {@linkplain Map#keySet() keys} 259 * 260 * @param <V> the type borne by the supplied entries' {@linkplain Map#values() values} 261 * 262 * @param entries the {@link Entry} instances to represent; must not be {@code null} 263 * 264 * @return an immutable {@link SortedMap} representing the supplied entries 265 * 266 * @exception NullPointerException if {@code entries} is {@code null} 267 * 268 * @nullability This method never returns {@code null}. 269 * 270 * @idempotency This method is idempotent and deterministic. 271 * 272 * @threadsafety This method is safe for concurrent use by multiple threads. 273 * 274 * @see #immutableSortedMapOf(Collection, Comparator) 275 */ 276 public static final <K, V> SortedMap<K, V> immutableSortedMapOf(final Collection<? extends Entry<K, V>> entries) { 277 return immutableSortedMapOf(entries, null); 278 } 279 280 /** 281 * Given a {@link Collection} of {@link Entry} instances, returns a {@link SortedMap} representing it that is 282 * immutable. 283 * 284 * @param <K> the type borne by the supplied entries' {@linkplain Map#keySet() keys} 285 * 286 * @param <V> the type borne by the supplied entries' {@linkplain Map#values() values} 287 * 288 * @param entries the {@link Entry} instances to represent; must not be {@code null} 289 * 290 * @param comparator the {@link Comparator} to use to order the returned {@link SortedMap}'s elements; may be {@code 291 * null} to indicate natural order should be used in which case the supplied keys and values must implement {@link 292 * Comparable} 293 * 294 * @return an immutable {@link SortedMap} representing the supplied entries 295 * 296 * @exception NullPointerException if {@code entries} is {@code null} 297 * 298 * @nullability This method never returns {@code null}. 299 * 300 * @idempotency This method is idempotent and deterministic. 301 * 302 * @threadsafety This method is safe for concurrent use by multiple threads. 303 */ 304 public static final <K, V> SortedMap<K, V> immutableSortedMapOf(final Collection<? extends Entry<K, V>> entries, 305 final Comparator<? super K> comparator) { 306 if (entries.isEmpty()) { 307 return comparator == null ? Collections.emptySortedMap() : immutableSortedMapOf(Map.of(), comparator); 308 } 309 final SortedMap<K, V> sm = new TreeMap<>(comparator); 310 for (final Entry<K, V> entry : entries) { 311 sm.put(entry.getKey(), entry.getValue()); 312 } 313 return Collections.unmodifiableSortedMap(sm); 314 } 315 316 /** 317 * Given a {@link Map}, returns a {@link SortedMap} representing it that is immutable. 318 * 319 * @param <K> the type borne by the supplied {@link Map}'s {@linkplain Map#keySet() keys} 320 * 321 * @param <V> the type borne by the supplied {@link Map}'s {@linkplain Map#values() values} 322 * 323 * @param map the {@link Map} to represent; must not be {@code null} 324 * 325 * @return an immutable {@link SortedMap} representing the supplied {@link Map} 326 * 327 * @exception NullPointerException if {@code map} is {@code null} 328 * 329 * @nullability This method never returns {@code null}. 330 * 331 * @idempotency This method is idempotent and deterministic. 332 * 333 * @threadsafety This method is safe for concurrent use by multiple threads. 334 * 335 * @see #immutableSortedMapOf(Map, Comparator) 336 */ 337 public static final <K, V> SortedMap<K, V> immutableSortedMapOf(final Map<? extends K, ? extends V> map) { 338 return map.isEmpty() ? Collections.emptySortedMap() : immutableSortedMapOf(map, null); 339 } 340 341 /** 342 * Given a {@link Map} or a {@link SortedMap}, returns a {@link SortedMap} representing it that is immutable. 343 * 344 * @param <K> the type borne by the supplied {@link Map}'s {@linkplain Map#keySet() keys} 345 * 346 * @param <V> the type borne by the supplied {@link Map}'s {@linkplain Map#values() values} 347 * 348 * @param map the {@link Map} to represent; must not be {@code null} 349 * 350 * @param comparator the {@link Comparator} to use to order the supplied {@link Map}'s elements; may be {@code null} 351 * to indicate natural order should be used in which case the supplied {@link Map}'s elements must implement {@link 352 * Comparable} 353 * 354 * @return an immutable {@link SortedMap} representing the supplied {@link Map} 355 * 356 * @exception NullPointerException if {@code map} is {@code null} 357 * 358 * @nullability This method never returns {@code null}. 359 * 360 * @idempotency This method is idempotent and deterministic. 361 * 362 * @threadsafety This method is safe for concurrent use by multiple threads. 363 */ 364 public static final <K, V> SortedMap<K, V> immutableSortedMapOf(final Map<? extends K, ? extends V> map, 365 final Comparator<? super K> comparator) { 366 @SuppressWarnings("unchecked") 367 final SortedMap<K, V> mutableSortedMap = 368 new TreeMap<>(comparator == null ? 369 map instanceof SortedMap<? extends K, ? extends V> sm ? (Comparator<? super K>)sm.comparator() : null : 370 comparator); 371 mutableSortedMap.putAll(map); 372 return Collections.unmodifiableSortedMap(mutableSortedMap); 373 } 374 375 /** 376 * Returns an immutable, empty {@link SortedSet}. 377 * 378 * @param <E> the {@link SortedSet}'s element type 379 * 380 * @param comparator the {@link Comparator} to be returned by the returned {@link SortedSet}'s {@link 381 * SortedSet#comparator()} method; may be {@code null} 382 * 383 * @return an immutable, empty {@link SortedSet} 384 * 385 * @exception NullPointerException if {@code set} is {@code null} 386 * 387 * @nullability This method never returns {@code null}. 388 * 389 * @idempotency This method is idempotent and deterministic. 390 * 391 * @threadsafety This method is safe for concurrent use by multiple threads. 392 * 393 * @see #immutableSortedSetOf(Collection, Comparator) 394 */ 395 public static final <E> SortedSet<E> immutableEmptySortedSet(final Comparator<? super E> comparator) { 396 return comparator == null ? Collections.emptySortedSet() : immutableSortedSetOf(List.of(), comparator); 397 } 398 399 /** 400 * Given a {@link Collection}, returns a {@link SortedSet} representing it that is immutable. 401 * 402 * @param <E> the type borne by the supplied {@link Collection}'s elements 403 * 404 * @param set the {@link Collection} to represent; must not be {@code null} 405 * 406 * @return an immutable {@link SortedSet} representing the supplied {@link Collection} 407 * 408 * @exception NullPointerException if {@code set} is {@code null} 409 * 410 * @nullability This method never returns {@code null}. 411 * 412 * @idempotency This method is idempotent and deterministic. 413 * 414 * @threadsafety This method is safe for concurrent use by multiple threads. 415 * 416 * @see #immutableSortedSetOf(Collection, Comparator) 417 */ 418 public static final <E> SortedSet<E> immutableSortedSetOf(final Collection<? extends E> set) { 419 return set.isEmpty() ? Collections.emptySortedSet() : immutableSortedSetOf(set, null); 420 } 421 422 /** 423 * Given a {@link Collection}, returns a {@link SortedSet} representing it that is immutable. 424 * 425 * @param <E> the type borne by the supplied {@link Collection}'s elements 426 * 427 * @param set the {@link Collection} to represent; must not be {@code null} 428 * 429 * @param comparator the {@link Comparator} to use to order the supplied {@link Collection}'s elements; may be {@code 430 * null} to indicate natural order should be used in which case the supplied {@link Collection}'s elements must 431 * implement {@link Comparable} 432 * 433 * @return an immutable {@link SortedSet} representing the supplied {@link Collection} 434 * 435 * @exception NullPointerException if {@code set} is {@code null} 436 * 437 * @nullability This method never returns {@code null}. 438 * 439 * @idempotency This method is idempotent and deterministic. 440 * 441 * @threadsafety This method is safe for concurrent use by multiple threads. 442 */ 443 public static final <E> SortedSet<E> immutableSortedSetOf(final Collection<? extends E> set, 444 final Comparator<? super E> comparator) { 445 @SuppressWarnings("unchecked") 446 final SortedSet<E> mutableSortedSet = 447 new TreeSet<>(comparator == null ? 448 set instanceof SortedSet<? extends E> ss ? (Comparator<? super E>)ss.comparator() : null : 449 comparator); 450 mutableSortedSet.addAll(set); 451 return Collections.unmodifiableSortedSet(mutableSortedSet); 452 } 453 454 455 /* 456 * private static methods. 457 */ 458 459 460 private static final MethodHandle findStaticMethodHandle(final Lookup lookup, 461 final String methodName, 462 final MethodType methodType, 463 final Class<?> targetClass) 464 throws Throwable { 465 return MethodHandles.privateLookupIn(targetClass, lookup).findStatic(targetClass, methodName, methodType); 466 } 467 468 private static final MethodHandle findStaticSetterMethodHandle(final Lookup lookup, 469 final String fieldName, 470 final Class<?> fieldType, 471 final Class<?> targetClass) 472 throws Throwable { 473 return 474 MethodHandles.privateLookupIn(targetClass, lookup).findStaticSetter(targetClass, fieldName, fieldType); 475 } 476 477 private static final MethodHandle findSetterMethodHandle(final Lookup lookup, 478 final String fieldName, 479 final Class<?> fieldType, 480 final Class<?> targetClass) 481 throws Throwable { 482 return 483 MethodHandles.privateLookupIn(targetClass, lookup).findSetter(targetClass, fieldName, fieldType); 484 } 485 486 private static final MethodHandle findVirtualMethodHandle(final Lookup lookup, 487 final String methodName, 488 final MethodType methodType, 489 final Class<?> targetClass) 490 throws Throwable { 491 return MethodHandles.privateLookupIn(targetClass, lookup).findVirtual(targetClass, methodName, methodType); 492 } 493 494 private static final MethodHandle findConstructorMethodHandle(final Lookup lookup, 495 final MethodType methodType, 496 final Class<?> targetClass) 497 throws Throwable { 498 if (void.class != methodType.returnType()) { 499 throw new IllegalArgumentException("void.class != methodType.returnType(); methodType: " + methodType); 500 } 501 return MethodHandles.privateLookupIn(targetClass, lookup).findConstructor(targetClass, methodType); 502 } 503 504 505}