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.constant;
015
016import java.lang.constant.ClassDesc;
017import java.lang.constant.Constable;
018import java.lang.constant.ConstantDesc;
019import java.lang.constant.DirectMethodHandleDesc;
020import java.lang.constant.DynamicConstantDesc;
021import java.lang.constant.MethodHandleDesc;
022import java.lang.constant.MethodTypeDesc;
023
024import java.util.Arrays;
025import java.util.Collection;
026import java.util.Comparator;
027import java.util.List;
028import java.util.Map;
029import java.util.Map.Entry;
030import java.util.Optional;
031import java.util.Set;
032import java.util.SortedMap;
033import java.util.SortedSet;
034
035import java.util.function.Function;
036
037import static java.lang.constant.ConstantDescs.BSM_INVOKE;
038import static java.lang.constant.ConstantDescs.CD_Collection;
039import static java.lang.constant.ConstantDescs.CD_List;
040import static java.lang.constant.ConstantDescs.CD_Map;
041import static java.lang.constant.ConstantDescs.CD_Object;
042import static java.lang.constant.ConstantDescs.CD_Set;
043import static java.lang.constant.ConstantDescs.NULL;
044
045import static org.microbean.constant.ConstantDescs.CD_Arrays;
046import static org.microbean.constant.ConstantDescs.CD_Collections;
047import static org.microbean.constant.ConstantDescs.CD_Comparator;
048import static org.microbean.constant.ConstantDescs.CD_Entry;
049import static org.microbean.constant.ConstantDescs.CD_HashSet;
050import static org.microbean.constant.ConstantDescs.CD_Optional;
051import static org.microbean.constant.ConstantDescs.CD_SimpleImmutableEntry;
052import static org.microbean.constant.ConstantDescs.CD_SortedMap;
053import static org.microbean.constant.ConstantDescs.CD_SortedSet;
054
055/**
056 * A utility class containing {@code static} methods that can describe various things in {@link Constable} ways.
057 *
058 * @author <a href="https://about.me/lairdnelson" target="_parent">Laird Nelson</a>
059 *
060 * @see #describeConstable(Collection)
061 *
062 * @see #describeConstable(Map)
063 *
064 * @see #describeConstable(Entry)
065 *
066 * @see Constable
067 */
068public final class Constables {
069
070
071  private static final ClassDesc CD_BootstrapMethods = ClassDesc.of("org.microbean.invoke.BootstrapMethods");
072
073  private static final ConstantDesc[] EMPTY_CONSTANTDESC_ARRAY = new ConstantDesc[0];
074
075
076  /*
077   * Constructors.
078   */
079
080
081  private Constables() {
082    super();
083  }
084
085
086  /*
087   * Static methods.
088   */
089
090
091  @SuppressWarnings("unchecked")
092  public static final Optional<? extends ConstantDesc> describeConstable(final Object o) {
093    return
094      o == null ? Optional.of(NULL) :
095      o instanceof Constable c ? c.describeConstable() :
096      o instanceof ConstantDesc cd ? Optional.of(cd) :
097      o instanceof List<?> l ? describeConstable(l) :
098      o instanceof Set<?> s ? describeConstable(s) :
099      o instanceof Map<?, ?> m ? describeConstable(m) :
100      o instanceof Entry<?, ?> e ? describeConstable(e) :
101      o instanceof Optional<?> opt ? describeConstable(opt) :
102      Optional.empty();
103  }
104
105  // Note that this describes the Optional itself, i.e. this is not a convenient shortcut to get to the optional's payload
106  public static final Optional<? extends ConstantDesc> describeConstable(final Optional<?> o) {
107    return describeConstable(o, Constables::describeConstable);
108  }
109
110  public static final <T> Optional<? extends ConstantDesc> describeConstable(final Optional<? extends T> o,
111                                                                             final Function<? super T, ? extends Optional<? extends ConstantDesc>> f) {
112    if (o == null) {
113      return Optional.of(NULL);
114    } else if (o.isEmpty()) {
115      return
116        Optional.of(callStatic(CD_Optional,
117                               "empty",
118                               MethodTypeDesc.of(CD_Optional)));
119    }
120    final Optional<? extends ConstantDesc> payload = f == null ? describeConstable(o.orElseThrow()) : f.apply(o.orElseThrow());
121    if (payload.isEmpty()) {
122      return Optional.empty();
123    }
124    return
125      Optional.of(callStatic(CD_Optional,
126                             "ofNullable",
127                             MethodTypeDesc.of(CD_Optional, CD_Object),
128                             payload.orElseThrow()));
129  }
130
131  public static final Optional<? extends ConstantDesc> describeConstable(final ConstantDesc cd) {
132    return
133      cd == null ? Optional.of(NULL) :
134      cd instanceof Constable c ? c.describeConstable() : // if something implements Constable, always give it a chance to modify its own description
135      Optional.of(cd);
136  }
137
138  public static final Optional<? extends ConstantDesc> describeConstable(final Constable c) {
139    return
140      c == null ? Optional.of(NULL) :
141      c.describeConstable();
142  }
143
144  public static final Optional<? extends ConstantDesc> describeConstable(final Collection<?> elements) {
145    return describeConstable(elements, Constables::empty, Constables::describeConstable);
146  }
147
148  public static final <E> Optional<? extends ConstantDesc> describeConstable(final Collection<? extends E> elements,
149                                                                             final Function<? super E, ? extends Optional<? extends ConstantDesc>> f) {
150    return describeConstable(elements, Constables::empty, f);
151  }
152
153  public static final <E> Optional<? extends ConstantDesc>
154    describeConstable(final Collection<? extends E> elements,
155                      final Function<? super Comparator<?>, ? extends Optional<? extends ConstantDesc>> cf,
156                      final Function<? super E, ? extends Optional<? extends ConstantDesc>> f) {
157    return
158      elements == null ? Optional.of(NULL) :
159      elements instanceof List<? extends E> l ? describeConstable0(l, CD_List, cf, f) :
160      elements instanceof Set<? extends E> s ? describeConstable0(s, CD_Set, cf, f) :
161      Optional.empty();
162  }
163
164  public static final Optional<? extends ConstantDesc> describeConstable(final List<?> elements) {
165    return describeConstable0(elements, CD_List, Constables::empty, Constables::describeConstable);
166  }
167
168  public static final <E> Optional<? extends ConstantDesc> describeConstable(final List<? extends E> elements,
169                                                                             final Function<? super E, ? extends Optional<? extends ConstantDesc>> f) {
170    return describeConstable0(elements, CD_List, Constables::empty, f);
171  }
172
173  public static final Optional<? extends ConstantDesc> describeConstable(final Set<?> elements) {
174    return describeConstable0(elements, CD_Set, Constables::empty, Constables::describeConstable);
175  }
176
177  public static final <E> Optional<? extends ConstantDesc> describeConstable(final Set<? extends E> elements,
178                                                                             final Function<? super E, ? extends Optional<? extends ConstantDesc>> f) {
179    return describeConstable0(elements, CD_Set, Constables::empty, f);
180  }
181
182  public static final <E> Optional<? extends ConstantDesc>
183    describeConstable(final Set<? extends E> elements,
184                      final Function<? super Comparator<?>, ? extends Optional<? extends ConstantDesc>> cf,
185                      final Function<? super E, ? extends Optional<? extends ConstantDesc>> f) {
186    return describeConstable0(elements, CD_Set, cf, f);
187  }
188
189  public static final Optional<? extends ConstantDesc> describeConstable(final Map<?, ?> map) {
190    return describeConstable0(map, Constables::empty, Constables::describeConstable, Constables::describeConstable);
191  }
192
193  public static final <K, V> Optional<? extends ConstantDesc> describeConstable(final Map<? extends K, ? extends V> map,
194                                                                                final Function<? super K, ? extends Optional<? extends ConstantDesc>> kf,
195                                                                                final Function<? super V, ? extends Optional<? extends ConstantDesc>> vf) {
196    return describeConstable0(map, Constables::empty, kf, vf);
197  }
198
199  public static final <K, V> Optional<? extends ConstantDesc>
200    describeConstable(final Map<? extends K, ? extends V> map,
201                      final Function<? super Comparator<?>, ? extends Optional<? extends ConstantDesc>> cf,
202                      final Function<? super K, ? extends Optional<? extends ConstantDesc>> kf,
203                      final Function<? super V, ? extends Optional<? extends ConstantDesc>> vf) {
204    return describeConstable0(map, cf, kf, vf);
205  }
206
207  public static final Optional<? extends ConstantDesc> describeConstable(final Entry<?, ?> entry) {
208    return describeConstable(entry, Constables::describeConstable, Constables::describeConstable);
209  }
210
211  public static final <K, V> Optional<? extends ConstantDesc> describeConstable(final Entry<? extends K, ? extends V> entry,
212                                                                                final Function<? super K, ? extends Optional<? extends ConstantDesc>> kf,
213                                                                                final Function<? super V, ? extends Optional<? extends ConstantDesc>> vf) {
214    return
215      entry == null ? Optional.of(NULL) :
216      entry instanceof Constable c ? c.describeConstable() :
217      describeConstable(entry.getKey(), entry.getValue(), kf, vf);
218  }
219
220  public static final <K, V> Optional<? extends ConstantDesc>
221    describeConstable(final K k,
222                      final V v,
223                      final Function<? super K, ? extends Optional<? extends ConstantDesc>> kf,
224                      final Function<? super V, ? extends Optional<? extends ConstantDesc>> vf) {
225    final Optional<? extends ConstantDesc> key =
226      k instanceof Constable c ? c.describeConstable() : kf == null ? describeConstable(k) : kf.apply(k);
227    if (key.isPresent()) {
228      final Optional<? extends ConstantDesc> value =
229        v instanceof Constable c ? c.describeConstable() : vf == null ? describeConstable(v) : vf.apply(v);
230      if (value.isPresent()) {
231        final ConstantDesc keyDesc = key.orElseThrow();
232        final ConstantDesc valueDesc = value.orElseThrow();
233        if (keyDesc == NULL || valueDesc == NULL) {
234          return
235            Optional.of(construct(CD_SimpleImmutableEntry,
236                                  new ClassDesc[] { CD_Object, CD_Object },// K and V erasures
237                                  keyDesc,
238                                  valueDesc));
239        }
240        // Map#entry(K, V)
241        return
242          Optional.of(callInterfaceStatic(CD_Map,
243                                          "entry",
244                                          MethodTypeDesc.of(CD_Entry,
245                                                            CD_Object, // K erasure
246                                                            CD_Object), // V erasure
247                                          keyDesc,
248                                          valueDesc));
249      }
250    }
251    return Optional.empty();
252  }
253
254
255  /*
256   * Private static methods.
257   */
258
259
260  private static final <E> Optional<? extends ConstantDesc>
261    describeConstable0(final SortedSet<? extends E> set,
262                       final Function<? super Comparator<?>, ? extends Optional<? extends ConstantDesc>> cf,
263                       final Function<? super E, ? extends Optional<? extends ConstantDesc>> f) {
264    if (set == null) {
265      return Optional.of(NULL);
266    } else if (set instanceof Constable c) {
267      return c.describeConstable();
268    }
269
270    // If the set has a user-supplied Comparator, we need to describe it as a ConstantDesc too.
271    final ConstantDesc comparatorDesc = describeComparator(set.comparator(), cf);
272    if (comparatorDesc == null) {
273      return Optional.empty();
274    }
275
276    if (set.isEmpty()) {
277      if (comparatorDesc == NULL) {
278        return Optional.of(callStatic(CD_Collections, "emptySortedSet", MethodTypeDesc.of(CD_SortedSet)));
279      }
280      return
281        Optional.of(callStatic(CD_BootstrapMethods,
282                               "immutableSortedSetOf",
283                               MethodTypeDesc.of(CD_SortedSet, CD_Comparator),
284                               comparatorDesc));
285    }
286
287    final ConstantDesc[] args = elements(set, f);
288    if (args.length <= 0) {
289      return Optional.empty();
290    }
291
292    final ConstantDesc unsortedListDesc = asList(args);
293
294    if (comparatorDesc == NULL) {
295      return
296        Optional.of(callStatic(CD_BootstrapMethods,
297                               "immutableSortedSetOf",
298                               MethodTypeDesc.of(CD_SortedSet, CD_Collection),
299                               unsortedListDesc));
300    }
301    return
302      Optional.of(callStatic(CD_BootstrapMethods,
303                             "immutableSortedSetOf",
304                             MethodTypeDesc.of(CD_SortedSet, CD_Collection, CD_Comparator),
305                             unsortedListDesc,
306                             comparatorDesc));
307  }
308
309  private static final <E> Optional<? extends ConstantDesc>
310    describeConstable0(final Collection<? extends E> elements,
311                       final ClassDesc listOrSetClassDesc,
312                       final Function<? super Comparator<?>, ? extends Optional<? extends ConstantDesc>> cf,
313                       Function<? super E, ? extends Optional<? extends ConstantDesc>> f) {
314    assert CD_List.equals(listOrSetClassDesc) || CD_Set.equals(listOrSetClassDesc) : String.valueOf(listOrSetClassDesc);
315    if (elements == null) {
316      return Optional.of(NULL);
317    } else if (elements instanceof Constable c) {
318      return c.describeConstable();
319    } else if (elements instanceof SortedSet<? extends E> ss) {
320      return describeConstable0(ss, cf, f);
321    } else if (elements.isEmpty()) {
322      return Optional.of(callInterfaceStatic(listOrSetClassDesc, "of", listOrSetClassDesc));
323    }
324
325    if (f == null) {
326      f = Constables::describeConstable;
327    }
328    final int elementsSize = elements.size();
329    final ConstantDesc[] args = new ConstantDesc[elementsSize];
330    boolean nulls = false;
331    int i = 0;
332    for (final E element : elements) {
333      final Optional<? extends ConstantDesc> arg = element instanceof Constable c ? c.describeConstable() : f.apply(element);
334      if (arg == null || arg.isEmpty()) {
335        // If there's even one thing that cannot be described, then the whole thing cannot be described.
336        return Optional.empty();
337      }
338      if (element == null) {
339        if (!nulls) {
340          nulls = true;
341        }
342      }
343      args[i++] = arg.orElseThrow();
344    }
345    if (nulls) {
346      final ConstantDesc cd = asList(args);
347      if (CD_List.equals(listOrSetClassDesc)) {
348        assert elements instanceof List;
349        return
350          Optional.of(callStatic(CD_Collections,
351                                 "unmodifiableList",
352                                 MethodTypeDesc.of(CD_List, CD_List),
353                                 cd));
354      }
355      assert elements instanceof Set;
356      return
357        Optional.of(callStatic(CD_Collections,
358                               "unmodifiableSet",
359                               MethodTypeDesc.of(CD_Set, CD_Set),
360                               construct(CD_HashSet, new ClassDesc[] { CD_Collection }, cd)));
361    }
362    final MethodTypeDesc ofMethodTypeDesc;
363    if (elementsSize <= 10) {
364      // List.of() and Set.of() have explicit polymorphic overrides for parameter counts of up to 10.
365      final ClassDesc[] parameterArray = new ClassDesc[elementsSize];
366      Arrays.fill(parameterArray, CD_Object); // Object is the erasure of E
367      ofMethodTypeDesc = MethodTypeDesc.of(listOrSetClassDesc, parameterArray);
368    } else {
369      // After 10 parameters, List.of() and Set.of() fall back on varargs.
370      ofMethodTypeDesc = MethodTypeDesc.of(listOrSetClassDesc, CD_Object.arrayType());
371    }
372    return
373      Optional.of(callInterfaceStatic(listOrSetClassDesc,
374                                      "of",
375                                      ofMethodTypeDesc,
376                                      args));
377  }
378
379  private static final <K, V> Optional<? extends ConstantDesc>
380    describeConstable0(final SortedMap<? extends K, ? extends V> map,
381                       final Function<? super Comparator<?>, ? extends Optional<? extends ConstantDesc>> cf,
382                       final Function<? super K, ? extends Optional<? extends ConstantDesc>> kf,
383                       final Function<? super V, ? extends Optional<? extends ConstantDesc>> vf) {
384    if (map == null) {
385      return Optional.of(NULL);
386    } else if (map instanceof Constable c) {
387      return c.describeConstable();
388    }
389
390    // If the map has a user-supplied Comparator, we need to describe it as a ConstantDesc too.
391    final ConstantDesc comparatorDesc = describeComparator(map.comparator(), cf);
392    if (comparatorDesc == null) {
393      return Optional.empty();
394    }
395
396    if (map.isEmpty()) {
397      if (comparatorDesc == NULL) {
398        return Optional.of(callStatic(CD_Collections, "emptySortedMap", MethodTypeDesc.of(CD_SortedMap)));
399      }
400      return
401        Optional.of(callStatic(CD_BootstrapMethods,
402                               "immutableEmptySortedMap",
403                               MethodTypeDesc.of(CD_SortedMap, CD_Comparator),
404                               comparatorDesc));
405    }
406
407    final ConstantDesc entriesListDesc = asList(entries(map, kf, vf, false));
408
409    if (comparatorDesc == NULL) {
410      return
411        Optional.of(callStatic(CD_BootstrapMethods,
412                               "immutableSortedMapOf",
413                               MethodTypeDesc.of(CD_SortedMap, CD_Collection),
414                               entriesListDesc));
415    }
416    return
417      Optional.of(callStatic(CD_BootstrapMethods,
418                             "immutableSortedMapOf",
419                             MethodTypeDesc.of(CD_SortedMap, CD_Collection, CD_Comparator),
420                             entriesListDesc,
421                             comparatorDesc));
422  }
423
424  private static final <K, V> Optional<? extends ConstantDesc>
425    describeConstable0(final Map<? extends K, ? extends V> map,
426                       final Function<? super Comparator<?>, ? extends Optional<? extends ConstantDesc>> cf,
427                       final Function<? super K, ? extends Optional<? extends ConstantDesc>> kf,
428                       final Function<? super V, ? extends Optional<? extends ConstantDesc>> vf) {
429    if (map == null) {
430      return Optional.of(NULL);
431    } else if (map instanceof Constable c) {
432      return c.describeConstable();
433    } else if (map instanceof SortedMap<? extends K, ? extends V> sm) {
434      return describeConstable0(sm, cf, kf, vf);
435    } else if (map.isEmpty()) {
436      // Map.of()
437      return Optional.of(callInterfaceStatic(CD_Map, "of", CD_Map));
438    }
439
440    final ConstantDesc[] args = entries(map, kf, vf, true);
441    if (args.length <= 0) {
442      return Optional.empty();
443    }
444
445    // Map.ofEntries(Map.Entry...)
446    return Optional.of(callInterfaceStatic(CD_Map, "ofEntries", MethodTypeDesc.of(CD_Map, CD_Entry.arrayType()), args));
447  }
448
449  private static final <E> ConstantDesc[] elements(final Collection<? extends E> source,
450                                                   Function<? super E, ? extends Optional<? extends ConstantDesc>> f) {
451    if (f == null) {
452      f = Constables::describeConstable;
453    }
454    final ConstantDesc[] args = new ConstantDesc[source.size()];
455    int i = 0;
456    for (final E element : source) {
457      final Optional<? extends ConstantDesc> arg = element instanceof Constable c ? c.describeConstable() : f.apply(element);
458      if (arg == null || arg.isEmpty()) {
459        // If there's even one thing that cannot be described, then the whole thing cannot be described.
460        return EMPTY_CONSTANTDESC_ARRAY;
461      }
462      args[i++] = arg.orElseThrow();
463    }
464    return args;
465  }
466
467  private static final <K, V> ConstantDesc[] entries(final Map<? extends K, ? extends V> map,
468                                                     final Function<? super K, ? extends Optional<? extends ConstantDesc>> kf,
469                                                     final Function<? super V, ? extends Optional<? extends ConstantDesc>> vf,
470                                                     final boolean rejectNulls) {
471    if (map.isEmpty()) {
472      return EMPTY_CONSTANTDESC_ARRAY;
473    }
474    final ConstantDesc[] args = new ConstantDesc[map.size()];
475    int i = 0;
476    for (final Entry<? extends K, ? extends V> entry : map.entrySet()) {
477      if (rejectNulls && (entry.getKey() == null || entry.getValue() == null)) {
478        return EMPTY_CONSTANTDESC_ARRAY;
479      }
480      final Optional<? extends ConstantDesc> e = describeConstable(entry, kf, vf);
481      if (e.isEmpty()) {
482        // If there's even one thing that cannot be described, then the whole thing can't be described.
483        return EMPTY_CONSTANTDESC_ARRAY;
484      }
485      args[i++] = e.orElseThrow();
486    }
487    return args;
488  }
489
490  private static final DynamicConstantDesc<?> construct(final ClassDesc cd, final ClassDesc[] constructorParameterTypes, final ConstantDesc... args) {
491    final ConstantDesc[] newArgs = new ConstantDesc[args == null || args.length <= 0 ? 1 : args.length + 1];
492    newArgs[0] = MethodHandleDesc.ofConstructor(cd, constructorParameterTypes);
493    if (newArgs.length > 1) {
494      System.arraycopy(args, 0, newArgs, 1, args.length);
495    }
496    return DynamicConstantDesc.of(BSM_INVOKE, newArgs);
497  }
498
499  private static final DynamicConstantDesc<?> callInterfaceStatic(final ClassDesc cd, final String name, final ClassDesc returnType) {
500    return callInterfaceStatic(cd, name, MethodTypeDesc.of(returnType));
501  }
502
503  private static final DynamicConstantDesc<?> callInterfaceStatic(final ClassDesc cd, final String name, final MethodTypeDesc sig, final ConstantDesc... args) {
504    return call(DirectMethodHandleDesc.Kind.INTERFACE_STATIC, cd, name, sig, args);
505  }
506
507  private static final DynamicConstantDesc<?> callStatic(final ClassDesc cd, final String name, final MethodTypeDesc sig, final ConstantDesc... args) {
508    return call(DirectMethodHandleDesc.Kind.STATIC, cd, name, sig, args);
509  }
510
511  private static final DynamicConstantDesc<?> call(final DirectMethodHandleDesc.Kind kind,
512                                                   final ClassDesc cd,
513                                                   final String name,
514                                                   final MethodTypeDesc sig,
515                                                   final ConstantDesc... args) {
516    final ConstantDesc[] newArgs = new ConstantDesc[args == null || args.length == 0 ? 1 : args.length + 1];
517    newArgs[0] = MethodHandleDesc.ofMethod(kind, cd, name, sig);
518    if (newArgs.length > 1) {
519      System.arraycopy(args, 0, newArgs, 1, args.length);
520    }
521    return DynamicConstantDesc.of(BSM_INVOKE, newArgs);
522  }
523
524  private static final <T> Optional<? extends ConstantDesc> empty(final T ignored) {
525    return Optional.empty();
526  }
527
528  private static final ConstantDesc describeComparator(final Comparator<?> comparator,
529                                                       final Function<? super Comparator<?>, ? extends Optional<? extends ConstantDesc>> cf) {
530    return
531      comparator == null ? NULL :
532      comparator instanceof Constable c ? c.describeConstable().orElse(null) :
533      cf == null ? null :
534      cf.apply(comparator).orElse(null);
535  }
536
537  private static final DynamicConstantDesc<?> asList(final ConstantDesc[] args) {
538    return callStatic(CD_Arrays, "asList", MethodTypeDesc.of(CD_List, CD_Object.arrayType()), args);
539  }
540
541}