001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
002 *
003 * Copyright © 2024–2026 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.construct.constant;
015
016import java.lang.constant.ClassDesc;
017import java.lang.constant.Constable;
018import java.lang.constant.ConstantDesc;
019import java.lang.constant.DynamicConstantDesc;
020import java.lang.constant.MethodTypeDesc;
021
022import java.util.List;
023import java.util.Map;
024import java.util.Map.Entry;
025import java.util.Optional;
026
027import java.util.function.Function;
028
029import javax.lang.model.AnnotatedConstruct;
030
031import javax.lang.model.element.Element;
032import javax.lang.model.element.ExecutableElement;
033import javax.lang.model.element.ModuleElement;
034import javax.lang.model.element.PackageElement;
035import javax.lang.model.element.Name;
036import javax.lang.model.element.RecordComponentElement;
037import javax.lang.model.element.TypeElement;
038import javax.lang.model.element.TypeParameterElement;
039import javax.lang.model.element.VariableElement;
040
041import javax.lang.model.type.ArrayType;
042import javax.lang.model.type.DeclaredType;
043import javax.lang.model.type.NoType;
044import javax.lang.model.type.NullType;
045import javax.lang.model.type.PrimitiveType;
046import javax.lang.model.type.TypeKind;
047import javax.lang.model.type.TypeMirror;
048import javax.lang.model.type.TypeVariable;
049import javax.lang.model.type.WildcardType;
050
051import org.microbean.construct.Domain;
052
053import static java.lang.constant.ConstantDescs.BSM_INVOKE;
054import static java.lang.constant.ConstantDescs.CD_List;
055import static java.lang.constant.ConstantDescs.CD_Map;
056import static java.lang.constant.ConstantDescs.CD_Object;
057import static java.lang.constant.ConstantDescs.NULL;
058
059import static java.lang.constant.DirectMethodHandleDesc.Kind.INTERFACE_STATIC;
060import static java.lang.constant.DirectMethodHandleDesc.Kind.VIRTUAL;
061
062import static java.lang.constant.MethodHandleDesc.ofMethod;
063
064import static java.util.Arrays.fill;
065
066import static org.microbean.construct.constant.ConstantDescs.CD_ArrayType;
067import static org.microbean.construct.constant.ConstantDescs.CD_CharSequence;
068import static org.microbean.construct.constant.ConstantDescs.CD_DeclaredType;
069import static org.microbean.construct.constant.ConstantDescs.CD_Element;
070import static org.microbean.construct.constant.ConstantDescs.CD_ExecutableElement;
071import static org.microbean.construct.constant.ConstantDescs.CD_ModuleElement;
072import static org.microbean.construct.constant.ConstantDescs.CD_Name;
073import static org.microbean.construct.constant.ConstantDescs.CD_NoType;
074import static org.microbean.construct.constant.ConstantDescs.CD_NullType;
075import static org.microbean.construct.constant.ConstantDescs.CD_PackageElement;
076import static org.microbean.construct.constant.ConstantDescs.CD_Parameterizable;
077import static org.microbean.construct.constant.ConstantDescs.CD_PrimitiveType;
078import static org.microbean.construct.constant.ConstantDescs.CD_RecordComponentElement;
079import static org.microbean.construct.constant.ConstantDescs.CD_TypeElement;
080import static org.microbean.construct.constant.ConstantDescs.CD_TypeKind;
081import static org.microbean.construct.constant.ConstantDescs.CD_TypeParameterElement;
082import static org.microbean.construct.constant.ConstantDescs.CD_TypeMirror;
083import static org.microbean.construct.constant.ConstantDescs.CD_TypeVariable;
084import static org.microbean.construct.constant.ConstantDescs.CD_VariableElement;
085import static org.microbean.construct.constant.ConstantDescs.CD_WildcardType;
086
087/**
088 * A utility class that returns nominal descriptors for constructs.
089 *
090 * @author <a href="https://about.me/lairdnelson" target="_top">Laird Nelson</a>
091 *
092 * @see #describe(Element, Domain)
093 *
094 * @see #describe(TypeMirror, Domain)
095 */
096@SuppressWarnings("try")
097public final class Constables {
098
099  private Constables() {
100    super();
101  }
102
103  /**
104   * Returns a nominal descriptor for the supplied argument, presuming it to have originated from the supplied {@link
105   * Domain}, or an {@linkplain Optional#empty() empty} {@link Optional} if the supplied argument cannot be described.
106   *
107   * @param n the argument; may be {@code null}
108   *
109   * @param d the {@link Domain} from which the argument originated; must not be {@code null}
110   *
111   * @return a non-{@code null} {@link Optional}
112   *
113   * @exception NullPointerException if {@code d} is {@code null}
114   */
115  public static final Optional<? extends ConstantDesc> describe(final Name n, final Domain d) {
116    return switch (n) {
117    case null -> Optional.of(NULL);
118    case Constable c -> c.describeConstable();
119    case ConstantDesc cd -> Optional.of(cd); // future proofing?
120    default -> (d instanceof Constable c ? c.describeConstable() : Optional.<ConstantDesc>empty())
121      .map(domainDesc -> DynamicConstantDesc.of(BSM_INVOKE,
122                                                ofMethod(VIRTUAL,
123                                                         Domain.class.describeConstable().orElseThrow(),
124                                                         "name",
125                                                         MethodTypeDesc.of(CD_Name,
126                                                                           CD_CharSequence)),
127                                                domainDesc,
128                                                d.toString(n)));
129    };
130  }
131
132  /**
133   * Returns a nominal descriptor for the supplied argument, presuming it to have originated from the supplied {@link
134   * Domain}, or an {@linkplain Optional#empty() empty} {@link Optional} if the supplied argument cannot be described.
135   *
136   * @param ac the argument; may be {@code null}
137   *
138   * @param d the {@link Domain} from which the argument originated; must not be {@code null}
139   *
140   * @return a non-{@code null} {@link Optional}
141   *
142   * @exception NullPointerException if {@code d} is {@code null}
143   */
144  public static final Optional<? extends ConstantDesc> describe(final AnnotatedConstruct ac, final Domain d) {
145    return switch (ac) {
146    case null -> Optional.of(NULL);
147    case Constable c -> c.describeConstable();
148    case ConstantDesc cd -> Optional.of(cd); // future proofing?
149    case Element e -> describe(e, d);
150    case TypeMirror t -> describe(t, d);
151    default -> Optional.empty();
152    };
153  }
154
155  /**
156   * Returns a nominal descriptor for the supplied argument, presuming it to have originated from the supplied {@link
157   * Domain}, or an {@linkplain Optional#empty() empty} {@link Optional} if the supplied argument cannot be described.
158   *
159   * @param e the argument; may be {@code null}
160   *
161   * @param d the {@link Domain} from which the argument originated; must not be {@code null}
162   *
163   * @return a non-{@code null} {@link Optional}
164   *
165   * @exception NullPointerException if {@code d} is {@code null}
166   */
167  public static final Optional<? extends ConstantDesc> describe(final Element e, final Domain d) {
168    return switch (e) {
169    case null -> Optional.of(NULL);
170    case Constable c -> c.describeConstable();
171    case ConstantDesc cd -> Optional.of(cd); // future proofing?
172    default -> {
173      try (var lock = d.lock()) {
174        yield switch (e.getKind()) {
175        case ANNOTATION_TYPE, CLASS, ENUM, INTERFACE, RECORD -> describe((TypeElement)e, d);
176        case BINDING_VARIABLE, EXCEPTION_PARAMETER, LOCAL_VARIABLE, OTHER, RESOURCE_VARIABLE ->
177          // No way to get these from javax.lang.model.Elements
178          Optional.empty();
179        case CONSTRUCTOR, INSTANCE_INIT, METHOD, STATIC_INIT -> describe((ExecutableElement)e, d);
180        case ENUM_CONSTANT, FIELD, PARAMETER -> describe((VariableElement)e, d);
181        case MODULE -> describe((ModuleElement)e, d);
182        case PACKAGE -> describe((PackageElement)e, d);
183        case RECORD_COMPONENT -> describe((RecordComponentElement)e, d);
184        case TYPE_PARAMETER -> describe((TypeParameterElement)e, d);
185        };
186      }
187    }
188    };
189  }
190
191  /**
192   * Returns a nominal descriptor for the supplied argument, presuming it to have originated from the supplied {@link
193   * Domain}, or an {@linkplain Optional#empty() empty} {@link Optional} if the supplied argument cannot be described.
194   *
195   * @param e the argument; may be {@code null}
196   *
197   * @param d the {@link Domain} from which the argument originated; must not be {@code null}
198   *
199   * @return a non-{@code null} {@link Optional}
200   *
201   * @exception NullPointerException if {@code d} is {@code null}
202   */
203  public static final Optional<? extends ConstantDesc> describe(final ExecutableElement e, final Domain d) {
204    return switch (e) {
205    case null -> Optional.of(NULL);
206    case Constable c -> c.describeConstable();
207    case ConstantDesc cd -> Optional.of(cd); // future proofing?
208    default -> {
209      final ConstantDesc domainDesc = d instanceof Constable c ? c.describeConstable().orElse(null) : null;
210      if (domainDesc == null) {
211        yield Optional.empty();
212      }
213      try (var lock = d.lock()) {
214        // Trying to do this via flatMap etc. simply will not work because type inference does not work properly, even
215        // with hints/coercion. Feel free to try again, but make sure you keep this implementation around to revert
216        // back to.
217        final List<? extends VariableElement> parameters = e.getParameters();
218        final int parameterCount = parameters.size();
219        final ConstantDesc[] args = new ConstantDesc[5 + parameterCount];
220        args[0] =
221          ofMethod(VIRTUAL,
222                   Domain.class.describeConstable().orElseThrow(),
223                   "executableElement",
224                   MethodTypeDesc.of(CD_ExecutableElement,
225                                     CD_TypeElement,
226                                     CD_TypeMirror,
227                                     CD_CharSequence,
228                                     CD_TypeMirror.arrayType()));
229        args[1] = domainDesc;
230        args[2] = describe(e.getEnclosingElement(), d).orElse(null);
231        if (args[2] == null) {
232          yield Optional.empty();
233        }
234        args[3] = describe(e.getReturnType(), d).orElse(null);
235        if (args[3] == null) {
236          yield Optional.empty();
237        }
238        args[4] = describe(e.getSimpleName(), d).orElse(null);
239        if (args[4] == null) {
240          yield Optional.empty();
241        }
242        for (int i = 0; i < parameterCount; i++) {
243          int index = i + 5;
244          args[index] = describe(parameters.get(i).asType(), d).orElse(null);
245          if (args[index] == null) {
246            yield Optional.empty();
247          }
248        }
249        yield Optional.of(DynamicConstantDesc.of(BSM_INVOKE, args));
250      }
251    }
252    };
253  }
254
255  /**
256   * Returns a nominal descriptor for the supplied argument, presuming it to have originated from the supplied {@link
257   * Domain}, or an {@linkplain Optional#empty() empty} {@link Optional} if the supplied argument cannot be described.
258   *
259   * @param e the argument; may be {@code null}
260   *
261   * @param d the {@link Domain} from which the argument originated; must not be {@code null}
262   *
263   * @return a non-{@code null} {@link Optional}
264   *
265   * @exception NullPointerException if {@code d} is {@code null}
266   */
267  public static final Optional<? extends ConstantDesc> describe(final ModuleElement e, final Domain d) {
268    return switch (e) {
269    case null -> Optional.of(NULL);
270    case Constable c -> c.describeConstable();
271    case ConstantDesc cd -> Optional.of(cd); // future proofing?
272    default -> describe(e.getQualifiedName(), d) // getQualifiedName() does not cause symbol completion
273      .map(nameDesc -> DynamicConstantDesc.of(BSM_INVOKE,
274                                              ofMethod(VIRTUAL,
275                                                       Domain.class.describeConstable().orElseThrow(),
276                                                       "moduleElement",
277                                                       MethodTypeDesc.of(CD_ModuleElement,
278                                                                         CD_CharSequence)),
279                                              ((Constable)d).describeConstable().orElseThrow(),
280                                              nameDesc));
281    };
282  }
283
284  /**
285   * Returns a nominal descriptor for the supplied argument, presuming it to have originated from the supplied {@link
286   * Domain}, or an {@linkplain Optional#empty() empty} {@link Optional} if the supplied argument cannot be described.
287   *
288   * @param e the argument; may be {@code null}
289   *
290   * @param d the {@link Domain} from which the argument originated; must not be {@code null}
291   *
292   * @return a non-{@code null} {@link Optional}
293   *
294   * @exception NullPointerException if {@code d} is {@code null}
295   */
296  public static final Optional<? extends ConstantDesc> describe(final PackageElement e, final Domain d) {
297    return switch (e) {
298    case null -> Optional.of(NULL);
299    case Constable c -> c.describeConstable();
300    case ConstantDesc cd -> Optional.of(cd); // future proofing?
301    default -> describe(e.getQualifiedName(), d) // getQualifiedName() does not cause symbol completion
302      .map(nameDesc -> DynamicConstantDesc.of(BSM_INVOKE,
303                                              ofMethod(VIRTUAL,
304                                                       Domain.class.describeConstable().orElseThrow(),
305                                                       "packageElement",
306                                                       MethodTypeDesc.of(CD_PackageElement,
307                                                                         CD_CharSequence)),
308                                              ((Constable)d).describeConstable().orElseThrow(),
309                                              nameDesc));
310    };
311  }
312
313  /**
314   * Returns a nominal descriptor for the supplied argument, presuming it to have originated from the supplied {@link
315   * Domain}, or an {@linkplain Optional#empty() empty} {@link Optional} if the supplied argument cannot be described.
316   *
317   * @param e the argument; may be {@code null}
318   *
319   * @param d the {@link Domain} from which the argument originated; must not be {@code null}
320   *
321   * @return a non-{@code null} {@link Optional}
322   *
323   * @exception NullPointerException if {@code d} is {@code null}
324   */
325  public static final Optional<? extends ConstantDesc> describe(final TypeElement e, final Domain d) {
326    return switch (e) {
327    case null -> Optional.of(NULL);
328    case Constable c -> c.describeConstable();
329    case ConstantDesc cd -> Optional.of(cd); // future proofing?
330    default -> describe(e.getQualifiedName(), d) // getQualifiedName() does not cause symbol completion
331      .map(nameDesc -> DynamicConstantDesc.of(BSM_INVOKE,
332                                              ofMethod(VIRTUAL,
333                                                       Domain.class.describeConstable().orElseThrow(),
334                                                       "typeElement",
335                                                       MethodTypeDesc.of(CD_TypeElement,
336                                                                         CD_CharSequence)),
337                                              ((Constable)d).describeConstable().orElseThrow(),
338                                              nameDesc));
339    };
340  }
341
342  /**
343   * Returns a nominal descriptor for the supplied argument, presuming it to have originated from the supplied {@link
344   * Domain}, or an {@linkplain Optional#empty() empty} {@link Optional} if the supplied argument cannot be described.
345   *
346   * @param e the argument; may be {@code null}
347   *
348   * @param d the {@link Domain} from which the argument originated; must not be {@code null}
349   *
350   * @return a non-{@code null} {@link Optional}
351   *
352   * @exception NullPointerException if {@code d} is {@code null}
353   */
354  public static final Optional<? extends ConstantDesc> describe(final TypeParameterElement e, final Domain d) {
355    return switch (e) {
356    case null -> Optional.of(NULL);
357    case Constable c -> c.describeConstable();
358    case ConstantDesc cd -> Optional.of(cd); // future proofing?
359    default -> {
360      try (var lock = d.lock()) {
361        yield describe(e.getEnclosingElement(), d)
362          .flatMap(parameterizableDesc -> describe(e.getSimpleName(), d)
363                   .map(nameDesc -> DynamicConstantDesc.of(BSM_INVOKE,
364                                                           ofMethod(VIRTUAL,
365                                                                    Domain.class.describeConstable().orElseThrow(),
366                                                                    "typeParameterElement",
367                                                                    MethodTypeDesc.of(CD_TypeParameterElement,
368                                                                                      CD_Parameterizable,
369                                                                                      CD_Name)),
370                                                           ((Constable)d).describeConstable().orElseThrow(),
371                                                           parameterizableDesc,
372                                                           nameDesc)));
373      }
374    }
375    };
376  }
377
378  /**
379   * Returns a nominal descriptor for the supplied argument, presuming it to have originated from the supplied {@link
380   * Domain}, or an {@linkplain Optional#empty() empty} {@link Optional} if the supplied argument cannot be described.
381   *
382   * @param e the argument; may be {@code null}
383   *
384   * @param d the {@link Domain} from which the argument originated; must not be {@code null}
385   *
386   * @return a non-{@code null} {@link Optional}
387   *
388   * @exception NullPointerException if {@code d} is {@code null}
389   */
390  public static final Optional<? extends ConstantDesc> describe(final RecordComponentElement e, final Domain d) {
391    return switch (e) {
392    case null -> Optional.of(NULL);
393    case Constable c -> c.describeConstable();
394    case ConstantDesc cd -> Optional.of(cd); // future proofing?
395    default -> {
396      try (var lock = d.lock()) {
397        yield describe((TypeElement)e.getEnclosingElement(), d)
398          .map(executableDesc -> DynamicConstantDesc.of(BSM_INVOKE,
399                                                        ofMethod(VIRTUAL,
400                                                                 Domain.class.describeConstable().orElseThrow(),
401                                                                 "recordComponentElement",
402                                                                 MethodTypeDesc.of(CD_RecordComponentElement,
403                                                                                   CD_ExecutableElement)),
404                                                        ((Constable)d).describeConstable().orElseThrow(),
405                                                        executableDesc));
406      }
407    }
408    };
409  }
410
411  /**
412   * Returns a nominal descriptor for the supplied argument, presuming it to have originated from the supplied {@link
413   * Domain}, or an {@linkplain Optional#empty() empty} {@link Optional} if the supplied argument cannot be described.
414   *
415   * @param e the argument; may be {@code null}
416   *
417   * @param d the {@link Domain} from which the argument originated; must not be {@code null}
418   *
419   * @return a non-{@code null} {@link Optional}
420   *
421   * @exception NullPointerException if {@code d} is {@code null}
422   */
423  public static final Optional<? extends ConstantDesc> describe(final VariableElement e, final Domain d) {
424    return switch (e) {
425    case null -> Optional.of(NULL);
426    case Constable c -> c.describeConstable();
427    case ConstantDesc cd -> Optional.of(cd); // future proofing?
428    default -> {
429      try (var lock = d.lock()) {
430        yield describe(e.getSimpleName(), d)
431          .flatMap(nameDesc -> describe(e.getEnclosingElement(), d)
432                   .map(enclosingElementDesc -> DynamicConstantDesc.of(BSM_INVOKE,
433                                                                       ofMethod(VIRTUAL,
434                                                                                Domain.class.describeConstable().orElseThrow(),
435                                                                                "variableElement",
436                                                                                MethodTypeDesc.of(CD_VariableElement,
437                                                                                                  CD_Element,
438                                                                                                  CD_CharSequence)),
439                                                                       ((Constable)d).describeConstable().orElseThrow(),
440                                                                       enclosingElementDesc,
441                                                                       nameDesc)));
442      }
443    }
444    };
445  }
446
447  /**
448   * Returns a nominal descriptor for the supplied argument, presuming it to have originated from the supplied {@link
449   * Domain}, or an {@linkplain Optional#empty() empty} {@link Optional} if the supplied argument cannot be described.
450   *
451   * @param t the argument; may be {@code null}
452   *
453   * @param d the {@link Domain} from which the argument originated; must not be {@code null}
454   *
455   * @return a non-{@code null} {@link Optional}
456   *
457   * @exception NullPointerException if {@code d} is {@code null}
458   */
459  public static final Optional<? extends ConstantDesc> describe(final TypeMirror t, final Domain d) {
460    return switch (t) {
461    case null -> Optional.of(NULL);
462    case Constable c -> c.describeConstable();
463    case ConstantDesc cd -> Optional.of(cd); // future proofing?
464    default -> {
465      try (var lock = d.lock()) {
466        yield switch (t.getKind()) {
467        case ARRAY -> describe((ArrayType)t, d);
468        case BOOLEAN, BYTE, CHAR, DOUBLE, FLOAT, INT, LONG, SHORT -> describe((PrimitiveType)t, d);
469        case DECLARED -> describe((DeclaredType)t, d);
470        case EXECUTABLE, INTERSECTION, UNION -> Optional.empty(); // No way to get these from javax.lang.model.util.Types
471        case ERROR, OTHER -> Optional.empty();
472        case MODULE, NONE, PACKAGE, VOID -> describe((NoType)t, d);
473        case NULL -> describe((NullType)t, d);
474        case TYPEVAR -> describe((TypeVariable)t, d); // Prefer working with TypeParameterElement instead
475        case WILDCARD -> describe((WildcardType)t, d);
476        };
477      }
478    }
479    };
480  }
481
482  /**
483   * Returns a nominal descriptor for the supplied argument, presuming it to have originated from the supplied {@link
484   * Domain}, or an {@linkplain Optional#empty() empty} {@link Optional} if the supplied argument cannot be described.
485   *
486   * @param t the argument; may be {@code null}
487   *
488   * @param d the {@link Domain} from which the argument originated; must not be {@code null}
489   *
490   * @return a non-{@code null} {@link Optional}
491   *
492   * @exception NullPointerException if {@code d} is {@code null}
493   */
494  public static final Optional<? extends ConstantDesc> describe(final ArrayType t, final Domain d) {
495    return switch (t) {
496    case null -> Optional.of(NULL);
497    case Constable c -> c.describeConstable();
498    case ConstantDesc cd -> Optional.of(cd); // future proofing?
499    default -> {
500      try (var lock = d.lock()) {
501        yield describe(t.getComponentType(), d)
502          .map(componentTypeDesc -> DynamicConstantDesc.of(BSM_INVOKE,
503                                                           ofMethod(VIRTUAL,
504                                                                    Domain.class.describeConstable().orElseThrow(),
505                                                                    "arrayTypeOf",
506                                                                    MethodTypeDesc.of(CD_ArrayType,
507                                                                                      CD_TypeMirror)),
508                                                           ((Constable)d).describeConstable().orElseThrow(),
509                                                           componentTypeDesc));
510      }
511    }
512    };
513  }
514
515  /**
516   * Returns a nominal descriptor for the supplied argument, presuming it to have originated from the supplied {@link
517   * Domain}, or an {@linkplain Optional#empty() empty} {@link Optional} if the supplied argument cannot be described.
518   *
519   * @param t the argument; may be {@code null}
520   *
521   * @param d the {@link Domain} from which the argument originated; must not be {@code null}
522   *
523   * @return a non-{@code null} {@link Optional}
524   *
525   * @exception NullPointerException if {@code d} is {@code null}
526   */
527  public static final Optional<? extends ConstantDesc> describe(final DeclaredType t, final Domain d) {
528    return switch (t) {
529    case null -> Optional.of(NULL);
530    case Constable c -> c.describeConstable();
531    case ConstantDesc cd -> Optional.of(cd); // future proofing?
532    default -> {
533      final ConstantDesc domainDesc = d instanceof Constable constableDomain ? constableDomain.describeConstable().orElse(null) : null;
534      if (domainDesc == null) {
535        yield Optional.empty();
536      }
537      try (var lock = d.lock()) {
538        yield switch (t.getKind()) {
539        case DECLARED -> {
540          final List<? extends TypeMirror> typeArguments = t.getTypeArguments();
541          final int typeArgumentCount = typeArguments.size();
542          final ConstantDesc[] args = new ConstantDesc[4 + typeArgumentCount];
543          final TypeMirror enclosingType = t.getEnclosingType();
544          // Call
545          args[0] = ofMethod(VIRTUAL,
546                             Domain.class.describeConstable().orElseThrow(),
547                             "declaredType",
548                             MethodTypeDesc.of(CD_DeclaredType,
549                                               CD_DeclaredType,
550                                               CD_TypeElement,
551                                               CD_TypeMirror.arrayType()));
552          args[1] = domainDesc;
553          args[2] = enclosingType.getKind() == TypeKind.NONE ? NULL : describe(enclosingType, d).orElse(null);
554          if (args[2] == null) {
555            yield Optional.empty();
556          }
557          args[3] = describe((TypeElement)t.asElement(), d).orElse(null);
558          if (args[3] == null) {
559            yield Optional.empty();
560          }
561          for (int i = 0; i < typeArgumentCount; i++) {
562            final int index = i + 4;
563            args[index] = describe(typeArguments.get(i), d).orElse(null);
564            if (args[index] == null) {
565              yield Optional.empty();
566            }
567          }
568          yield Optional.of(DynamicConstantDesc.of(BSM_INVOKE, args));
569        }
570        default -> Optional.empty(); // could be an error type
571        };
572      }
573    }
574    };
575  }
576
577  /**
578   * Returns a nominal descriptor for the supplied argument, presuming it to have originated from the supplied {@link
579   * Domain}, or an {@linkplain Optional#empty() empty} {@link Optional} if the supplied argument cannot be described.
580   *
581   * @param t the argument; may be {@code null}
582   *
583   * @param d the {@link Domain} from which the argument originated; must not be {@code null}
584   *
585   * @return a non-{@code null} {@link Optional}
586   *
587   * @exception NullPointerException if {@code d} is {@code null}
588   */
589  public static final Optional<? extends ConstantDesc> describe(final NoType t, final Domain d) {
590    return switch (t) {
591    case null -> Optional.of(NULL);
592    case Constable c -> c.describeConstable();
593    case ConstantDesc cd -> Optional.of(cd); // future proofing?
594    default -> {
595      final ConstantDesc domainDesc = d instanceof Constable c ? c.describeConstable().orElse(null) : null;
596      if (domainDesc == null) {
597        yield Optional.empty();
598      }
599      try (var lock = d.lock()) {
600        yield t.getKind().describeConstable()
601          .map(typeKindDesc -> DynamicConstantDesc.of(BSM_INVOKE,
602                                                      ofMethod(VIRTUAL,
603                                                               Domain.class.describeConstable().orElseThrow(),
604                                                               "noType",
605                                                               MethodTypeDesc.of(CD_NoType,
606                                                                                 CD_TypeKind)),
607                                                      domainDesc,
608                                                      typeKindDesc));
609      }
610    }
611    };
612  }
613
614  /**
615   * Returns a nominal descriptor for the supplied argument, presuming it to have originated from the supplied {@link
616   * Domain}, or an {@linkplain Optional#empty() empty} {@link Optional} if the supplied argument cannot be described.
617   *
618   * @param t the argument; may be {@code null}
619   *
620   * @param d the {@link Domain} from which the argument originated; must not be {@code null}
621   *
622   * @return a non-{@code null} {@link Optional}
623   *
624   * @exception NullPointerException if {@code d} is {@code null}
625   */
626  public static final Optional<? extends ConstantDesc> describe(final NullType t, final Domain d) {
627    return switch (t) {
628    case null -> Optional.of(NULL);
629    case Constable c -> c.describeConstable();
630    case ConstantDesc cd -> Optional.of(cd); // future proofing?
631    default -> (d instanceof Constable c ? c.describeConstable() : Optional.<ConstantDesc>empty())
632      .map(domainDesc -> DynamicConstantDesc.of(BSM_INVOKE,
633                                                ofMethod(VIRTUAL,
634                                                         Domain.class.describeConstable().orElseThrow(),
635                                                         "nullType",
636                                                         MethodTypeDesc.of(CD_NullType)),
637                                                domainDesc));
638    };
639  }
640
641  /**
642   * Returns a nominal descriptor for the supplied argument, presuming it to have originated from the supplied {@link
643   * Domain}, or an {@linkplain Optional#empty() empty} {@link Optional} if the supplied argument cannot be described.
644   *
645   * @param t the argument; may be {@code null}
646   *
647   * @param d the {@link Domain} from which the argument originated; must not be {@code null}
648   *
649   * @return a non-{@code null} {@link Optional}
650   *
651   * @exception NullPointerException if {@code d} is {@code null}
652   */
653  public static final Optional<? extends ConstantDesc> describe(final PrimitiveType t, final Domain d) {
654    return switch (t) {
655    case null -> Optional.of(NULL);
656    case Constable c -> c.describeConstable();
657    case ConstantDesc cd -> Optional.of(cd); // future proofing?
658    default -> {
659      final ConstantDesc domainDesc = d instanceof Constable constableDomain ? constableDomain.describeConstable().orElse(null) : null;
660      if (domainDesc == null) {
661        yield Optional.empty();
662      }
663      try (var lock = d.lock()) {
664        yield t.getKind().describeConstable()
665          .map(typeKindDesc -> DynamicConstantDesc.of(BSM_INVOKE,
666                                                      ofMethod(VIRTUAL,
667                                                               Domain.class.describeConstable().orElseThrow(),
668                                                               "primitiveType",
669                                                               MethodTypeDesc.of(CD_PrimitiveType,
670                                                                                 CD_TypeKind)),
671                                                      domainDesc,
672                                                      typeKindDesc));
673      }
674    }
675    };
676  }
677
678  /**
679   * Returns a nominal descriptor for the supplied argument, presuming it to have originated from the supplied {@link
680   * Domain}, or an {@linkplain Optional#empty() empty} {@link Optional} if the supplied argument cannot be described.
681   *
682   * @param t the argument; may be {@code null}
683   *
684   * @param d the {@link Domain} from which the argument originated; must not be {@code null}
685   *
686   * @return a non-{@code null} {@link Optional}
687   *
688   * @exception NullPointerException if {@code d} is {@code null}
689   */
690  public static final Optional<? extends ConstantDesc> describe(final TypeVariable t, final Domain d) {
691    return switch (t) {
692    case null -> Optional.of(NULL);
693    case Constable c -> c.describeConstable();
694    case ConstantDesc cd -> Optional.of(cd); // future proofing?
695    default -> {
696      final ConstantDesc domainDesc = d instanceof Constable constableDomain ? constableDomain.describeConstable().orElse(null) : null;
697      if (domainDesc == null) {
698        yield Optional.empty();
699      }
700      try (var lock = d.lock()) {
701        final TypeParameterElement e = (TypeParameterElement)t.asElement();
702        final ConstantDesc parameterizableDesc = describe(e.getEnclosingElement(), d).orElse(null);
703        if (parameterizableDesc == null) {
704          yield Optional.empty();
705        }
706        final String name = d.toString(e.getSimpleName());
707        yield Optional.of(DynamicConstantDesc.of(BSM_INVOKE,
708                                                 ofMethod(VIRTUAL,
709                                                          Domain.class.describeConstable().orElseThrow(),
710                                                          "typeVariable",
711                                                          MethodTypeDesc.of(CD_TypeVariable,
712                                                                            CD_Parameterizable,
713                                                                            CD_CharSequence)),
714                                                 domainDesc,
715                                                 parameterizableDesc,
716                                                 name));
717      }
718    }
719    };
720  }
721
722  /**
723   * Returns a nominal descriptor for the supplied argument, presuming it to have originated from the supplied {@link
724   * Domain}, or an {@linkplain Optional#empty() empty} {@link Optional} if the supplied argument cannot be described.
725   *
726   * @param t the argument; may be {@code null}
727   *
728   * @param d the {@link Domain} from which the argument originated; must not be {@code null}
729   *
730   * @return a non-{@code null} {@link Optional}
731   *
732   * @exception NullPointerException if {@code d} is {@code null}
733   */
734  public static final Optional<? extends ConstantDesc> describe(final WildcardType t, final Domain d) {
735    return switch (t) {
736    case null -> Optional.of(NULL);
737    case Constable c -> c.describeConstable();
738    case ConstantDesc cd -> Optional.of(cd); // future proofing?
739    default -> {
740      try (var lock = d.lock()) {
741        yield describe(t.getExtendsBound(), d)
742          .flatMap(domainDesc -> describe(t.getExtendsBound(), d)
743                   .flatMap(extendsBoundDesc -> describe(t.getSuperBound(), d)
744                            .map(superBoundDesc -> DynamicConstantDesc.of(BSM_INVOKE,
745                                                                          ofMethod(VIRTUAL,
746                                                                                   Domain.class.describeConstable().orElseThrow(),
747                                                                                   "wildcardType",
748                                                                                   MethodTypeDesc.of(CD_WildcardType,
749                                                                                                     CD_TypeMirror,
750                                                                                                     CD_TypeMirror)),
751                                                                          domainDesc,
752                                                                          extendsBoundDesc,
753                                                                          superBoundDesc))));
754      }
755    }
756    };
757  }
758
759  /**
760   * Returns a non-{@code null} nominal descriptor for the supplied {@link List}, or an {@linkplain Optional#empty()
761   * empty} {@link Optional} if the supplied {@link List} cannot be described.
762   *
763   * @param list a {@link List} to be described; may be {@code null}; if non-{@code null} <strong>must be immutable and
764   * must not contain {@code null} elements</strong> or undefined behavior will result
765   *
766   * @return a non-{@code null} {@link Optional}
767   *
768   * @see #describe(List, Function)
769   */
770  public static final Optional<? extends ConstantDesc> describe(final List<?> list) {
771    return describe(list, e -> e instanceof Constable c ? c.describeConstable() : Optional.empty());
772  }
773
774  /**
775   * Returns a nominal descriptor for the supplied {@link List}, or an {@linkplain Optional#empty() empty} {@link
776   * Optional} if the supplied {@link List} cannot be described.
777   *
778   * @param <E> the supplied {@code list}'s element type
779   *
780   * @param list a {@link List} to be described; may be {@code null}; if non-{@code null} <strong>must be immutable and
781   * must not contain {@code null} elements</strong> or undefined behavior will result
782   *
783   * @param f a {@link Function} that accepts an element from the supplied {@code list} and returns a non-{@code null}
784   * {@link Optional} housing a nominal descriptor for it, or a non-{@code null} {@linkplain Optional#empty() empty}
785   * {@link Optional} if the element cannot be described; must not be {@code null}
786   *
787   * @return a non-{@code null} {@link Optional}
788   *
789   * @exception NullPointerException if {@code f} is {@code null} and needs to be used
790   *
791   * @see List#of(Object...)
792   */
793  public static final <E> Optional<? extends ConstantDesc> describe(final List<? extends E> list,
794                                                                    final Function<? super E, ? extends Optional<? extends ConstantDesc>> f) {
795    return switch (list) {
796    case null -> Optional.of(NULL);
797    case Constable c -> c.describeConstable();
798    case ConstantDesc cd -> Optional.of(cd); // future proofing?
799    case List<?> el when el.isEmpty() ->
800      Optional.of(DynamicConstantDesc.of(BSM_INVOKE,
801                                         ofMethod(INTERFACE_STATIC,
802                                                  CD_List,
803                                                  "of",
804                                                  MethodTypeDesc.of(CD_List))));
805    default -> {
806      int size = list.size();
807      assert size > 0;
808      final MethodTypeDesc ofMethodTypeDesc;
809      if (size <= 10) {
810        // List.of() has explicit polymorphic overrides for parameter counts of up to 10, inclusive.
811        final ClassDesc[] parameterArray = new ClassDesc[size];
812        fill(parameterArray, CD_Object);
813        ofMethodTypeDesc = MethodTypeDesc.of(CD_List, parameterArray);
814      } else {
815        // After 10 parameters, List.of() falls back to varargs.
816        ofMethodTypeDesc = MethodTypeDesc.of(CD_List, CD_Object.arrayType());
817      }
818      final ConstantDesc[] args = new ConstantDesc[size + 1];
819      args[0] = ofMethod(INTERFACE_STATIC, CD_List, "of", ofMethodTypeDesc);
820      for (int i = 0; i < size; i++) {
821        final ConstantDesc eDesc = f.apply(list.get(i)).orElse(null);
822        if (eDesc == null) {
823          yield Optional.empty();
824        }
825        args[i + 1] = eDesc;
826      }
827      yield Optional.of(DynamicConstantDesc.of(BSM_INVOKE, args));
828    }
829    };
830  }
831
832  /**
833   * Returns a nominal descriptor for the supplied {@link Map}, or an {@linkplain Optional#empty() empty} {@link
834   * Optional} if the supplied {@link Map} cannot be described.
835   *
836   * @param <K> the supplied {@code map}'s key type
837   *
838   * @param <V> the supplied {@code map}'s value type
839   *
840   * @param map a {@link Map} to be described; may be {@code null}; if non-{@code null} <strong>must be immutable and
841   * must not contain {@code null} keys or values</strong> or undefined behavior will result
842   *
843   * @param kf a {@link Function} that accepts a key from the supplied {@code map} and returns a non-{@code null} {@link
844   * Optional} housing a nominal descriptor for it, or a non-{@code null} {@linkplain Optional#empty() empty} {@link
845   * Optional} if the key cannot be described; must not be {@code null}
846   *
847   * @param vf a {@link Function} that accepts a value from the supplied {@code map} and returns a non-{@code null}
848   * {@link Optional} housing a nominal descriptor for it, or a non-{@code null} {@linkplain Optional#empty() empty}
849   * {@link Optional} if the value cannot be described; must not be {@code null}
850   *
851   * @return a non-{@code null} {@link Optional}
852   *
853   * @exception NullPointerException if {@code kf} or {@code vf} is {@code null} and needs to be used
854   *
855   * @see Map#of(Object, Object)
856   */
857  public static <K, V> Optional<? extends ConstantDesc> describe(final Map<? extends K, ? extends V> map,
858                                                                 final Function<? super K, ? extends Optional<? extends ConstantDesc>> kf,
859                                                                 final Function<? super V, ? extends Optional<? extends ConstantDesc>> vf) {
860    return switch (map) {
861    case null -> Optional.of(NULL);
862    case Constable c -> c.describeConstable();
863    case ConstantDesc c -> Optional.of(c); // future proofing?
864    case Map<?, ?> em when em.isEmpty() ->
865      Optional.of(DynamicConstantDesc.of(BSM_INVOKE,
866                                         ofMethod(INTERFACE_STATIC,
867                                                  CD_Map,
868                                                  "of",
869                                                  MethodTypeDesc.of(CD_Map))));
870    default -> {
871      int size = 2 * map.size(); // 2 * entry size to account for key and value
872      assert size > 0;
873      final MethodTypeDesc ofMethodTypeDesc;
874      if (size <= 20) {
875        // Map.of() has explicit polymorphic overrides for (even) parameter counts of up to 20, inclusive.
876        final ClassDesc[] parameterArray = new ClassDesc[size];
877        fill(parameterArray, CD_Object);
878        ofMethodTypeDesc = MethodTypeDesc.of(CD_Map, parameterArray);
879      } else {
880        // After 20 parameters, Map.of() falls back to varargs.
881        ofMethodTypeDesc = MethodTypeDesc.of(CD_Map, CD_Object.arrayType());
882      }
883      final ConstantDesc[] args = new ConstantDesc[size + 1];
884      args[0] = ofMethod(INTERFACE_STATIC, CD_Map, "of", ofMethodTypeDesc);
885      int i = 1;
886      for (final Entry<? extends K, ? extends V> e : map.entrySet()) {
887        final ConstantDesc kDesc = kf.apply(e.getKey()).orElse(null);
888        if (kDesc == null) {
889          yield Optional.empty();
890        }
891        final ConstantDesc vDesc = vf.apply(e.getValue()).orElse(null);
892        if (vDesc == null) {
893          yield Optional.empty();
894        }
895        args[i++] = kDesc;
896        args[i++] = vDesc;
897      }
898      yield Optional.of(DynamicConstantDesc.of(BSM_INVOKE, args));
899    }
900    };
901  }
902
903}