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.bct;
018
019import static java.util.Optional.*;
020import static java.util.stream.Collectors.*;
021import static org.apache.juneau.commons.reflect.ReflectionUtils.*;
022import static org.apache.juneau.commons.utils.CollectionUtils.*;
023import static org.apache.juneau.commons.utils.ObjectUtils.*;
024import static org.apache.juneau.commons.utils.Shorts.*;
025import static org.apache.juneau.commons.utils.ThrowableUtils.*;
026
027import java.io.*;
028import java.lang.reflect.*;
029import java.util.*;
030import java.util.concurrent.*;
031import java.util.function.*;
032import java.util.stream.*;
033
034/**
035 * Default implementation of {@link BeanConverter} for Bean-Centric Test (BCT) object conversion.
036 *
037 * <p>This class provides a comprehensive, extensible framework for converting Java objects to strings
038 * and lists, with sophisticated property access capabilities. It's the core engine behind BCT testing
039 * assertions, handling complex object introspection and value extraction with high performance through
040 * intelligent caching and optimized lookup strategies.</p>
041 *
042 * <h5 class='section'>Key Features:</h5>
043 * <ul>
044 *    <li><b>Extensible Type Handlers:</b> Pluggable stringifiers, listifiers, and swappers for custom types</li>
045 *    <li><b>Performance Optimization:</b> ConcurrentHashMap caching for type-to-handler mappings</li>
046 *    <li><b>Comprehensive Defaults:</b> Built-in support for all common Java types and structures</li>
047 *    <li><b>Configurable Settings:</b> Customizable formatting, delimiters, and display options</li>
048 *    <li><b>Thread Safety:</b> Fully thread-safe implementation suitable for concurrent testing</li>
049 * </ul>
050 *
051 * <h5 class='section'>Architecture Overview:</h5>
052 * <p>The converter uses four types of pluggable handlers:</p>
053 * <dl>
054 *    <dt><b>Stringifiers:</b></dt>
055 *    <dd>Convert objects to string representations with custom formatting rules</dd>
056 *
057 *    <dt><b>Listifiers:</b></dt>
058 *    <dd>Convert collection-like objects to List&lt;Object&gt; for uniform iteration</dd>
059 *
060 *    <dt><b>Swappers:</b></dt>
061 *    <dd>Pre-process objects before conversion (unwrap Optional, call Supplier, etc.)</dd>
062 *
063 *    <dt><b>PropertyExtractors:</b></dt>
064 *    <dd>Define custom property access strategies for nested field navigation (e.g., <js>"user.address.city"</js>)</dd>
065 * </dl>
066 *
067 * <p>PropertyExtractors use a chain-of-responsibility pattern, where each extractor in the chain
068 * is tried until one can handle the property access. The framework includes built-in extractors for:</p>
069 * <ul>
070 *    <li><b>JavaBean properties:</b> Standard getter methods and public fields</li>
071 *    <li><b>Collection/Array access:</b> Numeric indices and size/length properties</li>
072 *    <li><b>Map access:</b> Key-based property retrieval and size property</li>
073 * </ul>
074 *
075 * <h5 class='section'>Default Type Support:</h5>
076 * <p>Out-of-the-box stringification support includes:</p>
077 * <ul>
078 *    <li><b>Collections:</b> List, Set, Queue → <js>"[item1,item2,item3]"</js> format</li>
079 *    <li><b>Maps:</b> Map, Properties → <js>"{key1=value1,key2=value2}"</js> format</li>
080 *    <li><b>Map Entries:</b> Map.Entry → <js>"key=value"</js> format</li>
081 *    <li><b>Arrays:</b> All array types → <js>"[element1,element2]"</js> format</li>
082 *    <li><b>Dates:</b> Date, Calendar → ISO-8601 format</li>
083 *    <li><b>Files/Streams:</b> File, InputStream, Reader → content as hex or text</li>
084 *    <li><b>Reflection:</b> Class, Method, Constructor → human-readable signatures</li>
085 *    <li><b>Enums:</b> Enum values → name() format</li>
086 * </ul>
087 *
088 * <p>Default listification support includes:</p>
089 * <ul>
090 *    <li><b>Collection types:</b> List, Set, Queue, and all subtypes</li>
091 *    <li><b>Iterable objects:</b> Any Iterable implementation</li>
092 *    <li><b>Iterators:</b> Iterator and Enumeration (consumed to list)</li>
093 *    <li><b>Streams:</b> Stream objects (terminated to list)</li>
094 *    <li><b>Optional:</b> Empty list or single-element list</li>
095 *    <li><b>Maps:</b> Converted to list of Map.Entry objects</li>
096 * </ul>
097 *
098 * <p>Default swapping support includes:</p>
099 * <ul>
100 *    <li><b>Optional:</b> Unwrapped to contained value or <jk>null</jk></li>
101 *    <li><b>Supplier:</b> Called to get supplied value</li>
102 *    <li><b>Future:</b> Extracts completed result or returns <js>"&lt;pending&gt;"</js> for incomplete futures (via {@link Swappers#futureSwapper()})</li>
103 * </ul>
104 *
105 * <h5 class='section'>Configuration Settings:</h5>
106 * <p>The converter supports extensive customization via settings:</p>
107 * <dl>
108 *    <dt><code>nullValue</code></dt>
109 *    <dd>String representation for null values (default: <js>"&lt;null&gt;"</js>)</dd>
110 *
111 *    <dt><code>selfValue</code></dt>
112 *    <dd>Special property name that returns the object itself (default: <js>"&lt;self&gt;"</js>)</dd>
113 *
114 *    <dt><code>emptyValue</code></dt>
115 *    <dd>String representation for empty collections (default: <js>"&lt;empty&gt;"</js>)</dd>
116 *
117 *    <dt><code>fieldSeparator</code></dt>
118 *    <dd>Delimiter between collection elements and map entries (default: <js>","</js>)</dd>
119 *
120 *    <dt><code>collectionPrefix/Suffix</code></dt>
121 *    <dd>Brackets around collection content (default: <js>"["</js> and <js>"]"</js>)</dd>
122 *
123 *    <dt><code>mapPrefix/Suffix</code></dt>
124 *    <dd>Brackets around map content (default: <js>"{"</js> and <js>"}"</js>)</dd>
125 *
126 *    <dt><code>mapEntrySeparator</code></dt>
127 *    <dd>Separator between map keys and values (default: <js>"="</js>)</dd>
128 *
129 *    <dt><code>calendarFormat</code></dt>
130 *    <dd>DateTimeFormatter for calendar objects (default: <jsf>ISO_INSTANT</jsf>)</dd>
131 *
132 *    <dt><code>classNameFormat</code></dt>
133 *    <dd>Format for class names: <js>"simple"</js>, <js>"canonical"</js>, or <js>"full"</js> (default: <js>"simple"</js>)</dd>
134 * </dl>
135 *
136 * <h5 class='section'>Usage Examples:</h5>
137 *
138 * <p><b>Basic Usage with Defaults:</b></p>
139 * <p class='bjava'>
140 *    <jc>// Use default converter</jc>
141 *    <jk>var</jk> <jv>converter</jv> = BasicBeanConverter.<jsf>DEFAULT</jsf>;
142 *    <jk>var</jk> <jv>result</jv> = <jv>converter</jv>.stringify(<jv>myObject</jv>);
143 * </p>
144 *
145 * <p><b>Custom Configuration:</b></p>
146 * <p class='bjava'>
147 *    <jc>// Build custom converter</jc>
148 *    <jk>var</jk> <jv>converter</jv> = BasicBeanConverter.<jsm>builder</jsm>()
149 *       .defaultSettings()
150 *       .addSetting(<jsf>SETTING_nullValue</jsf>, <js>"&lt;null&gt;"</js>)
151 *       .addSetting(<jsf>SETTING_fieldSeparator</jsf>, <js>" | "</js>)
152 *       .addStringifier(MyClass.<jk>class</jk>, (<jp>obj</jp>, <jp>conv</jp>) -> <js>"MyClass["</js> + <jp>obj</jp>.getName() + <js>"]"</js>)
153 *       .addListifier(MyIterable.<jk>class</jk>, (<jp>obj</jp>, <jp>conv</jp>) -> <jp>obj</jp>.toList())
154 *       .addSwapper(MyWrapper.<jk>class</jk>, (<jp>obj</jp>, <jp>conv</jp>) -> <jp>obj</jp>.getWrapped())
155 *       .build();
156 * </p>
157 *
158 * <p><b>Complex Property Access:</b></p>
159 * <p class='bjava'>
160 *    <jc>// Extract nested properties</jc>
161 *    <jk>var</jk> <jv>name</jv> = <jv>converter</jv>.getEntry(<jv>user</jv>, <js>"name"</js>);
162 *    <jk>var</jk> <jv>city</jv> = <jv>converter</jv>.getEntry(<jv>user</jv>, <js>"address.city"</js>);
163 *    <jk>var</jk> <jv>firstOrder</jv> = <jv>converter</jv>.getEntry(<jv>user</jv>, <js>"orders.0.id"</js>);
164 *    <jk>var</jk> <jv>orderCount</jv> = <jv>converter</jv>.getEntry(<jv>user</jv>, <js>"orders.length"</js>);
165 * </p>
166 *
167 * <p><b>Special Property Values:</b></p>
168 * <p class='bjava'>
169 *    <jc>// Use special property names</jc>
170 *    <jk>var</jk> <jv>userObj</jv> = <jv>converter</jv>.getEntry(<jv>user</jv>, <js>"&lt;self&gt;"</js>); <jc>// Returns the user object itself</jc>
171 *    <jk>var</jk> <jv>nullValue</jv> = <jv>converter</jv>.getEntry(<jv>user</jv>, <js>"&lt;null&gt;"</js>); <jc>// Returns null</jc>
172 *
173 *    <jc>// Custom self value</jc>
174 *    <jk>var</jk> <jv>customConverter</jv> = BasicBeanConverter.<jsm>builder</jsm>()
175 *       .defaultSettings()
176 *       .addSetting(<jsf>SETTING_selfValue</jsf>, <js>"this"</js>)
177 *       .build();
178 *    <jk>var</jk> <jv>selfRef</jv> = <jv>customConverter</jv>.getEntry(<jv>user</jv>, <js>"this"</js>); <jc>// Returns user object</jc>
179 * </p>
180 *
181 * <h5 class='section'>Performance Characteristics:</h5>
182 * <ul>
183 *    <li><b>Handler Lookup:</b> O(1) average case via ConcurrentHashMap caching</li>
184 *    <li><b>Type Registration:</b> Handlers checked in reverse registration order (last wins)</li>
185 *    <li><b>Inheritance Support:</b> Handlers support class inheritance and interface implementation</li>
186 *    <li><b>Thread Safety:</b> Full concurrency support with no locking overhead after initialization</li>
187 *    <li><b>Memory Efficiency:</b> Minimal object allocation during normal operation</li>
188 * </ul>
189 *
190 * <h5 class='section'>Extension Patterns:</h5>
191 *
192 * <p><b>Custom Type Stringification:</b></p>
193 * <p class='bjava'>
194 *    <jv>builder</jv>.addStringifier(LocalDateTime.<jk>class</jk>, (<jp>dt</jp>, <jp>conv</jp>) ->
195 *       <jp>dt</jp>.format(DateTimeFormatter.<jsf>ISO_LOCAL_DATE_TIME</jsf>));
196 * </p>
197 *
198 * <p><b>Custom Collection Handling:</b></p>
199 * <p class='bjava'>
200 *    <jv>builder</jv>.addListifier(MyCustomCollection.<jk>class</jk>, (<jp>coll</jp>, <jp>conv</jp>) ->
201 *       <jp>coll</jp>.stream().map(<jp>conv</jp>::swap).toList());
202 * </p>
203 *
204 * <p><b>Custom Object Transformation:</b></p>
205 * <p class='bjava'>
206 *    <jv>builder</jv>.addSwapper(LazyValue.<jk>class</jk>, (<jp>lazy</jp>, <jp>conv</jp>) ->
207 *       <jp>lazy</jp>.isEvaluated() ? <jp>lazy</jp>.getValue() : "&lt;unevaluated&gt;");
208 * </p>
209 *
210 * <h5 class='section'>Integration with BCT:</h5>
211 * <p>This class is used internally by all BCT assertion methods in {@link BctAssertions}:</p>
212 * <ul>
213 *    <li>{@link BctAssertions#assertBean(Object, String, String)} - Uses getEntry() for property access</li>
214 *    <li>{@link BctAssertions#assertList(List, Object...)} - Uses stringify() for element comparison</li>
215 *    <li>{@link BctAssertions#assertBeans(Collection, String, String...)} - Uses both getEntry() and stringify()</li>
216 * </ul>
217 *
218 * @see BeanConverter
219 */
220@SuppressWarnings({
221   "java:S115", // Constants use UPPER_snakeCase naming convention
222   "java:S1192", // Duplicated literals (argument/property names) read more clearly inline than as constants
223   "javabugs:S2259", // False positive - null checks are properly handled via framework patterns
224   "rawtypes", // Raw types necessary for generic type handling
225   "unchecked" // Entries registered per Class<T> are cast back to Listifier/Stringifier/Swapper/Sizer<T> when applied
226})
227public class BasicBeanConverter implements BeanConverter {
228
229   /**
230    * Builder for creating customized BasicBeanConverter instances.
231    *
232    * <p>This builder provides a fluent API for configuring custom type handlers, settings,
233    * and property extraction logic. The builder supports registration of four main types
234    * of customizations:</p>
235    *
236    * <h5 class='section'>Type Handlers:</h5>
237    * <ul>
238    *    <li><b>{@link #addStringifier(Class, Stringifier)}</b> - Custom string conversion logic</li>
239    *    <li><b>{@link #addListifier(Class, Listifier)}</b> - Custom list conversion for collection-like objects</li>
240    *    <li><b>{@link #addSwapper(Class, Swapper)}</b> - Pre-processing and object transformation</li>
241    *    <li><b>{@link #addPropertyExtractor(PropertyExtractor)}</b> - Custom property access strategies</li>
242    * </ul>
243    *
244    * <h5 class='section'>PropertyExtractors:</h5>
245    * <p>Property extractors define how the converter accesses object properties during nested
246    * field access (e.g., {@code "user.address.city"}). The converter uses a chain-of-responsibility
247    * pattern, trying each registered extractor until one succeeds:</p>
248    *
249    * <ul>
250    *    <li><b>{@link PropertyExtractors.ObjectPropertyExtractor}</b> - JavaBean-style properties via reflection</li>
251    *    <li><b>{@link PropertyExtractors.ListPropertyExtractor}</b> - Numeric indices and size/length for arrays/collections</li>
252    *    <li><b>{@link PropertyExtractors.MapPropertyExtractor}</b> - Key-based access for Map objects</li>
253    * </ul>
254    *
255    * <p>Custom extractors can be added to handle specialized property access patterns:</p>
256    * <p class='bjava'>
257    *    <jk>var</jk> <jv>converter</jv> = BasicBeanConverter.<jsm>builder</jsm>()
258    *       .defaultSettings()
259    *       .addPropertyExtractor(<jk>new</jk> MyCustomExtractor())
260    *       .addPropertyExtractor((<jp>obj</jp>, <jp>prop</jp>) -&gt; {
261    *          <jk>if</jk> (<jp>obj</jp> <jk>instanceof</jk> MySpecialType <jv>special</jv>) {
262    *             <jk>return</jk> <jv>special</jv>.getCustomProperty(<jp>prop</jp>);
263    *          }
264    *          <jk>return</jk> <jk>null</jk>; <jc>// Try next extractor</jc>
265    *       })
266    *       .build();
267    * </p>
268    *
269    * <h5 class='section'>Default Configuration:</h5>
270    * <p>The {@link #defaultSettings()} method pre-registers comprehensive type handlers
271    * and property extractors for common Java types, providing out-of-the-box functionality
272    * for most use cases while still allowing full customization.</p>
273    *
274    * @see PropertyExtractors
275    * @see PropertyExtractor
276    */
277   public static class Builder {
278      private final Map<String,Object> settings = map();
279      private final List<StringifierEntry<?>> stringifiers = list();
280      private final List<ListifierEntry<?>> listifiers = list();
281      private final List<SizerEntry<?>> sizers = list();
282      private final List<SwapperEntry<?>> swappers = list();
283      private final List<PropertyExtractor> propertyExtractors = list();
284
285      /**
286       * Registers a custom listifier for a specific type.
287       *
288       * <p>Listifiers convert collection-like objects to List&lt;Object&gt;. The BiFunction
289       * receives the object to convert and the converter instance for recursive calls.</p>
290       *
291       * @param <T> The type to handle
292       * @param c The class to register the listifier for
293       * @param l The listification function
294       * @return This builder for method chaining
295       */
296      public <T> Builder addListifier(Class<T> c, Listifier<T> l) {
297         listifiers.add(new ListifierEntry<>(c, l));
298         return this;
299      }
300
301      /**
302       * Registers a custom property extractor for specialized property access logic.
303       *
304       * <p>Property extractors enable custom property access patterns beyond standard JavaBean
305       * conventions. The converter tries extractors in registration order until one returns
306       * a non-<jk>null</jk> value. This allows for:</p>
307       * <ul>
308       *    <li><b>Custom data structures:</b> Special property access for non-standard objects</li>
309       *    <li><b>Database entities:</b> Property access via entity-specific methods</li>
310       *    <li><b>Dynamic properties:</b> Computed or cached property values</li>
311       *    <li><b>Legacy objects:</b> Bridging older APIs with modern property access</li>
312       * </ul>
313       *
314       * <h5 class='section'>Implementation Example:</h5>
315       * <p class='bjava'>
316       *    <jc>// Custom extractor for a specialized data class</jc>
317       *    PropertyExtractor <jv>customExtractor</jv> = (<jp>obj</jp>, <jp>property</jp>) -&gt; {
318       *       <jk>if</jk> (<jp>obj</jp> <jk>instanceof</jk> DatabaseEntity <jv>entity</jv>) {
319       *          <jk>switch</jk> (<jp>property</jp>) {
320       *             <jk>case</jk> <js>"id"</js>: <jk>return</jk> <jv>entity</jv>.getPrimaryKey();
321       *             <jk>case</jk> <js>"displayName"</js>: <jk>return</jk> <jv>entity</jv>.computeDisplayName();
322       *             <jk>case</jk> <js>"metadata"</js>: <jk>return</jk> <jv>entity</jv>.getMetadataAsMap();
323       *          }
324       *       }
325       *       <jk>return</jk> <jk>null</jk>; <jc>// Let next extractor try</jc>
326       *    };
327       *
328       *    <jk>var</jk> <jv>converter</jv> = BasicBeanConverter.<jsm>builder</jsm>()
329       *       .addPropertyExtractor(<jv>customExtractor</jv>)
330       *       .defaultSettings() <jc>// Adds standard extractors</jc>
331       *       .build();
332       * </p>
333       *
334       * <p><b>Execution Order:</b> Custom extractors are tried before the default extractors
335       * added by {@link #defaultSettings()}, allowing overrides of standard behavior.</p>
336       *
337       * @param e The property extractor to register
338       * @return This builder for method chaining
339       * @see PropertyExtractor
340       * @see PropertyExtractors
341       */
342      public Builder addPropertyExtractor(PropertyExtractor e) {
343         propertyExtractors.add(e);
344         return this;
345      }
346
347      /**
348       * Adds a configuration setting to the converter.
349       *
350       * @param key The setting key (use SETTING_* constants)
351       * @param value The setting value
352       * @return This builder for method chaining
353       */
354      public Builder addSetting(String key, Object value) {
355         settings.put(key, value);
356         return this;
357      }
358
359      /**
360       * Registers a custom sizer for a specific type.
361       *
362       * <p>Sizers compute the size of collection-like objects for test assertions.
363       * The function receives the object to size and the converter instance for accessing
364       * additional utilities if needed.</p>
365       *
366       * @param <T> The type to handle
367       * @param c The class to register the sizer for
368       * @param s The sizing function
369       * @return This builder for method chaining
370       */
371      public <T> Builder addSizer(Class<T> c, Sizer<T> s) {
372         sizers.add(new SizerEntry<>(c, s));
373         return this;
374      }
375
376      /**
377       * Registers a custom stringifier for a specific type.
378       *
379       * <p>Stringifiers convert objects to their string representations. The BiFunction
380       * receives the object to convert and the converter instance for recursive calls.</p>
381       *
382       * @param <T> The type to handle
383       * @param c The class to register the stringifier for
384       * @param s The stringification function
385       * @return This builder for method chaining
386       */
387      public <T> Builder addStringifier(Class<T> c, Stringifier<T> s) {
388         stringifiers.add(new StringifierEntry<>(c, s));
389         return this;
390      }
391
392      /**
393       * Registers a custom swapper for a specific type.
394       *
395       * <p>Swappers pre-process objects before conversion. Common uses include
396       * unwrapping Optional values, calling Supplier methods, or extracting values
397       * from wrapper objects.</p>
398       *
399       * @param <T> The type to handle
400       * @param c The class to register the swapper for
401       * @param s The swapping function
402       * @return This builder for method chaining
403       */
404      public <T> Builder addSwapper(Class<T> c, Swapper<T> s) {
405         swappers.add(new SwapperEntry<>(c, s));
406         return this;
407      }
408
409      /**
410       * Builds the configured BasicBeanConverter instance.
411       *
412       * <p>This method creates a new BasicBeanConverter with all registered handlers
413       * and settings. The builder can be reused to create multiple converters with
414       * the same configuration.</p>
415       *
416       * @return A new BasicBeanConverter instance
417       */
418      public BasicBeanConverter build() {
419         return new BasicBeanConverter(this);
420      }
421
422      /**
423       * Adds default handlers and settings for common Java types.
424       *
425       * <p>This method registers comprehensive support for:</p>
426       * <ul>
427       *    <li><b>Collections:</b> List, Set, Collection → bracket format</li>
428       *    <li><b>Maps:</b> Map, Properties → brace format with key=value pairs</li>
429       *    <li><b>Map Entries:</b> Map.Entry → <js>"key=value"</js> format</li>
430       *    <li><b>Dates:</b> Date, Calendar → ISO-8601 format</li>
431       *    <li><b>Files/Streams:</b> File, InputStream, Reader → content extraction</li>
432       *    <li><b>Arrays:</b> byte[], char[] → hex strings and direct string conversion</li>
433       *    <li><b>Reflection:</b> Class, Method, Constructor → readable signatures</li>
434       *    <li><b>Enums:</b> All enum types → name() format</li>
435       *    <li><b>Iterables:</b> Iterable, Iterator, Enumeration, Stream → list conversion</li>
436       *    <li><b>Wrappers:</b> Optional, Supplier → unwrapping/evaluation</li>
437       * </ul>
438       *
439       * <p>Default settings configured by this method:</p>
440       * <ul>
441       *    <li><code>{@link #SETTING_nullValue}</code> = <js>"&lt;null&gt;"</js> - String representation for null values</li>
442       *    <li><code>{@link #SETTING_selfValue}</code> = <js>"&lt;self&gt;"</js> - Special property name for self-reference</li>
443       *    <li><code>{@link #SETTING_classNameFormat}</code> = <js>"simple"</js> - Simple class name format</li>
444       * </ul>
445       *
446       * <p>Additional settings that can be configured (not set by default):</p>
447       * <ul>
448       *    <li><code>{@link #SETTING_fieldSeparator}</code> - Delimiter between collection elements (default: <js>","</js>)</li>
449       *    <li><code>{@link #SETTING_collectionPrefix}</code> - Prefix for collection content (default: <js>"["</js>)</li>
450       *    <li><code>{@link #SETTING_collectionSuffix}</code> - Suffix for collection content (default: <js>"]"</js>)</li>
451       *    <li><code>{@link #SETTING_mapPrefix}</code> - Prefix for map content (default: <js>"{"</js>)</li>
452       *    <li><code>{@link #SETTING_mapSuffix}</code> - Suffix for map content (default: <js>"}"</js>)</li>
453       *    <li><code>{@link #SETTING_mapEntrySeparator}</code> - Separator between map keys and values (default: <js>"="</js>)</li>
454       *    <li><code>{@link #SETTING_calendarFormat}</code> - DateTimeFormatter for calendar objects (default: <jsf>ISO_INSTANT</jsf>)</li>
455       * </ul>
456       *
457       * <p><b>Note:</b> This should typically be called after custom handlers to avoid
458       * overriding your custom configurations, since handlers are processed in reverse order.</p>
459       *
460       * @return This builder for method chaining
461       */
462      public Builder defaultSettings() {
463         addSetting(SETTING_nullValue, "<null>");
464         addSetting(SETTING_selfValue, "<self>");
465         addSetting(SETTING_classNameFormat, "simple");
466
467         addStringifier(Map.Entry.class, Stringifiers.mapEntryStringifier());
468         addStringifier(GregorianCalendar.class, Stringifiers.calendarStringifier());
469         addStringifier(Date.class, Stringifiers.dateStringifier());
470         addStringifier(InputStream.class, Stringifiers.inputStreamStringifier());
471         addStringifier(byte[].class, Stringifiers.byteArrayStringifier());
472         addStringifier(char[].class, Stringifiers.charArrayStringifier());
473         addStringifier(Reader.class, Stringifiers.readerStringifier());
474         addStringifier(File.class, Stringifiers.fileStringifier());
475         addStringifier(Enum.class, Stringifiers.enumStringifier());
476         addStringifier(Class.class, Stringifiers.classStringifier());
477         addStringifier(Constructor.class, Stringifiers.constructorStringifier());
478         addStringifier(Method.class, Stringifiers.methodStringifier());
479         addStringifier(List.class, Stringifiers.listStringifier());
480         addStringifier(Map.class, Stringifiers.mapStringifier());
481
482         // Note: Listifiers are processed in reverse registration order (last registered wins).
483         // Collection must be registered after Iterable so it takes precedence for Sets,
484         // ensuring TreeSet conversion for deterministic ordering.
485         addListifier(Iterable.class, Listifiers.iterableListifier());
486         addListifier(Collection.class, Listifiers.collectionListifier());
487         addListifier(Iterator.class, Listifiers.iteratorListifier());
488         addListifier(Enumeration.class, Listifiers.enumerationListifier());
489         addListifier(Stream.class, Listifiers.streamListifier());
490         addListifier(Map.class, Listifiers.mapListifier());
491
492         addSwapper(Optional.class, Swappers.optionalSwapper());
493         addSwapper(Supplier.class, Swappers.supplierSwapper());
494         addSwapper(Future.class, Swappers.futureSwapper());
495
496         addPropertyExtractor(new PropertyExtractors.ObjectPropertyExtractor());
497         addPropertyExtractor(new PropertyExtractors.ListPropertyExtractor());
498         addPropertyExtractor(new PropertyExtractors.MapPropertyExtractor());
499
500         return this;
501      }
502   }
503
504   static class ListifierEntry<T> {
505      private final Class<T> forClass;
506      private final Listifier<T> function;
507
508      private ListifierEntry(Class<T> forClass, Listifier<T> function) {
509         this.forClass = forClass;
510         this.function = function;
511      }
512   }
513
514   static class SizerEntry<T> {
515      private final Class<T> forClass;
516      private final Sizer<T> function;
517
518      private SizerEntry(Class<T> forClass, Sizer<T> function) {
519         this.forClass = forClass;
520         this.function = function;
521      }
522   }
523
524   static class StringifierEntry<T> {
525      private final Class<T> forClass;
526      private final Stringifier<T> function;
527
528      private StringifierEntry(Class<T> forClass, Stringifier function) {
529         this.forClass = forClass;
530         this.function = function;
531      }
532   }
533
534   static class SwapperEntry<T> {
535      private final Class<T> forClass;
536      private final Swapper<T> function;
537
538      private SwapperEntry(Class<T> forClass, Swapper<T> function) {
539         this.forClass = forClass;
540         this.function = function;
541      }
542   }
543
544   /**
545    * Default converter instance with standard settings and handlers.
546    *
547    * <p>This pre-configured instance provides comprehensive support for all common Java types
548    * with sensible default settings. It's suitable for most BCT testing scenarios and can be
549    * used directly without building a custom converter.</p>
550    *
551    * <h5 class='section'>Included Support:</h5>
552    * <ul>
553    *    <li><b>Collections:</b> List, Set, Queue with bracket formatting</li>
554    *    <li><b>Maps:</b> Map, Properties with brace formatting</li>
555    *    <li><b>Arrays:</b> All array types with bracket formatting</li>
556    *    <li><b>Dates:</b> Date, Calendar with ISO-8601 formatting</li>
557    *    <li><b>Files/Streams:</b> File, InputStream, Reader content extraction</li>
558    *    <li><b>Reflection:</b> Class, Method, Constructor readable signatures</li>
559    *    <li><b>Enums:</b> name() representation</li>
560    *    <li><b>Wrappers:</b> Optional, Supplier, Future unwrapping</li>
561    * </ul>
562    *
563    * <h5 class='section'>Default Settings:</h5>
564    * <ul>
565    *    <li><code>nullValue</code> = <js>"&lt;null&gt;"</js></li>
566    *    <li><code>selfValue</code> = <js>"&lt;self&gt;"</js></li>
567    *    <li><code>classNameFormat</code> = <js>"simple"</js></li>
568    * </ul>
569    *
570    * <h5 class='section'>Usage Example:</h5>
571    * <p class='bjava'>
572    *    <jc>// Use default converter for assertions</jc>
573    *    <jk>var</jk> <jv>result</jv> = BasicBeanConverter.<jsf>DEFAULT</jsf>.stringify(<jv>myObject</jv>);
574    *    <jk>var</jk> <jv>city</jv> = BasicBeanConverter.<jsf>DEFAULT</jsf>.getProperty(<jv>user</jv>, <js>"address.city"</js>);
575    * </p>
576    *
577    * @see #builder()
578    * @see Builder#defaultSettings()
579    */
580   public static final BasicBeanConverter DEFAULT = builder().defaultSettings().build();
581
582   /**
583    * Setting key for the string representation of null values.
584    *
585    * <p>Default value: <js>"&lt;null&gt;"</js></p>
586    *
587    * <p>This setting controls how null values are displayed when stringified.
588    * Used by the converter when encountering null objects during conversion.</p>
589    *
590    * @see Builder#addSetting(String, Object)
591    */
592   public static final String SETTING_nullValue = "nullValue";
593
594   /**
595    * Setting key for the special property name that returns the object itself.
596    *
597    * <p>Default value: <js>"&lt;self&gt;"</js></p>
598    *
599    * <p>This setting defines the property name that, when accessed, returns the
600    * object itself rather than a property of the object. Useful for self-referential
601    * property access in nested object navigation.</p>
602    *
603    * @see Builder#addSetting(String, Object)
604    */
605   public static final String SETTING_selfValue = "selfValue";
606
607   /**
608    * Setting key for the delimiter between collection elements and map entries.
609    *
610    * <p>Default value: <js>","</js></p>
611    *
612    * <p>This setting controls the separator used between elements when converting
613    * collections, arrays, and maps to string representations.</p>
614    *
615    * @see Builder#addSetting(String, Object)
616    */
617   public static final String SETTING_fieldSeparator = "fieldSeparator";
618
619   /**
620    * Setting key for the prefix character(s) used around collection content.
621    *
622    * <p>Default value: <js>"["</js></p>
623    *
624    * <p>This setting defines the opening bracket or prefix used when displaying
625    * collection and array contents in string format.</p>
626    *
627    * @see Builder#addSetting(String, Object)
628    * @see #SETTING_collectionSuffix
629    */
630   public static final String SETTING_collectionPrefix = "collectionPrefix";
631
632   /**
633    * Setting key for the suffix character(s) used around collection content.
634    *
635    * <p>Default value: <js>"]"</js></p>
636    *
637    * <p>This setting defines the closing bracket or suffix used when displaying
638    * collection and array contents in string format.</p>
639    *
640    * @see Builder#addSetting(String, Object)
641    * @see #SETTING_collectionPrefix
642    */
643   public static final String SETTING_collectionSuffix = "collectionSuffix";
644
645   /**
646    * Setting key for the prefix character(s) used around map content.
647    *
648    * <p>Default value: <js>"{"</js></p>
649    *
650    * <p>This setting defines the opening brace or prefix used when displaying
651    * map contents in string format.</p>
652    *
653    * @see Builder#addSetting(String, Object)
654    * @see #SETTING_mapSuffix
655    */
656   public static final String SETTING_mapPrefix = "mapPrefix";
657
658   /**
659    * Setting key for the suffix character(s) used around map content.
660    *
661    * <p>Default value: <js>"}"</js></p>
662    *
663    * <p>This setting defines the closing brace or suffix used when displaying
664    * map contents in string format.</p>
665    *
666    * @see Builder#addSetting(String, Object)
667    * @see #SETTING_mapPrefix
668    */
669   public static final String SETTING_mapSuffix = "mapSuffix";
670
671   /**
672    * Setting key for the separator between map keys and values.
673    *
674    * <p>Default value: <js>"="</js></p>
675    *
676    * <p>This setting controls the separator used between keys and values when
677    * converting map entries to string representations (e.g., "key=value").</p>
678    *
679    * @see Builder#addSetting(String, Object)
680    */
681   public static final String SETTING_mapEntrySeparator = "mapEntrySeparator";
682
683   /**
684    * Setting key for the DateTimeFormatter used for calendar objects.
685    *
686    * <p>Default value: <jsf>ISO_INSTANT</jsf></p>
687    *
688    * <p>This setting defines the date/time format used when converting Calendar
689    * objects to string representations. Must be a valid DateTimeFormatter instance.</p>
690    *
691    * @see Builder#addSetting(String, Object)
692    * @see java.time.format.DateTimeFormatter
693    */
694   public static final String SETTING_calendarFormat = "calendarFormat";
695
696   /**
697    * Setting key for the format used when displaying class names.
698    *
699    * <p>Default value: <js>"simple"</js></p>
700    *
701    * <p>This setting controls how class names are displayed in string representations.
702    * Valid values are:</p>
703    * <ul>
704    *    <li><js>"simple"</js> - Simple class name only (e.g., "String")</li>
705    *    <li><js>"canonical"</js> - Canonical class name (e.g., "java.lang.String")</li>
706    *    <li><js>"full"</js> - Full class name with package (e.g., "java.lang.String")</li>
707    * </ul>
708    *
709    * @see Builder#addSetting(String, Object)
710    */
711   public static final String SETTING_classNameFormat = "classNameFormat";
712
713   /**
714    * Creates a new builder for configuring a BasicBeanConverter instance.
715    *
716    * <p>The builder allows registration of custom stringifiers, listifiers, and swappers,
717    * as well as configuration of various formatting settings before building the converter.</p>
718    *
719    * @return A new Builder instance
720    */
721   public static Builder builder() {
722      return new Builder();
723   }
724
725   private final List<StringifierEntry<?>> stringifiers;
726   private final List<ListifierEntry<?>> listifiers;
727   private final List<SizerEntry<?>> sizers;
728   private final List<SwapperEntry<?>> swappers;
729
730   private final List<PropertyExtractor> propertyExtractors;
731
732   private final Map<String,Object> settings;
733
734   private final ConcurrentHashMap<Class,Optional<Stringifier<?>>> stringifierMap = new ConcurrentHashMap<>();
735
736   private final ConcurrentHashMap<Class,Optional<Listifier<?>>> listifierMap = new ConcurrentHashMap<>();
737
738   private final ConcurrentHashMap<Class,Optional<Sizer<?>>> sizerMap = new ConcurrentHashMap<>();
739
740   private final ConcurrentHashMap<Class,Optional<Swapper<?>>> swapperMap = new ConcurrentHashMap<>();
741
742   protected BasicBeanConverter(Builder b) {
743      stringifiers = cp(b.stringifiers);
744      listifiers = cp(b.listifiers);
745      sizers = cp(b.sizers);
746      swappers = cp(b.swappers);
747      propertyExtractors = cp(b.propertyExtractors);
748      settings = cp(b.settings);
749      Collections.reverse(stringifiers);
750      Collections.reverse(listifiers);
751      Collections.reverse(swappers);
752      Collections.reverse(propertyExtractors);
753   }
754
755   @Override
756   public boolean canListify(Object o) {
757      o = swap(o);
758      if (o == null)
759         return false;
760      var c = o.getClass();
761      return o instanceof List || c.isArray() || listifierMap.computeIfAbsent(c, this::findListifier).isPresent();
762   }
763
764   @Override
765   public String getNested(Object o, NestedTokenizer.Token token) {
766      reqnn("token", token);
767
768      if (o == null)
769         return getSetting(SETTING_nullValue, null);
770
771      // Handle #{...} syntax for iterating over collections/arrays
772      if (eq("#", token.getValue()) && canListify(o)) {
773         return listify(o).stream().map(x -> token.getNested().stream().map(x2 -> getNested(x, x2)).collect(joining(",", "{", "}"))).collect(joining(",", "[", "]"));
774      }
775
776      // Original logic for regular property access
777      var pn = token.getValue();
778      var selfValue = getSetting(SETTING_selfValue, "<self>");
779
780      // Handle special values
781      Object e;
782      if (pn.equals(selfValue)) {
783         e = o; // Return the object itself
784      } else {
785         e = o(getProperty(o, pn)).orElse(null);
786      }
787      if (e == null || ! token.hasNested())
788         return stringify(e);
789      return token.getNested().stream().map(x -> getNested(e, x)).map(this::stringify).collect(joining(",", "{", "}"));
790   }
791
792   @Override
793   public Object getProperty(Object object, String name) {
794      var o = swap(object);
795      // @formatter:off
796      return propertyExtractors
797         .stream()
798         .filter(x -> x.canExtract(this, o, name))
799         .findFirst()
800         .orElseThrow(() -> rex("Could not find extractor for object of type %s", cn(o))).extract(this, o, name);
801      // @formatter:on
802   }
803
804   @Override
805   public <T> T getSetting(String key, T def) {
806      return (T)settings.getOrDefault(key, def);
807   }
808
809   @Override
810   public List<Object> listify(Object o) {
811      reqnn("o", o);
812
813      o = swap(o);
814
815      if (o instanceof List)
816         return (List<Object>)o;
817      if (o.getClass().isArray())
818         return arrayToList(o);
819
820      var c = o.getClass();
821      var o2 = o;
822      // @formatter:off
823      return listifierMap
824         .computeIfAbsent(c, this::findListifier)
825         .map(x -> (Listifier)x)
826         .map(x -> (List<Object>)x.apply(this, o2))
827         .orElseThrow(() -> iaex("Object of type %s could not be converted to a list.", cns(o2)));
828      // @formatter:on
829   }
830
831   @Override
832   public int size(Object o) {
833      reqnn("o", o);
834
835      // Checks for Optional before unpacking.
836      if (o instanceof Optional o2)
837         return o2.isEmpty() ? 0 : 1;
838
839      o = swap(o);
840
841      // Check standard object types.
842      if (o == null)
843         return 0;
844      if (o instanceof Collection o2)
845         return o2.size();
846      if (o instanceof Map o3)
847         return o3.size();
848      if (o.getClass().isArray())
849         return Array.getLength(o);
850      if (o instanceof String o2)
851         return o2.length();
852
853      // Check for registered custom Sizer
854      var c = o.getClass();
855      var o2 = o;
856      var sizer = sizerMap.computeIfAbsent(c, this::findSizer);
857      if (sizer.isPresent())
858         return ((Sizer)sizer.get()).size(o2, this);
859
860      // Try to find size() or length() method via reflection
861      // @formatter:off
862      var sizeResult = info(c).getPublicMethods().stream()
863         .filter(m -> ! m.hasParameters())
864         .filter(m -> m.hasAnyName("size", "length"))
865         .filter(m -> m.getReturnType().isAny(int.class, Integer.class))
866         .findFirst()
867         .map(m -> safe(() -> (Integer) m.invoke(o2)))
868         .filter(Objects::nonNull);
869      // @formatter:on
870      if (sizeResult.isPresent())
871         return sizeResult.get();
872
873      // Fall back to listify
874      if (canListify(o))
875         return listify(o).size();
876
877      // Try to find isEmpty() method via reflection
878      // @formatter:off
879      var isEmpty = info(o).getPublicMethods().stream()
880         .filter(m -> ! m.hasParameters())
881         .filter(m -> m.hasName("isEmpty"))
882         .filter(m -> m.getReturnType().isAny(boolean.class, Boolean.class))
883         .map(m -> safe(() -> (Boolean)m.invoke(o2)))
884         .findFirst();
885      // @formatter:on
886      if (isEmpty.isPresent())
887         return isTrue(isEmpty.get()) ? 0 : 1;
888
889      throw iaex("Object of type %s does not have a determinable size.", cns(o));
890   }
891
892   @Override
893   public String stringify(Object o) {
894
895      o = swap(o);
896
897      if (o == null)
898         return getSetting(SETTING_nullValue, null);
899
900      var c = o.getClass();
901      var stringifier = stringifierMap.computeIfAbsent(c, this::findStringifier);
902      if (stringifier.isEmpty()) {
903         stringifier = of(canListify(o) ? (bc, o2) -> bc.stringify(bc.listify(o2)) : (bc, o2) -> this.safeToString(o2));
904         stringifierMap.putIfAbsent(c, stringifier);
905      }
906      var o2 = o;
907      return stringifier.map(x -> (Stringifier)x).map(x -> x.apply(this, o2)).map(this::safeToString).orElse(null);
908   }
909
910   /**
911    * Builder class for configuring BasicBeanConverter instances.
912    *
913    * <p>This builder provides a fluent interface for registering custom type handlers
914    * and configuring conversion settings. All registration methods support method chaining
915    * for convenient configuration.</p>
916    *
917    * <h5 class='section'>Handler Registration:</h5>
918    * <ul>
919    *    <li><b>Stringifiers:</b> Custom string conversion logic for specific types</li>
920    *    <li><b>Listifiers:</b> Custom list conversion logic for collection-like types</li>
921    *    <li><b>Swappers:</b> Pre-processing transformation logic for wrapper types</li>
922    * </ul>
923    *
924    * <h5 class='section'>Registration Order:</h5>
925    * <p>Handlers are checked in reverse registration order (last registered wins).
926    * This allows overriding default handlers by registering more specific ones later.</p>
927    *
928    * <h5 class='section'>Inheritance Support:</h5>
929    * <p>All handlers support class inheritance and interface implementation.
930    * When looking up a handler, the system checks:</p>
931    * <ol>
932    *    <li>Exact class match</li>
933    *    <li>Interface matches (in order of interface declaration)</li>
934    *    <li>Superclass matches (walking up the inheritance hierarchy)</li>
935    * </ol>
936    *
937    * <h5 class='section'>Usage Example:</h5>
938    * <p class='bjava'>
939    *    <jk>var</jk> <jv>converter</jv> = BasicBeanConverter.<jsm>builder</jsm>()
940    *       .defaultSettings()
941    *       <jc>// Custom stringification for LocalDateTime</jc>
942    *       .addStringifier(LocalDateTime.<jk>class</jk>, (<jp>dt</jp>, <jp>conv</jp>) ->
943    *          <jp>dt</jp>.format(DateTimeFormatter.<jsf>ISO_LOCAL_DATE_TIME</jsf>))
944    *
945    *       <jc>// Custom collection handling for custom type</jc>
946    *       .addListifier(MyIterable.<jk>class</jk>, (<jp>iter</jp>, <jp>conv</jp>) ->
947    *          <jp>iter</jp>.stream().collect(toList()))
948    *
949    *       <jc>// Custom transformation for wrapper type</jc>
950    *       .addSwapper(LazyValue.<jk>class</jk>, (<jp>lazy</jp>, <jp>conv</jp>) ->
951    *          <jp>lazy</jp>.isComputed() ? <jp>lazy</jp>.get() : <jk>null</jk>)
952    *
953    *       <jc>// Configure settings</jc>
954    *       .addSetting(<jsf>SETTING_nullValue</jsf>, <js>"&lt;null&gt;"</js>)
955    *       .addSetting(<jsf>SETTING_fieldSeparator</jsf>, <js>" | "</js>)
956    *
957    *       <jc>// Add default handlers for common types</jc>
958    *       .defaultSettings()
959    *       .build();
960    * </p>
961    */
962
963   @Override
964   public Object swap(Object o) {
965      if (o == null)
966         return null;
967      var c = o.getClass();
968      var swapper = swapperMap.computeIfAbsent(c, this::findSwapper);
969      if (swapper.isPresent())
970         return swap(swapper.map(x -> (Swapper)x).map(x -> x.apply(this, o)).orElse(null));
971      return o;
972   }
973
974   private Optional<Listifier<?>> findListifier(Class<?> c) {
975      if (c == null)
976         return empty();
977      var l = listifiers.stream().filter(x -> x.forClass.isAssignableFrom(c)).findFirst().orElse(null);
978      if (nn(l))
979         return of(l.function);
980      return findListifier(c.getSuperclass());
981   }
982
983   private Optional<Sizer<?>> findSizer(Class<?> c) {
984      if (c == null)
985         return empty();
986      var s = sizers.stream().filter(x -> x.forClass.isAssignableFrom(c)).findFirst().orElse(null);
987      if (nn(s))
988         return of(s.function);
989      return findSizer(c.getSuperclass());
990   }
991
992   private Optional<Stringifier<?>> findStringifier(Class<?> c) {
993      if (c == null)
994         return empty();
995      var s = stringifiers.stream().filter(x -> x.forClass.isAssignableFrom(c)).findFirst().orElse(null);
996      if (nn(s))
997         return of(s.function);
998      return findStringifier(c.getSuperclass());
999   }
1000
1001   private Optional<Swapper<?>> findSwapper(Class<?> c) {
1002      if (c == null)
1003         return empty();
1004      var s = swappers.stream().filter(x -> x.forClass.isAssignableFrom(c)).findFirst().orElse(null);
1005      if (nn(s))
1006         return of(s.function);
1007      return findSwapper(c.getSuperclass());
1008   }
1009
1010   /**
1011    * Safely converts an object to a string, catching any exceptions thrown by toString().
1012    *
1013    * <p>
1014    * This helper method ensures that exceptions thrown by problematic {@code toString()} implementations
1015    * don't propagate up the call stack. Instead, it returns a descriptive error message containing
1016    * the exception type and message.
1017    *
1018    * @param o The object to convert to a string. May be any object including <jk>null</jk>.
1019    * @return The string representation of the object, or a formatted error message if toString() throws an exception.
1020    *    Returns <js>"null"</js> if the object is <jk>null</jk>.
1021    */
1022   private String safeToString(Object o) {
1023      try {
1024         return o.toString();
1025      } catch (Exception t) {
1026         return cns(t) + ": " + t.getMessage();
1027      }
1028   }
1029}