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.utils;
018
019import static org.apache.juneau.commons.reflect.ReflectionUtils.*;
020import static org.apache.juneau.commons.utils.AssertionUtils.*;
021import static org.apache.juneau.commons.utils.Utils.*;
022
023import java.lang.reflect.*;
024import java.util.*;
025import java.util.function.*;
026
027import org.apache.juneau.commons.collections.*;
028import org.apache.juneau.commons.lang.*;
029import org.apache.juneau.commons.reflect.*;
030
031/**
032 * Utility methods for working with classes.
033 *
034 */
035public class ClassUtils {
036
037   /**
038    * Predicate check to filter out void classes.
039    */
040   public static final Predicate<Class<?>> NOT_VOID = ClassUtils::isNotVoid;
041
042   @SuppressWarnings("rawtypes")
043   private static Cache<Class,Boolean> MODIFIABLE_COLLECTION_TYPES = Cache.of(Class.class, Boolean.class).build();
044
045   /**
046    * Determines whether the specified collection supports modification operations (e.g., {@code add()}, {@code remove()}).
047    *
048    * <p>
049    * This method performs a heuristic check based on the collection's class name to determine if it's likely modifiable.
050    * It checks whether the class name contains indicators of immutability such as "Immutable", "Unmodifiable",
051    * or "Arrays$ArrayList" (which represents the unmodifiable list returned by {@link Arrays#asList(Object...)}).
052    *
053    * <p>
054    * Results are cached for performance, so repeated calls for the same collection type are very fast.
055    *
056    * <h5 class='section'>Examples:</h5>
057    * <p class='bjava'>
058    *    <jc>// Modifiable collections</jc>
059    *    canAddTo(<jk>new</jk> ArrayList&lt;&gt;());        <jc>// true</jc>
060    *    canAddTo(<jk>new</jk> LinkedList&lt;&gt;());       <jc>// true</jc>
061    *    canAddTo(<jk>new</jk> HashSet&lt;&gt;());          <jc>// true</jc>
062    *
063    *    <jc>// Unmodifiable collections</jc>
064    *    canAddTo(Collections.unmodifiableList(...));    <jc>// false</jc>
065    *    canAddTo(Collections.unmodifiableSet(...));     <jc>// false</jc>
066    *    canAddTo(Arrays.asList(<js>"a"</js>, <js>"b"</js>));               <jc>// false</jc>
067    *    canAddTo(List.of(<js>"a"</js>, <js>"b"</js>));                     <jc>// false (ImmutableCollections)</jc>
068    * </p>
069    *
070    * <p>
071    * <b>Note:</b> This is a heuristic check based on naming conventions. It does not attempt to actually
072    * modify the collection, so it never throws exceptions. However, it may produce false positives for
073    * custom collection implementations with misleading names.
074    *
075    * @param value The collection to check. Must not be <jk>null</jk>.
076    * @return <jk>true</jk> if the collection is likely modifiable, <jk>false</jk> if it's likely unmodifiable.
077    * @throws IllegalArgumentException If value is <jk>null</jk>.
078    */
079   public static boolean canAddTo(Collection<?> value) {
080      assertArgNotNull("value", value);
081      return canAddTo(value.getClass());
082   }
083
084   /**
085    * Determines whether the specified map supports modification operations (e.g., {@code put()}, {@code remove()}).
086    *
087    * <p>
088    * This method performs a heuristic check based on the map's class name to determine if it's likely modifiable.
089    * It checks whether the class name contains indicators of immutability such as "Immutable", "Unmodifiable",
090    * or "Arrays$ArrayList" (which represents unmodifiable collections).
091    *
092    * <p>
093    * Results are cached for performance, so repeated calls for the same map type are very fast.
094    *
095    * <h5 class='section'>Examples:</h5>
096    * <p class='bjava'>
097    *    <jc>// Modifiable maps</jc>
098    *    canPutTo(<jk>new</jk> HashMap&lt;&gt;());          <jc>// true</jc>
099    *    canPutTo(<jk>new</jk> LinkedHashMap&lt;&gt;());    <jc>// true</jc>
100    *    canPutTo(<jk>new</jk> TreeMap&lt;&gt;());          <jc>// true</jc>
101    *
102    *    <jc>// Unmodifiable maps</jc>
103    *    canPutTo(Collections.unmodifiableMap(...));     <jc>// false</jc>
104    *    canPutTo(Map.of(<js>"key"</js>, <js>"value"</js>));              <jc>// false (ImmutableCollections)</jc>
105    * </p>
106    *
107    * <p>
108    * <b>Note:</b> This is a heuristic check based on naming conventions. It does not attempt to actually
109    * modify the map, so it never throws exceptions. However, it may produce false positives for
110    * custom map implementations with misleading names.
111    *
112    * @param value The map to check. Must not be <jk>null</jk>.
113    * @return <jk>true</jk> if the map is likely modifiable, <jk>false</jk> if it's likely unmodifiable.
114    * @throws IllegalArgumentException If value is <jk>null</jk>.
115    */
116   public static boolean canPutTo(Map<?,?> value) {
117      assertArgNotNull("value", value);
118      return canAddTo(value.getClass());
119   }
120
121   /**
122    * Returns the fully-qualified class name for the specified object.
123    *
124    * <p>
125    * This method returns the canonical JVM class name including the full package path.
126    *
127    * <h5 class='section'>Examples:</h5>
128    * <p class='bjava'>
129    *    <jc>// Regular classes</jc>
130    *    className(String.<jk>class</jk>);                  <jc>// "java.lang.String"</jc>
131    *    className(<jk>new</jk> HashMap&lt;&gt;());                <jc>// "java.util.HashMap"</jc>
132    *
133    *    <jc>// Inner classes</jc>
134    *    className(Map.Entry.<jk>class</jk>);               <jc>// "java.util.Map$Entry"</jc>
135    *
136    *    <jc>// Primitives</jc>
137    *    className(<jk>int</jk>.<jk>class</jk>);                      <jc>// "int"</jc>
138    *    className(<jk>boolean</jk>.<jk>class</jk>);                  <jc>// "boolean"</jc>
139    *
140    *    <jc>// Arrays</jc>
141    *    className(String[].<jk>class</jk>);                <jc>// "[Ljava.lang.String;"</jc>
142    *    className(<jk>int</jk>[].<jk>class</jk>);                    <jc>// "[I"</jc>
143    *    className(String[][].<jk>class</jk>);              <jc>// "[[Ljava.lang.String;"</jc>
144    *
145    *    <jc>// Null</jc>
146    *    className(<jk>null</jk>);                          <jc>// null</jc>
147    * </p>
148    *
149    * @param value The object to get the class name for.
150    * @return The name of the class or <jk>null</jk> if the value was null.
151    */
152   public static String className(Object value) {
153      if (value == null)
154         return null;
155      if (value instanceof Class value2)
156         return value2.getName();
157      if (value instanceof ClassInfo value2)
158         return value2.getName();
159      return value.getClass().getName();
160   }
161
162   /**
163    * Returns the simple (non-qualified) class name for the specified object.
164    *
165    * <p>
166    * This method returns only the simple class name without any package or outer class information.
167    * For inner classes, only the innermost class name is returned.
168    *
169    * <h5 class='section'>Examples:</h5>
170    * <p class='bjava'>
171    *    <jc>// Regular classes</jc>
172    *    simpleClassName(String.<jk>class</jk>);            <jc>// "String"</jc>
173    *    simpleClassName(<jk>new</jk> HashMap&lt;&gt;());          <jc>// "HashMap"</jc>
174    *
175    *    <jc>// Inner classes</jc>
176    *    simpleClassName(Map.Entry.<jk>class</jk>);         <jc>// "Entry"</jc>
177    *
178    *    <jc>// Primitives</jc>
179    *    simpleClassName(<jk>int</jk>.<jk>class</jk>);                <jc>// "int"</jc>
180    *    simpleClassName(<jk>boolean</jk>.<jk>class</jk>);            <jc>// "boolean"</jc>
181    *
182    *    <jc>// Arrays</jc>
183    *    simpleClassName(String[].<jk>class</jk>);          <jc>// "String[]"</jc>
184    *    simpleClassName(<jk>int</jk>[].<jk>class</jk>);              <jc>// "int[]"</jc>
185    *    simpleClassName(String[][].<jk>class</jk>);        <jc>// "String[][]"</jc>
186    *
187    *    <jc>// Null</jc>
188    *    simpleClassName(<jk>null</jk>);                    <jc>// null</jc>
189    * </p>
190    *
191    * @param value The object to get the simple class name for.
192    * @return The simple name of the class or <jk>null</jk> if the value was null.
193    */
194   public static String classNameSimple(Object value) {
195      if (value == null)
196         return null;
197      if (value instanceof Class value2)
198         return value2.getSimpleName();
199      if (value instanceof ClassInfo value2)
200         return value2.getNameSimple();
201      return value.getClass().getSimpleName();
202   }
203
204   /**
205    * Returns the simple qualified class name for the specified object.
206    *
207    * <p>
208    * This returns the simple class name including outer class names, but without the package.
209    * Inner class separators ($) are replaced with dots (.).
210    * Array types are properly formatted with brackets.
211    *
212    * <h5 class='section'>Examples:</h5>
213    * <p class='bjava'>
214    *    <jc>// Regular classes</jc>
215    *    simpleQualifiedClassName(String.<jk>class</jk>);                     <jc>// "String"</jc>
216    *    simpleQualifiedClassName(<jk>new</jk> HashMap&lt;&gt;());                   <jc>// "HashMap"</jc>
217    *
218    *    <jc>// Inner classes</jc>
219    *    simpleQualifiedClassName(Map.Entry.<jk>class</jk>);                  <jc>// "Map.Entry"</jc>
220    *    simpleQualifiedClassName(Outer.Inner.Deep.<jk>class</jk>);           <jc>// "Outer.Inner.Deep"</jc>
221    *
222    *    <jc>// Primitives</jc>
223    *    simpleQualifiedClassName(<jk>int</jk>.<jk>class</jk>);                         <jc>// "int"</jc>
224    *    simpleQualifiedClassName(<jk>boolean</jk>.<jk>class</jk>);                     <jc>// "boolean"</jc>
225    *
226    *    <jc>// Object arrays</jc>
227    *    simpleQualifiedClassName(String[].<jk>class</jk>);                   <jc>// "String[]"</jc>
228    *    simpleQualifiedClassName(Map.Entry[].<jk>class</jk>);                <jc>// "Map.Entry[]"</jc>
229    *    simpleQualifiedClassName(String[][].<jk>class</jk>);                 <jc>// "String[][]"</jc>
230    *
231    *    <jc>// Primitive arrays</jc>
232    *    simpleQualifiedClassName(<jk>int</jk>[].<jk>class</jk>);                       <jc>// "int[]"</jc>
233    *    simpleQualifiedClassName(<jk>boolean</jk>[][].<jk>class</jk>);                 <jc>// "boolean[][]"</jc>
234    *
235    *    <jc>// Null</jc>
236    *    simpleQualifiedClassName(<jk>null</jk>);                             <jc>// null</jc>
237    * </p>
238    *
239    * @param value The object to get the simple qualified class name for.
240    * @return The simple qualified name of the class or <jk>null</jk> if the value was null.
241    */
242   public static String classNameSimpleQualified(Object value) {
243      if (value == null)
244         return null;
245      var clazz = value instanceof Class<?> ? (Class<?>)value : value.getClass();
246
247      // Handle array types by recursively getting component type
248      if (clazz.isArray()) {
249         return classNameSimpleQualified(clazz.getComponentType()) + "[]";
250      }
251
252      // Handle non-array types
253      var className = clazz.getName();
254      var lastDot = className.lastIndexOf('.');
255      var simpleName = lastDot == -1 ? className : className.substring(lastDot + 1);
256      return simpleName.replace('$', '.');
257   }
258
259   /**
260    * Returns the class types for the specified arguments.
261    *
262    * @param args The objects we're getting the classes of.
263    * @return The classes of the arguments.
264    */
265   public static Class<?>[] getClasses(Object...args) {
266      var pt = new Class<?>[args.length];
267      for (var i = 0; i < args.length; i++)
268         pt[i] = args[i] == null ? null : args[i].getClass();
269      return pt;
270   }
271
272   /**
273    * Matches arguments to a list of parameter types.
274    *
275    * <p>
276    * This method intelligently matches a variable number of arguments to a fixed set of parameter types,
277    * handling cases where arguments may be provided in a different order, or where some arguments are
278    * missing or extra arguments are provided. This is particularly useful for reflective method/constructor
279    * invocation where parameter order flexibility is desired.
280    *
281    * <h5 class='section'>Matching Rules:</h5>
282    * <ul>
283    *    <li>If arguments already match parameter types in order and count, they are returned as-is (fast path)
284    *    <li>Otherwise, each parameter type is matched with the first compatible argument
285    *    <li>Extra arguments are ignored
286    *    <li>Missing parameters are left as <jk>null</jk>
287    *    <li>Primitive types are automatically matched with their wrapper equivalents
288    *    <li>Type hierarchy is respected (subclasses match parent parameters)
289    * </ul>
290    *
291    * <h5 class='section'>Examples:</h5>
292    * <p class='bjava'>
293    *    <jc>// Already in correct order - fast path returns original array</jc>
294    *    Class&lt;?&gt;[] types = {String.<jk>class</jk>, Integer.<jk>class</jk>};
295    *    Object[] args = {<js>"hello"</js>, 42};
296    *    Object[] result = getMatchingArgs(types, args);
297    *    <jc>// Returns: ["hello", 42]</jc>
298    *
299    *    <jc>// Arguments in wrong order - method reorders them</jc>
300    *    Class&lt;?&gt;[] types = {Integer.<jk>class</jk>, String.<jk>class</jk>};
301    *    Object[] args = {<js>"hello"</js>, 42};
302    *    Object[] result = getMatchingArgs(types, args);
303    *    <jc>// Returns: [42, "hello"]</jc>
304    *
305    *    <jc>// Extra arguments are ignored</jc>
306    *    Class&lt;?&gt;[] types = {String.<jk>class</jk>};
307    *    Object[] args = {<js>"hello"</js>, 42, <jk>true</jk>};
308    *    Object[] result = getMatchingArgs(types, args);
309    *    <jc>// Returns: ["hello"]</jc>
310    *
311    *    <jc>// Missing arguments become null</jc>
312    *    Class&lt;?&gt;[] types = {String.<jk>class</jk>, Integer.<jk>class</jk>, Boolean.<jk>class</jk>};
313    *    Object[] args = {<js>"hello"</js>};
314    *    Object[] result = getMatchingArgs(types, args);
315    *    <jc>// Returns: ["hello", null, null]</jc>
316    *
317    *    <jc>// Handles primitive types and their wrappers</jc>
318    *    Class&lt;?&gt;[] types = {<jk>int</jk>.<jk>class</jk>, String.<jk>class</jk>};
319    *    Object[] args = {<js>"hello"</js>, 42};  <jc>// Integer object matches int.class</jc>
320    *    Object[] result = getMatchingArgs(types, args);
321    *    <jc>// Returns: [42, "hello"]</jc>
322    *
323    *    <jc>// Respects type hierarchy - subclasses match parent types</jc>
324    *    Class&lt;?&gt;[] types = {Number.<jk>class</jk>, String.<jk>class</jk>};
325    *    Object[] args = {<js>"hello"</js>, 42};  <jc>// Integer extends Number</jc>
326    *    Object[] result = getMatchingArgs(types, args);
327    *    <jc>// Returns: [42, "hello"]</jc>
328    * </p>
329    *
330    * <p>
331    * This method is used internally by {@link ClassInfo}, {@link MethodInfo}, and {@link ConstructorInfo}
332    * to provide flexible parameter matching during reflective invocation.
333    *
334    * @param paramTypes The parameter types to match against. Must not be <jk>null</jk>.
335    * @param args The arguments to match to the parameter types. Can be empty or contain <jk>null</jk> values.
336    * @return
337    *    An array of arguments matched to the parameter types. The returned array will always have
338    *    the same length as {@code paramTypes}. Returns the original {@code args} array if it already
339    *    matches (fast path optimization).
340    */
341   public static Object[] getMatchingArgs(Class<?>[] paramTypes, Object...args) {
342      assertArgNotNull("args", args);
343      var needsShuffle = paramTypes.length != args.length;
344      if (! needsShuffle) {
345         for (var i = 0; i < paramTypes.length; i++) {
346            if (! paramTypes[i].isInstance(args[i]))
347               needsShuffle = true;
348         }
349      }
350      if (! needsShuffle)
351         return args;
352      var params = new Object[paramTypes.length];
353      for (var i = 0; i < paramTypes.length; i++) {
354         var pt = info(paramTypes[i]).getWrapperIfPrimitive();
355         for (var arg : args) {
356            if (nn(arg) && pt.isParentOf(arg.getClass())) {
357               params[i] = arg;
358               break;
359            }
360         }
361      }
362      return params;
363   }
364
365   /**
366    * Attempts to unwrap a proxy object and return the underlying "real" class.
367    *
368    * <p>
369    * This method handles several common proxy types used in Java applications:
370    * <ul>
371    *    <li><b>JDK Dynamic Proxies</b> - Created via {@link Proxy#newProxyInstance}
372    *    <li><b>CGLIB Proxies</b> - Created by Spring's CGLIB enhancer (class name contains <js>"$$EnhancerBySpringCGLIB$$"</js>)
373    *    <li><b>Javassist Proxies</b> - Created by Javassist proxy factory (class name contains <js>"_$$_javassist"</js> or <js>"_$$_jvst"</js>)
374    *    <li><b>ByteBuddy Proxies</b> - Created by ByteBuddy (class name contains <js>"$ByteBuddy$"</js>)
375    * </ul>
376    *
377    * <h5 class='section'>Example:</h5>
378    * <p class='bjava'>
379    *    <jc>// Get the real class behind a Spring proxy</jc>
380    *    <ja>@Service</ja>
381    *    <jk>class</jk> MyService {}
382    *
383    *    MyService proxy = applicationContext.getBean(MyService.<jk>class</jk>);
384    *    Class&lt;?&gt; realClass = ClassUtils.<jsm>getProxyFor</jsm>(proxy);
385    *    <jc>// Returns MyService.class instead of the CGLIB proxy class</jc>
386    * </p>
387    *
388    * <h5 class='section'>Notes:</h5>
389    * <ul class='spaced-list'>
390    *    <li>For JDK dynamic proxies, returns the first interface implemented by the proxy
391    *    <li>For CGLIB/Javassist/ByteBuddy proxies, returns the superclass
392    *    <li>For Spring CGLIB proxies with a <c>getTargetClass()</c> method, invokes that method for more accurate results
393    *    <li>Returns <jk>null</jk> if the object is not a recognized proxy type
394    * </ul>
395    *
396    * @param o The object to unwrap. Can be <jk>null</jk>.
397    * @return
398    *    The underlying class, or <jk>null</jk> if:
399    *    <ul>
400    *       <li>The object is <jk>null</jk>
401    *       <li>The object is not a recognized proxy type
402    *       <li>The proxy cannot be unwrapped
403    *    </ul>
404    */
405   public static Class<?> getProxyFor(Object o) {
406      if (o == null)
407         return null;
408
409      var c = o.getClass();
410      var s = c.getName();
411
412      // Quick exit: Most classes aren't proxies, and proxies typically have '$' in their name
413      if (s.indexOf('$') == -1)
414         return null;
415
416      // JDK Dynamic Proxy: Created via Proxy.newProxyInstance()
417      // These implement interfaces and are instances of java.lang.reflect.Proxy
418      if (Proxy.isProxyClass(c)) {
419         var interfaces = c.getInterfaces();
420         return interfaces.length > 0 ? interfaces[0] : null;
421      }
422
423      // CGLIB Proxy: Spring's CGLIB enhancer creates subclasses
424      // Pattern: com.example.MyClass$$EnhancerBySpringCGLIB$$abc123
425      if (s.contains("$$EnhancerBySpringCGLIB$$")) {
426         // Try to invoke getTargetClass() if available (Spring specific)
427         var v = Value.<Class<?>>empty();
428         info(c).getPublicMethods().stream().filter(m -> m.hasName("getTargetClass") && m.getParameterCount() == 0 && m.hasReturnType(Class.class)).forEach(m -> safe(() -> v.set(m.invoke(o)))); // HTT - Requires bytecode manipulation to create classes with $$EnhancerBySpringCGLIB$$ in name
429         return v.isPresent() ? v.get() : c.getSuperclass(); // HTT - Requires bytecode manipulation to create classes with $$EnhancerBySpringCGLIB$$ in name
430      }
431
432      // Javassist Proxy: Created by Javassist ProxyFactory
433      // Pattern: com.example.MyClass_$$_javassist_123 or com.example.MyClass_$$_jvst123
434      // ByteBuddy Proxy: Created by ByteBuddy framework
435      // Pattern: com.example.MyClass$ByteBuddy$abc123
436      if (s.contains("_$$_javassist") || s.contains("_$$_jvst") || s.contains("$ByteBuddy$")) {
437         return c.getSuperclass(); // HTT - Requires bytecode manipulation to create classes with _$$_javassist, _$$_jvst, or $ByteBuddy$ in name
438      }
439
440      // Not a recognized proxy type
441      return null;
442   }
443
444   /**
445    * Returns <jk>false</jk> if the specific class is <jk>null</jk> or <c><jk>void</jk>.<jk>class</jk></c> or {@link Void} or has the simple name <js>"Void</js>.
446    *
447    * @param c The class to check.
448    * @return <jk>false</jk> if the specific class is <jk>null</jk> or <c><jk>void</jk>.<jk>class</jk></c> or {@link Void} or has the simple name <js>"Void</js>.
449    */
450   @SuppressWarnings("rawtypes")
451   public static boolean isNotVoid(Class c) {
452      return ! isVoid(c);
453   }
454
455   /**
456    * Returns <jk>true</jk> if the specific class is <jk>null</jk> or <c><jk>void</jk>.<jk>class</jk></c> or {@link Void} or has the simple name <js>"Void</js>.
457    *
458    * @param c The class to check.
459    * @return <jk>true</jk> if the specific class is <jk>null</jk> or <c><jk>void</jk>.<jk>class</jk></c> or {@link Void} or has the simple name <js>"Void</js>.
460    */
461   @SuppressWarnings("rawtypes")
462   public static boolean isVoid(Class c) {
463      return c == null || c == void.class || c == Void.class || cns(c).equalsIgnoreCase("void");
464   }
465
466   /**
467    * Attempts to call <code>x.setAccessible(<jk>true</jk>)</code> and quietly ignores security exceptions.
468    *
469    * @param x The constructor.
470    * @return <jk>true</jk> if call was successful.
471    */
472   public static boolean setAccessible(Constructor<?> x) {
473      assertArgNotNull("x", x);
474      return safeOpt(() -> {
475         x.setAccessible(true);
476         return true;
477      }).orElse(false);
478   }
479
480   /**
481    * Attempts to call <code>x.setAccessible(<jk>true</jk>)</code> and quietly ignores security exceptions.
482    *
483    * @param x The field.
484    * @return <jk>true</jk> if call was successful.
485    */
486   public static boolean setAccessible(Field x) {
487      assertArgNotNull("x", x);
488      return safeOpt(() -> {
489         x.setAccessible(true);
490         return true;
491      }).orElse(false);
492   }
493
494   /**
495    * Attempts to call <code>x.setAccessible(<jk>true</jk>)</code> and quietly ignores security exceptions.
496    *
497    * @param x The method.
498    * @return <jk>true</jk> if call was successful.
499    */
500   public static boolean setAccessible(Method x) {
501      assertArgNotNull("x", x);
502      return safeOpt(() -> {
503         x.setAccessible(true);
504         return true;
505      }).orElse(false);
506   }
507
508   /**
509    * Returns the specified type as a <c>Class</c>.
510    *
511    * <p>
512    * If it's already a <c>Class</c>, it just does a cast.
513    * <br>If it's a <c>ParameterizedType</c>, it returns the raw type.
514    *
515    * @param t The type to convert.
516    * @return The type converted to a <c>Class</c>, or <jk>null</jk> if it could not be converted.
517    */
518   public static Class<?> toClass(Type t) {
519      if (t instanceof Class<?> c)
520         return c;
521      if (t instanceof ParameterizedType t2) {
522         // The raw type should always be a class (right?)
523         return (Class<?>)t2.getRawType();
524      }
525      return null;
526   }
527
528   private static boolean canAddTo(Class<?> c) {
529      var b = MODIFIABLE_COLLECTION_TYPES.get(c);
530      if (b == null) {
531         var name = c.getName();
532         b = (! name.contains("Immutable") && ! name.contains("Unmodifiable") && ! name.contains("Arrays$ArrayList"));
533         MODIFIABLE_COLLECTION_TYPES.put(c, b);
534      }
535      return b;
536   }
537}