001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.juneau.commons.reflect;
018
019import static java.util.Collections.*;
020import static java.util.stream.Collectors.*;
021import static java.util.stream.Stream.*;
022import static org.apache.juneau.commons.function.Suppliers.*;
023import static org.apache.juneau.commons.reflect.ClassArrayFormat.*;
024import static org.apache.juneau.commons.reflect.ClassNameFormat.*;
025import static org.apache.juneau.commons.utils.AnnotationUtils.*;
026import static org.apache.juneau.commons.utils.ClassUtils.*;
027import static org.apache.juneau.commons.utils.CollectionUtils.*;
028import static org.apache.juneau.commons.utils.CollectionUtils.list;
029import static org.apache.juneau.commons.utils.CollectionUtils.toList;
030import static org.apache.juneau.commons.utils.Shorts.*;
031import static org.apache.juneau.commons.utils.Shorts.eqa;
032import static org.apache.juneau.commons.utils.Shorts.eqic;
033import static org.apache.juneau.commons.utils.Shorts.neq;
034import static org.apache.juneau.commons.utils.Shorts.eq;
035
036import java.lang.annotation.*;
037import java.lang.reflect.*;
038import java.security.*;
039import java.util.*;
040import java.util.function.*;
041import java.util.stream.*;
042
043import org.apache.juneau.commons.collections.*;
044import org.apache.juneau.commons.inject.*;
045
046/**
047 * Lightweight utility class for introspecting information about a class.
048 *
049 * <p>
050 * Provides various convenience methods for introspecting fields/methods/annotations
051 * that aren't provided by the standard Java reflection APIs.
052 *
053 * <p>
054 * Objects are designed to be lightweight to create and threadsafe.
055 *
056 * <h5 class='figure'>Example:</h5>
057 * <p class='bjava'>
058 *    <jc>// Wrap our class inside a ClassInfo.</jc>
059 *    ClassInfo <jv>classInfo</jv> = ClassInfo.<jsm>of</jsm>(MyClass.<jk>class</jk>);
060 *
061 *    <jc>// Get all methods in parent-to-child order, sorted alphabetically per class.</jc>
062 *    <jk>for</jk> (MethodInfo <jv>methodInfo</jv> : <jv>classInfo</jv>.getAllMethods()) {
063 *       <jc>// Do something with it.</jc>
064 *    }
065 *
066 *    <jc>// Get all class-level annotations in parent-to-child order.</jc>
067 *    <jk>for</jk> (MyAnnotation <jv>annotation</jv> : <jv>classInfo</jv>.getAnnotations(MyAnnotation.<jk>class</jk>)) {
068 *       <jc>// Do something with it.</jc>
069 *    }
070 * </p>
071 *
072 */
073@SuppressWarnings({
074   "java:S115", // Constants use UPPER_snakeCase naming convention
075   "java:S1192", // Duplicated literals (argument/property names) read more clearly inline than as constants
076   "java:S1452", // Wildcard required - List<AnnotationInfo<? extends Annotation>>, List<TypeVariable<?>>
077   "java:S3776", // appendNameFormatted(), getParameterType() and findToString() resolve class names/type variables/toString methods through many branching cases
078   "java:S6539", // Monster class; ClassInfo is intentionally a single cohesive reflection-metadata facade over Class
079   "javabugs:S2259", // of(Object) requires a non-null object, and findParents() loops only while nn(pc) holds; list() is never null.
080   "rawtypes", // Raw types necessary for generic type handling
081   "unchecked" // Type erasure requires unchecked casts
082})
083public non-sealed class ClassInfo extends ElementInfo implements Annotatable, Type, Comparable<ClassInfo> {
084
085   private static final Cache<Class,ClassInfo> CACHE = Cache.of(Class.class, ClassInfo.class).build();
086
087   private static final Map<Class<?>,Class<?>> pmap1 = new HashMap<>();
088   private static final Map<Class<?>,Class<?>> pmap2 = new HashMap<>();
089
090   static {
091      pmap1.put(boolean.class, Boolean.class);
092      pmap1.put(byte.class, Byte.class);
093      pmap1.put(short.class, Short.class);
094      pmap1.put(char.class, Character.class);
095      pmap1.put(int.class, Integer.class);
096      pmap1.put(long.class, Long.class);
097      pmap1.put(float.class, Float.class);
098      pmap1.put(double.class, Double.class);
099      pmap2.put(Boolean.class, boolean.class);
100      pmap2.put(Byte.class, byte.class);
101      pmap2.put(Short.class, short.class);
102      pmap2.put(Character.class, char.class);
103      pmap2.put(Integer.class, int.class);
104      pmap2.put(Long.class, long.class);
105      pmap2.put(Float.class, float.class);
106      pmap2.put(Double.class, double.class);
107   }
108
109   // @formatter:off
110   private static final Map<Class,Object> primitiveDefaultMap =
111      mapBuilder(Class.class,Object.class)
112         .unmodifiable()
113         .add(Boolean.TYPE, false)
114         .add(Character.TYPE, (char)0)
115         .add(Short.TYPE, (short)0)
116         .add(Integer.TYPE, 0)
117         .add(Long.TYPE, 0L)
118         .add(Float.TYPE, 0f)
119         .add(Double.TYPE, 0d)
120         .add(Byte.TYPE, (byte)0)
121         .add(Boolean.class, false)
122         .add(Character.class, (char)0)
123         .add(Short.class, (short)0)
124         .add(Integer.class, 0)
125         .add(Long.class, 0L)
126         .add(Float.class, 0f)
127         .add(Double.class, 0d)
128         .add(Byte.class, (byte)0)
129         .build();
130   // @formatter:on
131
132   /**
133    * Returns a class info wrapper around the specified class type.
134    *
135    * @param inner The class type.  Can be <jk>null</jk> if <c>innerType</c> is specified.
136    * @param innerType The generic type (if parameterized type).  Can be <jk>null</jk> if <c>inner</c> is specified.
137    * @return The constructed class info.
138    */
139   public static ClassInfo of(Class<?> inner, Type innerType) {
140      if (inner == innerType)
141         return of(inner);
142      if (inner != null)
143         return new ClassInfoTyped<>(inner, innerType);
144      return new ClassInfo(null, innerType);
145   }
146
147   /**
148    * Returns a class info wrapper around the specified class type.
149    *
150    * @param <T> The class type.
151    * @param inner The class type.
152    * @return The constructed class info.
153    */
154   public static <T> ClassInfoTyped<T> of(Class<T> inner) {
155      return (ClassInfoTyped<T>) CACHE.get(inner, () -> new ClassInfoTyped<>(inner));
156   }
157
158   /**
159    * Same as using the constructor, but operates on an object instance.
160    *
161    * @param object The class instance.
162    * @return The constructed class info.
163    */
164   public static ClassInfo of(Object object) {
165      return of(object instanceof Class object2 ? (Class<?>)object2 : object.getClass());
166   }
167
168   /**
169    * Returns a class info wrapper around the specified class type.
170    *
171    * @param innerType The class type.
172    * @return The constructed class info.
173    */
174   public static ClassInfo of(Type innerType) {
175      if (innerType instanceof Class<?> innerType2)
176         return of(innerType2);
177      return new ClassInfo(toClass(innerType), innerType);
178   }
179
180   /**
181    * Same as {@link #of(Object)} but attempts to deproxify the object if it's wrapped in a CGLIB proxy.
182    *
183    * @param object The class instance.
184    * @return The constructed class info.
185    */
186   public static ClassInfo ofProxy(Object object) {
187      var inner = getProxyFor(object);
188      return inner == null ? ClassInfo.of(object) : ClassInfo.of(inner);
189   }
190
191   private final Type innerType;  // The underlying Type object (may be Class, ParameterizedType, GenericArrayType, etc.).
192   private final Class<?> inner;  // The underlying Class object (null for non-class types like TypeVariable).
193   private final boolean isParameterizedType;  // True if this represents a ParameterizedType (e.g., List<String>).
194
195   private final Supplier<Integer> dimensions;  // Number of array dimensions (0 if not an array).
196   private final Supplier<ClassInfo> componentType;  // Base component type for arrays (e.g., String for String[][]), also handles GenericArrayType.  Cached and never null.
197   private final Supplier<PackageInfo> packageInfo;  // The package this class belongs to (null for primitive types and arrays).
198   private final Supplier<List<ClassInfo>> parents;  // All superclasses of this class in child-to-parent order, starting with this class.
199   private final Supplier<List<AnnotationInfo>> declaredAnnotations;  // All annotations declared directly on this class, wrapped in AnnotationInfo.
200   private final Supplier<String> nameFull;  // Fully qualified class name with generics (e.g., "java.util.List<java.lang.String>").
201   private final Supplier<String> nameShort;  // Simple class name with generics (e.g., "List<String>").
202   private final Supplier<String> nameReadable;  // Human-readable class name without generics (e.g., "List").
203   private final Supplier<String> toString;  // String representation with modifiers, class type, name, and type parameters.
204   private final Supplier<List<ClassInfo>> declaredInterfaces;  // All interfaces declared directly by this class.
205   private final Supplier<List<ClassInfo>> interfaces;  // All interfaces implemented by this class and its parents, in child-to-parent order.
206   private final Supplier<List<ClassInfo>> allParents;  // All parent classes and interfaces, classes first, then in child-to-parent order.
207   private final Supplier<List<ClassInfo>> parentsAndInterfaces;  // All parent classes and interfaces with proper traversal of interface hierarchy to avoid duplicates.
208   private final Supplier<List<AnnotationInfo<? extends Annotation>>> annotationInfos;  // All annotations on this class and parent classes/interfaces in child-to-parent order.
209   private final Supplier<List<RecordComponent>> recordComponents;  // All record components if this is a record class (Java 14+).
210   private final Supplier<List<Type>> genericInterfaces;  // All generic interface types (e.g., List<String> implements Comparable<List<String>>).
211   private final Supplier<List<TypeVariable<?>>> typeParameters;  // All type parameters declared on this class (e.g., <T,U> in class Foo<T,U>).
212   private final Supplier<List<AnnotatedType>> annotatedInterfaces;  // All annotated interface types with their annotations.
213   private final Supplier<List<Object>> signers;  // All signers of this class (for signed JARs).
214   private final Supplier<List<MethodInfo>> publicMethods;  // All public methods on this class and inherited, excluding Object methods.
215   private final Supplier<List<MethodInfo>> declaredMethods;  // All methods declared directly on this class (public, protected, package, private).
216   private final Supplier<List<MethodInfo>> allMethods;  // All methods from this class and all parents, in child-to-parent order.
217   private final Supplier<List<MethodInfo>> allMethodsTopDown;  // All methods from this class and all parents, in parent-to-child order.
218   private final Supplier<List<FieldInfo>> publicFields;  // All public fields from this class and parents, deduplicated by name (child wins).
219   private final Supplier<List<FieldInfo>> declaredFields;  // All fields declared directly on this class (public, protected, package, private).
220   private final Supplier<List<FieldInfo>> declaredFieldsInDeclarationOrder;  // Same as declaredFields, but in JVM-reported (declaration) order instead of alphabetical.
221   private final Supplier<List<FieldInfo>> allFields;  // All fields from this class and all parents, in parent-to-child order.
222   private final Supplier<List<ConstructorInfo>> publicConstructors;  // All public constructors declared on this class.
223   private final Supplier<List<ConstructorInfo>> declaredConstructors;  // All constructors declared on this class (public, protected, package, private).
224   private final Supplier<MethodInfo> repeatedAnnotationMethod;  // The repeated annotation method (value()) if this class is a @Repeatable container.
225   private final Cache<Method,MethodInfo> methodCache;  // Cache of wrapped Method objects.
226   private final Cache<Field,FieldInfo> fieldCache;  // Cache of wrapped Field objects.
227   private final Cache<Constructor,ConstructorInfo> constructorCache;  // Cache of wrapped Constructor objects.
228
229   /**
230    * Constructor.
231    *
232    * @param inner The class type.  Can be <jk>null</jk> if <c>innerType</c> is specified.
233    * @param innerType The generic type (if parameterized type).  Can be <jk>null</jk> if <c>inner</c> is specified.
234    */
235   protected ClassInfo(Class<?> inner, Type innerType) {
236      // @formatter:off
237      super(inner == null ? 0 : inner.getModifiers());
238      req(inner != null || innerType != null, "At least one of inner or innerType must be specified.");
239      this.innerType = innerType;
240      this.inner = inner;
241      this.isParameterizedType = innerType instanceof ParameterizedType;
242      this.dimensions = memoize(this::findDimensions);
243      this.componentType = memoize(this::findComponentType);
244      this.packageInfo = memoize(() -> o(inner).map(Class::getPackage).filter(p -> p != null).map(PackageInfo::of).orElse(null));  // PackageInfo may be null for primitive types and arrays.
245      this.parents = memoize(this::findParents);
246      this.declaredAnnotations = memoize(() -> (List)o(inner).map(x -> u(l(x.getDeclaredAnnotations()))).orElse(emptyList()).stream().flatMap(a -> streamRepeated(a)).map(a -> ai(this, a)).toList());
247      this.nameFull = memoize(() -> getNameFormatted(FULL, true, '$', BRACKETS));
248      this.nameShort = memoize(() -> getNameFormatted(SHORT, true, '$', BRACKETS));
249      this.nameReadable = memoize(() -> getNameFormatted(SIMPLE, false, '$', WORD));
250      this.toString = memoize(this::findToString);
251      this.declaredInterfaces = memoize(() -> o(inner).map(x -> stream(x.getGenericInterfaces()).map(ClassInfo::of).map(ClassInfo.class::cast).toList()).orElse(emptyList()));
252      this.interfaces = memoize(() -> getParents().stream().flatMap(x -> x.getDeclaredInterfaces().stream()).flatMap(ci2 -> concat(Stream.of(ci2), ci2.getInterfaces().stream())).distinct().toList());
253      this.allParents = memoize(() -> concat(getParents().stream(), getInterfaces().stream()).toList());
254      this.parentsAndInterfaces = memoize(this::findParentsAndInterfaces);
255      this.annotationInfos = memoize(this::findAnnotations);
256      this.recordComponents = memoize(() -> o(inner).filter(Class::isRecord).map(x -> u(l(x.getRecordComponents()))).orElse(emptyList()));
257      this.genericInterfaces = memoize(() -> o(inner).map(x -> u(l(x.getGenericInterfaces()))).orElse(emptyList()));
258      this.typeParameters = memoize(() -> o(inner).map(x -> u(l((TypeVariable<?>[])x.getTypeParameters()))).orElse(emptyList()));
259      this.annotatedInterfaces = memoize(() -> o(inner).map(x -> u(l(x.getAnnotatedInterfaces()))).orElse(emptyList()));
260      this.signers = memoize(() -> o(inner).map(Class::getSigners).map(x -> u(l(x))).orElse(emptyList()));
261      this.publicMethods = memoize(() -> o(inner).map(x -> stream(x.getMethods()).filter(m -> neq(m.getDeclaringClass(), Object.class)).map(this::getMethod).sorted().toList()).orElse(emptyList()));
262      this.declaredMethods = memoize(() -> o(inner).map(x -> stream(x.getDeclaredMethods()).filter(m -> neq("$jacocoInit", m.getName())).map(this::getMethod).sorted().toList()).orElse(emptyList()));
263      this.allMethods = memoize(() -> allParents.get().stream().flatMap(c2 -> c2.getDeclaredMethods().stream()).toList());
264      this.allMethodsTopDown = memoize(() -> rstream(getAllParents()).flatMap(c2 -> c2.getDeclaredMethods().stream()).toList());
265      this.publicFields = memoize(() -> parents.get().stream().flatMap(c2 -> c2.getDeclaredFields().stream()).filter(f -> f.isPublic() && neq("$jacocoData", f.getName())).collect(toMap(FieldInfo::getName, x -> x, (a, b) -> a, LinkedHashMap::new)).values().stream().sorted().toList());
266      this.declaredFields = memoize(() -> o(inner).map(x -> stream(x.getDeclaredFields()).filter(f -> neq("$jacocoData", f.getName())).map(this::getField).sorted().toList()).orElse(emptyList()));
267      this.declaredFieldsInDeclarationOrder = memoize(() -> o(inner).map(x -> stream(x.getDeclaredFields()).filter(f -> neq("$jacocoData", f.getName())).map(this::getField).toList()).orElse(emptyList()));
268      this.allFields = memoize(() -> rstream(allParents.get()).flatMap(c2 -> c2.getDeclaredFields().stream()).toList());
269      this.publicConstructors = memoize(() -> o(inner).map(x -> stream(x.getConstructors()).map(this::getConstructor).sorted().toList()).orElse(emptyList()));
270      this.declaredConstructors = memoize(() -> o(inner).map(x -> stream(x.getDeclaredConstructors()).map(this::getConstructor).sorted().toList()).orElse(emptyList()));
271      this.repeatedAnnotationMethod = memoize(this::findRepeatedAnnotationMethod);
272      this.methodCache = Cache.of(Method.class, MethodInfo.class).build();
273      this.fieldCache = Cache.of(Field.class, FieldInfo.class).build();
274      this.constructorCache = Cache.of(Constructor.class, ConstructorInfo.class).build();
275      // @formatter:on
276   }
277
278   /**
279    * Appends a formatted class name to a StringBuilder with configurable options.
280    *
281    * <p>
282    * This is the core implementation method used by all other name formatting methods.
283    * Using this method directly avoids String allocations when building complex strings.
284    * The method is recursive to handle nested generic type parameters.
285    *
286    * <h5 class='section'>Example:</h5>
287    * <p class='bjava'>
288    *    StringBuilder sb = <jk>new</jk> StringBuilder();
289    *    sb.append(<js>"Class: "</js>);
290    *    ClassInfo.<jsm>of</jsm>(HashMap.<jk>class</jk>).appendFormattedName(sb, ClassNameFormat.<jsf>FULL</jsf>, <jk>true</jk>, '$', ClassArrayFormat.<jsf>BRACKETS</jsf>);
291    *    <jc>// sb now contains: "Class: java.util.HashMap"</jc>
292    * </p>
293    *
294    * @param sb
295    *    The StringBuilder to append to.
296    * @param nameFormat
297    *    Controls which parts of the class name to include (package, outer classes).
298    * @param includeTypeParams
299    *    If <jk>true</jk>, include generic type parameters recursively.
300    * @param separator
301    *    Character to use between outer and inner class names.
302    *    <br>Ignored when <c>nameFormat</c> is {@link ClassNameFormat#SIMPLE}.
303    * @param arrayFormat
304    *    How to format array dimensions.
305    * @return
306    *    The same StringBuilder for method chaining.
307    */
308   @SuppressWarnings({
309      "java:S6541" // Synchronization not needed for local StringBuilder operations
310   })
311   public StringBuilder appendNameFormatted(StringBuilder sb, ClassNameFormat nameFormat, boolean includeTypeParams, char separator, ClassArrayFormat arrayFormat) {
312      var dim = getDimensions();
313
314      // Handle arrays - format component type recursively, then add array notation
315      if (dim > 0) {
316         var componentType2 = getComponentType();
317         componentType2.appendNameFormatted(sb, nameFormat, includeTypeParams, separator, arrayFormat);
318
319         if (arrayFormat == ClassArrayFormat.WORD) {
320            for (var i = 0; i < dim; i++)
321               sb.append("Array");
322         } else if (arrayFormat == ClassArrayFormat.BRACKETS) {
323            for (var i = 0; i < dim; i++)
324               sb.append("[]");
325         }
326         // JVM format is already in getName() - would need special handling
327
328         return sb;
329      }
330
331      // Get the raw class - for ParameterizedType, extract the raw type
332      var ct = inner;
333      if (ct == null && isParameterizedType) {
334         var pt = (ParameterizedType)innerType;
335         ct = (Class<?>)pt.getRawType();
336      }
337
338      // Append base class name based on format
339      switch (nameFormat) {
340         case FULL:
341            // Full package name + outer classes
342            if (nn(ct)) {
343               sb.append(ct.getName());
344               // Apply separator if not '$'
345               if (separator != '$' && sb.indexOf("$") != -1) {
346                  for (var i = 0; i < sb.length(); i++) {
347                     if (sb.charAt(i) == '$')
348                        sb.setCharAt(i, separator);
349                  }
350               }
351            } else {
352               sb.append(innerType.getTypeName());
353            }
354            break;
355
356         case SHORT:
357            // Outer classes but no package
358            if (nn(ct)) {
359               if (ct.isLocalClass()) {
360                  // Local class: recursively include all enclosing classes
361                  appendShortNameWithOuters(sb, of(ct.getEnclosingClass()), separator).append(separator).append(ct.getSimpleName());
362               } else if (ct.isMemberClass()) {
363                  // Member class: recursively include all declaring classes
364                  appendShortNameWithOuters(sb, of(ct.getDeclaringClass()), separator).append(separator).append(ct.getSimpleName());
365               } else {
366                  // Regular class: just simple name
367                  sb.append(ct.getSimpleName());
368               }
369            } else {
370               sb.append(innerType.getTypeName());
371            }
372            break;
373
374         default /* SIMPLE */:
375            // Simple name only - no package, no outer classes
376            if (nn(ct)) {
377               sb.append(ct.getSimpleName());
378            } else {
379               sb.append(innerType.getTypeName());
380            }
381            break;
382      }
383
384      // Append type parameters if requested
385      if (includeTypeParams && isParameterizedType) {
386         var pt = (ParameterizedType)innerType;
387         sb.append('<');
388         var first = true;
389         for (var t2 : pt.getActualTypeArguments()) {
390            if (! first)
391               sb.append(',');
392            first = false;
393            of(t2).appendNameFormatted(sb, nameFormat, includeTypeParams, separator, arrayFormat);
394         }
395         sb.append('>');
396      }
397
398      return sb;
399   }
400
401   /**
402    * Helper method to recursively append short name with all outer classes for SHORT format.
403    * This ensures multiply-nested classes include all outer class names.
404    *
405    * @param sb The StringBuilder to append to.
406    * @param classInfo The ClassInfo to append.
407    * @param separator The separator character between outer and inner classes.
408    */
409   private StringBuilder appendShortNameWithOuters(StringBuilder sb, ClassInfo classInfo, char separator) {
410      var ct = classInfo.inner();
411      if (ct == null) {
412         sb.append(classInfo.getNameSimple());
413         return sb;
414      }
415
416      if (ct.isLocalClass()) {
417         appendShortNameWithOuters(sb, of(ct.getEnclosingClass()), separator).append(separator).append(ct.getSimpleName());
418      } else if (ct.isMemberClass()) {
419         appendShortNameWithOuters(sb, of(ct.getDeclaringClass()), separator).append(separator).append(ct.getSimpleName());
420      } else {
421         sb.append(ct.getSimpleName());
422      }
423      return sb;
424   }
425
426   /**
427    * Returns a {@link ClassInfo} for an array type whose component type is this class.
428    *
429    * @return A {@link ClassInfo} representing an array type whose component type is this class.
430    */
431   public ClassInfo arrayType() {
432      if (inner == null)
433         return null;
434      return of(inner.arrayType());
435   }
436
437   /**
438    * Casts this {@link ClassInfo} object to represent a subclass of the class represented by the specified class object.
439    *
440    * @param <U> The type to cast to.
441    * @param clazz The class of the type to cast to.
442    * @return This {@link ClassInfo} object, cast to represent a subclass of the specified class object.
443    * @throws ClassCastException If this class is not assignable to the specified class.
444    */
445   public <U> ClassInfo asSubclass(Class<U> clazz) {
446      if (inner == null)
447         return null;
448      inner.asSubclass(clazz);  // Throws ClassCastException if not assignable
449      return this;
450   }
451
452   /**
453    * Returns <jk>true</jk> if this type can be used as a parameter for the specified object.
454    *
455    * <p>
456    * For null values, returns <jk>true</jk> unless this type is a primitive
457    * (since primitives cannot accept null values in Java).
458    *
459    * <h5 class='section'>Examples:</h5>
460    * <ul class='spaced-list'>
461    *    <li><c>ClassInfo.of(String.class).canAcceptArg("foo")</c> - returns <jk>true</jk>
462    *    <li><c>ClassInfo.of(String.class).canAcceptArg(null)</c> - returns <jk>true</jk>
463    *    <li><c>ClassInfo.of(int.class).canAcceptArg(5)</c> - returns <jk>true</jk>
464    *    <li><c>ClassInfo.of(int.class).canAcceptArg(null)</c> - returns <jk>false</jk> (primitives can't be null)
465    *    <li><c>ClassInfo.of(Integer.class).canAcceptArg(null)</c> - returns <jk>true</jk>
466    * </ul>
467    *
468    * @param child The argument to check.
469    * @return <jk>true</jk> if this type can be used as a parameter for the specified object.
470    */
471   public boolean canAcceptArg(Object child) {
472      if (inner == null)
473         return false;
474      if (child == null)
475         return ! isPrimitive();  // Primitives can't accept null, all other types can
476      if (inner.isInstance(child))
477         return true;
478      if (this.isPrimitive())
479         return this.getWrapperIfPrimitive().isAssignableFrom(of(child).getWrapperIfPrimitive());
480      return false;
481   }
482
483   /**
484    * Casts an object to the class represented by this {@link ClassInfo} object.
485    *
486    * @param <T> The type to cast to.
487    * @param obj The object to be cast.
488    * @return The object after casting, or <jk>null</jk> if obj is <jk>null</jk>.
489    * @throws ClassCastException If the object is not <jk>null</jk> and is not assignable to this class.
490    */
491   public <T> T cast(Object obj) {
492      return inner == null ? null : (T)inner.cast(obj);
493   }
494
495   /**
496    * Returns the component type of this class if it is an array type.
497    *
498    * <p>
499    * This is equivalent to {@link Class#getComponentType()} but returns a {@link ClassInfo} instead.
500    * Note that {@link #getComponentType()} also exists and returns the base component type for multi-dimensional arrays.
501    *
502    * @return The {@link ClassInfo} representing the component type, or <jk>null</jk> if this class does not represent an array type.
503    */
504   public ClassInfo componentType() {
505      if (inner == null)
506         return null;
507      var ct = inner.componentType();
508      return ct == null ? null : of(ct);
509   }
510
511   /**
512    * Returns the descriptor string of this class.
513    *
514    * <p>
515    * The descriptor is a string representing the type of the class, as specified in JVMS 4.3.2.
516    * For example, the descriptor for <c>String</c> is <js>"Ljava/lang/String;"</js>.
517    *
518    * @return The descriptor string of this class.
519    */
520   public String descriptorString() {
521      return inner == null ? null : inner.descriptorString();
522   }
523
524   @Override
525   public boolean equals(Object o) {
526      return (o instanceof ClassInfo o2) && eq(this, o2, (x, y) -> eq(x.innerType, y.innerType));
527   }
528
529   @Override
530   public int compareTo(ClassInfo o) {
531      if (o == null)
532         return 1;
533      return cmp(getName(), o.getName());
534   }
535
536   /**
537    * Returns all fields on this class and all parent classes.
538    *
539    * <p>
540    *    Results are ordered parent-to-child, and then alphabetical per class.
541    *
542    * @return
543    *    All declared fields on this class.
544    *    <br>List is unmodifiable.
545    */
546   public List<FieldInfo> getAllFields() { return allFields.get(); }
547
548   /**
549    * Returns all declared methods on this class and all parent classes in <b>child-to-parent order</b>.
550    *
551    * <p>
552    * This method returns methods of <b>all visibility levels</b> (public, protected, package-private, and private).
553    *
554    * <p>
555    * Methods are returned in <b>child-to-parent order</b> - methods from the current class appear first,
556    * followed by methods from the immediate parent, then grandparent, etc. Within each class, methods
557    * are sorted alphabetically.
558    *
559    * <h5 class='section'>Example:</h5>
560    * <p class='bjava'>
561    *    <jc>// Given class hierarchy:</jc>
562    *    <jk>class</jk> Parent {
563    *       <jk>void</jk> method1() {}
564    *       <jk>void</jk> method2() {}
565    *    }
566    *    <jk>class</jk> Child <jk>extends</jk> Parent {
567    *       <jk>void</jk> method3() {}
568    *       <jk>void</jk> method4() {}
569    *    }
570    *
571    *    <jc>// getAllMethods() returns in child-to-parent order:</jc>
572    *    ClassInfo <jv>ci</jv> = ClassInfo.<jsm>of</jsm>(Child.<jk>class</jk>);
573    *    List&lt;MethodInfo&gt; <jv>methods</jv> = <jv>ci</jv>.getAllMethods();
574    *    <jc>// Returns: [method3, method4, method1, method2]</jc>
575    *    <jc>//          ^Child methods^   ^Parent methods^</jc>
576    * </p>
577    *
578    * <h5 class='section'>Comparison with Similar Methods:</h5>
579    * <ul class='spaced-list'>
580    *    <li>{@link #getDeclaredMethods()} - Returns methods declared on this class only (all visibility levels)
581    *    <li>{@link #getAllMethods()} - Returns all methods in <b>child-to-parent order</b> ← This method
582    *    <li>{@link #getAllMethodsTopDown()} - Returns all methods in <b>parent-to-child order</b>
583    *    <li>{@link #getPublicMethods()} - Returns public methods only on this class and parents
584    * </ul>
585    *
586    * <h5 class='section'>Notes:</h5>
587    * <ul class='spaced-list'>
588    *    <li>Unlike Java's {@link Class#getMethods()}, this returns methods of all visibility levels, not just public ones.
589    *    <li>Methods from {@link Object} class are excluded from the results.
590    *    <li>Use {@link #getAllMethodsTopDown()} if you need parent methods to appear before child methods.
591    * </ul>
592    *
593    * @return
594    *    All declared methods on this class and all parent classes (all visibility levels).
595    *    <br>Results are ordered child-to-parent, and then alphabetically per class.
596    *    <br>List is unmodifiable.
597    */
598   public List<MethodInfo> getAllMethods() { return allMethods.get(); }
599
600   /**
601    * Returns all declared methods on this class and all parent classes in <b>parent-to-child order</b>.
602    *
603    * <p>
604    * This method returns methods of <b>all visibility levels</b> (public, protected, package-private, and private).
605    *
606    * <p>
607    * Methods are returned in <b>parent-to-child order</b> - methods from the most distant ancestor appear first,
608    * followed by methods from each subsequent child class, ending with methods from the current class. Within
609    * each class, methods are sorted alphabetically.
610    *
611    * <p>
612    * This ordering is useful for initialization hooks and lifecycle methods where parent methods should execute
613    * before child methods (e.g., <c>@RestInit</c>, <c>@PostConstruct</c>, etc.).
614    *
615    * <h5 class='section'>Example:</h5>
616    * <p class='bjava'>
617    *    <jc>// Given class hierarchy:</jc>
618    *    <jk>class</jk> Parent {
619    *       <jk>void</jk> method1() {}
620    *       <jk>void</jk> method2() {}
621    *    }
622    *    <jk>class</jk> Child <jk>extends</jk> Parent {
623    *       <jk>void</jk> method3() {}
624    *       <jk>void</jk> method4() {}
625    *    }
626    *
627    *    <jc>// getAllMethodsParentFirst() returns in parent-to-child order:</jc>
628    *    ClassInfo <jv>ci</jv> = ClassInfo.<jsm>of</jsm>(Child.<jk>class</jk>);
629    *    List&lt;MethodInfo&gt; <jv>methods</jv> = <jv>ci</jv>.getAllMethodsParentFirst();
630    *    <jc>// Returns: [method1, method2, method3, method4]</jc>
631    *    <jc>//          ^Parent methods^  ^Child methods^</jc>
632    * </p>
633    *
634    * <h5 class='section'>Comparison with Similar Methods:</h5>
635    * <ul class='spaced-list'>
636    *    <li>{@link #getDeclaredMethods()} - Returns methods declared on this class only (all visibility levels)
637    *    <li>{@link #getAllMethods()} - Returns all methods in <b>child-to-parent order</b>
638    *    <li>{@link #getAllMethodsTopDown()} - Returns all methods in <b>parent-to-child order</b> ← This method
639    *    <li>{@link #getPublicMethods()} - Returns public methods only on this class and parents
640    * </ul>
641    *
642    * <h5 class='section'>Notes:</h5>
643    * <ul class='spaced-list'>
644    *    <li>Methods from {@link Object} class are excluded from the results.
645    *    <li>Use {@link #getAllMethods()} if you need child methods to appear before parent methods.
646    * </ul>
647    *
648    * @return
649    *    All declared methods on this class and all parent classes (all visibility levels).
650    *    <br>Results are ordered parent-to-child, and then alphabetically per class.
651    *    <br>List is unmodifiable.
652    */
653   public List<MethodInfo> getAllMethodsTopDown() { return allMethodsTopDown.get(); }
654
655   /**
656    * Returns a list including this class and all parent classes and interfaces.
657    *
658    * <p>
659    * Results are classes-before-interfaces, then child-to-parent order.
660    *
661    * @return An unmodifiable list including this class and all parent classes.
662    *    <br>Results are ordered child-to-parent order with classes listed before interfaces.
663    */
664   public List<ClassInfo> getAllParents() { return allParents.get(); }
665
666   @Override /* Annotatable */
667   public AnnotatableType getAnnotatableType() { return AnnotatableType.CLASS_TYPE; }
668
669   /**
670    * Returns a list of {@link AnnotatedType} objects that represent the annotated interfaces
671    * implemented by this class.
672    *
673    * <p>
674    * Returns a cached, unmodifiable list.
675    * If this class represents a class or interface whose superinterfaces are annotated,
676    * the returned objects reflect the annotations used in the source code to declare the superinterfaces.
677    *
678    * @return
679    *    An unmodifiable list of {@link AnnotatedType} objects representing the annotated superinterfaces.
680    *    <br>Returns an empty list if this class implements no interfaces.
681    */
682   public List<AnnotatedType> getAnnotatedInterfaces() { return annotatedInterfaces.get(); }
683
684   /**
685    * Returns an {@link AnnotatedType} object that represents the annotated superclass of this class.
686    *
687    * <p>
688    * If this class represents a class type whose superclass is annotated, the returned object reflects
689    * the annotations used in the source code to declare the superclass.
690    *
691    * @return
692    *    An {@link AnnotatedType} object representing the annotated superclass,
693    *    or <jk>null</jk> if this class represents {@link Object}, an interface, a primitive type, or void.
694    */
695   public AnnotatedType getAnnotatedSuperclass() { return inner == null ? null : inner.getAnnotatedSuperclass(); }
696
697   /**
698    * Returns all annotations on this class and parent classes/interfaces in child-to-parent order.
699    *
700    * <p>
701    * This returns all declared annotations from:
702    * <ol>
703    *    <li>This class
704    *    <li>Parent classes in child-to-parent order
705    *    <li>For each class, interfaces declared on that class and their parent interfaces
706    *    <li>The package of this class
707    * </ol>
708    *
709    * <p>
710    * This does NOT include runtime annotations. For runtime annotation support, use
711    * {@link AnnotationProvider}.
712    *
713    * @return An unmodifiable list of all annotation infos.
714    */
715   public List<AnnotationInfo<? extends Annotation>> getAnnotations() { return annotationInfos.get(); }
716
717   /**
718    * Returns all annotations of the specified type on this class and parent classes/interfaces in child-to-parent order.
719    *
720    * <p>
721    * This returns all declared annotations from:
722    * <ol>
723    *    <li>This class
724    *    <li>Parent classes in child-to-parent order
725    *    <li>For each class, interfaces declared on that class and their parent interfaces
726    *    <li>The package of this class
727    * </ol>
728    *
729    * <p>
730    * This does NOT include runtime annotations. For runtime annotation support, use
731    * {@link AnnotationProvider}.
732    *
733    * @param <A> The annotation type.
734    * @param type The annotation type to filter by.  Must not be <jk>null</jk>.
735    * @return A stream of annotation infos of the specified type.
736    */
737   public <A extends Annotation> Stream<AnnotationInfo<A>> getAnnotations(Class<A> type) {
738      reqnn("type", type);
739      return getAnnotations().stream().filter(a -> a.isType(type)).map(a -> (AnnotationInfo<A>)a);
740   }
741
742   /**
743    * Returns the {@link ClassLoader} for this class.
744    *
745    * <p>
746    * If this class represents a primitive type or void, <jk>null</jk> is returned.
747    *
748    * @return The class loader for this class, or <jk>null</jk> if it doesn't have one.
749    */
750   public ClassLoader getClassLoader() { return inner == null ? null : inner.getClassLoader(); }
751
752   /**
753    * Returns the base component type of this class.
754    *
755    * <p>
756    * For array types (e.g., <c>String[][]</c>), returns the deepest component type (e.g., <c>String</c>).
757    * <br>For non-array types, returns this class itself.
758    *
759    * <p>
760    * <b>Note:</b> Unlike {@link Class#getComponentType()}, this method also handles generic array types (e.g., <c>List&lt;String&gt;[]</c>)
761    * and returns the full parameterized type information (e.g., <c>List&lt;String&gt;</c>).
762    * Additionally, this method never returns <jk>null</jk> - non-array types return <jk>this</jk> instead.
763    *
764    * @return The base component type of an array, or this class if not an array.
765    */
766   public ClassInfo getComponentType() { return componentType.get(); }
767
768   /**
769    * Returns all annotations declared directly on this class, wrapped in {@link AnnotationInfo} objects.
770    *
771    * <p>
772    * This includes annotations explicitly applied to the class declaration, but excludes inherited annotations
773    * from parent classes. Each annotation is wrapped for additional functionality such as annotation member
774    * access and metadata inspection.
775    *
776    * <p>
777    * <b>Note on Repeatable Annotations:</b>
778    * Repeatable annotations (those marked with {@link Repeatable @Repeatable}) are automatically
779    * expanded into their individual annotation instances. For example, if a class has multiple {@code @Marshalled} annotations,
780    * this method returns each {@code @Marshalled} annotation separately, rather than the container annotation.
781    *
782    * @return
783    *    An unmodifiable list of {@link AnnotationInfo} wrappers for annotations declared directly on this class.
784    *    <br>List is empty if no annotations are declared.
785    *    <br>Results are in declaration order.
786    *    <br>Repeatable annotations are expanded into individual instances.
787    */
788   public List<AnnotationInfo> getDeclaredAnnotations() { return declaredAnnotations.get(); }
789
790   /**
791    * Returns the first matching declared constructor on this class.
792    *
793    * @param filter A predicate to apply to the entries to determine if value should be used.  Can be <jk>null</jk> (if null, all entries are accepted).
794    * @return The declared constructor that matches the specified predicate.
795    */
796   public Optional<ConstructorInfo> getDeclaredConstructor(Predicate<ConstructorInfo> filter) {
797      return declaredConstructors.get().stream().filter(x -> t(filter, x)).findFirst();
798   }
799
800   /**
801    * Returns all the constructors defined on this class.
802    *
803    * @return
804    *    All constructors defined on this class.
805    *    <br>List is unmodifiable.
806    */
807   public List<ConstructorInfo> getDeclaredConstructors() { return declaredConstructors.get(); }
808
809   /**
810    * Returns the first matching declared field on this class.
811    *
812    * @param filter A predicate to apply to the entries to determine if value should be used.  Can be <jk>null</jk> (if null, all entries are accepted).
813    * @return The declared field, or <jk>null</jk> if not found.
814    */
815   public Optional<FieldInfo> getDeclaredField(Predicate<FieldInfo> filter) {
816      return declaredFields.get().stream().filter(x -> t(filter, x)).findFirst();
817   }
818
819   /**
820    * Returns all declared fields on this class.
821    *
822    * @return
823    *    All declared fields on this class.
824    *    <br>Results are in alphabetical order.
825    *    <br>List is unmodifiable.
826    */
827   public List<FieldInfo> getDeclaredFields() { return declaredFields.get(); }
828
829   /**
830    * Returns all fields declared directly on this class, in the order reported by {@link Class#getDeclaredFields()}.
831    *
832    * <p>
833    * Unlike {@link #getDeclaredFields()}, the result is <b>not</b> sorted.  On HotSpot and OpenJ9 this is source
834    * declaration order.  The JLS does not guarantee it, so treat it as an ordering <i>hint</i> only.
835    * Used by {@code BeanMeta} to seed bean properties in declaration order when unsorted properties are enabled.
836    *
837    * <h5 class='section'>Example:</h5>
838    * <p class='bjava'>
839    *    <jk>public class</jk> MyBean {
840    *       <jk>public</jk> String <jf>zeta</jf>;
841    *       <jk>public int</jk> <jf>alpha</jf>;
842    *    }
843    *
844    *    <jc>// [zeta, alpha]</jc>
845    *    List&lt;String&gt; <jv>names</jv> = ClassInfo.<jsm>of</jsm>(MyBean.<jk>class</jk>)
846    *       .getDeclaredFieldsInDeclarationOrder().stream().map(FieldInfo::getName).toList();
847    * </p>
848    *
849    * @return
850    *    All declared fields on this class (including static and private fields, excluding the jacoco probe field).
851    *    <br>List is unmodifiable.  Elements are the same cached {@link FieldInfo} instances returned by {@link #getDeclaredFields()}.
852    */
853   public List<FieldInfo> getDeclaredFieldsInDeclarationOrder() { return declaredFieldsInDeclarationOrder.get(); }
854
855   /**
856    * Returns a list of interfaces declared on this class.
857    *
858    * <p>
859    * Does not include interfaces declared on parent classes.
860    *
861    * <p>
862    * Results are in the same order as Class.getInterfaces().
863    *
864    * @return
865    *    An unmodifiable list of interfaces declared on this class.
866    *    <br>Results are in the same order as {@link Class#getInterfaces()}.
867    */
868   public List<ClassInfo> getDeclaredInterfaces() { return declaredInterfaces.get(); }
869
870   /**
871    * Returns all classes and interfaces declared as members of this class.
872    *
873    * <p>
874    * This includes public, protected, default (package) access, and private classes and interfaces
875    * declared by the class, but excludes inherited classes and interfaces.
876    *
877    * @return
878    *    An unmodifiable list of all classes and interfaces declared as members of this class.
879    *    <br>Returns an empty list if this class declares no classes or interfaces as members.
880    */
881   public List<ClassInfo> getDeclaredMemberClasses() {
882      if (inner == null)
883         return u(l());
884      Class<?>[] classes = inner.getDeclaredClasses();
885      List<ClassInfo> l = listOfSize(classes.length);
886      for (Class<?> cc : classes)
887         l.add(of(cc));
888      return u(l);
889   }
890
891   /**
892    * Returns the first matching declared method on this class.
893    *
894    * @param filter A predicate to apply to the entries to determine if value should be used.  Can be <jk>null</jk> (if null, all entries are accepted).
895    * @return The first matching method, or <jk>null</jk> if no methods matched.
896    */
897   public Optional<MethodInfo> getDeclaredMethod(Predicate<MethodInfo> filter) {
898      return declaredMethods.get().stream().filter(x -> t(filter, x)).findFirst();
899   }
900
901   /**
902    * Returns all methods declared on this class.
903    *
904    * <p>
905    * This method returns methods of <b>all visibility levels</b> (public, protected, package-private, and private)
906    * declared directly on this class only (does not include inherited methods).
907    *
908    * <h5 class='section'>Comparison with Similar Methods:</h5>
909    * <ul class='spaced-list'>
910    *    <li>{@link #getDeclaredMethods()} - Returns all declared methods on this class only (all visibility levels) ← This method
911    *    <li>{@link #getAllMethods()} - Returns all declared methods on this class and parents (all visibility levels)
912    *    <li>{@link #getPublicMethods()} - Returns public methods only on this class and parents
913    * </ul>
914    *
915    * <h5 class='section'>Notes:</h5>
916    * <ul class='spaced-list'>
917    *    <li>Unlike Java's {@link Class#getDeclaredMethods()}, results are filtered to exclude synthetic methods like <c>$jacocoInit</c>.
918    * </ul>
919    *
920    * @return
921    *    All methods declared on this class (all visibility levels).
922    *    <br>Results are ordered alphabetically.
923    *    <br>List is unmodifiable.
924    */
925   public List<MethodInfo> getDeclaredMethods() { return declaredMethods.get(); }
926
927   /**
928    * Returns the {@link Class} object representing the class or interface that declares the member class
929    * represented by this class.
930    *
931    * <p>
932    * This method returns the class in which this class is explicitly declared as a member.
933    * It only returns non-null for <b>member classes</b> (static and non-static nested classes).
934    *
935    * <p>
936    * <h5 class='section'>Examples:</h5>
937    * <ul class='spaced-list'>
938    *    <li><c>class Outer { class Inner {} }</c> - <c>Inner.getDeclaringClass()</c> returns <c>Outer</c>
939    *    <li><c>class Outer { static class Nested {} }</c> - <c>Nested.getDeclaringClass()</c> returns <c>Outer</c>
940    *    <li><c>class Outer { void method() { class Local {} } }</c> - <c>Local.getDeclaringClass()</c> returns <jk>null</jk>
941    *    <li>Top-level class - <c>getDeclaringClass()</c> returns <jk>null</jk>
942    *    <li>Anonymous class - <c>getDeclaringClass()</c> returns <jk>null</jk>
943    * </ul>
944    *
945    * <h5 class='section'>See Also:</h5><ul>
946    *    <li class='ja'>{@link #getEnclosingClass()} - Returns the immediately enclosing class (works for local and anonymous classes too)
947    * </ul>
948    *
949    * @return The declaring class, or <jk>null</jk> if this class is not a member of another class.
950    */
951   public ClassInfo getDeclaringClass() {
952      if (inner == null)
953         return null;
954      var dc = inner.getDeclaringClass();
955      return dc == null ? null : of(dc);
956   }
957
958   /**
959    * Returns the number of dimensions if this is an array type.
960    *
961    * @return The number of dimensions if this is an array type, or <c>0</c> if it is not.
962    */
963   public int getDimensions() { return dimensions.get(); }
964
965   /**
966    * Returns the immediately enclosing class of this class.
967    *
968    * <p>
969    * This method returns the lexically enclosing class, regardless of whether this class is a member,
970    * local, or anonymous class. Unlike {@link #getDeclaringClass()}, this method works for all types of nested classes.
971    *
972    * <p>
973    * <h5 class='section'>Examples:</h5>
974    * <ul class='spaced-list'>
975    *    <li><c>class Outer { class Inner {} }</c> - <c>Inner.getEnclosingClass()</c> returns <c>Outer</c>
976    *    <li><c>class Outer { static class Nested {} }</c> - <c>Nested.getEnclosingClass()</c> returns <c>Outer</c>
977    *    <li><c>class Outer { void method() { class Local {} } }</c> - <c>Local.getEnclosingClass()</c> returns <c>Outer</c>
978    *    <li><c>class Outer { void method() { new Runnable() {...} } }</c> - Anonymous class <c>getEnclosingClass()</c> returns <c>Outer</c>
979    *    <li>Top-level class - <c>getEnclosingClass()</c> returns <jk>null</jk>
980    * </ul>
981    *
982    * <h5 class='section'>Differences from {@link #getDeclaringClass()}:</h5>
983    * <ul class='spaced-list'>
984    *    <li><c>getDeclaringClass()</c> - Returns non-null only for member classes (static or non-static nested classes)
985    *    <li><c>getEnclosingClass()</c> - Returns non-null for all nested classes (member, local, and anonymous)
986    * </ul>
987    *
988    * <h5 class='section'>See Also:</h5><ul>
989    *    <li class='ja'>{@link #getDeclaringClass()} - Returns the declaring class (only for member classes)
990    *    <li class='ja'>{@link #getEnclosingConstructor()} - Returns the enclosing constructor (for classes defined in constructors)
991    *    <li class='ja'>{@link #getEnclosingMethod()} - Returns the enclosing method (for classes defined in methods)
992    * </ul>
993    *
994    * @return The enclosing class, or <jk>null</jk> if this is a top-level class.
995    */
996   public ClassInfo getEnclosingClass() {
997      if (inner == null)
998         return null;
999      var ec = inner.getEnclosingClass();
1000      return ec == null ? null : of(ec);
1001   }
1002
1003   /**
1004    * Returns the {@link ConstructorInfo} object representing the constructor that declares this class if this is a
1005    * local or anonymous class declared within a constructor.
1006    *
1007    * <p>
1008    * Returns <jk>null</jk> if this class was not declared within a constructor.
1009    *
1010    * @return The enclosing constructor, or <jk>null</jk> if this class was not declared within a constructor.
1011    */
1012   public ConstructorInfo getEnclosingConstructor() {
1013      if (inner == null)
1014         return null;
1015      Constructor<?> ec = inner.getEnclosingConstructor();
1016      return ec == null ? null : getConstructor(ec);
1017   }
1018
1019   /**
1020    * Returns the {@link MethodInfo} object representing the method that declares this class if this is a
1021    * local or anonymous class declared within a method.
1022    *
1023    * <p>
1024    * Returns <jk>null</jk> if this class was not declared within a method.
1025    *
1026    * @return The enclosing method, or <jk>null</jk> if this class was not declared within a method.
1027    */
1028   public MethodInfo getEnclosingMethod() {
1029      if (inner == null)
1030         return null;
1031      var em = inner.getEnclosingMethod();
1032      return em == null ? null : getMethod(em);
1033   }
1034
1035   /**
1036    * Returns the {@link Type}s representing the interfaces directly implemented by this class.
1037    *
1038    * <p>
1039    * Returns a cached, unmodifiable list.
1040    * If a superinterface is a parameterized type, the {@link Type} returned for it reflects the actual
1041    * type parameters used in the source code.
1042    *
1043    * @return
1044    *    An unmodifiable list of {@link Type}s representing the interfaces directly implemented by this class.
1045    *    <br>Returns an empty list if this class implements no interfaces.
1046    */
1047   public List<Type> getGenericInterfaces() { return genericInterfaces.get(); }
1048
1049   /**
1050    * Returns the {@link Type} representing the direct superclass of this class.
1051    *
1052    * <p>
1053    * If the superclass is a parameterized type, the {@link Type} returned reflects the actual
1054    * type parameters used in the source code.
1055    *
1056    * @return
1057    *    The superclass of this class as a {@link Type},
1058    *    or <jk>null</jk> if this class represents {@link Object}, an interface, a primitive type, or void.
1059    */
1060   public Type getGenericSuperclass() { return inner == null ? null : inner.getGenericSuperclass(); }
1061
1062   /**
1063    * Returns a list of interfaces defined on this class and superclasses.
1064    *
1065    * <p>
1066    * Results are in child-to-parent order.
1067    *
1068    * @return
1069    *    An unmodifiable list of interfaces defined on this class and superclasses.
1070    *    <br>Results are in child-to-parent order.
1071    */
1072   public List<ClassInfo> getInterfaces() { return interfaces.get(); }
1073
1074   @Override /* Annotatable */
1075   public String getLabel() { return getNameSimple(); }
1076
1077   /**
1078    * Returns all public member classes and interfaces declared by this class and its superclasses.
1079    *
1080    * <p>
1081    * This includes public class and interface members inherited from superclasses and
1082    * public class and interface members declared by the class.
1083    *
1084    * @return
1085    *    An unmodifiable list of all public member classes and interfaces declared by this class.
1086    *    <br>Returns an empty list if this class has no public member classes or interfaces.
1087    */
1088   public List<ClassInfo> getMemberClasses() {
1089      if (inner == null)
1090         return u(l());
1091      Class<?>[] classes = inner.getClasses();
1092      List<ClassInfo> l = listOfSize(classes.length);
1093      for (Class<?> cc : classes)
1094         l.add(of(cc));
1095      return u(l);
1096   }
1097
1098   /**
1099    * Returns the first matching method on this class.
1100    *
1101    * @param filter A predicate to apply to the entries to determine if value should be used.  Can be <jk>null</jk> (if null, all entries are accepted).
1102    * @return The first matching method, or <jk>null</jk> if no methods matched.
1103    */
1104   public Optional<MethodInfo> getMethod(Predicate<MethodInfo> filter) {
1105      return allMethods.get().stream().filter(x -> t(filter, x)).findFirst();
1106   }
1107
1108   /**
1109    * Returns the module that this class is a member of.
1110    *
1111    * <p>
1112    * If this class is not in a named module, returns the unnamed module of the class loader for this class.
1113    *
1114    * @return The module that this class is a member of.
1115    */
1116   public Module getModule() { return inner == null ? null : inner.getModule(); }
1117
1118   /**
1119    * Returns the name of the underlying class.
1120    *
1121    * <p>
1122    * Equivalent to calling {@link Class#getName()} or {@link Type#getTypeName()} depending on whether
1123    * this is a class or type.
1124    *
1125    * <p>
1126    * This method returns the JVM internal format for class names:
1127    * <ul>
1128    *    <li>Uses fully qualified package names
1129    *    <li>Uses <js>'$'</js> separator for nested classes
1130    *    <li>Uses JVM notation for arrays (e.g., <js>"[Ljava.lang.String;"</js>)
1131    *    <li>Uses single letters for primitive arrays (e.g., <js>"[I"</js> for <c>int[]</c>)
1132    * </ul>
1133    *
1134    * <h5 class='section'>Examples:</h5>
1135    * <ul>
1136    *    <li><js>"java.lang.String"</js> - Normal class
1137    *    <li><js>"[Ljava.lang.String;"</js> - Array
1138    *    <li><js>"[[Ljava.lang.String;"</js> - Multi-dimensional array
1139    *    <li><js>"java.util.Map$Entry"</js> - Nested class
1140    *    <li><js>"int"</js> - Primitive class
1141    *    <li><js>"[I"</js> - Primitive array
1142    *    <li><js>"[[I"</js> - Multi-dimensional primitive array
1143    * </ul>
1144    *
1145    * <h5 class='section'>See Also:</h5>
1146    * <ul class='spaced-list'>
1147    *    <li><c>{@link #getNameCanonical()}</c> - Java source code format (uses <js>'.'</js> and <js>"[]"</js>)
1148    *    <li><c>{@link #getNameSimple()}</c> - Simple class name without package
1149    *    <li><c>{@link #getNameFull()}</c> - Full name with type parameters
1150    *    <li><c>{@link #getNameShort()}</c> - Short name with type parameters
1151    *    <li><c>{@link #getNameReadable()}</c> - Human-readable name (uses <js>"Array"</js> suffix)
1152    * </ul>
1153    *
1154    * @return The name of the underlying class in JVM format.
1155    */
1156   public String getName() { return nn(inner) ? inner.getName() : innerType.getTypeName(); }
1157
1158   /**
1159    * Returns the canonical name of the underlying class.
1160    *
1161    * <p>
1162    * The canonical name is the name that would be used in Java source code to refer to the class.
1163    * For example:
1164    * <ul>
1165    *    <li><js>"java.lang.String"</js> - Normal class
1166    *    <li><js>"java.lang.String[]"</js> - Array
1167    *    <li><js>"java.util.Map.Entry"</js> - Nested class
1168    *    <li><jk>null</jk> - Local or anonymous class
1169    * </ul>
1170    *
1171    * @return The canonical name of the underlying class, or <jk>null</jk> if this class doesn't have a canonical name.
1172    */
1173   public String getNameCanonical() {
1174      // For Class objects, delegate to Class.getCanonicalName() which handles local/anonymous classes
1175      // For Type objects (ParameterizedType, etc.), compute the canonical name
1176      if (inner != null && ! isParameterizedType) {
1177         return inner.getCanonicalName();
1178      }
1179      // For ParameterizedType, we can't have a true canonical name with type parameters
1180      // Return null to maintain consistency with Class.getCanonicalName() behavior
1181      return null;
1182   }
1183
1184   /**
1185    * Returns a formatted class name with configurable options.
1186    *
1187    * <p>
1188    * This is a unified method that can produce output equivalent to all other name methods
1189    * by varying the parameters.
1190    *
1191    * <h5 class='section'>Examples:</h5>
1192    * <p class='bjava'>
1193    *    <jc>// Given: java.util.HashMap&lt;String,Integer&gt;[]</jc>
1194    *
1195    *    <jc>// Full name with generics</jc>
1196    *    getFormattedName(ClassNameFormat.<jsf>FULL</jsf>, <jk>true</jk>, '$', ClassArrayFormat.<jsf>BRACKETS</jsf>)
1197    *    <jc>// → "java.util.HashMap&lt;java.lang.String,java.lang.Integer&gt;[]"</jc>
1198    *
1199    *    <jc>// Short name with generics</jc>
1200    *    getFormattedName(ClassNameFormat.<jsf>SHORT</jsf>, <jk>true</jk>, '$', ClassArrayFormat.<jsf>BRACKETS</jsf>)
1201    *    <jc>// → "HashMap&lt;String,Integer&gt;[]"</jc>
1202    *
1203    *    <jc>// Simple name</jc>
1204    *    getFormattedName(ClassNameFormat.<jsf>SIMPLE</jsf>, <jk>false</jk>, '$', ClassArrayFormat.<jsf>BRACKETS</jsf>)
1205    *    <jc>// → "HashMap[]"</jc>
1206    *
1207    *    <jc>// With dot separator</jc>
1208    *    getFormattedName(ClassNameFormat.<jsf>SHORT</jsf>, <jk>false</jk>, '.', ClassArrayFormat.<jsf>BRACKETS</jsf>)
1209    *    <jc>// → "Map.Entry"</jc>
1210    *
1211    *    <jc>// Word format for arrays</jc>
1212    *    getFormattedName(ClassNameFormat.<jsf>SIMPLE</jsf>, <jk>false</jk>, '$', ClassArrayFormat.<jsf>WORD</jsf>)
1213    *    <jc>// → "HashMapArray"</jc>
1214    * </p>
1215    *
1216    * <h5 class='section'>Equivalent Methods:</h5>
1217    * <ul class='spaced-list'>
1218    *    <li><c>getName()</c> = <c>getFormattedName(FULL, <jk>false</jk>, '$', JVM)</c>
1219    *    <li><c>getCanonicalName()</c> = <c>getFormattedName(FULL, <jk>false</jk>, '.', BRACKETS)</c>
1220    *    <li><c>getSimpleName()</c> = <c>getFormattedName(SIMPLE, <jk>false</jk>, '$', BRACKETS)</c>
1221    *    <li><c>getFullName()</c> = <c>getFormattedName(FULL, <jk>true</jk>, '$', BRACKETS)</c>
1222    *    <li><c>getShortName()</c> = <c>getFormattedName(SHORT, <jk>true</jk>, '$', BRACKETS)</c>
1223    *    <li><c>getReadableName()</c> = <c>getFormattedName(SIMPLE, <jk>false</jk>, '$', WORD)</c>
1224    * </ul>
1225    *
1226    * @param nameFormat
1227    *    Controls which parts of the class name to include (package, outer classes).
1228    * @param includeTypeParams
1229    *    If <jk>true</jk>, include generic type parameters recursively.
1230    *    <br>For example: <js>"HashMap&lt;String,Integer&gt;"</js> instead of <js>"HashMap"</js>
1231    * @param separator
1232    *    Character to use between outer and inner class names.
1233    *    <br>Typically <js>'$'</js> (JVM format) or <js>'.'</js> (canonical format).
1234    *    <br>Ignored when <c>nameFormat</c> is {@link ClassNameFormat#SIMPLE}.
1235    * @param arrayFormat
1236    *    How to format array dimensions.
1237    * @return
1238    *    The formatted class name.
1239    */
1240   public String getNameFormatted(ClassNameFormat nameFormat, boolean includeTypeParams, char separator, ClassArrayFormat arrayFormat) {
1241      var sb = new StringBuilder(128);
1242      appendNameFormatted(sb, nameFormat, includeTypeParams, separator, arrayFormat);
1243      return sb.toString();
1244   }
1245
1246   /**
1247    * Returns the full name of this class.
1248    *
1249    * <h5 class='section'>Examples:</h5>
1250    * <ul>
1251    *    <li><js>"com.foo.MyClass"</js> - Normal class
1252    *    <li><js>"com.foo.MyClass[][]"</js> - Array.
1253    *    <li><js>"com.foo.MyClass$InnerClass"</js> - Inner class.
1254    *    <li><js>"com.foo.MyClass$InnerClass[][]"</js> - Inner class array.
1255    *    <li><js>"int"</js> - Primitive class.
1256    *    <li><js>"int[][]"</js> - Primitive class class.
1257    *    <li><js>"java.util.Map&lt;java.lang.String,java.lang.Object&gt;"</js> - Parameterized type.
1258    *    <li><js>"java.util.AbstractMap&lt;K,V&gt;"</js> - Parameterized generic type.
1259    *    <li><js>"V"</js> - Parameterized generic type argument.
1260    * </ul>
1261    *
1262    * @return The underlying class name.
1263    */
1264   public String getNameFull() { return nameFull.get(); }
1265
1266   /**
1267    * Same as {@link #getNameSimple()} but uses <js>"Array"</js> instead of <js>"[]"</js>.
1268    *
1269    * @return The readable name for this class.
1270    */
1271   public String getNameReadable() { return nameReadable.get(); }
1272
1273   /**
1274    * Returns all possible names for this class.
1275    *
1276    * @return
1277    *    An array consisting of:
1278    *    <ul>
1279    *       <li>{@link #getNameFull()}
1280    *       <li>{@link Class#getName()} - Note that this might be a dup.
1281    *       <li>{@link #getNameShort()}
1282    *       <li>{@link #getNameSimple()}
1283    *    </ul>
1284    */
1285   public String[] getNames() { return a(getNameFull(), inner.getName(), getNameShort(), getNameSimple()); }
1286
1287   /**
1288    * Returns the short name of the underlying class.
1289    *
1290    * <p>
1291    * Similar to {@link #getNameSimple()} but also renders local or member class name prefixes.
1292    *
1293    * @return The short name of the underlying class.
1294    */
1295   public String getNameShort() { return nameShort.get(); }
1296
1297   /**
1298    * Returns the simple name of the underlying class.
1299    *
1300    * <p>
1301    * Returns either {@link Class#getSimpleName()} or {@link Type#getTypeName()} depending on whether
1302    * this is a class or type.
1303    *
1304    * @return The simple name of the underlying class;
1305    */
1306   public String getNameSimple() { return nn(inner) ? inner.getSimpleName() : innerType.getTypeName(); }
1307
1308   /**
1309    * Returns the nest host of this class.
1310    *
1311    * <p>
1312    * Every class belongs to exactly one nest. A class that is not a member of a nest
1313    * is its own nest host.
1314    *
1315    * @return The nest host of this class.
1316    */
1317   public ClassInfo getNestHost() {
1318      if (inner == null)
1319         return null;
1320      return of(inner.getNestHost());
1321   }
1322
1323   /**
1324    * Returns an array containing all the classes and interfaces that are members of the nest
1325    * to which this class belongs.
1326    *
1327    * @return
1328    *    An unmodifiable list of all classes and interfaces in the same nest as this class.
1329    *    <br>Returns an empty list if this object does not represent a class.
1330    */
1331   public List<ClassInfo> getNestMembers() {
1332      if (inner == null)
1333         return u(l());
1334      var members = inner.getNestMembers();
1335      List<ClassInfo> l = listOfSize(members.length);
1336      for (Class<?> cc : members)
1337         l.add(of(cc));
1338      return u(l);
1339   }
1340
1341   /**
1342    * Locates the no-arg constructor for this class.
1343    *
1344    * <p>
1345    * Constructor must match the visibility requirements specified by parameter 'v'.
1346    * If class is abstract, always returns <jk>null</jk>.
1347    * Note that this also returns the 1-arg constructor for non-static member classes.
1348    *
1349    * @param v The minimum visibility.
1350    * @return The constructor, or <jk>null</jk> if no no-arg constructor exists with the required visibility.
1351    */
1352   public Optional<ConstructorInfo> getNoArgConstructor(Visibility v) {
1353      if (isAbstract())
1354         return oe();
1355      int expectedParams = isNonStaticMemberClass() ? 1 : 0;
1356      // @formatter:off
1357      return getDeclaredConstructors().stream()
1358         .filter(cc -> cc.hasNumParameters(expectedParams))
1359         .filter(cc -> cc.isVisible(v))
1360         .map(cc -> cc.accessible())
1361         .findFirst();
1362      // @formatter:on
1363   }
1364
1365   /**
1366    * Returns the package of this class.
1367    *
1368    * <p>
1369    * Returns <jk>null</jk> in the following cases:
1370    * <ul>
1371    *    <li>Primitive types (e.g., <c>int.class</c>, <c>boolean.class</c>)
1372    *    <li>Array types of primitives (e.g., <c>int[].class</c>)
1373    *    <li>Classes in the default (unnamed) package - while the default package is technically a package,
1374    *       Java's <c>Class.getPackage()</c> returns <jk>null</jk> for classes in the default package because
1375    *       it has no name or associated <c>Package</c> object
1376    * </ul>
1377    *
1378    * @return The package of this class wrapped in a {@link PackageInfo}, or <jk>null</jk> if this class has no package
1379    *    or is in the default (unnamed) package.
1380    */
1381   public PackageInfo getPackage() { return packageInfo.get(); }
1382
1383   /**
1384    * Returns the specified annotation only if it's been declared on the package of this class.
1385    *
1386    * @param <A> The annotation type to look for.
1387    * @param type The annotation class.
1388    * @return The annotation, or <jk>null</jk> if not found.
1389    */
1390   public <A extends Annotation> A getPackageAnnotation(Class<A> type) {
1391      PackageInfo pi = getPackage();
1392      if (pi == null)
1393         return null;
1394      var ai = pi.getAnnotations(type).findFirst().orElse(null);
1395      return ai == null ? null : ai.inner();
1396   }
1397
1398   /**
1399    * Finds the real parameter type of this class.
1400    *
1401    * @param index The zero-based index of the parameter to resolve.
1402    * @param pt The parameterized type class containing the parameterized type to resolve (e.g. <c>HashMap</c>).  Must not be <jk>null</jk>.
1403    * @return The resolved real class.
1404    */
1405   public Class<?> getParameterType(int index, Class<?> pt) {
1406      reqnn("pt", pt);
1407
1408      // We need to make up a mapping of type names.
1409      var typeMap = new HashMap<Type,Type>();
1410      var cc = inner;
1411      while (pt != cc.getSuperclass()) {
1412         extractTypes(typeMap, cc);
1413         cc = cc.getSuperclass();
1414         req(nn(cc), "Class '%s' is not a subclass of parameterized type '%s'", inner.getSimpleName(), pt.getSimpleName());
1415      }
1416
1417      Type gsc = cc.getGenericSuperclass();
1418
1419      req(gsc instanceof ParameterizedType, "Class '%s' is not a parameterized type", pt.getSimpleName());
1420
1421      var cpt = (ParameterizedType)gsc;
1422      Type[] atArgs = cpt.getActualTypeArguments();
1423      req(index < atArgs.length, "Invalid type index. index=%s, argsLength=%s", index, atArgs.length);
1424      Type actualType = cpt.getActualTypeArguments()[index];
1425
1426      if (typeMap.containsKey(actualType))
1427         actualType = typeMap.get(actualType);
1428
1429      if (actualType instanceof Class actualType2) {
1430         return actualType2;
1431
1432      } else if (actualType instanceof GenericArrayType actualType2) {
1433         Type gct = actualType2.getGenericComponentType();
1434         if (gct instanceof ParameterizedType gct2)
1435            return Array.newInstance((Class<?>)gct2.getRawType(), 0).getClass();
1436      } else if (actualType instanceof TypeVariable<?> actualType3) {
1437         for (Class<?> ec = cc.getEnclosingClass(); nn(ec); ec = ec.getEnclosingClass()) {
1438            var outerClass = ec;
1439            var outerTypeMap = new HashMap<Type,Type>();
1440            extractTypes(outerTypeMap, outerClass);
1441            for (var entry : outerTypeMap.entrySet()) {
1442               // HTT - Hard to test - requires nested generics where a type variable in an inner class
1443               // maps to another TypeVariable (not a Class) in the outer class's type map, which then
1444               // needs to be resolved further up the enclosing class chain.
1445               var key = entry.getKey();
1446               var value = entry.getValue();
1447               if (key instanceof TypeVariable<?> key2 && key2.getName().equals(actualType3.getName()) && isInnerClass(key2.getGenericDeclaration(), actualType3.getGenericDeclaration())) {
1448                  if (value instanceof Class<?> value2)
1449                     return value2;
1450                  actualType3 = (TypeVariable<?>)entry.getValue();
1451               }
1452            }
1453         }
1454      } else if (actualType instanceof ParameterizedType actualType2) {
1455         return (Class<?>)actualType2.getRawType();
1456      }
1457      throw iaex("Could not resolve variable '%s' to a type.", actualType.getTypeName());
1458   }
1459
1460   /**
1461    * Extracts generic type parameter mappings from a class's superclass declaration and adds them to a type map.
1462    *
1463    * <p>
1464    * This method builds a mapping between type variables (e.g., <c>T</c>, <c>K</c>, <c>V</c>) and their actual
1465    * type arguments in the inheritance hierarchy. It's used to resolve generic type parameters when navigating
1466    * through class hierarchies.
1467    *
1468    * <p>
1469    * The method handles transitive type mappings by checking if an actual type argument is itself a type variable
1470    * that's already been mapped, and resolving it to its final type.
1471    *
1472    * @param typeMap
1473    *    The map to populate with type variable to actual type mappings.
1474    *    <br>Existing entries are used to resolve transitive mappings.
1475    * @param c
1476    *    The class whose generic superclass should be examined for type parameters.
1477    */
1478   private static void extractTypes(Map<Type,Type> typeMap, Class<?> c) {
1479      var gs = c.getGenericSuperclass();
1480      if (gs instanceof ParameterizedType gs2) {
1481         var typeParameters = ((Class<?>)gs2.getRawType()).getTypeParameters();
1482         var actualTypeArguments = gs2.getActualTypeArguments();
1483         for (var i = 0; i < typeParameters.length; i++) {
1484            if (typeMap.containsKey(actualTypeArguments[i]))
1485               actualTypeArguments[i] = typeMap.get(actualTypeArguments[i]);
1486            typeMap.put(typeParameters[i], actualTypeArguments[i]);
1487         }
1488      }
1489   }
1490
1491   /**
1492    * Checks if one generic declaration (typically a class) is an inner class of another.
1493    *
1494    * <p>
1495    * This method walks up the enclosing class hierarchy of the potential inner class to determine if it's
1496    * nested within the outer class at any level. It's used during generic type resolution to determine if
1497    * a type variable belongs to an inner class that's nested within an outer class.
1498    *
1499    * @param od
1500    *    The potential outer declaration (typically an outer class).
1501    * @param id
1502    *    The potential inner declaration (typically an inner class).
1503    * @return
1504    *    <jk>true</jk> if <c>id</c> is nested within <c>od</c> at any level, <jk>false</jk> otherwise.
1505    *    <br>Returns <jk>false</jk> if either parameter is not a <c>Class</c>.
1506    */
1507   private static boolean isInnerClass(GenericDeclaration od, GenericDeclaration id) {
1508      if (od instanceof Class<?> od2 && id instanceof Class<?> id2) {
1509         while (nn(id2 = id2.getEnclosingClass()))
1510            if (id2 == od2)
1511               return true;
1512      }
1513      return false;
1514   }
1515
1516   /**
1517    * Returns a list including this class and all parent classes.
1518    *
1519    * <p>
1520    * Does not include interfaces.
1521    *
1522    * <p>
1523    * Results are in child-to-parent order.
1524    *
1525    * @return An unmodifiable list including this class and all parent classes.
1526    *    <br>Results are in child-to-parent order.
1527    */
1528   public List<ClassInfo> getParents() { return parents.get(); }
1529
1530   /**
1531    * Returns all parent classes and interfaces in proper traversal order.
1532    *
1533    * <p>
1534    * This method returns a unique list of all parent classes (including this class) and all interfaces
1535    * (including interface hierarchies) with proper handling of duplicates. The order is:
1536    * <ol>
1537    *    <li>This class
1538    *    <li>Parent classes in child-to-parent order
1539    *    <li>For each class, interfaces declared on that class and their parent interfaces
1540    * </ol>
1541    *
1542    * <p>
1543    * This is useful for annotation processing where you need to traverse the complete type hierarchy
1544    * without duplicates.
1545    *
1546    * <h5 class='section'>Example:</h5>
1547    * <p class='bjava'>
1548    *    <jc>// Interface hierarchy:</jc>
1549    *    <jk>interface</jk> ISuperGrandParent {}
1550    *    <jk>interface</jk> IGrandParent <jk>extends</jk> ISuperGrandParent {}
1551    *    <jk>interface</jk> ISuperParent {}
1552    *    <jk>interface</jk> IParent <jk>extends</jk> ISuperParent {}
1553    *    <jk>interface</jk> IChild {}
1554    *
1555    *    <jc>// Class hierarchy:</jc>
1556    *    <jk>class</jk> GrandParent <jk>implements</jk> IGrandParent {}
1557    *    <jk>class</jk> Parent <jk>extends</jk> GrandParent <jk>implements</jk> IParent {}
1558    *    <jk>class</jk> Child <jk>extends</jk> Parent <jk>implements</jk> IChild {}
1559    *
1560    *    <jc>// For Child, returns (in this order):</jc>
1561    *    ClassInfo <jv>ci</jv> = ClassInfo.<jsm>of</jsm>(Child.<jk>class</jk>);
1562    *    List&lt;ClassInfo&gt; <jv>result</jv> = <jv>ci</jv>.getParentsAndInterfaces();
1563    *    <jc>// Result: [</jc>
1564    *    <jc>//   Child,                  // 1. This class</jc>
1565    *    <jc>//   IChild,                 // 2. Interface on Child</jc>
1566    *    <jc>//   Parent,                 // 3. Parent class</jc>
1567    *    <jc>//   IParent,                // 4. Interface on Parent</jc>
1568    *    <jc>//   ISuperParent,           // 5. Parent interface of IParent</jc>
1569    *    <jc>//   GrandParent,            // 6. Grandparent class</jc>
1570    *    <jc>//   IGrandParent,           // 7. Interface on GrandParent</jc>
1571    *    <jc>//   ISuperGrandParent       // 8. Parent interface of IGrandParent</jc>
1572    *    <jc>// ]</jc>
1573    * </p>
1574    *
1575    * @return An unmodifiable list of all parent classes and interfaces, properly ordered without duplicates.
1576    */
1577   public List<ClassInfo> getParentsAndInterfaces() { return parentsAndInterfaces.get(); }
1578
1579   /**
1580    * Returns the permitted subclasses of this sealed class.
1581    *
1582    * <p>
1583    * If this class is not sealed, returns an empty list.
1584    *
1585    * @return
1586    *    An unmodifiable list of permitted subclasses if this is a sealed class.
1587    *    <br>Returns an empty list if this class is not sealed.
1588    */
1589   public List<ClassInfo> getPermittedSubclasses() {
1590      if (inner == null || ! inner.isSealed())
1591         return u(l());
1592      return u(stream(inner.getPermittedSubclasses()).map(ClassInfo::of).toList());
1593   }
1594
1595   /**
1596    * Returns the default value for this primitive class.
1597    *
1598    * @return The default value, or <jk>null</jk> if this is not a primitive class.
1599    */
1600   public Object getPrimitiveDefault() { return primitiveDefaultMap.get(inner); }
1601
1602   /**
1603    * If this class is a primitive wrapper (e.g. <code><jk>Integer</jk>.<jk>class</jk></code>) returns it's
1604    * primitive class (e.g. <code>int.<jk>class</jk></code>).
1605    *
1606    * @return The primitive class, or <jk>null</jk> if class is not a primitive wrapper.
1607    */
1608   public Class<?> getPrimitiveForWrapper() { return pmap2.get(inner); }
1609
1610   /**
1611    * If this class is a primitive (e.g. <code><jk>int</jk>.<jk>class</jk></code>) returns it's wrapper class
1612    * (e.g. <code>Integer.<jk>class</jk></code>).
1613    *
1614    * @return The wrapper class, or <jk>null</jk> if class is not a primitive.
1615    */
1616   public Class<?> getPrimitiveWrapper() { return pmap1.get(inner); }
1617
1618   /**
1619    * Returns the {@link ProtectionDomain} of this class.
1620    *
1621    * <p>
1622    * If a security manager is installed, this method requires <c>RuntimePermission("getProtectionDomain")</c>.
1623    *
1624    * @return The {@link ProtectionDomain} of this class, or <jk>null</jk> if the class does not have a protection domain.
1625    */
1626   public ProtectionDomain getProtectionDomain() { return inner == null ? null : inner.getProtectionDomain(); }
1627
1628   /**
1629    * Returns the first matching public constructor on this class.
1630    *
1631    * @param filter A predicate to apply to the entries to determine if value should be used.  Can be <jk>null</jk> (if null, all entries are accepted).
1632    * @return The public constructor that matches the specified predicate.
1633    */
1634   public Optional<ConstructorInfo> getPublicConstructor(Predicate<ConstructorInfo> filter) {
1635      return publicConstructors.get().stream().filter(x -> t(filter, x)).findFirst();
1636   }
1637
1638   /**
1639    * Returns all the public constructors defined on this class.
1640    *
1641    * @return All public constructors defined on this class.
1642    */
1643   public List<ConstructorInfo> getPublicConstructors() { return publicConstructors.get(); }
1644
1645   /**
1646    * Returns the first matching public field on this class.
1647    *
1648    * @param filter A predicate to apply to the entries to determine if value should be used.  Can be <jk>null</jk> (if null, all entries are accepted).
1649    * @return The public field, or <jk>null</jk> if not found.
1650    */
1651   public Optional<FieldInfo> getPublicField(Predicate<FieldInfo> filter) {
1652      return publicFields.get().stream().filter(x -> t(filter, x)).findFirst();
1653   }
1654
1655   /**
1656    * Returns all public fields on this class.
1657    *
1658    * <p>
1659    * Hidden fields are excluded from the results.
1660    *
1661    * @return
1662    *    All public fields on this class.
1663    *    <br>Results are in alphabetical order.
1664    *    <br>List is unmodifiable.
1665    */
1666   public List<FieldInfo> getPublicFields() { return publicFields.get(); }
1667
1668   /**
1669    * Returns the first matching public method on this class.
1670    *
1671    * @param filter A predicate to apply to the entries to determine if value should be used.  Can be <jk>null</jk> (if null, all entries are accepted).
1672    * @return The first matching method, or <jk>null</jk> if no methods matched.
1673    */
1674   public Optional<MethodInfo> getPublicMethod(Predicate<MethodInfo> filter) {
1675      return publicMethods.get().stream().filter(x -> t(filter, x)).findFirst();
1676   }
1677
1678   /**
1679    * Finds a public getter method for the specified bean property name.
1680    *
1681    * <p>
1682    * Matches any public no-argument method named {@code get<PropertyName>} with a non-void return type,
1683    * or {@code is<PropertyName>} with a {@code boolean}/{@link Boolean} return type.
1684    *
1685    * <h5 class='section'>Example:</h5>
1686    * <p class='bjava'>
1687    *    <jc>// Find the getter for the "name" property on MyBean</jc>
1688    *    Optional&lt;MethodInfo&gt; <jv>getter</jv> = ClassInfo.<jsm>info</jsm>(MyBean.<jk>class</jk>).findGetter(<js>"name"</js>);
1689    *    <jv>getter</jv>.ifPresent(<jv>m</jv> -&gt; System.<jsf>out</jsf>.println(<jv>m</jv>.getName()));  <jc>// "getName"</jc>
1690    * </p>
1691    *
1692    * @param propertyName The bean property name (e.g. <js>"name"</js> to match {@code getName()} or {@code isName()}).
1693    * @return The matching getter method, or an empty {@link Optional} if not found.
1694    */
1695   public Optional<MethodInfo> findGetter(String propertyName) {
1696      return getPublicMethod(m -> m.isGetter() && m.getPropertyName().equals(propertyName));
1697   }
1698
1699   /**
1700    * Finds a public setter method for the specified bean property name.
1701    *
1702    * <p>
1703    * Matches any public single-argument method named {@code set<PropertyName>}.
1704    *
1705    * <h5 class='section'>Example:</h5>
1706    * <p class='bjava'>
1707    *    <jc>// Find the setter for the "name" property on MyBean</jc>
1708    *    Optional&lt;MethodInfo&gt; <jv>setter</jv> = ClassInfo.<jsm>info</jsm>(MyBean.<jk>class</jk>).findSetter(<js>"name"</js>);
1709    *    <jv>setter</jv>.ifPresent(<jv>m</jv> -&gt; System.<jsf>out</jsf>.println(<jv>m</jv>.getName()));  <jc>// "setName"</jc>
1710    * </p>
1711    *
1712    * @param propertyName The bean property name (e.g. <js>"name"</js> to match {@code setName(X)}).
1713    * @return The matching setter method, or an empty {@link Optional} if not found.
1714    */
1715   public Optional<MethodInfo> findSetter(String propertyName) {
1716      return getPublicMethod(m -> m.isSetter() && m.getPropertyName().equals(propertyName));
1717   }
1718
1719   /**
1720    * Returns all public methods on this class and parent classes.
1721    *
1722    * <p>
1723    * This method returns <b>public methods only</b>, from this class and all parent classes and interfaces.
1724    *
1725    * <h5 class='section'>Comparison with Similar Methods:</h5>
1726    * <ul class='spaced-list'>
1727    *    <li>{@link #getDeclaredMethods()} - Returns all declared methods on this class only (all visibility levels)
1728    *    <li>{@link #getAllMethods()} - Returns all declared methods on this class and parents (all visibility levels)
1729    *    <li>{@link #getPublicMethods()} - Returns public methods only on this class and parents ← This method
1730    * </ul>
1731    *
1732    * <h5 class='section'>Notes:</h5>
1733    * <ul class='spaced-list'>
1734    *    <li>This method behaves similarly to Java's {@link Class#getMethods()}, returning only public methods from this class and parent classes/interfaces.
1735    *    <li>Bridge and synthetic methods are included (matching {@link Class#getMethods()} behavior).
1736    *    <li>Methods defined on the {@link Object} class are excluded from the results.
1737    * </ul>
1738    *
1739    * @return
1740    *    All public methods on this class and parent classes.
1741    *    <br>Results are ordered alphabetically.
1742    *    <br>List is unmodifiable.
1743    */
1744   public List<MethodInfo> getPublicMethods() { return publicMethods.get(); }
1745
1746   /**
1747    * Returns the record components of this record class.
1748    *
1749    * <p>
1750    * Returns a cached, unmodifiable list of record components.
1751    * The components are returned in the same order as they appear in the record declaration.
1752    * If this class is not a record, returns an empty list.
1753    *
1754    * @return An unmodifiable list of record components, or an empty list if this class is not a record.
1755    */
1756   public List<RecordComponent> getRecordComponents() { return recordComponents.get(); }
1757
1758   /**
1759    * Returns the repeated annotation method on this class.
1760    *
1761    * <p>
1762    * The repeated annotation method is the <code>value()</code> method that returns an array
1763    * of annotations who themselves are marked with the {@link Repeatable @Repeatable} annotation
1764    * of this class.
1765    *
1766    * @return The repeated annotation method on this class, or <jk>null</jk> if it doesn't exist.
1767    */
1768   public MethodInfo getRepeatedAnnotationMethod() { return repeatedAnnotationMethod.get(); }
1769
1770   /**
1771    * Finds a resource with a given name.
1772    *
1773    * <p>
1774    * The rules for searching resources associated with a given class are implemented by the defining class loader.
1775    *
1776    * @param name The resource name.
1777    * @return A URL object for reading the resource, or <jk>null</jk> if the resource could not be found.
1778    */
1779   public java.net.URL getResource(String name) {
1780      return inner == null ? null : inner.getResource(name);
1781   }
1782
1783   /**
1784    * Finds a resource with a given name and returns an input stream for reading the resource.
1785    *
1786    * <p>
1787    * The rules for searching resources associated with a given class are implemented by the defining class loader.
1788    *
1789    * @param name The resource name.
1790    * @return An input stream for reading the resource, or <jk>null</jk> if the resource could not be found.
1791    */
1792   @SuppressWarnings({
1793      "resource" // Caller takes ownership of the returned InputStream
1794   })
1795   public java.io.InputStream getResourceAsStream(String name) {
1796      return inner == null ? null : inner.getResourceAsStream(name);
1797   }
1798
1799   /**
1800    * Returns the signers of this class.
1801    *
1802    * <p>
1803    * Returns a cached, unmodifiable list.
1804    *
1805    * @return An unmodifiable list of signers, or an empty list if there are no signers.
1806    */
1807   public List<Object> getSigners() { return signers.get(); }
1808
1809   /**
1810    * Returns the parent class.
1811    *
1812    * @return
1813    *    The parent class, or <jk>null</jk> if the class has no parent.
1814    */
1815   public ClassInfo getSuperclass() {
1816      if (inner == null)
1817         return null;
1818      var sc = inner.getSuperclass();
1819      return sc == null ? null : of(sc);
1820   }
1821
1822   /**
1823    * Returns a list of {@link TypeVariable} objects that represent the type variables declared by this class.
1824    *
1825    * <p>
1826    * Returns a cached, unmodifiable list.
1827    * The type variables are returned in the same order as they appear in the class declaration.
1828    *
1829    * @return
1830    *    An unmodifiable list of {@link TypeVariable} objects representing the type parameters of this class.
1831    *    <br>Returns an empty list if this class declares no type parameters.
1832    */
1833   public List<TypeVariable<?>> getTypeParameters() { return typeParameters.get(); }
1834
1835   /**
1836    * Returns the wrapper class if this is a primitive, otherwise returns this class.
1837    *
1838    * <p>
1839    * If this class is a primitive (e.g. {@code int.class}), returns its wrapper class
1840    * (e.g. {@code Integer.class}) wrapped in a {@link ClassInfo}.
1841    * Otherwise, returns this {@link ClassInfo} instance.
1842    *
1843    * <h5 class='section'>Example:</h5>
1844    * <p class='bjava'>
1845    *    ClassInfo <jv>intClass</jv> = ClassInfo.<jsm>of</jsm>(<jk>int</jk>.<jk>class</jk>);
1846    *    ClassInfo <jv>wrapper</jv> = <jv>intClass</jv>.getWrapperIfPrimitive();  <jc>// Returns ClassInfo for Integer.class</jc>
1847    *
1848    *    ClassInfo <jv>stringClass</jv> = ClassInfo.<jsm>of</jsm>(String.<jk>class</jk>);
1849    *    ClassInfo <jv>same</jv> = <jv>stringClass</jv>.getWrapperIfPrimitive();  <jc>// Returns same ClassInfo</jc>
1850    * </p>
1851    *
1852    * @return The wrapper {@link ClassInfo} if this is a primitive, or this {@link ClassInfo} if not a primitive.
1853    */
1854   public ClassInfo getWrapperIfPrimitive() {
1855      if (inner == null || ! inner.isPrimitive())
1856         return this;
1857      return of(pmap1.get(inner));
1858   }
1859
1860   /**
1861    * Returns <jk>true</jk> if this class has the specified annotation.
1862    *
1863    * @param <A> The annotation type to look for.
1864    * @param type The annotation to look for.
1865    * @return The <jk>true</jk> if annotation if found.
1866    */
1867   public <A extends Annotation> boolean hasAnnotation(Class<A> type) {
1868      return getAnnotations(type).findFirst().isPresent();
1869   }
1870
1871   @Override
1872   public int hashCode() {
1873      return innerType.hashCode();
1874   }
1875
1876   /**
1877    * Returns <jk>true</jk> if this class is not in the root package.
1878    *
1879    * @return <jk>true</jk> if this class is not in the root package.
1880    */
1881   public boolean hasPackage() {
1882      return nn(getPackage());
1883   }
1884
1885   /**
1886    * Returns <jk>true</jk> if the {@link #getPrimitiveWrapper()} method returns a value.
1887    *
1888    * @return <jk>true</jk> if the {@link #getPrimitiveWrapper()} method returns a value.
1889    */
1890   public boolean hasPrimitiveWrapper() {
1891      return pmap1.containsKey(inner);
1892   }
1893
1894   /**
1895    * Returns the wrapped class as a {@link Class}.
1896    *
1897    * @param <T> The inner class type.
1898    * @return The wrapped class as a {@link Class}, or <jk>null</jk> if it's not a class (e.g. it's a {@link ParameterizedType}).
1899    */
1900   public <T> Class<T> inner() {
1901      return (Class<T>)inner;
1902   }
1903
1904   /**
1905    * Returns the wrapped class as a {@link Type}.
1906    *
1907    * @return The wrapped class as a {@link Type}.
1908    */
1909   public Type innerType() {
1910      return innerType;
1911   }
1912
1913   /**
1914    * Checks for equality with the specified class.
1915    *
1916    * @param c The class to check equality with.
1917    * @return <jk>true</jk> if the specified class is the same as this one.
1918    */
1919   public boolean is(Class<?> c) {
1920      return nn(this.inner) && this.inner.equals(c);
1921   }
1922
1923   /**
1924    * Checks for equality with the specified class.
1925    *
1926    * @param c The class to check equality with.
1927    * @return <jk>true</jk> if the specified class is the same as this one.
1928    */
1929   public boolean is(ClassInfo c) {
1930      if (nn(this.inner))
1931         return this.inner.equals(c.inner());
1932      return innerType.equals(c.innerType);
1933   }
1934
1935   /**
1936    * Returns <jk>true</jk> if all specified flags are applicable to this class.
1937    *
1938    * @param flag The flag to test for.
1939    * @return <jk>true</jk> if all specified flags are applicable to this class.
1940    */
1941   @Override
1942   public boolean is(ElementFlag flag) {
1943      return switch (flag) {
1944         case ANNOTATION -> isAnnotation();
1945         case NOT_ANNOTATION -> ! isAnnotation();
1946         case ANONYMOUS -> isAnonymousClass();
1947         case NOT_ANONYMOUS -> ! isAnonymousClass();
1948         case ARRAY -> isArray();
1949         case NOT_ARRAY -> ! isArray();
1950         case CLASS -> ! isInterface();
1951         case DEPRECATED -> isDeprecated();
1952         case NOT_DEPRECATED -> isNotDeprecated();
1953         case ENUM -> isEnum();
1954         case NOT_ENUM -> ! isEnum();
1955         case LOCAL -> isLocalClass();
1956         case NOT_LOCAL -> ! isLocalClass();
1957         case MEMBER -> isMemberClass();
1958         case NOT_MEMBER -> isNotMemberClass();
1959         case NON_STATIC_MEMBER -> isNonStaticMemberClass();
1960         case NOT_NON_STATIC_MEMBER -> ! isNonStaticMemberClass();
1961         case PRIMITIVE -> isPrimitive();
1962         case NOT_PRIMITIVE -> ! isPrimitive();
1963         case RECORD -> isRecord();
1964         case NOT_RECORD -> ! isRecord();
1965         case SEALED -> isSealed();
1966         case NOT_SEALED -> ! isSealed();
1967         case SYNTHETIC -> isSynthetic();
1968         case NOT_SYNTHETIC -> ! isSynthetic(); // HTT - synthetic classes are compiler-generated and difficult to create in tests
1969         default -> super.is(flag);
1970      };
1971   }
1972
1973   /**
1974    * Returns <jk>true</jk> if this class is an annotation.
1975    *
1976    * @return <jk>true</jk> if this class is an annotation.
1977    */
1978   public boolean isAnnotation() { return nn(inner) && inner.isAnnotation(); }
1979
1980   /**
1981    * Returns <jk>true</jk> if this class is an anonymous class.
1982    *
1983    * <p>
1984    * An anonymous class is a local class declared within a method or constructor that has no name.
1985    *
1986    * @return <jk>true</jk> if this class is an anonymous class.
1987    */
1988   public boolean isAnonymousClass() { return nn(inner) && inner.isAnonymousClass(); }
1989
1990   /**
1991    * Returns <jk>true</jk> if this class is any of the specified types.
1992    *
1993    * @param types The types to check against.
1994    * @return <jk>true</jk> if this class is any of the specified types.
1995    */
1996   public boolean isAny(Class<?>...types) {
1997      for (var cc : types)
1998         if (is(cc))
1999            return true;
2000      return false;
2001   }
2002
2003   /**
2004    * Returns <jk>true</jk> if this class is any of the specified types.
2005    *
2006    * <p>
2007    * This is a high-performance overload that avoids array creation and iteration.
2008    * Use this method instead of {@link #isAny(Class...)} when checking against exactly 2 types.
2009    *
2010    * @param t1 The first type to check against.
2011    * @param t2 The second type to check against.
2012    * @return <jk>true</jk> if this class is any of the specified types.
2013    */
2014   public boolean isAny(Class<?> t1, Class<?> t2) {
2015      return inner == t1 || inner == t2;
2016   }
2017
2018   /**
2019    * Returns <jk>true</jk> if this class is any of the specified types.
2020    *
2021    * <p>
2022    * This is a high-performance overload that avoids array creation and iteration.
2023    * Use this method instead of {@link #isAny(Class...)} when checking against exactly 3 types.
2024    *
2025    * @param t1 The first type to check against.
2026    * @param t2 The second type to check against.
2027    * @param t3 The third type to check against.
2028    * @return <jk>true</jk> if this class is any of the specified types.
2029    */
2030   public boolean isAny(Class<?> t1, Class<?> t2, Class<?> t3) {
2031      return inner == t1 || inner == t2 || inner == t3;
2032   }
2033
2034   /**
2035    * Returns <jk>true</jk> if this class is any of the specified types.
2036    *
2037    * <p>
2038    * This is a high-performance overload that avoids array creation and iteration.
2039    * Use this method instead of {@link #isAny(Class...)} when checking against exactly 4 types.
2040    *
2041    * @param t1 The first type to check against.
2042    * @param t2 The second type to check against.
2043    * @param t3 The third type to check against.
2044    * @param t4 The fourth type to check against.
2045    * @return <jk>true</jk> if this class is any of the specified types.
2046    */
2047   public boolean isAny(Class<?> t1, Class<?> t2, Class<?> t3, Class<?> t4) {
2048      return inner == t1 || inner == t2 || inner == t3 || inner == t4;
2049   }
2050
2051   /**
2052    * Returns <jk>true</jk> if this class is any of the specified types.
2053    *
2054    * <p>
2055    * This is a high-performance overload that avoids array creation and iteration.
2056    * Use this method instead of {@link #isAny(Class...)} when checking against exactly 5 types.
2057    *
2058    * @param t1 The first type to check against.
2059    * @param t2 The second type to check against.
2060    * @param t3 The third type to check against.
2061    * @param t4 The fourth type to check against.
2062    * @param t5 The fifth type to check against.
2063    * @return <jk>true</jk> if this class is any of the specified types.
2064    */
2065   public boolean isAny(Class<?> t1, Class<?> t2, Class<?> t3, Class<?> t4, Class<?> t5) {
2066      return inner == t1 || inner == t2 || inner == t3 || inner == t4 || inner == t5;
2067   }
2068
2069   /**
2070    * Returns <jk>true</jk> if this class is an array.
2071    *
2072    * @return <jk>true</jk> if this class is an array.
2073    */
2074   public boolean isArray() { return nn(inner) && inner.isArray(); }
2075
2076   /**
2077    * Returns <jk>true</jk> if this class is a child or the same as <c>parent</c>.
2078    *
2079    * @param parent The parent class.
2080    * @return <jk>true</jk> if this class is a child or the same as <c>parent</c>.
2081    */
2082   public boolean isAssignableTo(Class<?> parent) {
2083      return and(nn(inner), nn(parent)) && parent.isAssignableFrom(inner);
2084   }
2085
2086   /**
2087    * Returns <jk>true</jk> if this class is strictly a child of <c>parent</c> (not the same class).
2088    *
2089    * <p>
2090    * This method returns <jk>true</jk> only if this class is a child of the parent class,
2091    * excluding the case where they are the same class.
2092    *
2093    * <h5 class='section'>Example:</h5>
2094    * <p class='bjava'>
2095    *    ClassInfo <jv>child</jv> = ClassInfo.<jsm>of</jsm>(ArrayList.<jk>class</jk>);
2096    *    ClassInfo <jv>parent</jv> = ClassInfo.<jsm>of</jsm>(List.<jk>class</jk>);
2097    *    <jk>boolean</jk> <jv>b1</jv> = <jv>child</jv>.isChildOf(List.<jk>class</jk>);  <jc>// true</jc>
2098    *    <jk>boolean</jk> <jv>b2</jv> = <jv>child</jv>.isChildOf(ArrayList.<jk>class</jk>);  <jc>// false (same class)</jc>
2099    * </p>
2100    *
2101    * @param parent The parent class.
2102    * @return <jk>true</jk> if this class is strictly a child of <c>parent</c>.
2103    */
2104   public boolean isChildOf(Class<?> parent) {
2105      return and(nn(inner), nn(parent)) && neq(inner, parent) && parent.isAssignableFrom(inner);
2106   }
2107
2108   /**
2109    * Returns <jk>true</jk> if this class is strictly a child of <c>parent</c> (not the same class).
2110    *
2111    * <p>
2112    * Same as {@link #isChildOf(Class)} but takes in a {@link ClassInfo}.
2113    *
2114    * @param parent The parent class.
2115    * @return <jk>true</jk> if this class is strictly a child of <c>parent</c>.
2116    */
2117   public boolean isChildOf(ClassInfo parent) {
2118      return and(nn(inner), nn(parent)) && neq(inner, parent.inner()) && parent.inner().isAssignableFrom(inner);
2119   }
2120
2121   /**
2122    * Returns <jk>true</jk> if this class is strictly a child of <c>parent</c> (not the same class).
2123    *
2124    * @param parent The parent class.
2125    * @return <jk>true</jk> if this class is strictly a child of <c>parent</c>.
2126    */
2127   public boolean isChildOf(Type parent) {
2128      if (parent instanceof Class<?> parent2)
2129         return isChildOf(parent2);
2130      return false;
2131   }
2132
2133   /**
2134    * Returns <jk>true</jk> if this class is a child or the same as <c>parent</c>.
2135    *
2136    * @param parent The parent class.
2137    * @return <jk>true</jk> if this class is a parent or the same as <c>parent</c>.
2138    */
2139   public boolean isAssignableTo(ClassInfo parent) {
2140      return isAssignableTo(parent.inner());
2141   }
2142
2143   /**
2144    * Returns <jk>true</jk> if this class is a child or the same as <c>parent</c>.
2145    *
2146    * @param parent The parent class.
2147    * @return <jk>true</jk> if this class is a parent or the same as <c>parent</c>.
2148    */
2149   public boolean isAssignableTo(Type parent) {
2150      if (parent instanceof Class<?> parent2)
2151         return isAssignableTo(parent2);
2152      return false;
2153   }
2154
2155   /**
2156    * Returns <jk>true</jk> if this class is a child or the same as any of the <c>parents</c>.
2157    *
2158    * @param parents The parents class.
2159    * @return <jk>true</jk> if this class is a child or the same as any of the <c>parents</c>.
2160    */
2161   public boolean isAssignableToAny(Class<?>...parents) {
2162      for (var p : parents)
2163         if (isAssignableTo(p))
2164            return true;
2165      return false;
2166   }
2167
2168   /**
2169    * Returns <jk>true</jk> if this class is <jk>boolean</jk> or {@link Boolean}.
2170    *
2171    * @return <jk>true</jk> if this class is <jk>boolean</jk> or {@link Boolean}.
2172    */
2173   public boolean isBoolean() { return isAny(boolean.class, Boolean.class); }
2174
2175   /**
2176    * Returns <jk>true</jk> if this class is assignable to {@link CharSequence}.
2177    *
2178    * @return <jk>true</jk> if this class is assignable to {@link CharSequence}.
2179    */
2180   public boolean isCharSequence() { return isAssignableTo(CharSequence.class); }
2181
2182   /**
2183    * Returns <jk>true</jk> if this class is not an interface.
2184    *
2185    * @return <jk>true</jk> if this class is not an interface.
2186    */
2187   public boolean isClass() { return nn(inner) && ! inner.isInterface(); }
2188
2189   /**
2190    * Returns <jk>true</jk> if this class is assignable to {@link Collection}.
2191    *
2192    * @return <jk>true</jk> if this class is assignable to {@link Collection}.
2193    */
2194   public boolean isCollection() { return isAssignableTo(Collection.class); }
2195
2196   /**
2197    * Returns <jk>true</jk> if this class is a {@link Collection} or an array.
2198    *
2199    * @return <jk>true</jk> if this class is a {@link Collection} or an array.
2200    */
2201   public boolean isCollectionOrArray() { return nn(inner) && (Collection.class.isAssignableFrom(inner) || inner.isArray()); }
2202
2203   /**
2204    * Returns <jk>true</jk> if this class is assignable to {@link Date} or {@link Calendar}.
2205    *
2206    * @return <jk>true</jk> if this class is assignable to {@link Date} or {@link Calendar}.
2207    */
2208   public boolean isDateOrCalendar() { return isAssignableToAny(Date.class, Calendar.class); }
2209
2210   /**
2211    * Returns <jk>true</jk> if this class is a decimal number type ({@link Float}, {@link Double}, <jk>float</jk>, or <jk>double</jk>).
2212    *
2213    * @return <jk>true</jk> if this class is a decimal number type.
2214    */
2215   public boolean isDecimal() { return isAssignableToAny(Float.class, Double.class) || isAny(float.class, double.class); }
2216
2217   /**
2218    * Returns <jk>true</jk> if this class has the {@link Deprecated @Deprecated} annotation on it.
2219    *
2220    * @return <jk>true</jk> if this class has the {@link Deprecated @Deprecated} annotation on it.
2221    */
2222   public boolean isDeprecated() { return nn(inner) && inner.isAnnotationPresent(Deprecated.class); }
2223
2224   /**
2225    * Returns <jk>true</jk> if this class is {@link Double} or <jk>double</jk>.
2226    *
2227    * @return <jk>true</jk> if this class is {@link Double} or <jk>double</jk>.
2228    */
2229   public boolean isDouble() { return isAny(Double.class, double.class); }
2230
2231   /**
2232    * Returns <jk>true</jk> if this class is an enum.
2233    *
2234    * @return <jk>true</jk> if this class is an enum.
2235    */
2236   public boolean isEnum() { return nn(inner) && inner.isEnum(); }
2237
2238   /**
2239    * Returns <jk>true</jk> if this class is {@link Long} or <jk>long</jk>.
2240    *
2241    * @return <jk>true</jk> if this class is {@link Long} or <jk>long</jk>.
2242    */
2243   public boolean isLong() { return isAny(Long.class, long.class); }
2244
2245   /**
2246    * Returns <jk>true</jk> if this class is assignable to {@link Map}.
2247    *
2248    * @return <jk>true</jk> if this class is assignable to {@link Map}.
2249    */
2250   public boolean isMap() { return isAssignableTo(Map.class); }
2251
2252   /**
2253    * Returns <jk>true</jk> if this class is a numeric type (assignable to {@link Number} or a numeric primitive).
2254    *
2255    * @return <jk>true</jk> if this class is a numeric type.
2256    */
2257   public boolean isNumber() { return isAssignableTo(Number.class) || isAny(byte.class, short.class, int.class, long.class, float.class, double.class); }
2258
2259   /**
2260    * Returns <jk>true</jk> if this class is exactly {@link Object}.
2261    *
2262    * @return <jk>true</jk> if this class is {@link Object}.
2263    */
2264   public boolean isObject() { return is(Object.class); }
2265
2266   /**
2267    * Returns <jk>true</jk> if this class is {@link Optional}.
2268    *
2269    * @return <jk>true</jk> if this class is {@link Optional}.
2270    */
2271   public boolean isOptional() { return is(Optional.class); }
2272
2273   /**
2274    * Returns <jk>true</jk> if this class is assignable to {@link java.time.temporal.Temporal}.
2275    *
2276    * @return <jk>true</jk> if this class is assignable to {@link java.time.temporal.Temporal}.
2277    */
2278   public boolean isTemporal() { return isAssignableTo(java.time.temporal.Temporal.class); }
2279
2280   /**
2281    * Returns <jk>true</jk> if the specified value is an instance of this class.
2282    *
2283    * @param value The value to check.
2284    * @return <jk>true</jk> if the specified value is an instance of this class.
2285    */
2286   public boolean isInstance(Object value) {
2287      if (nn(this.inner))
2288         return inner.isInstance(value);
2289      return false;
2290   }
2291
2292   /**
2293    * Returns <jk>true</jk> if this class is a local class.
2294    *
2295    * @return <jk>true</jk> if this class is a local class.
2296    */
2297   public boolean isLocalClass() { return nn(inner) && inner.isLocalClass(); }
2298
2299   /**
2300    * Returns <jk>true</jk> if this class is a member class.
2301    *
2302    * @return <jk>true</jk> if this class is a member class.
2303    */
2304   public boolean isMemberClass() { return nn(inner) && inner.isMemberClass(); }
2305
2306   /**
2307    * Determines if this class and the specified class are nestmates.
2308    *
2309    * <p>
2310    * Two classes are nestmates if they belong to the same nest.
2311    *
2312    * @param c The class to check.
2313    * @return <jk>true</jk> if this class and the specified class are nestmates.
2314    */
2315   public boolean isNestmateOf(Class<?> c) {
2316      return nn(this.inner) && nn(c) && this.inner.isNestmateOf(c);
2317   }
2318
2319   /**
2320    * Returns <jk>true</jk> if this class is a member class and not static.
2321    *
2322    * @return <jk>true</jk> if this class is a member class and not static.
2323    */
2324   public boolean isNonStaticMemberClass() { return nn(inner) && inner.isMemberClass() && ! isStatic(); }
2325
2326   /**
2327    * Returns <jk>true</jk> if this class doesn't have the {@link Deprecated @Deprecated} annotation on it.
2328    *
2329    * @return <jk>true</jk> if this class doesn't have the {@link Deprecated @Deprecated} annotation on it.
2330    */
2331   public boolean isNotDeprecated() { return inner == null || ! inner.isAnnotationPresent(Deprecated.class); }
2332
2333   /**
2334    * Returns <jk>true</jk> if this class is a local class.
2335    *
2336    * @return <jk>true</jk> if this class is a local class.
2337    */
2338   public boolean isNotLocalClass() { return inner == null || ! inner.isLocalClass(); }
2339
2340   /**
2341    * Returns <jk>true</jk> if this class is a member class.
2342    *
2343    * @return <jk>true</jk> if this class is a member class.
2344    */
2345   public boolean isNotMemberClass() { return inner == null || ! inner.isMemberClass(); }
2346
2347   /**
2348    * Returns <jk>false</jk> if this class is a member class and not static.
2349    *
2350    * @return <jk>false</jk> if this class is a member class and not static.
2351    */
2352   public boolean isNotNonStaticMemberClass() { return ! isNonStaticMemberClass(); }
2353
2354   /**
2355    * Returns <jk>true</jk> if this is not a primitive class.
2356    *
2357    * @return <jk>true</jk> if this is not a primitive class.
2358    */
2359   public boolean isNotPrimitive() { return inner == null || ! inner.isPrimitive(); }
2360
2361   /**
2362    * Returns <jk>true</jk> if this class is <c><jk>void</jk>.<jk>class</jk></c> or {@link Void} or has the simple name <js>"Void</js>.
2363    *
2364    * @return <jk>true</jk> if this class is <c><jk>void</jk>.<jk>class</jk></c> or {@link Void} or has the simple name <js>"Void</js>.
2365    */
2366   public boolean isVoid() {
2367      return inner != null && (inner == void.class || inner == Void.class || eqic(inner.getSimpleName(), "void"));
2368   }
2369
2370   /**
2371    * Returns <jk>true</jk> if this class is not <c><jk>void</jk>.<jk>class</jk></c> and not {@link Void} and does not have the simple name <js>"Void</js>.
2372    *
2373    * @return <jk>true</jk> if this class is not <c><jk>void</jk>.<jk>class</jk></c> and not {@link Void} and does not have the simple name <js>"Void</js>.
2374    */
2375   public boolean isNotVoid() { return ! isVoid(); }
2376
2377   /**
2378    * Returns <jk>true</jk> if this class is a parent or the same as <c>child</c>.
2379    *
2380    * @param child The child class.
2381    * @return <jk>true</jk> if this class is a parent or the same as <c>child</c>.
2382    */
2383   public boolean isAssignableFrom(Class<?> child) {
2384      return nn(inner) && nn(child) && inner.isAssignableFrom(child);
2385   }
2386
2387   /**
2388    * Returns <jk>true</jk> if this class is strictly a parent of <c>child</c> (not the same class).
2389    *
2390    * <p>
2391    * This method returns <jk>true</jk> only if this class is a parent of the child class,
2392    * excluding the case where they are the same class.
2393    *
2394    * <h5 class='section'>Example:</h5>
2395    * <p class='bjava'>
2396    *    ClassInfo <jv>parent</jv> = ClassInfo.<jsm>of</jsm>(List.<jk>class</jk>);
2397    *    ClassInfo <jv>child</jv> = ClassInfo.<jsm>of</jsm>(ArrayList.<jk>class</jk>);
2398    *    <jk>boolean</jk> <jv>b1</jv> = <jv>parent</jv>.isParentOf(ArrayList.<jk>class</jk>);  <jc>// true</jc>
2399    *    <jk>boolean</jk> <jv>b2</jv> = <jv>parent</jv>.isParentOf(List.<jk>class</jk>);  <jc>// false (same class)</jc>
2400    * </p>
2401    *
2402    * @param child The child class.
2403    * @return <jk>true</jk> if this class is strictly a parent of <c>child</c>.
2404    */
2405   public boolean isParentOf(Class<?> child) {
2406      return nn(inner) && nn(child) && neq(inner, child) && inner.isAssignableFrom(child);
2407   }
2408
2409   /**
2410    * Returns <jk>true</jk> if this class is strictly a parent of <c>child</c> (not the same class).
2411    *
2412    * <p>
2413    * Same as {@link #isParentOf(Class)} but takes in a {@link ClassInfo}.
2414    *
2415    * @param child The child class.
2416    * @return <jk>true</jk> if this class is strictly a parent of <c>child</c>.
2417    */
2418   public boolean isParentOf(ClassInfo child) {
2419      return nn(inner) && nn(child) && neq(inner, child.inner()) && inner.isAssignableFrom(child.inner());
2420   }
2421
2422   /**
2423    * Returns <jk>true</jk> if this class is strictly a parent of <c>child</c> (not the same class).
2424    *
2425    * @param child The child class.
2426    * @return <jk>true</jk> if this class is strictly a parent of <c>child</c>.
2427    */
2428   public boolean isParentOf(Type child) {
2429      if (child instanceof Class<?> child2)
2430         return isParentOf(child2);
2431      return false;
2432   }
2433
2434   /**
2435    * Returns <jk>true</jk> if this class is a parent or the same as <c>child</c>.
2436    *
2437    * <p>
2438    * Same as {@link #isAssignableFrom(Class)} but takes in a {@link ClassInfo}.
2439    *
2440    * <h5 class='section'>Example:</h5>
2441    * <p class='bjava'>
2442    *    ClassInfo <jv>parent</jv> = ClassInfo.<jsm>of</jsm>(List.<jk>class</jk>);
2443    *    ClassInfo <jv>child</jv> = ClassInfo.<jsm>of</jsm>(ArrayList.<jk>class</jk>);
2444    *    <jk>boolean</jk> <jv>b</jv> = <jv>parent</jv>.isParentOf(<jv>child</jv>);  <jc>// true</jc>
2445    * </p>
2446    *
2447    * @param child The child class.
2448    * @return <jk>true</jk> if this class is a parent or the same as <c>child</c>.
2449    */
2450   public boolean isAssignableFrom(ClassInfo child) {
2451      return nn(inner) && nn(child) && inner.isAssignableFrom(child.inner());
2452   }
2453
2454   /**
2455    * Returns <jk>true</jk> if this class is a parent or the same as <c>child</c>.
2456    *
2457    * @param child The child class.
2458    * @return <jk>true</jk> if this class is a parent or the same as <c>child</c>.
2459    */
2460   public boolean isAssignableFrom(Type child) {
2461      if (child instanceof Class<?> child2)
2462         return isAssignableFrom(child2);
2463      return false;
2464   }
2465
2466   /**
2467    * Returns <jk>true</jk> if this class is a parent or the same as <c>child</c>.
2468    *
2469    * <p>
2470    * Primitive classes are converted to wrapper classes and compared.
2471    *
2472    * <h5 class='section'>Examples:</h5>
2473    * <p class='bjava'>
2474    *       ClassInfo.<jsm>of</jsm>(String.<jk>class</jk>).isParentOfLenient(String.<jk>class</jk>);  <jc>// true</jc>
2475    *       ClassInfo.<jsm>of</jsm>(CharSequence.<jk>class</jk>).isParentOfLenient(String.<jk>class</jk>);  <jc>// true</jc>
2476    *       ClassInfo.<jsm>of</jsm>(String.<jk>class</jk>).isParentOfLenient(CharSequence.<jk>class</jk>);  <jc>// false</jc>
2477    *       ClassInfo.<jsm>of</jsm>(<jk>int</jk>.<jk>class</jk>).isParentOfLenient(Integer.<jk>class</jk>);  <jc>// true</jc>
2478    *       ClassInfo.<jsm>of</jsm>(Integer.<jk>class</jk>).isParentOfLenient(<jk>int</jk>.<jk>class</jk>);  <jc>// true</jc>
2479    *       ClassInfo.<jsm>of</jsm>(Number.<jk>class</jk>).isParentOfLenient(<jk>int</jk>.<jk>class</jk>);  <jc>// true</jc>
2480    *       ClassInfo.<jsm>of</jsm>(<jk>int</jk>.<jk>class</jk>).isParentOfLenient(Number.<jk>class</jk>);  <jc>// false</jc>
2481    *       ClassInfo.<jsm>of</jsm>(<jk>int</jk>.<jk>class</jk>).isParentOfLenient(<jk>long</jk>.<jk>class</jk>);  <jc>// false</jc>
2482    * </p>
2483    *
2484    * @param child The child class.
2485    * @return <jk>true</jk> if this class is a parent or the same as <c>child</c>.
2486    */
2487   public boolean isParentOfLenient(Class<?> child) {
2488      if (inner == null || child == null)
2489         return false;
2490      if (inner.isAssignableFrom(child))
2491         return true;
2492      if (this.isPrimitive() || child.isPrimitive()) {
2493         return this.getWrapperIfPrimitive().isAssignableFrom(of(child).getWrapperIfPrimitive());
2494      }
2495      return false;
2496   }
2497
2498   /**
2499    * Same as {@link #isParentOfLenient(Class)} but takes in a {@link ClassInfo}.
2500    *
2501    * @param child The child class.
2502    * @return <jk>true</jk> if this class is a parent or the same as <c>child</c>.
2503    */
2504   public boolean isParentOfLenient(ClassInfo child) {
2505      if (inner == null || child == null)
2506         return false;
2507      if (inner.isAssignableFrom(child.inner()))
2508         return true;
2509      // If at least one is a primitive, convert both to wrappers and compare
2510      // Note: If both are primitives and the same type, we would have returned true above
2511      // So this branch is only reached when at least one is a primitive and they're not directly assignable
2512      if (this.isPrimitive() || child.isPrimitive()) {
2513         return this.getWrapperIfPrimitive().isAssignableFrom(child.getWrapperIfPrimitive());
2514      }
2515      return false;
2516   }
2517
2518   /**
2519    * Returns <jk>true</jk> if this class is a parent or the same as <c>child</c>.
2520    *
2521    * @param child The child class.
2522    * @return <jk>true</jk> if this class is a parent or the same as <c>child</c>.
2523    */
2524   public boolean isParentOfLenient(Type child) {
2525      if (child instanceof Class<?> child2)
2526         return isParentOfLenient(child2);
2527      return false;
2528   }
2529
2530   /**
2531    * Returns <jk>true</jk> if this is a primitive class.
2532    *
2533    * @return <jk>true</jk> if this is a primitive class.
2534    */
2535   public boolean isPrimitive() { return nn(inner) && inner.isPrimitive(); }
2536
2537   /**
2538    * Returns <jk>true</jk> if this class is a record class.
2539    *
2540    * <p>
2541    * A record class is a final class that extends {@link Record}.
2542    *
2543    * @return <jk>true</jk> if this class is a record class.
2544    */
2545   public boolean isRecord() { return nn(inner) && inner.isRecord(); }
2546
2547   /**
2548    * Returns <jk>true</jk> if this is a repeated annotation class.
2549    *
2550    * <p>
2551    * A repeated annotation has a single <code>value()</code> method that returns an array
2552    * of annotations who themselves are marked with the {@link Repeatable @Repeatable} annotation
2553    * of this class.
2554    *
2555    * @return <jk>true</jk> if this is a repeated annotation class.
2556    */
2557   public boolean isRepeatedAnnotation() { return getRepeatedAnnotationMethod() != null; }
2558
2559   /**
2560    * Returns <jk>true</jk> if this class is a {@link RuntimeException}.
2561    *
2562    * @return <jk>true</jk> if this class is a {@link RuntimeException}.
2563    */
2564   public boolean isRuntimeException() { return isAssignableTo(RuntimeException.class); }
2565
2566   /**
2567    * Returns <jk>true</jk> if this class is a sealed class.
2568    *
2569    * <p>
2570    * A sealed class is a class that can only be extended by a permitted set of subclasses.
2571    *
2572    * @return <jk>true</jk> if this class is a sealed class.
2573    */
2574   public boolean isSealed() { return nn(inner) && inner.isSealed(); }
2575
2576   /**
2577    * Returns <jk>true</jk> if this class is a child of <c>parent</c>.
2578    *
2579    * @param parent The parent class.
2580    * @return <jk>true</jk> if this class is a parent of <c>child</c>.
2581    */
2582   public boolean isStrictChildOf(Class<?> parent) {
2583      return nn(inner) && nn(parent) && parent.isAssignableFrom(inner) && ! inner.equals(parent);
2584   }
2585
2586   /**
2587    * Returns <jk>true</jk> if this class is a synthetic class.
2588    *
2589    * <p>
2590    * A synthetic class is one that is generated by the compiler and does not appear in source code.
2591    *
2592    * @return <jk>true</jk> if this class is synthetic.
2593    */
2594   public boolean isSynthetic() { return nn(inner) && inner.isSynthetic(); }  // HTT
2595
2596   /**
2597    * Identifies if the specified visibility matches this constructor.
2598    *
2599    * @param v The visibility to validate against.
2600    * @return <jk>true</jk> if this visibility matches the modifier attribute of this constructor.
2601    */
2602   public boolean isVisible(Visibility v) {
2603      return nn(inner) && v.isVisible(inner);
2604   }
2605
2606   /**
2607    * Shortcut for calling <c>Class.getDeclaredConstructor().newInstance()</c> on the underlying class.
2608    *
2609    * @return A new instance of the underlying class
2610    * @throws ExecutableException Exception occurred on invoked constructor/method/field.
2611    */
2612   public Object newInstance() throws ExecutableException {
2613      if (inner == null)
2614         throw exex("Type '%s' cannot be instantiated", getNameFull());
2615      try {
2616         return inner.getDeclaredConstructor().newInstance();
2617      } catch (InvocationTargetException | NoSuchMethodException | InstantiationException | IllegalAccessException e) {
2618         throw exex(e);
2619      }
2620   }
2621
2622   /**
2623    * Returns a detailed string representation of this class.
2624    *
2625    * <p>
2626    * The returned string includes:
2627    * <ul>
2628    *    <li>Modifiers (public, private, protected, static, final, abstract, etc.)
2629    *    <li>Class type (class, interface, enum, annotation, or record)
2630    *    <li>Fully qualified class name
2631    *    <li>Generic type parameters with bounds (if any)
2632    * </ul>
2633    *
2634    * <h5 class='section'>Examples:</h5>
2635    * <p class='bjava'>
2636    *    <jc>// Regular class</jc>
2637    *    ClassInfo.<jsm>of</jsm>(String.<jk>class</jk>).toString();
2638    *    <jc>// Returns: "public final class java.lang.String"</jc>
2639    *
2640    *    <jc>// Interface</jc>
2641    *    ClassInfo.<jsm>of</jsm>(List.<jk>class</jk>).toString();
2642    *    <jc>// Returns: "public interface java.util.List"</jc>
2643    *
2644    *    <jc>// Generic class</jc>
2645    *    ClassInfo.<jsm>of</jsm>(ArrayList.<jk>class</jk>).toString();
2646    *    <jc>// Returns: "public class java.util.ArrayList&lt;E&gt;"</jc>
2647    *
2648    *    <jc>// Generic class with bounds</jc>
2649    *    ClassInfo <jv>ci</jv> = ClassInfo.<jsm>of</jsm>(Comparable.<jk>class</jk>);
2650    *    <jv>ci</jv>.toString();
2651    *    <jc>// Returns: "public interface java.lang.Comparable&lt;T&gt;"</jc>
2652    *
2653    *    <jc>// Enum</jc>
2654    *    ClassInfo.<jsm>of</jsm>(Month.<jk>class</jk>).toString();
2655    *    <jc>// Returns: "public enum java.time.Month"</jc>
2656    *
2657    *    <jc>// Record</jc>
2658    *    ClassInfo <jv>record</jv> = ClassInfo.<jsm>of</jsm>(MyRecord.<jk>class</jk>);
2659    *    <jv>record</jv>.toString();
2660    *    <jc>// Returns: "public record org.example.MyRecord"</jc>
2661    * </p>
2662    *
2663    * <h5 class='section'>Notes:</h5>
2664    * <ul class='spaced-list'>
2665    *    <li>For interfaces, the "abstract" modifier is omitted since it's implicit.
2666    *    <li>For non-class types (e.g., TypeVariable, ParameterizedType), returns the result of {@code Type.toString()}.
2667    *    <li>Type parameters include bounds when present (e.g., "&lt;T extends Comparable&lt;T&gt;&gt;").
2668    * </ul>
2669    *
2670    * <h5 class='section'>Comparison with Other Methods:</h5>
2671    * <ul class='spaced-list'>
2672    *    <li>{@link #getNameSimple()} - Returns simple class name without package (e.g., "String")
2673    *    <li>{@link #getNameShort()} - Returns short name with outer classes (e.g., "MyClass$Inner")
2674    *    <li>{@link #getNameFull()} - Returns fully qualified name with type parameters (e.g., "java.util.List&lt;String&gt;")
2675    * </ul>
2676    *
2677    * @return A detailed string representation including modifiers, class type, name, and type parameters.
2678    */
2679   @Override
2680   public String toString() {
2681      return toString.get();
2682   }
2683
2684   private String findToString() {
2685      var sb = new StringBuilder(256);
2686
2687      // Class type (class, interface, enum, annotation, record)
2688      if (inner == null) {
2689         // For non-class types (e.g., TypeVariable, ParameterizedType), use innerType.toString()
2690         return innerType.toString();
2691      }
2692
2693      // Modifiers (filter out implicit modifiers)
2694      var mods = Modifier.toString(getModifiers());
2695      if (nn(mods) && ! mods.isEmpty()) {
2696         // Remove "abstract" for interfaces since it's implicit and redundant
2697         // Remove "final" for enums and records since it's implicit and redundant
2698         // Also remove "interface" if it somehow got included (shouldn't happen, but be safe)
2699         if (isInterface()) {
2700            mods = mods.replace("abstract", "").replace("interface", "").trim().replaceAll("\\s+", " ");
2701         } else if (isEnum() || isRecord()) {
2702            mods = mods.replace("final", "").trim().replaceAll("\\s+", " ");
2703         }
2704         if (! mods.isEmpty()) {
2705            sb.append(mods).append(" ");
2706         }
2707      }
2708
2709      if (isRecord()) {
2710         sb.append("record ");
2711      } else if (isAnnotation()) {
2712         sb.append("@interface ");
2713      } else if (isEnum()) {
2714         sb.append("enum ");
2715      } else if (isInterface()) {
2716         sb.append("interface ");
2717      } else {
2718         sb.append("class ");
2719      }
2720
2721      // Full name
2722      appendNameFormatted(sb, FULL, false, '$', BRACKETS);
2723
2724      // Type parameters (if declared on the class itself, not as a parameterized type)
2725      var typeParams = getTypeParameters();
2726      if (! typeParams.isEmpty()) {
2727         sb.append('<');
2728         var first = true;
2729         for (var tv : typeParams) {
2730            if (! first)
2731               sb.append(", ");
2732            first = false;
2733            sb.append(tv.getName());
2734            var bounds = tv.getBounds();
2735            if (bounds.length > 0 && (bounds.length > 1 || ! bounds[0].equals(Object.class))) {
2736               sb.append(" extends ");
2737               var firstBound = true;
2738               for (var bound : bounds) {
2739                  if (! firstBound)
2740                     sb.append(" & ");
2741                  firstBound = false;
2742                  if (bound instanceof Class<?> bound2) {
2743                     of(bound2).appendNameFormatted(sb, FULL, true, '$', BRACKETS);
2744                  } else {
2745                     sb.append(bound.getTypeName());
2746                  }
2747               }
2748            }
2749         }
2750         sb.append('>');
2751      }
2752
2753      return sb.toString();
2754   }
2755
2756   /**
2757    * Unwrap this class if it's a parameterized type of the specified type such as {@link Value} or {@link Optional}.
2758    *
2759    * @param wrapperTypes The parameterized types to unwrap if this class is one of those types.
2760    * @return The class info on the unwrapped type, or just this type if this isn't one of the specified types.
2761    */
2762   public ClassInfo unwrap(Class<?>...wrapperTypes) {
2763      for (var wt : wrapperTypes) {
2764         if (isParameterizedTypeOf(wt)) {
2765            Type t = getFirstParameterType(wt);
2766            if (nn(t))
2767               return of(t).unwrap(wrapperTypes); // Recursively do it again.
2768         }
2769      }
2770      return this;
2771   }
2772
2773   /**
2774    * Helper method to recursively add an interface and its parent interfaces to the set.
2775    *
2776    * @param set The set to add to.
2777    * @param iface The interface to add.
2778    */
2779   private void addInterfaceHierarchy(LinkedHashSet<ClassInfo> set, ClassInfo iface) {
2780      if (! set.add(iface))
2781         return;
2782
2783      // Process parent interfaces recursively
2784      var parentInterfaces = iface.getDeclaredInterfaces();
2785      for (var i = 0; i < parentInterfaces.size(); i++)
2786         addInterfaceHierarchy(set, parentInterfaces.get(i));
2787   }
2788
2789   /**
2790    * Finds all annotations on this class and parent classes/interfaces in child-to-parent order.
2791    *
2792    * <p>
2793    * This is similar to {@link AnnotationProvider#xfind(Class)} but without runtime annotations.
2794    *
2795    * <p>
2796    * Order of traversal:
2797    * <ol>
2798    *    <li>Annotations declared on this class
2799    *    <li>Annotations declared on parent classes (child-to-parent order)
2800    *    <li>For each parent class, annotations on interfaces declared on that class (child-to-parent interface hierarchy)
2801    *    <li>Annotations on the package of this class
2802    * </ol>
2803    *
2804    * @return A list of all annotation infos in child-to-parent order.
2805    */
2806   private List<AnnotationInfo<? extends Annotation>> findAnnotations() {
2807      var list = new ArrayList<AnnotationInfo<Annotation>>();
2808
2809      // On all parent classes and interfaces (properly traversed to avoid duplicates)
2810      var parentsAndInterfaces2 = getParentsAndInterfaces();
2811      for (var i = 0; i < parentsAndInterfaces2.size(); i++) {
2812         var ci = parentsAndInterfaces2.get(i);
2813         // Add declared annotations from this class/interface
2814         for (var a : ci.inner().getDeclaredAnnotations())
2815            streamRepeated(a).forEach(a2 -> list.add(ai(ci, a2)));
2816      }
2817
2818      // On the package of this class
2819      var pkg = getPackage();
2820      if (nn(pkg)) {
2821         var pi = PackageInfo.of(pkg.inner());
2822         for (var a : pkg.inner().getAnnotations())
2823            streamRepeated(a).forEach(a2 -> list.add(ai(pi, a2)));
2824      }
2825
2826      return u(list);
2827   }
2828
2829   private ClassInfo findComponentType() {
2830      Type ct = innerType;
2831      var cc = inner;
2832
2833      // Handle GenericArrayType (e.g., List<String>[])
2834      while (ct instanceof GenericArrayType ct2) {
2835         ct = ct2.getGenericComponentType();
2836      }
2837
2838      // Handle regular arrays
2839      while (nn(cc) && cc.isArray()) {
2840         cc = cc.getComponentType();
2841      }
2842
2843      // Return the deepest component type found
2844      if (ct != innerType) {
2845         return of(ct);
2846      } else if (cc != inner) {
2847         return of(cc);
2848      } else {
2849         return this;
2850      }
2851   }
2852
2853   private int findDimensions() {
2854      int d = 0;
2855      Type ct = innerType;
2856
2857      // Handle GenericArrayType (e.g., List<String>[])
2858      while (ct instanceof GenericArrayType ct2) {
2859         d++;
2860         ct = ct2.getGenericComponentType();
2861      }
2862
2863      // Handle regular arrays
2864      var cc = inner;
2865      while (nn(cc) && cc.isArray()) {
2866         d++;
2867         cc = cc.getComponentType();
2868      }
2869
2870      return d;
2871   }
2872
2873   private List<ClassInfo> findParents() {
2874      List<ClassInfo> l = list();
2875      var pc = inner;
2876      while (nn(pc) && pc != Object.class) {
2877         l.add(of(pc));
2878         pc = pc.getSuperclass();
2879      }
2880      return u(l);
2881   }
2882
2883   /**
2884    * Finds all parent classes and interfaces with proper traversal of interface hierarchy.
2885    *
2886    * @return A list of all parent classes and interfaces without duplicates.
2887    */
2888   private List<ClassInfo> findParentsAndInterfaces() {
2889      var set = new LinkedHashSet<ClassInfo>();
2890
2891      // Process all parent classes (includes this class)
2892      var parents2 = getParents();
2893      for (var i = 0; i < parents2.size(); i++) {
2894         var parent = parents2.get(i);
2895         set.add(parent);
2896
2897         // Process interfaces declared on this parent (and their parent interfaces)
2898         var declaredInterfaces2 = parent.getDeclaredInterfaces();
2899         for (var j = 0; j < declaredInterfaces2.size(); j++)
2900            addInterfaceHierarchy(set, declaredInterfaces2.get(j));
2901      }
2902
2903      return u(toList(set));
2904   }
2905
2906   private MethodInfo findRepeatedAnnotationMethod() {
2907      // @formatter:off
2908      return getPublicMethods().stream()
2909         .filter(m -> m.hasName("value"))
2910         .filter(m -> m.getReturnType().isArray())
2911         .filter(m -> {
2912            var rct = m.getReturnType().getComponentType();
2913            if (rct.hasAnnotation(Repeatable.class)) {
2914               return rct.getAnnotations(Repeatable.class).findFirst().map(AnnotationInfo::inner).orElse(null).value().equals(inner);
2915            }
2916            return false;
2917         })
2918         .findFirst()
2919         .orElse(null);
2920      // @formatter:on
2921   }
2922
2923   //-----------------------------------------------------------------------------------------------------------------
2924   // Find methods
2925   //-----------------------------------------------------------------------------------------------------------------
2926
2927   /**
2928    * Returns the first type parameter of this type if it's parameterized (e.g., T in Optional&lt;T&gt;).
2929    *
2930    * @param parameterizedType The expected parameterized class (e.g., Optional.class).
2931    * @return The first type parameter, or <jk>null</jk> if not parameterized or no parameters exist.
2932    */
2933   private Type getFirstParameterType(Class<?> parameterizedType) {
2934      if (innerType instanceof ParameterizedType innerType2) {
2935         var ta = innerType2.getActualTypeArguments();
2936         if (ta.length > 0)
2937            return ta[0];
2938      } else if (innerType instanceof Class<?> innerType3 && innerType3 != parameterizedType && parameterizedType.isAssignableFrom(innerType3)) /* Class that extends Optional<T> */ {
2939         return ClassInfo.of(innerType3).getParameterType(0, parameterizedType);
2940      }
2941      return null;
2942   }
2943
2944   /**
2945    * Returns <jk>true</jk> if this type is a parameterized type of the specified class or a subclass of it.
2946    *
2947    * @param c The class to check against (e.g., Optional.class, List.class).
2948    * @return <jk>true</jk> if this is Optional&lt;T&gt; or a subclass like MyOptional extends Optional&lt;String&gt;.
2949    */
2950   private boolean isParameterizedTypeOf(Class<?> c) {
2951      return (innerType instanceof ParameterizedType innerType3 && innerType3.getRawType() == c) || (innerType instanceof Class innerType2 && c.isAssignableFrom(innerType2));
2952   }
2953
2954   /**
2955    * Returns a {@link ConstructorInfo} wrapper for the specified raw {@link Constructor} object.
2956    *
2957    * <p>
2958    * This is an internal method used to wrap raw reflection objects into their cached Info wrappers.
2959    * The wrappers provide additional functionality and are cached to avoid creating duplicate objects.
2960    *
2961    * <p>
2962    * This method is called internally when building the lists of constructors (public, declared, etc.)
2963    * and ensures that the same {@link Constructor} object always maps to the same {@link ConstructorInfo} instance.
2964    *
2965    * @param x The raw constructor to wrap.
2966    * @return The cached {@link ConstructorInfo} wrapper for this constructor.
2967    */
2968   ConstructorInfo getConstructor(Constructor<?> x) {
2969      return constructorCache.get(x, () -> new ConstructorInfo(this, x));
2970   }
2971
2972   //-----------------------------------------------------------------------------------------------------------------
2973   // Annotatable interface methods
2974   //-----------------------------------------------------------------------------------------------------------------
2975
2976   /**
2977    * Returns a {@link FieldInfo} wrapper for the specified raw {@link Field} object.
2978    *
2979    * <p>
2980    * This is an internal method used to wrap raw reflection objects into their cached Info wrappers.
2981    * The wrappers provide additional functionality and are cached to avoid creating duplicate objects.
2982    *
2983    * <p>
2984    * This method is called internally when building the lists of fields (public, declared, all, etc.)
2985    * and ensures that the same {@link Field} object always maps to the same {@link FieldInfo} instance.
2986    *
2987    * @param x The raw field to wrap.
2988    * @return The cached {@link FieldInfo} wrapper for this field.
2989    */
2990   FieldInfo getField(Field x) {
2991      return fieldCache.get(x, () -> new FieldInfo(this, x));
2992   }
2993
2994   /**
2995    * Returns a {@link MethodInfo} wrapper for the specified raw {@link Method} object.
2996    *
2997    * <p>
2998    * This is an internal method used to wrap raw reflection objects into their cached Info wrappers.
2999    * The wrappers provide additional functionality and are cached to avoid creating duplicate objects.
3000    *
3001    * <p>
3002    * This method is called internally when building the lists of methods (public, declared, all, etc.)
3003    * and ensures that the same {@link Method} object always maps to the same {@link MethodInfo} instance.
3004    *
3005    * @param x The raw method to wrap.
3006    * @return The cached {@link MethodInfo} wrapper for this method.
3007    */
3008   MethodInfo getMethod(Method x) {
3009      return methodCache.get(x, () -> new MethodInfo(eq(this.inner, x.getDeclaringClass()) ? this : of(x.getDeclaringClass()), x));
3010   }
3011
3012   /**
3013    * Returns <jk>true</jk> if this type is a collection type that should be populated with multiple beans.
3014    *
3015    * <p>
3016    * Supported collection types:
3017    * <ul>
3018    *    <li>Arrays (<c>T[]</c>)
3019    *    <li><c>List&lt;T&gt;</c> (interface only, not concrete implementations like <c>ArrayList</c> or <c>LinkedList</c>)
3020    *    <li><c>Set&lt;T&gt;</c> (interface only, not concrete implementations like <c>HashSet</c> or <c>LinkedHashSet</c>)
3021    *    <li><c>Map&lt;String,T&gt;</c> (interface only, not concrete implementations like <c>HashMap</c> or <c>LinkedHashMap</c>)
3022    * </ul>
3023    *
3024    * <p>
3025    * <b>Note:</b> This matches Spring's behavior where only the base interfaces trigger automatic collection injection.
3026    * Concrete implementations like <c>ArrayList&lt;T&gt;</c> are treated as regular bean types, not collection types.
3027    *
3028    * @return <jk>true</jk> if this type is a supported collection, array, or map type.
3029    */
3030   public boolean isInjectCollectionType() {
3031      if (isArray())
3032         return true;
3033      var inner2 = o(this.inner()).orElse(Object.class);
3034      // Only match the exact interfaces, not their implementations (matches Spring's behavior)
3035      return eqa(inner2, List.class, Set.class, Map.class);
3036   }
3037
3038   /**
3039    * Injects beans from the bean store into the specified object's fields and methods.
3040    *
3041    * <p>
3042    * This method performs field and method injection based on {@code @Inject} or {@code @Autowired} annotations.
3043    * After all injection is complete, it calls any methods annotated with {@code @PostConstruct}.
3044    *
3045    * <h5 class='section'>Injectable Fields:</h5>
3046    * <ul class='spaced-list'>
3047    *    <li>Must be annotated with {@code @Inject} or {@code @Autowired} (matched by simple class name)
3048    *    <li>Must not be final
3049    *    <li>May have any valid name
3050    *    <li>Supports the same parameter types as parameter resolution: single beans, {@code Optional}, arrays, {@code List}, {@code Set}, {@code Map}
3051    * </ul>
3052    *
3053    * <h5 class='section'>Injectable Methods:</h5>
3054    * <ul class='spaced-list'>
3055    *    <li>Must be annotated with {@code @Inject} or {@code @Autowired} (matched by simple class name)
3056    *    <li>Must not be abstract
3057    *    <li>Must not declare type parameters
3058    *    <li>May return any type (return value is ignored)
3059    *    <li>May have any valid name
3060    *    <li>May accept zero or more dependencies as arguments
3061    *    <li>Supports the same parameter types as parameter resolution: single beans, {@code Optional}, arrays, {@code List}, {@code Set}, {@code Map}
3062    * </ul>
3063    *
3064    * <h5 class='section'>PostConstruct Methods:</h5>
3065    * <ul class='spaced-list'>
3066    *    <li>Must be annotated with {@code @PostConstruct} (matched by simple class name)
3067    *    <li>Must return {@code void}
3068    *    <li>Must have no parameters
3069    *    <li>Must not be abstract
3070    *    <li>Must not declare type parameters
3071    *    <li>Called after all field and method injection is complete
3072    *    <li>Called in parent-to-child order (parent methods execute before child methods)
3073    *    <li>Multiple methods can be annotated, and all will be called in order
3074    * </ul>
3075    *
3076    * <h5 class='section'>Example:</h5>
3077    * <p class='bjava'>
3078    *    <jc>// Class with injectable fields and methods</jc>
3079    *    <jk>public</jk> <jk>class</jk> MyService {
3080    *       <ja>@Inject</ja>
3081    *       <jk>private</jk> MyDependency <jv>dependency</jv>;
3082    *
3083    *       <ja>@Autowired</ja>
3084    *       <jk>private</jk> List&lt;MyService&gt; <jv>allServices</jv>;
3085    *
3086    *       <ja>@Inject</ja>
3087    *       <jk>public</jk> <jk>void</jk> init(MyConfig <jv>config</jv>) {
3088    *          <jc>// Initialization logic</jc>
3089    *       }
3090    *
3091    *       <ja>@PostConstruct</ja>
3092    *       <jk>public</jk> <jk>void</jk> postConstruct() {
3093    *          <jc>// Called after all injection is complete</jc>
3094    *       }
3095    *    }
3096    *
3097    *    <jc>// Inject beans</jc>
3098    *    MyService <jv>service</jv> = <jk>new</jk> MyService();
3099    *    ClassInfo.<jsm>of</jsm>(<jv>service</jv>).<jsm>inject</jsm>(<jv>beanStore</jv>);
3100    * </p>
3101    *
3102    * @param <T> The bean type.
3103    * @param bean The object to inject beans into.
3104    * @param beanStore The bean store to resolve beans from.
3105    * @return The same bean instance (for method chaining).
3106    * @throws ExecutableException If any field or method cannot be injected (e.g., required bean not found in the bean store).
3107    */
3108   public <T> T inject(T bean, BeanStore beanStore) {
3109      return inject(bean, beanStore, null);
3110   }
3111
3112   /**
3113    * Same as {@link #inject(Object, BeanStore)} but runs an additional hook after all
3114    * {@code @Inject}/{@code @Autowired}/{@code @Value} field and method injection has completed but
3115    * <i>before</i> any {@code @PostConstruct} method is dispatched.
3116    *
3117    * <p>
3118    * This is the seam that lets a caller in another package (e.g. {@code BeanInstantiator} running a
3119    * whole-object {@code @ConfigProperties} bind) populate additional fields between field injection and
3120    * {@code @PostConstruct}, so that a {@code @PostConstruct} method observes the fully-populated object.
3121    * Keeping the hook here — rather than teaching {@code ClassInfo} about any specific binder — preserves the
3122    * {@code reflect} package's independence from the {@code inject} package's higher-level binders.
3123    *
3124    * <p>
3125    * Passing a {@code null} hook is exactly equivalent to {@link #inject(Object, BeanStore)}, which is the form
3126    * all existing callers use.
3127    *
3128    * @param <T> The bean type.
3129    * @param bean The object to inject beans into.
3130    * @param beanStore The bean store to resolve beans from.
3131    * @param afterFieldInjection A hook run after field/method injection and before {@code @PostConstruct} dispatch.  Can be <jk>null</jk> (no hook).
3132    * @return The same bean instance (for method chaining).
3133    * @throws ExecutableException If any field or method cannot be injected (e.g., required bean not found in the bean store).
3134    * @since 10.0.0
3135    */
3136   public <T> T inject(T bean, BeanStore beanStore, Runnable afterFieldInjection) {
3137      // Inject into fields.
3138      getAllFields().stream()
3139         .filter(x -> x.isNotFinal() && x.getAnnotations().stream().anyMatch(ClassInfo::isInjectOrValueAnnotation))
3140         .forEach(x -> x.inject(beanStore, bean));
3141
3142      // Inject into methods.
3143      getAllMethods().stream()
3144      .filter(x -> x.isNotAbstract() && eq(x.getTypeParameters().length, 0) && x.getAnnotations().stream().anyMatch(ClassInfo::isInjectOrValueAnnotation))
3145         .forEach(x -> x.inject(beanStore, bean));
3146
3147      // Post-field-injection seam: bind whole-object config (@ConfigProperties) before @PostConstruct so
3148      // that a @PostConstruct method observes the fully-bound fields.
3149      if (afterFieldInjection != null)
3150         afterFieldInjection.run();
3151
3152      // Call @PostConstruct methods after all injection is complete
3153      // Use getAllMethodsTopDown() to ensure parent methods are called before child methods
3154      getAllMethodsTopDown().stream()
3155         .filter(x -> x.isNotAbstract() && eq(x.getTypeParameters().length, 0))
3156         .filter(x -> x.getReturnType().is(void.class) && eq(x.getParameterCount(), 0))
3157         .filter(x -> x.getAnnotations().stream().anyMatch(JsrSupport::isPostConstructAnnotation))
3158         .forEach(x -> x.inject(beanStore, bean));
3159
3160      return bean;
3161   }
3162
3163   private static boolean isInjectOrValueAnnotation(AnnotationInfo<?> ai) {
3164      return JsrSupport.isInjectAnnotation(ai) || JsrSupport.isValueAnnotation(ai);
3165   }
3166
3167}