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