View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under  the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.juneau;
18  
19  import static org.apache.juneau.ClassMeta.Category.*;
20  import static org.apache.juneau.commons.reflect.ReflectionUtils.*;
21  import static org.apache.juneau.commons.utils.CollectionUtils.*;
22  import static org.apache.juneau.commons.utils.ThrowableUtils.*;
23  import static org.apache.juneau.commons.utils.Utils.*;
24  
25  import java.io.*;
26  import java.lang.annotation.*;
27  import java.lang.reflect.*;
28  import java.lang.reflect.Proxy;
29  import java.net.*;
30  import java.time.temporal.*;
31  import java.util.*;
32  import java.util.List;
33  import java.util.concurrent.*;
34  import java.util.function.*;
35  
36  import org.apache.juneau.annotation.*;
37  import org.apache.juneau.commons.collections.*;
38  import org.apache.juneau.commons.function.*;
39  import org.apache.juneau.commons.reflect.*;
40  import org.apache.juneau.commons.utils.*;
41  import org.apache.juneau.cp.*;
42  import org.apache.juneau.json.*;
43  import org.apache.juneau.reflect.*;
44  import org.apache.juneau.swap.*;
45  
46  /**
47   * A wrapper class around the {@link Class} object that provides cached information about that class.
48   *
49   * <p>
50   * Instances of this class can be created through the {@link BeanContext#getClassMeta(Class)} method.
51   *
52   * <p>
53   * The {@link BeanContext} class will cache and reuse instances of this class except for the following class types:
54   * <ul>
55   * 	<li>Arrays
56   * 	<li>Maps with non-Object key/values.
57   * 	<li>Collections with non-Object key/values.
58   * </ul>
59   *
60   * <p>
61   * This class is tied to the {@link BeanContext} class because it's that class that makes the determination of what is
62   * a bean.
63   *
64   *
65   * @param <T> The class type of the wrapped class.
66   */
67  @Bean(properties = "innerClass,elementType,keyType,valueType,notABeanReason,initException,beanMeta")
68  public class ClassMeta<T> extends ClassInfoTyped<T> {
69  
70  	private static class Categories {
71  		int bits;
72  
73  		public boolean same(Categories cat) {
74  			return cat.bits == bits;
75  		}
76  
77  		boolean is(Category c) {
78  			return (bits & c.mask) != 0;
79  		}
80  
81  		boolean isUnknown() {
82  			return bits == 0;
83  		}
84  
85  		Categories set(Category c) {
86  			bits |= c.mask;
87  			return this;
88  		}
89  	}
90  
91  	enum Category {
92  		MAP(0),
93  		COLLECTION(1),
94  		NUMBER(2),
95  		DECIMAL(3),
96  		DATE(4),
97  		ARRAY(5),
98  		ENUM(6),
99  		CHARSEQ(8),
100 		STR(9),
101 		URI(10),
102 		BEANMAP(11),
103 		READER(12),
104 		INPUTSTREAM(13),
105 		ARGS(14),
106 		CALENDAR(15),
107 		TEMPORAL(16),
108 		LIST(17),
109 		SET(18),
110 		DELEGATE(19),
111 		BEAN(20);
112 
113 		private final int mask;
114 
115 		Category(int bitPosition) {
116 			this.mask = 1 << bitPosition;
117 		}
118 	}
119 
120 	/**
121 	 * Checks if the specified category is set in the bitmap.
122 	 *
123 	 * @param category The category to check.
124 	 * @return {@code true} if the category is set, {@code false} otherwise.
125 	 */
126 
127 	/**
128 	 * Generated classes shouldn't be cacheable to prevent needlessly filling up the cache.
129 	 */
130 	private static boolean isCacheable(Class<?> c) {
131 		var n = cn(c);
132 		var x = n.charAt(n.length() - 1);  // All generated classes appear to end with digits.
133 		if (x >= '0' && x <= '9') {
134 			if (n.indexOf("$$") != -1 || n.startsWith("sun") || n.startsWith("com.sun") || n.indexOf("$Proxy") != -1)
135 				return false;
136 		}
137 		return true;
138 	}
139 
140 	private final List<ClassMeta<?>> args;                                     // Arg types if this is an array of args.
141 	private final BeanContext beanContext;                                     // The bean context that created this object.
142 	private final Supplier<BuilderSwap<T,?>> builderSwap;                      // The builder swap associated with this bean (if it has one).
143 	private final Categories cat;                                              // The class category.
144 	private final Cache<Class<?>,ObjectSwap<?,?>> childSwapMap;                // Maps normal subclasses to ObjectSwaps.
145 	private final Supplier<List<ObjectSwap<?,?>>> childSwaps;                  // Any ObjectSwaps where the normal type is a subclass of this class.
146 	private final Cache<Class<?>,ObjectSwap<?,?>> childUnswapMap;              // Maps swap subclasses to ObjectSwaps.
147 	private final Supplier<String> beanDictionaryName;                             // The dictionary name of this class if it has one.
148 	private final Supplier<ClassMeta<?>> elementType;                          // If ARRAY or COLLECTION, the element class type.
149 	private final OptionalSupplier<String> example;                            // Example JSON.
150 	private final OptionalSupplier<FieldInfo> exampleField;                    // The @Example-annotated field (if it has one).
151 	private final OptionalSupplier<MethodInfo> exampleMethod;                  // The example() or @Example-annotated method (if it has one).
152 	private final Supplier<BidiMap<Object,String>> enumValues;
153 	private final Map<Class<?>,Mutater<?,T>> fromMutaters = new ConcurrentHashMap<>();
154 	private final OptionalSupplier<MethodInfo> fromStringMethod;               // Static fromString(String) or equivalent method
155 	private final OptionalSupplier<ClassInfoTyped<? extends T>> implClass;     // The implementation class to use if this is an interface.
156 	private final Supplier<KeyValueTypes> keyValueTypes;                        // Key and value types for MAP types.
157 	private final OptionalSupplier<MarshalledFilter> marshalledFilter;
158 	private final Supplier<Property<T,Object>> nameProperty;                   // The method to set the name on an object (if it has one).
159 	private final OptionalSupplier<ConstructorInfo> noArgConstructor;          // The no-arg constructor for this class (if it has one).
160 	private final Supplier<Property<T,Object>> parentProperty;                 // The method to set the parent on an object (if it has one).
161 	private final Cache<String,Optional<?>> properties;
162 	private final Mutater<String,T> stringMutater;
163 	private final OptionalSupplier<ConstructorInfo> stringConstructor;         // The X(String) constructor (if it has one).
164 	private final Supplier<List<ObjectSwap<T,?>>> swaps;                       // The object POJO swaps associated with this bean (if it has any).
165 	private final Map<Class<?>,Mutater<T,?>> toMutaters = new ConcurrentHashMap<>();
166 	private final OptionalSupplier<BeanMeta.BeanMetaValue<T>> beanMeta;
167 
168 	private record KeyValueTypes(ClassMeta<?> keyType, ClassMeta<?> valueType) {
169 		Optional<ClassMeta<?>> optKeyType() { return opt(keyType()); }
170 		Optional<ClassMeta<?>> optValueType() { return opt(valueType()); }
171 	}
172 
173 	/**
174 	 * Construct a new {@code ClassMeta} based on the specified {@link Class}.
175 	 *
176 	 * @param innerClass The class being wrapped.
177 	 * @param beanContext The bean context that created this object.
178 	 * @param delayedInit
179 	 * 	Don't call init() in constructor.
180 	 * 	Used for delayed initialization when the possibility of class reference loops exist.
181 	 */
182 	ClassMeta(Class<T> innerClass, BeanContext beanContext) {
183 		super(innerClass);
184 		this.beanContext = beanContext;
185 		this.cat = new Categories();
186 
187 		// We always immediately add this class meta to the bean context cache so that we can resolve recursive references.
188 		if (nn(beanContext) && nn(beanContext.getCmCache()) && isCacheable(innerClass))
189 			beanContext.getCmCache().put(innerClass, this);
190 
191 		var ap = beanContext.getAnnotationProvider();
192 
193 		if (isChildOf(Delegate.class)) {
194 			cat.set(DELEGATE);
195 		}
196 		if (isEnum()) {
197 			cat.set(ENUM);
198 		} else if (isChildOf(CharSequence.class)) {
199 			cat.set(CHARSEQ);
200 			if (is(String.class)) {
201 				cat.set(STR);
202 			}
203 		} else if (isChildOf(Number.class) || isAny(byte.class, short.class, int.class, long.class, float.class, double.class)) {
204 			cat.set(NUMBER);
205 			if (isChildOfAny(Float.class, Double.class) || isAny(float.class, double.class)) {
206 				cat.set(DECIMAL);
207 			}
208 		} else if (isChildOf(Collection.class)) {
209 			cat.set(COLLECTION);
210 			if (isChildOf(Set.class)) {
211 				cat.set(SET);
212 			} else if (isChildOf(List.class)) {
213 				cat.set(LIST);
214 			}
215 		} else if (isChildOf(Map.class)) {
216 			cat.set(MAP);
217 			if (isChildOf(BeanMap.class)) {
218 				cat.set(BEANMAP);
219 			}
220 		} else if (isChildOfAny(Date.class, Calendar.class)) {
221 			if (isChildOf(Date.class)) {
222 				cat.set(DATE);
223 			} else if (isChildOf(Calendar.class)) {
224 				cat.set(CALENDAR);
225 			}
226 		} else if (isChildOf(Temporal.class)) {
227 			cat.set(TEMPORAL);
228 		} else if (inner().isArray()) {
229 			cat.set(ARRAY);
230 		} else if (isChildOfAny(URL.class, URI.class) || ap.has(Uri.class, this)) {
231 			cat.set(URI);
232 		} else if (isChildOf(Reader.class)) {
233 			cat.set(READER);
234 		} else if (isChildOf(InputStream.class)) {
235 			cat.set(INPUTSTREAM);
236 		}
237 
238 		beanMeta = mem(()->findBeanMeta());
239 		builderSwap = mem(()->findBuilderSwap());
240 		childSwapMap = Cache.<Class<?>,ObjectSwap<?,?>>create().supplier(x -> findSwap(x)).build();
241 		childSwaps = mem(()->findChildSwaps());
242 		childUnswapMap = Cache.<Class<?>,ObjectSwap<?,?>>create().supplier(x -> findUnswap(x)).build();
243 		beanDictionaryName = mem(()->findBeanDictionaryName());
244 		elementType = mem(()->findElementType());
245 		enumValues = mem(()->findEnumValues());
246 		example = mem(()->findExample());
247 		exampleField = mem(()->findExampleField());
248 		exampleMethod = mem(()->findExampleMethod());
249 		fromStringMethod = mem(()->findFromStringMethod());
250 		implClass = mem(()->findImplClass());
251 		keyValueTypes = mem(()->findKeyValueTypes());
252 		marshalledFilter = mem(()->findMarshalledFilter());
253 		nameProperty = mem(()->findNameProperty());
254 		noArgConstructor = mem(()->findNoArgConstructor());
255 		parentProperty = mem(()->findParentProperty());
256 		properties = Cache.<String,Optional<?>>create().build();
257 		stringConstructor = mem(()->findStringConstructor());
258 		swaps = mem(()->findSwaps());
259 
260 		this.args = null;
261 		this.stringMutater = Mutaters.get(String.class, inner());
262 	}
263 
264 	protected ObjectSwap<?,?> findSwap(Class<?> c) {
265 		return childSwaps.get().stream().filter(x -> x.getNormalClass().isParentOf(c)).findFirst().orElse(null);
266 	}
267 
268 	protected ObjectSwap<?,?> findUnswap(Class<?> c) {
269 		return childSwaps.get().stream().filter(x -> x.getSwapClass().isParentOf(c)).findFirst().orElse(null);
270 	}
271 
272 
273 	/**
274 	 * Constructor for args-arrays.
275 	 */
276 	@SuppressWarnings("unchecked")
277 	ClassMeta(List<ClassMeta<?>> args) {
278 		super((Class<T>)Object[].class);
279 		this.args = args;
280 		this.childSwaps = mem(()->findChildSwaps());
281 		this.childSwapMap = null;
282 		this.childUnswapMap = null;
283 		this.cat = new Categories().set(ARGS);
284 		this.beanContext = null;
285 		this.elementType = mem(()->findElementType());
286 		this.keyValueTypes = mem(()->findKeyValueTypes());
287 		this.beanMeta = mem(()->findBeanMeta());
288 		this.swaps = mem(()->findSwaps());
289 		this.stringMutater = null;
290 		this.fromStringMethod = mem(()->findFromStringMethod());
291 		this.exampleMethod = mem(()->findExampleMethod());
292 		this.parentProperty = mem(()->findParentProperty());
293 		this.nameProperty = mem(()->findNameProperty());
294 		this.exampleField = mem(()->findExampleField());
295 		this.noArgConstructor = mem(()->findNoArgConstructor());
296 		this.properties = Cache.<String,Optional<?>>create().build();
297 		this.stringConstructor = mem(()->findStringConstructor());
298 		this.marshalledFilter = mem(()->findMarshalledFilter());
299 		this.builderSwap = mem(()->findBuilderSwap());
300 		this.example = mem(()->findExample());
301 		this.implClass = mem(()->findImplClass());
302 		this.enumValues = mem(()->findEnumValues());
303 		this.beanDictionaryName = mem(()->findBeanDictionaryName());
304 	}
305 
306 	/**
307 	 * Copy constructor.
308 	 *
309 	 * <p>
310 	 * Used for creating Map and Collection class metas that shouldn't be cached.
311 	 */
312 	ClassMeta(ClassMeta<T> mainType, ClassMeta<?> keyType, ClassMeta<?> valueType, ClassMeta<?> elementType) {
313 		super(mainType.inner());
314 		this.childSwaps = mainType.childSwaps;
315 		this.childSwapMap = mainType.childSwapMap;
316 		this.childUnswapMap = mainType.childUnswapMap;
317 		this.cat = mainType.cat;
318 		this.fromStringMethod = mainType.fromStringMethod;
319 		this.beanContext = mainType.beanContext;
320 		this.elementType = elementType != null ? mem(()->elementType) : mainType.elementType;
321 		this.keyValueTypes = (keyType != null || valueType != null) ? mem(()->new KeyValueTypes(keyType, valueType)) : mainType.keyValueTypes;
322 		this.beanMeta = mainType.beanMeta;
323 		this.swaps = mainType.swaps;
324 		this.exampleMethod = mainType.exampleMethod;
325 		this.args = null;
326 		this.stringMutater = mainType.stringMutater;
327 		this.parentProperty = mainType.parentProperty;
328 		this.nameProperty = mainType.nameProperty;
329 		this.exampleField = mainType.exampleField;
330 		this.noArgConstructor = mainType.noArgConstructor;
331 		this.properties = mainType.properties;
332 		this.stringConstructor = mainType.stringConstructor;
333 		this.marshalledFilter = mainType.marshalledFilter;
334 		this.builderSwap = mainType.builderSwap;
335 		this.example = mainType.example;
336 		this.implClass = mainType.implClass;
337 		this.enumValues = mainType.enumValues;
338 		this.beanDictionaryName = mainType.beanDictionaryName;
339 	}
340 
341 	/**
342 	 * Returns <jk>true</jk> if this class can be instantiated as a bean.
343 	 * Returns <jk>false</jk> if this is a non-static member class and the outer object does not match the class type of
344 	 * the defining class.
345 	 *
346 	 * @param outer
347 	 * 	The outer class object for non-static member classes.  Can be <jk>null</jk> for non-member or static classes.
348 	 * @return
349 	 * 	<jk>true</jk> if a new instance of this bean can be created within the context of the specified outer object.
350 	 */
351 	public boolean canCreateNewBean(Object outer) {
352 		var bm = getBeanMeta();
353 		if (bm == null || ! bm.hasConstructor())
354 			return false;
355 		if (isMemberClass() && isNotStatic())
356 			return nn(outer) && bm.getConstructor().hasParameterTypes(outer.getClass());
357 		return true;
358 	}
359 
360 	/**
361 	 * Returns <jk>true</jk> if this class has a no-arg constructor or invocation handler.
362 	 *
363 	 * @return <jk>true</jk> if a new instance of this class can be constructed.
364 	 */
365 	public boolean canCreateNewInstance() {
366 		if (isMemberClass() && isNotStatic())
367 			return false;
368 		var bm = getBeanMeta();
369 		if (noArgConstructor.isPresent() || (bm != null && bm.getBeanProxyInvocationHandler() != null) || (isArray() && elementType.get().canCreateNewInstance()))
370 			return true;
371 		return false;
372 	}
373 
374 	/**
375 	 * Returns <jk>true</jk> if this class has a no-arg constructor or invocation handler.
376 	 * Returns <jk>false</jk> if this is a non-static member class and the outer object does not match the class type of
377 	 * the defining class.
378 	 *
379 	 * @param outer
380 	 * 	The outer class object for non-static member classes.  Can be <jk>null</jk> for non-member or static classes.
381 	 * @return
382 	 * 	<jk>true</jk> if a new instance of this class can be created within the context of the specified outer object.
383 	 */
384 	public boolean canCreateNewInstance(Object outer) {
385 		if (isMemberClass() && isNotStatic())
386 			return nn(outer) && noArgConstructor.map(x -> x.hasParameterTypes(outer.getClass())).orElse(false);
387 		return canCreateNewInstance();
388 	}
389 
390 	/**
391 	 * Returns <jk>true</jk> if this class can call the {@link #newInstanceFromString(Object, String)} method.
392 	 *
393 	 * @param outer
394 	 * 	The outer class object for non-static member classes.
395 	 * 	Can be <jk>null</jk> for non-member or static classes.
396 	 * @return <jk>true</jk> if this class has a no-arg constructor or invocation handler.
397 	 */
398 	public boolean canCreateNewInstanceFromString(Object outer) {
399 		if (fromStringMethod.isPresent())
400 			return true;
401 		if (stringConstructor.isPresent()) {
402 			if (isMemberClass() && isNotStatic())
403 				return nn(outer) && stringConstructor.map(x -> x.hasParameterTypes(outer.getClass(), String.class)).orElse(false);
404 			return true;
405 		}
406 		return false;
407 	}
408 
409 	@Override /* Overridden from Object */
410 	public boolean equals(Object o) {
411 		return (o instanceof ClassMeta<?>) && super.equals(o);
412 	}
413 
414 	/**
415 	 * Performs an action on all matching annotations of the specified type defined on this class or parent classes/interfaces in parent-to-child order.
416 	 *
417 	 * @param <A> The annotation type to look for.
418 	 * @param type The annotation to search for.
419 	 * @param filter A predicate to apply to the entries to determine if action should be performed.  Can be <jk>null</jk>.
420 	 * @param action An action to perform on the entry.
421 	 * @return This object.
422 	 */
423 	public <A extends Annotation> ClassMeta<T> forEachAnnotation(Class<A> type, Predicate<A> filter, Consumer<A> action) {
424 		if (beanContext != null) {
425 			beanContext.getAnnotationProvider().find(type, this).stream().map(AnnotationInfo::inner).filter(x -> filter == null || filter.test(x)).forEach(x -> action.accept(x));
426 		}
427 		return this;
428 	}
429 
430 	/**
431 	 * Returns the argument metadata at the specified index if this is an args metadata object.
432 	 *
433 	 * @param index The argument index.
434 	 * @return The The argument metadata.  Never <jk>null</jk>.
435 	 * @throws BeanRuntimeException If this metadata object is not a list of arguments, or the index is out of range.
436 	 */
437 	public ClassMeta<?> getArg(int index) {
438 		if (nn(args) && index >= 0 && index < args.size())
439 			return args.get(index);
440 		throw bex("Invalid argument index specified:  {0}.  Only {1} arguments are defined.", index, args == null ? 0 : args.size());
441 	}
442 
443 	/**
444 	 * Returns the argument types of this meta.
445 	 *
446 	 * @return The argument types of this meta, or <jk>null</jk> if this isn't an array of argument types.
447 	 */
448 	public List<ClassMeta<?>> getArgs() { return args; }
449 
450 	/**
451 	 * Returns the {@link BeanContext} that created this object.
452 	 *
453 	 * @return The bean context.
454 	 */
455 	public BeanContext getBeanContext() { return beanContext; }
456 
457 	/**
458 	 * Returns the {@link BeanMeta} associated with this class.
459 	 *
460 	 * @return
461 	 * 	The {@link BeanMeta} associated with this class, or <jk>null</jk> if there is no bean meta associated with
462 	 * 	this class.
463 	 */
464 	public BeanMeta<T> getBeanMeta() {
465 		return beanMeta.get().beanMeta();
466 	}
467 
468 	/**
469 	 * Returns the bean registry for this class.
470 	 *
471 	 * <p>
472 	 * This bean registry contains names specified in the {@link Bean#dictionary() @Bean(dictionary)} annotation
473 	 * defined on the class, regardless of whether the class is an actual bean.
474 	 * This allows interfaces to define subclasses with type names.
475 	 *
476 	 * <p>
477 	 * This is a shortcut for calling getBeanMeta().getBeanRegistry().
478 	 *
479 	 * @return The bean registry for this class, or <jk>null</jk> if no bean registry is associated with it.
480 	 */
481 	public BeanRegistry getBeanRegistry() {
482 		return beanMeta.get().optBeanMeta().map(x -> x.getBeanRegistry()).orElse(null);
483 	}
484 
485 	/**
486 	 * Returns the builder swap associated with this class.
487 	 *
488 	 * @param session The current bean session.
489 	 * @return The builder swap associated with this class, or <jk>null</jk> if it doesn't exist.
490 	 */
491 	public BuilderSwap<T,?> getBuilderSwap(BeanSession session) {
492 		return builderSwap.get();
493 	}
494 
495 	/**
496 	 * Returns the bean dictionary name associated with this class.
497 	 *
498 	 * <p>
499 	 * The lexical name is defined by {@link Bean#typeName() @Bean(typeName)}.
500 	 *
501 	 * @return
502 	 * 	The type name associated with this bean class, or <jk>null</jk> if there is no type name defined or this
503 	 * 	isn't a bean.
504 	 */
505 	public String getBeanDictionaryName() {
506 		return beanDictionaryName.get();
507 	}
508 
509 	/**
510 	 * For array and {@code Collection} types, returns the class type of the components of the array or
511 	 * {@code Collection}.
512 	 *
513 	 * @return The element class type, or <jk>null</jk> if this class is not an array or Collection.
514 	 */
515 	public ClassMeta<?> getElementType() { return elementType.get(); }
516 
517 	/**
518 	 * Returns the example of this class.
519 	 *
520 	 * @param session
521 	 * 	The bean session.
522 	 * 	<br>Required because the example method may take it in as a parameter.
523 	 * @param jpSession The JSON parser for parsing examples into POJOs.
524 	 * @return The serialized class type, or this object if no swap is associated with the class.
525 	 */
526 	@SuppressWarnings({ "unchecked" })
527 	public T getExample(BeanSession session, JsonParserSession jpSession) {
528 		try {
529 			if (example.isPresent())
530 				return jpSession.parse(example.get(), this);
531 			if (exampleMethod.isPresent())
532 				return (T)exampleMethod.get().invokeLenient(null, session);
533 			if (exampleField.isPresent())
534 				return (T)exampleField.get().get(null);
535 
536 			if (isCollection()) {
537 				var etExample = getElementType().getExample(session, jpSession);
538 				if (nn(etExample)) {
539 					if (canCreateNewInstance()) {
540 						var c = (Collection<Object>)newInstance();
541 						c.add(etExample);
542 						return (T)c;
543 					}
544 					return (T)Collections.singleton(etExample);
545 				}
546 			} else if (super.isArray()) {
547 				var etExample = getElementType().getExample(session, jpSession);
548 				if (nn(etExample)) {
549 					var o = Array.newInstance(getElementType().inner(), 1);
550 					Array.set(o, 0, etExample);
551 					return (T)o;
552 				}
553 			} else if (isMap()) {
554 				var vtExample = getValueType().getExample(session, jpSession);
555 				var ktExample = getKeyType().getExample(session, jpSession);
556 				if (nn(ktExample) && nn(vtExample)) {
557 					if (canCreateNewInstance()) {
558 						var m = (Map<Object,Object>)newInstance();
559 						m.put(ktExample, vtExample);
560 						return (T)m;
561 					}
562 					return (T)Collections.singletonMap(ktExample, vtExample);
563 				}
564 			}
565 
566 			return null;
567 		} catch (Exception e) {
568 			throw new ClassMetaRuntimeException(e);
569 		}
570 	}
571 
572 	/**
573 	 * Returns the transform for this class for creating instances from other object types.
574 	 *
575 	 * @param <I> The transform-from class.
576 	 * @param c The transform-from class.
577 	 * @return The transform, or <jk>null</jk> if no such transform exists.
578 	 */
579 	@SuppressWarnings({ "rawtypes", "unchecked" })
580 	public <I> Mutater<I,T> getFromMutater(Class<I> c) {
581 		Mutater t = fromMutaters.get(c);
582 		if (t == Mutaters.NULL)
583 			return null;
584 		if (t == null) {
585 			t = Mutaters.get(c, inner());
586 			if (t == null)
587 				t = Mutaters.NULL;
588 			fromMutaters.put(c, t);
589 		}
590 		return t == Mutaters.NULL ? null : t;
591 	}
592 
593 	/**
594 	 * Returns the no-arg constructor for this class based on the {@link Marshalled#implClass()} value.
595 	 *
596 	 * @param conVis The constructor visibility.
597 	 * @return The no-arg constructor for this class, or <jk>null</jk> if it does not exist.
598 	 */
599 	public ConstructorInfo getImplClassConstructor(Visibility conVis) {
600 		return implClass.map(x -> x.getNoArgConstructor(conVis).orElse(null)).orElse(null);
601 	}
602 
603 	/**
604 	 * Returns the transform for this class for creating instances from an InputStream.
605 	 *
606 	 * @return The transform, or <jk>null</jk> if no such transform exists.
607 	 */
608 	public Mutater<InputStream,T> getInputStreamMutater() { return getFromMutater(InputStream.class); }
609 
610 	/**
611 	 * For {@code Map} types, returns the class type of the keys of the {@code Map}.
612 	 *
613 	 * @return The key class type, or <jk>null</jk> if this class is not a Map.
614 	 */
615 	public ClassMeta<?> getKeyType() {
616 		return keyValueTypes.get().keyType();
617 	}
618 
619 	/**
620 	 * Returns the method or field annotated with {@link NameProperty @NameProperty}.
621 	 *
622 	 * @return
623 	 * 	The method or field  annotated with {@link NameProperty @NameProperty} or <jk>null</jk> if method does not
624 	 * 	exist.
625 	 */
626 	public Property<T,Object> getNameProperty() { return nameProperty.get(); }
627 
628 	/**
629 	 * Returns the reason why this class is not a bean, or <jk>null</jk> if it is a bean.
630 	 *
631 	 * @return The reason why this class is not a bean, or <jk>null</jk> if it is a bean.
632 	 */
633 	public synchronized String getNotABeanReason() {
634 		return beanMeta.get().notABeanReason();
635 	}
636 
637 	/**
638 	 * If this is an {@link Optional}, returns an empty optional.
639 	 *
640 	 * <p>
641 	 * Note that if this is a nested optional, will recursively create empty optionals.
642 	 *
643 	 * @return An empty optional, or <jk>null</jk> if this isn't an optional.
644 	 */
645 	public Optional<?> getOptionalDefault() {
646 		if (isOptional())
647 			return opt(getElementType().getOptionalDefault());
648 		return null;
649 	}
650 
651 	/**
652 	 * Returns the method or field annotated with {@link ParentProperty @ParentProperty}.
653 	 *
654 	 * @return
655 	 * 	The method or field annotated with {@link ParentProperty @ParentProperty} or <jk>null</jk> if method does not
656 	 * 	exist.
657 	 */
658 	public Property<T,Object> getParentProperty() { return parentProperty.get(); }
659 
660 	/**
661 	 * Returns a lazily-computed, cached property value for this {@link ClassMeta} instance.
662 	 *
663 	 * <p>
664 	 * This method provides a memoization mechanism for expensive computations. The property value is computed
665 	 * on the first call using the provided function, then cached for subsequent calls with the same property name.
666 	 *
667 	 * <p>
668 	 * The function is only invoked once per property name per {@link ClassMeta} instance. Subsequent calls
669 	 * with the same name will return the cached value without re-invoking the function.
670 	 *
671 	 * <h5 class='section'>Thread Safety:</h5>
672 	 * <p>
673 	 * This method is thread-safe. If multiple threads call this method simultaneously with the same property name,
674 	 * the function may be invoked multiple times, but only one result will be cached and returned.
675 	 *
676 	 * <h5 class='section'>Usage:</h5>
677 	 * <p class='bjava'>
678 	 * 	<jc>// Compute and cache an expensive property</jc>
679 	 * 	Optional&lt;String&gt; <jv>computedValue</jv> = classMeta.<jsm>getProperty</jsm>(<js>"expensiveProperty"</js>, cm -&gt; {
680 	 * 		<jc>// Expensive computation that only runs once</jc>
681 	 * 		<jk>return</jk> performExpensiveComputation(cm);
682 	 * 	});
683 	 *
684 	 * 	<jc>// Subsequent calls return cached value</jc>
685 	 * 	Optional&lt;String&gt; <jv>cached</jv> = classMeta.<jsm>getProperty</jsm>(<js>"expensiveProperty"</js>, cm -&gt; {
686 	 * 		<jc>// This function is NOT called again</jc>
687 	 * 		<jk>return</jk> performExpensiveComputation(cm);
688 	 * 	});
689 	 * </p>
690 	 *
691 	 * @param <T2> The type of the property value.
692 	 * @param name The unique name identifying this property. Used as the cache key.
693 	 * @param function The function that computes the property value. Receives this {@link ClassMeta} instance as input.
694 	 * 	Only invoked if the property hasn't been computed yet. Can return <jk>null</jk>.
695 	 * @return An {@link Optional} containing the property value if the function returned a non-null value,
696 	 * 	otherwise an empty {@link Optional}. Never <jk>null</jk>.
697 	 */
698 	@SuppressWarnings("unchecked")
699 	public <T2> Optional<T2> getProperty(String name, Function<ClassMeta<?>,T2> function) {
700 		return (Optional<T2>)properties.get(name, () -> opt(function.apply(this)));
701 	}
702 
703 	/**
704 	 * Returns the interface proxy invocation handler for this class.
705 	 *
706 	 * @return The interface proxy invocation handler, or <jk>null</jk> if it does not exist.
707 	 */
708 	public InvocationHandler getProxyInvocationHandler() {
709 		return beanMeta.get().optBeanMeta().map(x -> x.getBeanProxyInvocationHandler()).orElse(null);
710 	}
711 
712 	/**
713 	 * Returns the transform for this class for creating instances from a Reader.
714 	 *
715 	 * @return The transform, or <jk>null</jk> if no such transform exists.
716 	 */
717 	public Mutater<Reader,T> getReaderMutater() { return getFromMutater(Reader.class); }
718 
719 	/**
720 	 * Returns the serialized (swapped) form of this class if there is an {@link ObjectSwap} associated with it.
721 	 *
722 	 * @param session
723 	 * 	The bean session.
724 	 * 	<br>Required because the swap used may depend on the media type being serialized or parsed.
725 	 * @return The serialized class type, or this object if no swap is associated with the class.
726 	 */
727 	public ClassMeta<?> getSerializedClassMeta(BeanSession session) {
728 		var ps = getSwap(session);
729 		return (ps == null ? this : ps.getSwapClassMeta(session));
730 	}
731 
732 	/**
733 	 * Returns the transform for this class for creating instances from a String.
734 	 *
735 	 * @return The transform, or <jk>null</jk> if no such transform exists.
736 	 */
737 	public Mutater<String,T> getStringMutater() { return stringMutater; }
738 
739 	/**
740 	 * Returns the {@link ObjectSwap} associated with this class that's the best match for the specified session.
741 	 *
742 	 * @param session
743 	 * 	The current bean session.
744 	 * 	<br>If multiple swaps are associated with a class, only the first one with a matching media type will
745 	 * 	be returned.
746 	 * @return
747 	 * 	The {@link ObjectSwap} associated with this class, or <jk>null</jk> if there are no POJO swaps associated with
748 	 * 	this class.
749 	 */
750 	public ObjectSwap<T,?> getSwap(BeanSession session) {
751 		var swapsList = swaps.get();
752 		if (! swapsList.isEmpty()) {
753 			var matchQuant = 0;
754 			ObjectSwap<T,?> matchSwap = null;
755 
756 			for (var swap : swapsList) {
757 				var q = swap.match(session);
758 				if (q > matchQuant) {
759 					matchQuant = q;
760 					matchSwap = swap;
761 				}
762 			}
763 
764 			if (matchSwap != null)
765 				return matchSwap;
766 		}
767 		return null;
768 	}
769 
770 	/**
771 	 * Returns the transform for this class for creating instances from other object types.
772 	 *
773 	 * @param <O> The transform-to class.
774 	 * @param c The transform-from class.
775 	 * @return The transform, or <jk>null</jk> if no such transform exists.
776 	 */
777 	@SuppressWarnings({ "rawtypes", "unchecked" })
778 	public <O> Mutater<T,O> getToMutater(Class<O> c) {
779 		Mutater t = toMutaters.get(c);
780 		if (t == Mutaters.NULL)
781 			return null;
782 		if (t == null) {
783 			t = Mutaters.get(inner(), c);
784 			if (t == null)
785 				t = Mutaters.NULL;
786 			toMutaters.put(c, t);
787 		}
788 		return t == Mutaters.NULL ? null : t;
789 	}
790 
791 	/**
792 	 * For {@code Map} types, returns the class type of the values of the {@code Map}.
793 	 *
794 	 * @return The value class type, or <jk>null</jk> if this class is not a Map.
795 	 */
796 	public ClassMeta<?> getValueType() {
797 		return keyValueTypes.get().valueType();
798 	}
799 
800 	/**
801 	 * Returns <jk>true</jk> if this class has a transform associated with it that allows it to be created from an InputStream.
802 	 *
803 	 * @return <jk>true</jk> if this class has a transform associated with it that allows it to be created from an InputStream.
804 	 */
805 	public boolean hasInputStreamMutater() {
806 		return hasMutaterFrom(InputStream.class);
807 	}
808 
809 	/**
810 	 * Returns <jk>true</jk> if this class can be instantiated from the specified type.
811 	 *
812 	 * @param c The class type to convert from.
813 	 * @return <jk>true</jk> if this class can be instantiated from the specified type.
814 	 */
815 	public boolean hasMutaterFrom(Class<?> c) {
816 		return nn(getFromMutater(c));
817 	}
818 
819 	/**
820 	 * Returns <jk>true</jk> if this class can be instantiated from the specified type.
821 	 *
822 	 * @param c The class type to convert from.
823 	 * @return <jk>true</jk> if this class can be instantiated from the specified type.
824 	 */
825 	public boolean hasMutaterFrom(ClassMeta<?> c) {
826 		return nn(getFromMutater(c.inner()));
827 	}
828 
829 	/**
830 	 * Returns <jk>true</jk> if this class can be transformed to the specified type.
831 	 *
832 	 * @param c The class type to convert from.
833 	 * @return <jk>true</jk> if this class can be transformed to the specified type.
834 	 */
835 	public boolean hasMutaterTo(Class<?> c) {
836 		return nn(getToMutater(c));
837 	}
838 
839 	/**
840 	 * Returns <jk>true</jk> if this class can be transformed to the specified type.
841 	 *
842 	 * @param c The class type to convert from.
843 	 * @return <jk>true</jk> if this class can be transformed to the specified type.
844 	 */
845 	public boolean hasMutaterTo(ClassMeta<?> c) {
846 		return nn(getToMutater(c.inner()));
847 	}
848 
849 	/**
850 	 * Returns <jk>true</jk> if this class has a transform associated with it that allows it to be created from a Reader.
851 	 *
852 	 * @return <jk>true</jk> if this class has a transform associated with it that allows it to be created from a Reader.
853 	 */
854 	public boolean hasReaderMutater() {
855 		return hasMutaterFrom(Reader.class);
856 	}
857 
858 	/**
859 	 * Returns <jk>true</jk> if this class has a transform associated with it that allows it to be created from a String.
860 	 *
861 	 * @return <jk>true</jk> if this class has a transform associated with it that allows it to be created from a String.
862 	 */
863 	public boolean hasStringMutater() {
864 		return nn(stringMutater);
865 	}
866 
867 	/**
868 	 * Returns <jk>true</jk> if this metadata represents an array of argument types.
869 	 *
870 	 * @return <jk>true</jk> if this metadata represents an array of argument types.
871 	 */
872 	public boolean isArgs() { return cat.is(ARGS); }
873 
874 	/**
875 	 * Returns <jk>true</jk> if this class is a bean.
876 	 *
877 	 * @return <jk>true</jk> if this class is a bean.
878 	 */
879 	public boolean isBean() { return nn(getBeanMeta()); }
880 
881 	/**
882 	 * Returns <jk>true</jk> if this class is a subclass of {@link BeanMap}.
883 	 *
884 	 * @return <jk>true</jk> if this class is a subclass of {@link BeanMap}.
885 	 */
886 	public boolean isBeanMap() { return cat.is(BEANMAP); }
887 
888 	/**
889 	 * Returns <jk>true</jk> if this class is a {@link Boolean}.
890 	 *
891 	 * @return <jk>true</jk> if this class is a {@link Boolean}.
892 	 */
893 	public boolean isBoolean() { return isAny(boolean.class, Boolean.class); }
894 
895 	/**
896 	 * Returns <jk>true</jk> if this class is <code><jk>byte</jk>[]</code>.
897 	 *
898 	 * @return <jk>true</jk> if this class is <code><jk>byte</jk>[]</code>.
899 	 */
900 	public boolean isByteArray() { return is(byte[].class); }
901 
902 	/**
903 	 * Returns <jk>true</jk> if this class is a {@link Calendar}.
904 	 *
905 	 * @return <jk>true</jk> if this class is a {@link Calendar}.
906 	 */
907 	public boolean isCalendar() { return cat.is(CALENDAR); }
908 
909 	/**
910 	 * Returns <jk>true</jk> if this class is a {@link Character}.
911 	 *
912 	 * @return <jk>true</jk> if this class is a {@link Character}.
913 	 */
914 	public boolean isChar() { return isAny(char.class, Character.class); }
915 
916 	/**
917 	 * Returns <jk>true</jk> if this class is a subclass of {@link CharSequence}.
918 	 *
919 	 * @return <jk>true</jk> if this class is a subclass of {@link CharSequence}.
920 	 */
921 	public boolean isCharSequence() { return cat.is(CHARSEQ); }
922 
923 	/**
924 	 * Returns <jk>true</jk> if this class is a subclass of {@link Collection}.
925 	 *
926 	 * @return <jk>true</jk> if this class is a subclass of {@link Collection}.
927 	 */
928 	public boolean isCollection() { return cat != null && cat.is(COLLECTION); }
929 
930 	/**
931 	 * Returns <jk>true</jk> if this class is a subclass of {@link Collection} or is an array or {@link Optional}.
932 	 *
933 	 * @return <jk>true</jk> if this class is a subclass of {@link Collection} or is an array or {@link Optional}.
934 	 */
935 	public boolean isCollectionOrArrayOrOptional() { return cat.is(ARRAY) || is(Optional.class) || cat.is(COLLECTION); }
936 
937 	/**
938 	 * Returns <jk>true</jk> if this class is a {@link Date}.
939 	 *
940 	 * @return <jk>true</jk> if this class is a {@link Date}.
941 	 */
942 	public boolean isDate() { return cat.is(DATE); }
943 
944 	/**
945 	 * Returns <jk>true</jk> if this class is a {@link Date} or {@link Calendar}.
946 	 *
947 	 * @return <jk>true</jk> if this class is a {@link Date} or {@link Calendar}.
948 	 */
949 	public boolean isDateOrCalendar() { return cat.is(DATE) || cat.is(CALENDAR); }
950 
951 	/**
952 	 * Returns <jk>true</jk> if this class is a {@link Date} or {@link Calendar} or {@link Temporal}.
953 	 *
954 	 * @return <jk>true</jk> if this class is a {@link Date} or {@link Calendar} or {@link Temporal}.
955 	 */
956 	public boolean isDateOrCalendarOrTemporal() { return cat.is(DATE) || cat.is(CALENDAR) || cat.is(TEMPORAL); }
957 
958 	/**
959 	 * Returns <jk>true</jk> if this class is a subclass of {@link Float} or {@link Double}.
960 	 *
961 	 * @return <jk>true</jk> if this class is a subclass of {@link Float} or {@link Double}.
962 	 */
963 	public boolean isDecimal() { return cat.is(DECIMAL); }
964 
965 	/**
966 	 * Returns <jk>true</jk> if this class implements {@link Delegate}, meaning it's a representation of some other
967 	 * object.
968 	 *
969 	 * @return <jk>true</jk> if this class implements {@link Delegate}.
970 	 */
971 	public boolean isDelegate() { return cat.is(DELEGATE); }
972 
973 	/**
974 	 * Returns <jk>true</jk> if this class is either {@link Double} or <jk>double</jk>.
975 	 *
976 	 * @return <jk>true</jk> if this class is either {@link Double} or <jk>double</jk>.
977 	 */
978 	public boolean isDouble() { return isAny(Double.class, double.class); }
979 
980 	/**
981 	 * Returns <jk>true</jk> if this class is either {@link Float} or <jk>float</jk>.
982 	 *
983 	 * @return <jk>true</jk> if this class is either {@link Float} or <jk>float</jk>.
984 	 */
985 	public boolean isFloat() { return isAny(Float.class, float.class); }
986 
987 	/**
988 	 * Returns <jk>true</jk> if this class is an {@link InputStream}.
989 	 *
990 	 * @return <jk>true</jk> if this class is an {@link InputStream}.
991 	 */
992 	public boolean isInputStream() { return cat.is(INPUTSTREAM); }
993 
994 	/**
995 	 * Returns <jk>true</jk> if this class is either {@link Integer} or <jk>int</jk>.
996 	 *
997 	 * @return <jk>true</jk> if this class is either {@link Integer} or <jk>int</jk>.
998 	 */
999 	public boolean isInteger() { return isAny(Integer.class, int.class); }
1000 
1001 	/**
1002 	 * Returns <jk>true</jk> if this class extends from {@link List}.
1003 	 *
1004 	 * @return <jk>true</jk> if this class extends from {@link List}.
1005 	 */
1006 	public boolean isList() { return cat.is(LIST); }
1007 
1008 	/**
1009 	 * Returns <jk>true</jk> if this class is either {@link Long} or <jk>long</jk>.
1010 	 *
1011 	 * @return <jk>true</jk> if this class is either {@link Long} or <jk>long</jk>.
1012 	 */
1013 	public boolean isLong() { return isAny(Long.class, long.class); }
1014 
1015 	/**
1016 	 * Returns <jk>true</jk> if this class is a subclass of {@link Map}.
1017 	 *
1018 	 * @return <jk>true</jk> if this class is a subclass of {@link Map}.
1019 	 */
1020 	public boolean isMap() {
1021 		// TODO - Figure out how cat can be null.
1022 		return cat != null && cat.is(MAP);
1023 	}
1024 
1025 	/**
1026 	 * Returns <jk>true</jk> if this class is a subclass of {@link Map} or it's a bean.
1027 	 *
1028 	 * @return <jk>true</jk> if this class is a subclass of {@link Map} or it's a bean.
1029 	 */
1030 	public boolean isMapOrBean() { return cat.is(MAP) || nn(getBeanMeta()); }
1031 
1032 	/**
1033 	 * Returns <jk>true</jk> if this class is {@link Method}.
1034 	 *
1035 	 * @return <jk>true</jk> if this class is {@link Method}.
1036 	 */
1037 	public boolean isMethod() { return is(Method.class); }
1038 
1039 	/**
1040 	 * Returns <jk>true</jk> if instance of this object can be <jk>null</jk>.
1041 	 *
1042 	 * <p>
1043 	 * Objects can be <jk>null</jk>, but primitives cannot, except for chars which can be represented by
1044 	 * <code>(<jk>char</jk>)0</code>.
1045 	 *
1046 	 * @return <jk>true</jk> if instance of this class can be null.
1047 	 */
1048 	public boolean isNullable() {
1049 		if (isPrimitive())
1050 			return is(char.class);
1051 		return true;
1052 	}
1053 
1054 	/**
1055 	 * Returns <jk>true</jk> if this class is a subclass of {@link Number}.
1056 	 *
1057 	 * @return <jk>true</jk> if this class is a subclass of {@link Number}.
1058 	 */
1059 	public boolean isNumber() { return cat.is(NUMBER); }
1060 
1061 	/**
1062 	 * Returns <jk>true</jk> if this class is {@link Object}.
1063 	 *
1064 	 * @return <jk>true</jk> if this class is {@link Object}.
1065 	 */
1066 	public boolean isObject() { return is(Object.class); }
1067 
1068 	/**
1069 	 * Returns <jk>true</jk> if this class is a subclass of {@link Optional}.
1070 	 *
1071 	 * @return <jk>true</jk> if this class is a subclass of {@link Optional}.
1072 	 */
1073 	public boolean isOptional() { return is(Optional.class); }
1074 
1075 	/**
1076 	 * Returns <jk>true</jk> if this class is a {@link Reader}.
1077 	 *
1078 	 * @return <jk>true</jk> if this class is a {@link Reader}.
1079 	 */
1080 	public boolean isReader() { return cat.is(READER); }
1081 
1082 	/**
1083 	 * Returns <jk>true</jk> if this class extends from {@link Set}.
1084 	 *
1085 	 * @return <jk>true</jk> if this class extends from {@link Set}.
1086 	 */
1087 	public boolean isSet() { return cat.is(SET); }
1088 
1089 	/**
1090 	 * Returns <jk>true</jk> if this class is either {@link Short} or <jk>short</jk>.
1091 	 *
1092 	 * @return <jk>true</jk> if this class is either {@link Short} or <jk>short</jk>.
1093 	 */
1094 	public boolean isShort() { return isAny(Short.class, short.class); }
1095 
1096 	/**
1097 	 * Returns <jk>true</jk> if this class is a {@link String}.
1098 	 *
1099 	 * @return <jk>true</jk> if this class is a {@link String}.
1100 	 */
1101 	public boolean isString() { return is(String.class); }
1102 
1103 	/**
1104 	 * Returns <jk>true</jk> if this class is a {@link Temporal}.
1105 	 *
1106 	 * @return <jk>true</jk> if this class is a {@link Temporal}.
1107 	 */
1108 	public boolean isTemporal() { return cat.is(TEMPORAL); }
1109 
1110 	/**
1111 	 * Returns <jk>true</jk> if this class is a {@link URI} or {@link URL}.
1112 	 *
1113 	 * @return <jk>true</jk> if this class is a {@link URI} or {@link URL}.
1114 	 */
1115 	public boolean isUri() { return cat != null && cat.is(URI); }
1116 
1117 	/**
1118 	 * Transforms the specified object into an instance of this class.
1119 	 *
1120 	 * @param o The object to transform.
1121 	 * @return The transformed object.
1122 	 */
1123 	@SuppressWarnings({ "unchecked", "rawtypes" })
1124 	public T mutateFrom(Object o) {
1125 		Mutater t = getFromMutater(o.getClass());
1126 		return (T)(t == null ? null : t.mutate(o));
1127 	}
1128 
1129 	/**
1130 	 * Transforms the specified object into an instance of this class.
1131 	 *
1132 	 * @param <O> The transform-to class.
1133 	 * @param o The object to transform.
1134 	 * @param c The class
1135 	 * @return The transformed object.
1136 	 */
1137 	@SuppressWarnings({ "unchecked" })
1138 	public <O> O mutateTo(Object o, Class<O> c) {
1139 		Mutater<Object,O> t = (Mutater<Object,O>)getToMutater(c);
1140 		return t == null ? null : t.mutate(o);
1141 	}
1142 
1143 	/**
1144 	 * Transforms the specified object into an instance of this class.
1145 	 *
1146 	 * @param <O> The transform-to class.
1147 	 * @param o The object to transform.
1148 	 * @param c The class
1149 	 * @return The transformed object.
1150 	 */
1151 	public <O> O mutateTo(Object o, ClassMeta<O> c) {
1152 		return mutateTo(o, c.inner());
1153 	}
1154 
1155 	/**
1156 	 * Create a new instance of the main class of this declared type.
1157 	 *
1158 	 * @return A new instance of the object, or <jk>null</jk> if there is no no-arg constructor on the object.
1159 	 * @throws ExecutableException Exception occurred on invoked constructor/method/field.
1160 	 */
1161 	@Override
1162 	@SuppressWarnings("unchecked")
1163 	public T newInstance() throws ExecutableException {
1164 		if (super.isArray())
1165 			return (T)Array.newInstance(inner().getComponentType(), 0);
1166 		if (noArgConstructor.isPresent())
1167 			return noArgConstructor.get().newInstance();
1168 		var h = getProxyInvocationHandler();
1169 		if (nn(h))
1170 			return (T)Proxy.newProxyInstance(this.getClass().getClassLoader(), a(inner(), java.io.Serializable.class), h);
1171 		return null;
1172 	}
1173 
1174 	/**
1175 	 * Same as {@link #newInstance()} except for instantiating non-static member classes.
1176 	 *
1177 	 * @param outer
1178 	 * 	The instance of the owning object of the member class instance.
1179 	 * 	Can be <jk>null</jk> if instantiating a non-member or static class.
1180 	 * @return A new instance of the object, or <jk>null</jk> if there is no no-arg constructor on the object.
1181 	 * @throws ExecutableException Exception occurred on invoked constructor/method/field.
1182 	 */
1183 	public T newInstance(Object outer) throws ExecutableException {
1184 		if (isMemberClass() && isNotStatic() && noArgConstructor.isPresent())
1185 			return noArgConstructor.get().<T>newInstance(outer);
1186 		return newInstance();
1187 	}
1188 
1189 	/**
1190 	 * Create a new instance of the main class of this declared type from a <c>String</c> input.
1191 	 *
1192 	 * <p>
1193 	 * In order to use this method, the class must have one of the following methods:
1194 	 * <ul>
1195 	 * 	<li><code><jk>public static</jk> T valueOf(String in);</code>
1196 	 * 	<li><code><jk>public static</jk> T fromString(String in);</code>
1197 	 * 	<li><code><jk>public</jk> T(String in);</code>
1198 	 * </ul>
1199 	 *
1200 	 * @param outer
1201 	 * 	The outer class object for non-static member classes.  Can be <jk>null</jk> for non-member or static classes.
1202 	 * @param arg The input argument value.
1203 	 * @return A new instance of the object, or <jk>null</jk> if there is no string constructor on the object.
1204 	 * @throws ExecutableException Exception occurred on invoked constructor/method/field.
1205 	 */
1206 	@SuppressWarnings({ "unchecked" })
1207 	public T newInstanceFromString(Object outer, String arg) throws ExecutableException {
1208 
1209 		if (isEnum()) {
1210 			var t = (T)enumValues.get().getKey(arg);
1211 			if (t == null && ! beanContext.isIgnoreUnknownEnumValues())
1212 				throw new ExecutableException("Could not resolve enum value ''{0}'' on class ''{1}''", arg, cn(inner()));
1213 			return t;
1214 		}
1215 
1216 		if (fromStringMethod.isPresent())
1217 			return (T)fromStringMethod.get().invoke(null, arg);
1218 
1219 		if (stringConstructor.isPresent()) {
1220 			if (isMemberClass() && isNotStatic())
1221 				return stringConstructor.get().<T>newInstance(outer, arg);
1222 			return stringConstructor.get().<T>newInstance(arg);
1223 		}
1224 		throw new ExecutableException("No string constructor or valueOf(String) method found for class '" + cn(inner()) + "'");
1225 	}
1226 
1227 	/**
1228 	 * Similar to {@link #equals(Object)} except primitive and Object types that are similar are considered the same.
1229 	 * (e.g. <jk>boolean</jk> == <c>Boolean</c>).
1230 	 *
1231 	 * @param cm The class meta to compare to.
1232 	 * @return <jk>true</jk> if the specified class-meta is equivalent to this one.
1233 	 */
1234 	public boolean same(ClassMeta<?> cm) {
1235 		if (equals(cm))
1236 			return true;
1237 		return (isPrimitive() && cat.same(cm.cat));
1238 	}
1239 
1240 	@Override /* Overridden from Object */
1241 	public String toString() {
1242 		return toString(false);
1243 	}
1244 
1245 	/**
1246 	 * Same as {@link #toString()} except use simple class names.
1247 	 *
1248 	 * @param simple Print simple class names only (no package).
1249 	 * @return A new string.
1250 	 */
1251 	public String toString(boolean simple) {
1252 		return toString(new StringBuilder(), simple).toString();
1253 	}
1254 
1255 	/**
1256 	 * Converts the specified object to a string.
1257 	 *
1258 	 * @param t The object to convert.
1259 	 * @return The object converted to a string, or <jk>null</jk> if the object was null.
1260 	 */
1261 	public String toString(Object t) {
1262 		if (t == null)
1263 			return null;
1264 		if (isEnum() && beanContext.isUseEnumNames())
1265 			return ((Enum<?>)t).name();
1266 		return t.toString();
1267 	}
1268 
1269 	@SuppressWarnings("unchecked")
1270 	private ObjectSwap<T,?> createSwap(Swap s) {
1271 		var c = s.value();
1272 		if (ClassUtils.isVoid(c))
1273 			c = s.impl();
1274 		var ci = info(c);
1275 
1276 		if (ci.isChildOf(ObjectSwap.class)) {
1277 			var ps = BeanCreator.of(ObjectSwap.class).type(ci).run();
1278 			if (s.mediaTypes().length > 0)
1279 				ps.forMediaTypes(MediaType.ofAll(s.mediaTypes()));
1280 			if (! s.template().isEmpty())
1281 				ps.withTemplate(s.template());
1282 			return ps;
1283 		}
1284 
1285 		if (ci.isChildOf(Surrogate.class)) {
1286 			List<SurrogateSwap<?,?>> l = SurrogateSwap.findObjectSwaps(c, beanContext);
1287 			if (! l.isEmpty())
1288 				return (ObjectSwap<T,?>)l.iterator().next();
1289 		}
1290 
1291 		throw new ClassMetaRuntimeException(c, "Invalid swap class ''{0}'' specified.  Must extend from ObjectSwap or Surrogate.", c);
1292 	}
1293 
1294 	private String findBeanDictionaryName() {
1295 		if (beanContext == null)
1296 			return null;
1297 
1298 		var d = beanMeta.get().optBeanMeta().map(x -> x.getDictionaryName()).orElse(null);
1299 		if (nn(d))
1300 			return d;
1301 
1302 		// Note that @Bean(typeName) can be defined on non-bean types, so
1303 		// we have to check again.
1304 		return beanContext.getAnnotationProvider().find(Bean.class, this)
1305 			.stream()
1306 			.map(AnnotationInfo::inner)
1307 			.filter(x -> ! x.typeName().isEmpty())
1308 			.map(x -> x.typeName())
1309 			.findFirst()
1310 			.orElse(null);
1311 	}
1312 
1313 	private BeanMeta.BeanMetaValue<T> findBeanMeta() {
1314 		if (! cat.isUnknown())
1315 			return new BeanMeta.BeanMetaValue<>(null, "Known non-bean type");
1316 		return BeanMeta.create(this, implClass.get());
1317 	}
1318 
1319 	private KeyValueTypes findKeyValueTypes() {
1320 		if (cat.is(MAP) && ! cat.is(BEANMAP)) {
1321 			// If this is a MAP, see if it's parameterized (e.g. AddressBook extends HashMap<String,Person>)
1322 			var parameters = beanContext.findParameters(inner(), inner());
1323 			if (nn(parameters) && parameters.length == 2) {
1324 				return new KeyValueTypes(parameters[0], parameters[1]);
1325 			}
1326 			return new KeyValueTypes(beanContext.getClassMeta(Object.class), beanContext.getClassMeta(Object.class));
1327 		}
1328 		return new KeyValueTypes(null, null);
1329 	}
1330 
1331 	private ClassMeta<?> findElementType() {
1332 		if (beanContext == null)
1333 			return null;
1334 		if (cat.is(ARRAY)) {
1335 			return beanContext.getClassMeta(inner().getComponentType());
1336 		} else if (cat.is(COLLECTION) || is(Optional.class)) {
1337 			// If this is a COLLECTION, see if it's parameterized (e.g. AddressBook extends LinkedList<Person>)
1338 			var parameters = beanContext.findParameters(inner(), inner());
1339 			if (nn(parameters) && parameters.length == 1) {
1340 				return parameters[0];
1341 			}
1342 			return beanContext.getClassMeta(Object.class);
1343 		}
1344 		return null;
1345 	}
1346 
1347 	@SuppressWarnings("unchecked")
1348 	private BuilderSwap<T,?> findBuilderSwap() {
1349 		var bc = beanContext;
1350 		if (bc == null)
1351 			return null;
1352 		return (BuilderSwap<T,?>)BuilderSwap.findSwapFromObjectClass(bc, inner(), bc.getBeanConstructorVisibility(), bc.getBeanMethodVisibility());
1353 	}
1354 
1355 	@SuppressWarnings("unchecked")
1356 	private List<ObjectSwap<T,?>> findSwaps() {
1357 		if (beanContext == null)
1358 			return l();
1359 
1360 		var list = new ArrayList<ObjectSwap<T,?>>();
1361 		var swapArray = beanContext.getSwaps();
1362 		if (! swapArray.isEmpty()) {
1363 			var innerClass = inner();
1364 			for (var f : swapArray)
1365 				if (f.getNormalClass().isParentOf(innerClass))
1366 					list.add((ObjectSwap<T,?>)f);
1367 		}
1368 
1369 		var ap = beanContext.getAnnotationProvider();
1370 		ap.find(Swap.class, this).stream().map(AnnotationInfo::inner).forEach(x -> list.add(createSwap(x)));
1371 		var ds = DefaultSwaps.find(this);
1372 		if (ds == null)
1373 			ds = AutoObjectSwap.find(beanContext, this);
1374 		if (ds == null)
1375 			ds = AutoNumberSwap.find(beanContext, this);
1376 		if (ds == null)
1377 			ds = AutoMapSwap.find(beanContext, this);
1378 		if (ds == null)
1379 			ds = AutoListSwap.find(beanContext, this);
1380 
1381 		if (nn(ds))
1382 			list.add((ObjectSwap<T,?>)ds);
1383 
1384 		return u(list);
1385 	}
1386 
1387 	private List<ObjectSwap<?,?>> findChildSwaps() {
1388 		if (beanContext == null)
1389 			return l();
1390 		var swapArray = beanContext.getSwaps();
1391 		if (swapArray.isEmpty())
1392 			return l();
1393 		var list = new ArrayList<ObjectSwap<?,?>>();
1394 		var innerClass = inner();
1395 		for (var f : swapArray)
1396 			if (f.getNormalClass().isChildOf(innerClass))
1397 				list.add(f);
1398 		return u(list);
1399 	}
1400 
1401 	private BidiMap<Object,String> findEnumValues() {
1402 		if (! isEnum())
1403 			return null;
1404 
1405 		var bc = beanContext;
1406 		var useEnumNames = nn(bc) && bc.isUseEnumNames();
1407 
1408 		var m = BidiMap.<Object,String>create().unmodifiable();
1409 		var c = inner().asSubclass(Enum.class);
1410 		stream(c.getEnumConstants()).forEach(x -> m.add(x, useEnumNames ? x.name() : x.toString()));
1411 		return m.build();
1412 	}
1413 
1414 	@SuppressWarnings("unchecked")
1415 	private String findExample() {
1416 
1417 		var example = beanMeta.get().optBeanMeta().map(x -> x.getBeanFilter()).map(x -> x.getExample()).orElse(null);
1418 
1419 		if (example == null)
1420 			example = marshalledFilter.map(x -> x.getExample()).orElse(null);
1421 
1422 		if (example == null && nn(beanContext))
1423 			example = beanContext.getAnnotationProvider().find(Example.class, this).stream().map(x -> x.inner().value()).filter(Utils::ne).findFirst().orElse(null);
1424 
1425 		if (example == null) {
1426 			if (isAny(boolean.class, Boolean.class)) {
1427 				example = "true";
1428 			} else if (isAny(char.class, Character.class)) {
1429 				example = "a";
1430 			} else if (cat.is(CHARSEQ)) {
1431 				example = "foo";
1432 			} else if (cat.is(ENUM)) {
1433 				Iterator<? extends Enum<?>> i = EnumSet.allOf(inner().asSubclass(Enum.class)).iterator();
1434 				example = i.hasNext() ? (beanContext.isUseEnumNames() ? i.next().name() : i.next().toString()) : null;
1435 			} else if (isAny(float.class, Float.class, double.class, Double.class)) {
1436 				example = "1.0";
1437 			} else if (isAny(short.class, Short.class, int.class, Integer.class, long.class, Long.class)) {
1438 				example = "1";
1439 			}
1440 		}
1441 
1442 		return example;
1443 	}
1444 
1445 	private FieldInfo findExampleField() {
1446 		var ap = beanContext.getAnnotationProvider();
1447 
1448 		return getDeclaredFields()
1449 			.stream()
1450 			.filter(x -> x.isStatic() && isParentOf(x.getFieldType()) && ap.has(Example.class, x))
1451 			.map(x -> x.accessible())
1452 			.findFirst()
1453 			.orElse(null);
1454 	}
1455 
1456 	private MethodInfo findExampleMethod() {
1457 		// @formatter:off
1458 		var ap = beanContext.getAnnotationProvider();
1459 
1460 		// Option 1:  Public example() or @Example method.
1461 		var m = getPublicMethod(
1462 			x -> x.isStatic() && x.isNotDeprecated() && (x.hasName("example") || ap.has(Example.class, x)) && x.hasParameterTypesLenient(BeanSession.class)
1463 		);
1464 		if (m.isPresent()) return m.get();
1465 
1466 		// Option 2:  Non-public @Example method.
1467 		return getDeclaredMethods()
1468 			.stream()
1469 			.flatMap(x -> x.getMatchingMethods().stream())
1470 			.filter(x -> x.isStatic() && ap.has(Example.class, x))
1471 			.map(x -> x.accessible())
1472 			.findFirst()
1473 			.orElse(null);
1474 		// @formatter:on
1475 	}
1476 
1477 	private MethodInfo findFromStringMethod() {
1478 		// Find static fromString(String) or equivalent method.
1479 		// fromString() must be checked before valueOf() so that Enum classes can create their own
1480 		//		specialized fromString() methods to override the behavior of Enum.valueOf(String).
1481 		// valueOf() is used by enums.
1482 		// parse() is used by the java logging Level class.
1483 		// forName() is used by Class and Charset
1484 		// @formatter:off
1485 		var names = a("fromString", "fromValue", "valueOf", "parse", "parseString", "forName", "forString");
1486 		return getPublicMethod(
1487 			x -> x.isStatic() && x.isNotDeprecated() && x.hasReturnType(this) && x.hasParameterTypes(String.class) && contains(x.getName(), names)
1488 		).orElse(null);
1489 		// @formatter:on
1490 	}
1491 
1492 	@SuppressWarnings("unchecked")
1493 	private ClassInfoTyped<? extends T> findImplClass() {
1494 
1495 		if (is(Object.class))
1496 			return null;
1497 
1498 		var v = beanContext.getAnnotationProvider().find(Bean.class, this).stream().map(x -> x.inner()).filter(x -> neq(x.implClass(), void.class)).map(x -> ClassInfo.of(x)).findFirst().orElse(null);
1499 
1500 		if (v == null)
1501 			v = marshalledFilter.map(x -> x.getImplClass()).map(ReflectionUtils::info).orElse(null);
1502 
1503 		return (ClassInfoTyped<? extends T>)v;
1504 	}
1505 
1506 	private MarshalledFilter findMarshalledFilter() {
1507 		var ap = beanContext.getAnnotationProvider();
1508 		var l = ap.find(Marshalled.class, this);
1509 		if (l.isEmpty())
1510 			return null;
1511 		return MarshalledFilter.create(inner()).applyAnnotations(reverse(l.stream().map(AnnotationInfo::inner).toList())).build();
1512 	}
1513 
1514 	private Property<T,Object> findNameProperty() {
1515 		var ap = beanContext.getAnnotationProvider();
1516 
1517 		var s = getAllFields()
1518 			.stream()
1519 			.filter(x -> ap.has(NameProperty.class, x))
1520 			.map(x -> x.accessible())
1521 			.map(x -> Property.<T,Object>create().field(x).build())
1522 			.findFirst();
1523 
1524 		if (s.isPresent()) return s.get();
1525 
1526 		var builder = Property.<T,Object>create();
1527 
1528 		// Look for setter method (1 parameter) with @NameProperty
1529 		var setterMethod = getAllMethods()
1530 			.stream()
1531 			.filter(x -> ap.has(NameProperty.class, x) && x.hasNumParameters(1))
1532 			.findFirst();
1533 
1534 		if (setterMethod.isPresent()) {
1535 			builder.setter(setterMethod.get().accessible());
1536 
1537 			// Try to find a corresponding getter method (even if not annotated)
1538 			// If setter is "setName", look for "getName" or "isName"
1539 			var setterName = setterMethod.get().getSimpleName();
1540 			if (setterName.startsWith("set") && setterName.length() > 3) {
1541 				var propertyName = setterName.substring(3);
1542 				var getterName1 = "get" + propertyName;
1543 				var getterName2 = "is" + propertyName;
1544 
1545 				var getter = getAllMethods()
1546 					.stream()
1547 					.filter(x -> !x.isStatic() && x.hasNumParameters(0) &&
1548 						(x.hasName(getterName1) || x.hasName(getterName2)) &&
1549 						!x.getReturnType().is(Void.TYPE))
1550 					.findFirst();
1551 
1552 				if (getter.isPresent()) {
1553 					builder.getter(getter.get().accessible());
1554 				} else {
1555 					// Try to find a field with the property name (lowercase first letter)
1556 					var fieldName = Character.toLowerCase(propertyName.charAt(0)) + propertyName.substring(1);
1557 					var field = getAllFields()
1558 						.stream()
1559 						.filter(x -> !x.isStatic() && x.hasName(fieldName))
1560 						.findFirst();
1561 
1562 					if (field.isPresent()) {
1563 						var f = field.get().accessible();
1564 						builder.getter(obj -> f.get(obj));
1565 					}
1566 				}
1567 			}
1568 		}
1569 
1570 		// Look for getter method (0 parameters, non-void return) with @NameProperty
1571 		var getterMethod = getAllMethods()
1572 			.stream()
1573 			.filter(x -> ap.has(NameProperty.class, x) && x.hasNumParameters(0) && !x.getReturnType().is(Void.TYPE))
1574 			.findFirst();
1575 
1576 		if (getterMethod.isPresent()) {
1577 			builder.getter(getterMethod.get().accessible());
1578 		}
1579 
1580 		// Return null if neither setter nor getter was found
1581 		if (setterMethod.isEmpty() && getterMethod.isEmpty())
1582 			return null;
1583 
1584 		return builder.build();
1585 	}
1586 
1587 	private ConstructorInfo findNoArgConstructor() {
1588 
1589 		if (is(Object.class))
1590 			return null;
1591 
1592 		if (implClass.isPresent())
1593 			return implClass.get().getPublicConstructor(x -> x.hasNumParameters(0)).orElse(null);
1594 
1595 		if (isAbstract())
1596 			return null;
1597 
1598 		var numParams = isMemberClass() && isNotStatic() ? 1 : 0;
1599 		return getPublicConstructors()
1600 			.stream()
1601 			.filter(x -> x.isPublic() && x.isNotDeprecated() && x.hasNumParameters(numParams))
1602 			.findFirst()
1603 			.orElse(null);
1604 	}
1605 
1606 	private Property<T,Object> findParentProperty() {
1607 		var ap = beanContext.getAnnotationProvider();
1608 
1609 		var s = getAllFields()
1610 			.stream()
1611 			.filter(x -> ap.has(ParentProperty.class, x))
1612 			.map(x -> x.accessible())
1613 			.map(x -> Property.<T,Object>create().field(x).build())
1614 			.findFirst();
1615 
1616 		if (s.isPresent()) return s.get();
1617 
1618 		var builder = Property.<T,Object>create();
1619 
1620 		// Look for setter method (1 parameter) with @ParentProperty
1621 		var setterMethod = getAllMethods()
1622 			.stream()
1623 			.filter(x -> ap.has(ParentProperty.class, x) && x.hasNumParameters(1))
1624 			.findFirst();
1625 
1626 		if (setterMethod.isPresent()) {
1627 			builder.setter(setterMethod.get().accessible());
1628 
1629 			// Try to find a corresponding getter method (even if not annotated)
1630 			// If setter is "setParent", look for "getParent" or "isParent"
1631 			var setterName = setterMethod.get().getSimpleName();
1632 			if (setterName.startsWith("set") && setterName.length() > 3) {
1633 				var propertyName = setterName.substring(3);
1634 				var getterName1 = "get" + propertyName;
1635 				var getterName2 = "is" + propertyName;
1636 
1637 				var getter = getAllMethods()
1638 					.stream()
1639 					.filter(x -> !x.isStatic() && x.hasNumParameters(0) &&
1640 						(x.hasName(getterName1) || x.hasName(getterName2)) &&
1641 						!x.getReturnType().is(Void.TYPE))
1642 					.findFirst();
1643 
1644 				if (getter.isPresent()) {
1645 					builder.getter(getter.get().accessible());
1646 				} else {
1647 					// Try to find a field with the property name (lowercase first letter)
1648 					var fieldName = Character.toLowerCase(propertyName.charAt(0)) + propertyName.substring(1);
1649 					var field = getAllFields()
1650 						.stream()
1651 						.filter(x -> !x.isStatic() && x.hasName(fieldName))
1652 						.findFirst();
1653 
1654 					if (field.isPresent()) {
1655 						var f = field.get().accessible();
1656 						builder.getter(obj -> f.get(obj));
1657 					}
1658 				}
1659 			}
1660 		}
1661 
1662 		// Look for getter method (0 parameters, non-void return) with @ParentProperty
1663 		var getterMethod = getAllMethods()
1664 			.stream()
1665 			.filter(x -> ap.has(ParentProperty.class, x) && x.hasNumParameters(0) && !x.getReturnType().is(Void.TYPE))
1666 			.findFirst();
1667 
1668 		if (getterMethod.isPresent()) {
1669 			builder.getter(getterMethod.get().accessible());
1670 		}
1671 
1672 		// Return null if neither setter nor getter was found
1673 		if (setterMethod.isEmpty() && getterMethod.isEmpty())
1674 			return null;
1675 
1676 		return builder.build();
1677 	}
1678 
1679 	private ConstructorInfo findStringConstructor() {
1680 
1681 		if (is(Object.class) || isAbstract())
1682 			return null;
1683 
1684 		if (implClass.isPresent())
1685 			return implClass.get().getPublicConstructor(x -> x.hasParameterTypes(String.class)).orElse(null);
1686 
1687 		if (isAbstract())
1688 			return null;
1689 
1690 		var numParams = isMemberClass() && isNotStatic() ? 2 : 1;
1691 		return getPublicConstructors()
1692 			.stream()
1693 			.filter(x -> x.isPublic() && x.isNotDeprecated() && x.hasNumParameters(numParams))
1694 			.filter(x -> x.getParameter(numParams == 2 ? 1 : 0).isType(String.class))
1695 			.findFirst()
1696 			.orElse(null);
1697 	}
1698 
1699 	/**
1700 	 * Returns the {@link ObjectSwap} where the specified class is the same/subclass of the normal class of one of the
1701 	 * child POJO swaps associated with this class.
1702 	 *
1703 	 * @param normalClass The normal class being resolved.
1704 	 * @return The resolved {@link ObjectSwap} or <jk>null</jk> if none were found.
1705 	 */
1706 	protected ObjectSwap<?,?> getChildObjectSwapForSwap(Class<?> normalClass) {
1707 		return childSwapMap.get(normalClass);
1708 	}
1709 
1710 	/**
1711 	 * Returns the {@link ObjectSwap} where the specified class is the same/subclass of the swap class of one of the child
1712 	 * POJO swaps associated with this class.
1713 	 *
1714 	 * @param swapClass The swap class being resolved.
1715 	 * @return The resolved {@link ObjectSwap} or <jk>null</jk> if none were found.
1716 	 */
1717 	protected ObjectSwap<?,?> getChildObjectSwapForUnswap(Class<?> swapClass) {
1718 		return childUnswapMap.get(swapClass);
1719 	}
1720 
1721 	/**
1722 	 * Returns <jk>true</jk> if this class or any child classes has a {@link ObjectSwap} associated with it.
1723 	 *
1724 	 * <p>
1725 	 * Used when transforming bean properties to prevent having to look up transforms if we know for certain that no
1726 	 * transforms are associated with a bean property.
1727 	 *
1728 	 * @return <jk>true</jk> if this class or any child classes has a {@link ObjectSwap} associated with it.
1729 	 */
1730 	protected boolean hasChildSwaps() {
1731 		return ! childSwaps.get().isEmpty();
1732 	}
1733 
1734 	/**
1735 	 * Appends this object as a readable string to the specified string builder.
1736 	 *
1737 	 * @param sb The string builder to append this object to.
1738 	 * @param simple Print simple class names only (no package).
1739 	 * @return The passed-in string builder.
1740 	 */
1741 	protected StringBuilder toString(StringBuilder sb, boolean simple) {
1742 		var n = simple ? cnsq(inner()) : cn(inner());
1743 		if (cat.is(ARRAY))
1744 			return elementType.get().toString(sb, simple).append('[').append(']');
1745 		if (cat.is(BEANMAP))
1746 			return sb.append(cn(BeanMap.class)).append('<').append(n).append('>');
1747 		if (cat.is(MAP)) {
1748 			var kvTypes = keyValueTypes.get();
1749 			var kt = kvTypes.optKeyType();
1750 			var vt = kvTypes.optValueType();
1751 			if (kt.isPresent() && vt.isPresent() && kt.get().isObject() && vt.get().isObject())
1752 				return sb.append(n);
1753 			return sb.append(n).append('<').append(kt.map(x -> x.toString(simple)).orElse("?")).append(',').append(vt.map(x -> x.toString(simple)).orElse("?")).append('>');
1754 		}
1755 		if (cat.is(COLLECTION) || is(Optional.class)) {
1756 			var et = elementType.get();
1757 			return sb.append(n).append(et != null && et.isObject() ? "" : "<" + (et == null ? "?" : et.toString(simple)) + ">");
1758 		}
1759 		return sb.append(n);
1760 	}
1761 }