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   * @return a non-{@code null} {@link Optional}
766   *
767   * @see #describe(List, Function)
768   */
769  public static final <E> Optional<? extends ConstantDesc> describe(final List<? extends E> list) {
770    return describe(list, e -> e instanceof Constable c ? c.describeConstable() : Optional.empty());
771  }
772  
773  /**
774   * Returns a nominal descriptor for the supplied {@link List}, or an {@linkplain Optional#empty() empty} {@link
775   * Optional} if the supplied {@link List} cannot be described.
776   *
777   * @param <E> the supplied {@code list}'s element type
778   *
779   * @param list a {@link List} to be described; may be {@code null}; if non-{@code null} <strong>must be immutable and
780   * must not contain {@code null} elements</strong> or undefined behavior will result
781   *
782   * @param f a {@link Function} that accepts an element from the supplied {@code list} and returns a non-{@code null}
783   * {@link Optional} housing a nominal descriptor for it, or a non-{@code null} {@linkplain Optional#empty() empty}
784   * {@link Optional} if the element cannot be described; must not be {@code null}
785   *
786   * @return a non-{@code null} {@link Optional}
787   *
788   * @exception NullPointerException if {@code f} is {@code null} and needs to be used
789   *
790   * @see List#of(Object...)
791   */
792  public static final <E> Optional<? extends ConstantDesc> describe(final List<? extends E> list,
793                                                                    final Function<? super E, ? extends Optional<? extends ConstantDesc>> f) {
794    return switch (list) {
795    case null -> Optional.of(NULL);
796    case Constable c -> c.describeConstable();
797    case ConstantDesc cd -> Optional.of(cd); // future proofing?
798    case List<?> el when el.isEmpty() ->
799      Optional.of(DynamicConstantDesc.of(BSM_INVOKE,
800                                         ofMethod(INTERFACE_STATIC,
801                                                  CD_List,
802                                                  "of",
803                                                  MethodTypeDesc.of(CD_List))));
804    default -> {
805      int size = list.size();
806      assert size > 0;
807      final MethodTypeDesc ofMethodTypeDesc;
808      if (size <= 10) {
809        // List.of() has explicit polymorphic overrides for parameter counts of up to 10, inclusive.
810        final ClassDesc[] parameterArray = new ClassDesc[size];
811        fill(parameterArray, CD_Object);
812        ofMethodTypeDesc = MethodTypeDesc.of(CD_List, parameterArray);
813      } else {
814        // After 10 parameters, List.of() falls back to varargs.
815        ofMethodTypeDesc = MethodTypeDesc.of(CD_List, CD_Object.arrayType());
816      }
817      final ConstantDesc[] args = new ConstantDesc[++size];
818      args[0] = ofMethod(INTERFACE_STATIC, CD_List, "of", ofMethodTypeDesc);
819      for (int i = 1; i < size; i++) {
820        final ConstantDesc eDesc = f.apply(list.get(i)).orElse(null);
821        if (eDesc == null) {
822          yield Optional.empty();
823        }
824        args[i] = eDesc;
825      }
826      yield Optional.of(DynamicConstantDesc.of(BSM_INVOKE, args));
827    }
828    };
829  }
830
831  /**
832   * Returns a nominal descriptor for the supplied {@link Map}, or an {@linkplain Optional#empty() empty} {@link
833   * Optional} if the supplied {@link Map} cannot be described.
834   *
835   * @param <K> the supplied {@code map}'s key type
836   *
837   * @param <V> the supplied {@code map}'s value type
838   *
839   * @param map a {@link Map} to be described; may be {@code null}; if non-{@code null} <strong>must be immutable and
840   * must not contain {@code null} keys or values</strong> or undefined behavior will result
841   *
842   * @param kf a {@link Function} that accepts a key from the supplied {@code map} and returns a non-{@code null} {@link
843   * Optional} housing a nominal descriptor for it, or a non-{@code null} {@linkplain Optional#empty() empty} {@link
844   * Optional} if the key cannot be described; must not be {@code null}
845   *
846   * @param vf a {@link Function} that accepts a value from the supplied {@code map} and returns a non-{@code null}
847   * {@link Optional} housing a nominal descriptor for it, or a non-{@code null} {@linkplain Optional#empty() empty}
848   * {@link Optional} if the value cannot be described; must not be {@code null}
849   *
850   * @return a non-{@code null} {@link Optional}
851   *
852   * @exception NullPointerException if {@code kf} or {@code vf} is {@code null} and needs to be used
853   *
854   * @see Map#of(Object, Object)
855   */
856  public static <K, V> Optional<? extends ConstantDesc> describe(final Map<? extends K, ? extends V> map,
857                                                                 final Function<? super K, ? extends Optional<? extends ConstantDesc>> kf,
858                                                                 final Function<? super V, ? extends Optional<? extends ConstantDesc>> vf) {
859    return switch (map) {
860    case null -> Optional.of(NULL);
861    case Constable c -> c.describeConstable();
862    case ConstantDesc c -> Optional.of(c); // future proofing?
863    case Map<?, ?> em when em.isEmpty() ->
864      Optional.of(DynamicConstantDesc.of(BSM_INVOKE,
865                                         ofMethod(INTERFACE_STATIC,
866                                                  CD_Map,
867                                                  "of",
868                                                  MethodTypeDesc.of(CD_Map))));
869    default -> {
870      int size = 2 * map.size(); // 2 * entry size to account for key and value
871      assert size > 0;
872      final MethodTypeDesc ofMethodTypeDesc;
873      if (size <= 20) {
874        // Map.of() has explicit polymorphic overrides for (even) parameter counts of up to 20, inclusive.
875        final ClassDesc[] parameterArray = new ClassDesc[size];
876        fill(parameterArray, CD_Object);
877        ofMethodTypeDesc = MethodTypeDesc.of(CD_Map, parameterArray);
878      } else {
879        // After 20 parameters, Map.of() falls back to varargs.
880        ofMethodTypeDesc = MethodTypeDesc.of(CD_Map, CD_Object.arrayType());
881      }
882      final ConstantDesc[] args = new ConstantDesc[++size];
883      args[0] = ofMethod(INTERFACE_STATIC, CD_Map, "of", ofMethodTypeDesc);
884      int i = 1;
885      for (final Entry<? extends K, ? extends V> e : map.entrySet()) {
886        final ConstantDesc kDesc = kf.apply(e.getKey()).orElse(null);
887        if (kDesc == null) {
888          yield Optional.empty();
889        }
890        final ConstantDesc vDesc = vf.apply(e.getValue()).orElse(null);
891        if (vDesc == null) {
892          yield Optional.empty();
893        }
894        args[i] = kDesc;
895        args[++i] = vDesc;
896        ++i;
897      }
898      yield Optional.of(DynamicConstantDesc.of(BSM_INVOKE, args));
899    }
900    };
901  }
902
903}