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}