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.junit.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.AssertionUtils.*;
023import static org.apache.juneau.commons.utils.CollectionUtils.*;
024import static org.apache.juneau.commons.utils.ThrowableUtils.*;
025import static org.apache.juneau.commons.utils.Utils.*;
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("rawtypes")
221public class BasicBeanConverter implements BeanConverter {
222
223   /**
224    * Builder for creating customized BasicBeanConverter instances.
225    *
226    * <p>This builder provides a fluent API for configuring custom type handlers, settings,
227    * and property extraction logic. The builder supports registration of four main types
228    * of customizations:</p>
229    *
230    * <h5 class='section'>Type Handlers:</h5>
231    * <ul>
232    *    <li><b>{@link #addStringifier(Class, Stringifier)}</b> - Custom string conversion logic</li>
233    *    <li><b>{@link #addListifier(Class, Listifier)}</b> - Custom list conversion for collection-like objects</li>
234    *    <li><b>{@link #addSwapper(Class, Swapper)}</b> - Pre-processing and object transformation</li>
235    *    <li><b>{@link #addPropertyExtractor(PropertyExtractor)}</b> - Custom property access strategies</li>
236    * </ul>
237    *
238    * <h5 class='section'>PropertyExtractors:</h5>
239    * <p>Property extractors define how the converter accesses object properties during nested
240    * field access (e.g., {@code "user.address.city"}). The converter uses a chain-of-responsibility
241    * pattern, trying each registered extractor until one succeeds:</p>
242    *
243    * <ul>
244    *    <li><b>{@link PropertyExtractors.ObjectPropertyExtractor}</b> - JavaBean-style properties via reflection</li>
245    *    <li><b>{@link PropertyExtractors.ListPropertyExtractor}</b> - Numeric indices and size/length for arrays/collections</li>
246    *    <li><b>{@link PropertyExtractors.MapPropertyExtractor}</b> - Key-based access for Map objects</li>
247    * </ul>
248    *
249    * <p>Custom extractors can be added to handle specialized property access patterns:</p>
250    * <p class='bjava'>
251    *    <jk>var</jk> <jv>converter</jv> = BasicBeanConverter.<jsm>builder</jsm>()
252    *       .defaultSettings()
253    *       .addPropertyExtractor(<jk>new</jk> MyCustomExtractor())
254    *       .addPropertyExtractor((<jp>obj</jp>, <jp>prop</jp>) -&gt; {
255    *          <jk>if</jk> (<jp>obj</jp> <jk>instanceof</jk> MySpecialType <jv>special</jv>) {
256    *             <jk>return</jk> <jv>special</jv>.getCustomProperty(<jp>prop</jp>);
257    *          }
258    *          <jk>return</jk> <jk>null</jk>; <jc>// Try next extractor</jc>
259    *       })
260    *       .build();
261    * </p>
262    *
263    * <h5 class='section'>Default Configuration:</h5>
264    * <p>The {@link #defaultSettings()} method pre-registers comprehensive type handlers
265    * and property extractors for common Java types, providing out-of-the-box functionality
266    * for most use cases while still allowing full customization.</p>
267    *
268    * @see PropertyExtractors
269    * @see PropertyExtractor
270    */
271   public static class Builder {
272      private Map<String,Object> settings = map();
273      private List<StringifierEntry<?>> stringifiers = list();
274      private List<ListifierEntry<?>> listifiers = list();
275      private List<SizerEntry<?>> sizers = list();
276      private List<SwapperEntry<?>> swappers = list();
277      private List<PropertyExtractor> propertyExtractors = list();
278
279      /**
280       * Registers a custom listifier for a specific type.
281       *
282       * <p>Listifiers convert collection-like objects to List&lt;Object&gt;. The BiFunction
283       * receives the object to convert and the converter instance for recursive calls.</p>
284       *
285       * @param <T> The type to handle
286       * @param c The class to register the listifier for
287       * @param l The listification function
288       * @return This builder for method chaining
289       */
290      public <T> Builder addListifier(Class<T> c, Listifier<T> l) {
291         listifiers.add(new ListifierEntry<>(c, l));
292         return this;
293      }
294
295      /**
296       * Registers a custom property extractor for specialized property access logic.
297       *
298       * <p>Property extractors enable custom property access patterns beyond standard JavaBean
299       * conventions. The converter tries extractors in registration order until one returns
300       * a non-<jk>null</jk> value. This allows for:</p>
301       * <ul>
302       *    <li><b>Custom data structures:</b> Special property access for non-standard objects</li>
303       *    <li><b>Database entities:</b> Property access via entity-specific methods</li>
304       *    <li><b>Dynamic properties:</b> Computed or cached property values</li>
305       *    <li><b>Legacy objects:</b> Bridging older APIs with modern property access</li>
306       * </ul>
307       *
308       * <h5 class='section'>Implementation Example:</h5>
309       * <p class='bjava'>
310       *    <jc>// Custom extractor for a specialized data class</jc>
311       *    PropertyExtractor <jv>customExtractor</jv> = (<jp>obj</jp>, <jp>property</jp>) -&gt; {
312       *       <jk>if</jk> (<jp>obj</jp> <jk>instanceof</jk> DatabaseEntity <jv>entity</jv>) {
313       *          <jk>switch</jk> (<jp>property</jp>) {
314       *             <jk>case</jk> <js>"id"</js>: <jk>return</jk> <jv>entity</jv>.getPrimaryKey();
315       *             <jk>case</jk> <js>"displayName"</js>: <jk>return</jk> <jv>entity</jv>.computeDisplayName();
316       *             <jk>case</jk> <js>"metadata"</js>: <jk>return</jk> <jv>entity</jv>.getMetadataAsMap();
317       *          }
318       *       }
319       *       <jk>return</jk> <jk>null</jk>; <jc>// Let next extractor try</jc>
320       *    };
321       *
322       *    <jk>var</jk> <jv>converter</jv> = BasicBeanConverter.<jsm>builder</jsm>()
323       *       .addPropertyExtractor(<jv>customExtractor</jv>)
324       *       .defaultSettings() <jc>// Adds standard extractors</jc>
325       *       .build();
326       * </p>
327       *
328       * <p><b>Execution Order:</b> Custom extractors are tried before the default extractors
329       * added by {@link #defaultSettings()}, allowing overrides of standard behavior.</p>
330       *
331       * @param e The property extractor to register
332       * @return This builder for method chaining
333       * @see PropertyExtractor
334       * @see PropertyExtractors
335       */
336      public Builder addPropertyExtractor(PropertyExtractor e) {
337         propertyExtractors.add(e);
338         return this;
339      }
340
341      /**
342       * Adds a configuration setting to the converter.
343       *
344       * @param key The setting key (use SETTING_* constants)
345       * @param value The setting value
346       * @return This builder for method chaining
347       */
348      public Builder addSetting(String key, Object value) {
349         settings.put(key, value);
350         return this;
351      }
352
353      /**
354       * Registers a custom sizer for a specific type.
355       *
356       * <p>Sizers compute the size of collection-like objects for test assertions.
357       * The function receives the object to size and the converter instance for accessing
358       * additional utilities if needed.</p>
359       *
360       * @param <T> The type to handle
361       * @param c The class to register the sizer for
362       * @param s The sizing function
363       * @return This builder for method chaining
364       */
365      public <T> Builder addSizer(Class<T> c, Sizer<T> s) {
366         sizers.add(new SizerEntry<>(c, s));
367         return this;
368      }
369
370      /**
371       * Registers a custom stringifier for a specific type.
372       *
373       * <p>Stringifiers convert objects to their string representations. The BiFunction
374       * receives the object to convert and the converter instance for recursive calls.</p>
375       *
376       * @param <T> The type to handle
377       * @param c The class to register the stringifier for
378       * @param s The stringification function
379       * @return This builder for method chaining
380       */
381      public <T> Builder addStringifier(Class<T> c, Stringifier<T> s) {
382         stringifiers.add(new StringifierEntry<>(c, s));
383         return this;
384      }
385
386      /**
387       * Registers a custom swapper for a specific type.
388       *
389       * <p>Swappers pre-process objects before conversion. Common uses include
390       * unwrapping Optional values, calling Supplier methods, or extracting values
391       * from wrapper objects.</p>
392       *
393       * @param <T> The type to handle
394       * @param c The class to register the swapper for
395       * @param s The swapping function
396       * @return This builder for method chaining
397       */
398      public <T> Builder addSwapper(Class<T> c, Swapper<T> s) {
399         swappers.add(new SwapperEntry<>(c, s));
400         return this;
401      }
402
403      /**
404       * Builds the configured BasicBeanConverter instance.
405       *
406       * <p>This method creates a new BasicBeanConverter with all registered handlers
407       * and settings. The builder can be reused to create multiple converters with
408       * the same configuration.</p>
409       *
410       * @return A new BasicBeanConverter instance
411       */
412      public BasicBeanConverter build() {
413         return new BasicBeanConverter(this);
414      }
415
416      /**
417       * Adds default handlers and settings for common Java types.
418       *
419       * <p>This method registers comprehensive support for:</p>
420       * <ul>
421       *    <li><b>Collections:</b> List, Set, Collection → bracket format</li>
422       *    <li><b>Maps:</b> Map, Properties → brace format with key=value pairs</li>
423       *    <li><b>Map Entries:</b> Map.Entry → <js>"key=value"</js> format</li>
424       *    <li><b>Dates:</b> Date, Calendar → ISO-8601 format</li>
425       *    <li><b>Files/Streams:</b> File, InputStream, Reader → content extraction</li>
426       *    <li><b>Arrays:</b> byte[], char[] → hex strings and direct string conversion</li>
427       *    <li><b>Reflection:</b> Class, Method, Constructor → readable signatures</li>
428       *    <li><b>Enums:</b> All enum types → name() format</li>
429       *    <li><b>Iterables:</b> Iterable, Iterator, Enumeration, Stream → list conversion</li>
430       *    <li><b>Wrappers:</b> Optional, Supplier → unwrapping/evaluation</li>
431       * </ul>
432       *
433       * <p>Default settings configured by this method:</p>
434       * <ul>
435       *    <li><code>{@link #SETTING_nullValue}</code> = <js>"&lt;null&gt;"</js> - String representation for null values</li>
436       *    <li><code>{@link #SETTING_selfValue}</code> = <js>"&lt;self&gt;"</js> - Special property name for self-reference</li>
437       *    <li><code>{@link #SETTING_classNameFormat}</code> = <js>"simple"</js> - Simple class name format</li>
438       * </ul>
439       *
440       * <p>Additional settings that can be configured (not set by default):</p>
441       * <ul>
442       *    <li><code>{@link #SETTING_fieldSeparator}</code> - Delimiter between collection elements (default: <js>","</js>)</li>
443       *    <li><code>{@link #SETTING_collectionPrefix}</code> - Prefix for collection content (default: <js>"["</js>)</li>
444       *    <li><code>{@link #SETTING_collectionSuffix}</code> - Suffix for collection content (default: <js>"]"</js>)</li>
445       *    <li><code>{@link #SETTING_mapPrefix}</code> - Prefix for map content (default: <js>"{"</js>)</li>
446       *    <li><code>{@link #SETTING_mapSuffix}</code> - Suffix for map content (default: <js>"}"</js>)</li>
447       *    <li><code>{@link #SETTING_mapEntrySeparator}</code> - Separator between map keys and values (default: <js>"="</js>)</li>
448       *    <li><code>{@link #SETTING_calendarFormat}</code> - DateTimeFormatter for calendar objects (default: <jsf>ISO_INSTANT</jsf>)</li>
449       * </ul>
450       *
451       * <p><b>Note:</b> This should typically be called after custom handlers to avoid
452       * overriding your custom configurations, since handlers are processed in reverse order.</p>
453       *
454       * @return This builder for method chaining
455       */
456      public Builder defaultSettings() {
457         addSetting(SETTING_nullValue, "<null>");
458         addSetting(SETTING_selfValue, "<self>");
459         addSetting(SETTING_classNameFormat, "simple");
460
461         addStringifier(Map.Entry.class, Stringifiers.mapEntryStringifier());
462         addStringifier(GregorianCalendar.class, Stringifiers.calendarStringifier());
463         addStringifier(Date.class, Stringifiers.dateStringifier());
464         addStringifier(InputStream.class, Stringifiers.inputStreamStringifier());
465         addStringifier(byte[].class, Stringifiers.byteArrayStringifier());
466         addStringifier(char[].class, Stringifiers.charArrayStringifier());
467         addStringifier(Reader.class, Stringifiers.readerStringifier());
468         addStringifier(File.class, Stringifiers.fileStringifier());
469         addStringifier(Enum.class, Stringifiers.enumStringifier());
470         addStringifier(Class.class, Stringifiers.classStringifier());
471         addStringifier(Constructor.class, Stringifiers.constructorStringifier());
472         addStringifier(Method.class, Stringifiers.methodStringifier());
473         addStringifier(List.class, Stringifiers.listStringifier());
474         addStringifier(Map.class, Stringifiers.mapStringifier());
475
476         // Note: Listifiers are processed in reverse registration order (last registered wins).
477         // Collection must be registered after Iterable so it takes precedence for Sets,
478         // ensuring TreeSet conversion for deterministic ordering.
479         addListifier(Iterable.class, Listifiers.iterableListifier());
480         addListifier(Collection.class, Listifiers.collectionListifier());
481         addListifier(Iterator.class, Listifiers.iteratorListifier());
482         addListifier(Enumeration.class, Listifiers.enumerationListifier());
483         addListifier(Stream.class, Listifiers.streamListifier());
484         addListifier(Map.class, Listifiers.mapListifier());
485
486         addSwapper(Optional.class, Swappers.optionalSwapper());
487         addSwapper(Supplier.class, Swappers.supplierSwapper());
488         addSwapper(Future.class, Swappers.futureSwapper());
489
490         addPropertyExtractor(new PropertyExtractors.ObjectPropertyExtractor());
491         addPropertyExtractor(new PropertyExtractors.ListPropertyExtractor());
492         addPropertyExtractor(new PropertyExtractors.MapPropertyExtractor());
493
494         return this;
495      }
496   }
497
498   static class ListifierEntry<T> {
499      private Class<T> forClass;
500      private Listifier<T> function;
501
502      private ListifierEntry(Class<T> forClass, Listifier<T> function) {
503         this.forClass = forClass;
504         this.function = function;
505      }
506   }
507
508   static class SizerEntry<T> {
509      private Class<T> forClass;
510      private Sizer<T> function;
511
512      private SizerEntry(Class<T> forClass, Sizer<T> function) {
513         this.forClass = forClass;
514         this.function = function;
515      }
516   }
517
518   static class StringifierEntry<T> {
519      private Class<T> forClass;
520      private Stringifier<T> function;
521
522      @SuppressWarnings("unchecked")
523      private StringifierEntry(Class<T> forClass, Stringifier function) {
524         this.forClass = forClass;
525         this.function = function;
526      }
527   }
528
529   static class SwapperEntry<T> {
530      private Class<T> forClass;
531      private Swapper<T> function;
532
533      private SwapperEntry(Class<T> forClass, Swapper<T> function) {
534         this.forClass = forClass;
535         this.function = function;
536      }
537   }
538
539   /**
540    * Default converter instance with standard settings and handlers.
541    *
542    * <p>This pre-configured instance provides comprehensive support for all common Java types
543    * with sensible default settings. It's suitable for most BCT testing scenarios and can be
544    * used directly without building a custom converter.</p>
545    *
546    * <h5 class='section'>Included Support:</h5>
547    * <ul>
548    *    <li><b>Collections:</b> List, Set, Queue with bracket formatting</li>
549    *    <li><b>Maps:</b> Map, Properties with brace formatting</li>
550    *    <li><b>Arrays:</b> All array types with bracket formatting</li>
551    *    <li><b>Dates:</b> Date, Calendar with ISO-8601 formatting</li>
552    *    <li><b>Files/Streams:</b> File, InputStream, Reader content extraction</li>
553    *    <li><b>Reflection:</b> Class, Method, Constructor readable signatures</li>
554    *    <li><b>Enums:</b> name() representation</li>
555    *    <li><b>Wrappers:</b> Optional, Supplier, Future unwrapping</li>
556    * </ul>
557    *
558    * <h5 class='section'>Default Settings:</h5>
559    * <ul>
560    *    <li><code>nullValue</code> = <js>"&lt;null&gt;"</js></li>
561    *    <li><code>selfValue</code> = <js>"&lt;self&gt;"</js></li>
562    *    <li><code>classNameFormat</code> = <js>"simple"</js></li>
563    * </ul>
564    *
565    * <h5 class='section'>Usage Example:</h5>
566    * <p class='bjava'>
567    *    <jc>// Use default converter for assertions</jc>
568    *    <jk>var</jk> <jv>result</jv> = BasicBeanConverter.<jsf>DEFAULT</jsf>.stringify(<jv>myObject</jv>);
569    *    <jk>var</jk> <jv>city</jv> = BasicBeanConverter.<jsf>DEFAULT</jsf>.getProperty(<jv>user</jv>, <js>"address.city"</js>);
570    * </p>
571    *
572    * @see #builder()
573    * @see Builder#defaultSettings()
574    */
575   public static final BasicBeanConverter DEFAULT = builder().defaultSettings().build();
576
577   /**
578    * Setting key for the string representation of null values.
579    *
580    * <p>Default value: <js>"&lt;null&gt;"</js></p>
581    *
582    * <p>This setting controls how null values are displayed when stringified.
583    * Used by the converter when encountering null objects during conversion.</p>
584    *
585    * @see Builder#addSetting(String, Object)
586    */
587   public static final String SETTING_nullValue = "nullValue";
588
589   /**
590    * Setting key for the special property name that returns the object itself.
591    *
592    * <p>Default value: <js>"&lt;self&gt;"</js></p>
593    *
594    * <p>This setting defines the property name that, when accessed, returns the
595    * object itself rather than a property of the object. Useful for self-referential
596    * property access in nested object navigation.</p>
597    *
598    * @see Builder#addSetting(String, Object)
599    */
600   public static final String SETTING_selfValue = "selfValue";
601
602   /**
603    * Setting key for the delimiter between collection elements and map entries.
604    *
605    * <p>Default value: <js>","</js></p>
606    *
607    * <p>This setting controls the separator used between elements when converting
608    * collections, arrays, and maps to string representations.</p>
609    *
610    * @see Builder#addSetting(String, Object)
611    */
612   public static final String SETTING_fieldSeparator = "fieldSeparator";
613
614   /**
615    * Setting key for the prefix character(s) used around collection content.
616    *
617    * <p>Default value: <js>"["</js></p>
618    *
619    * <p>This setting defines the opening bracket or prefix used when displaying
620    * collection and array contents in string format.</p>
621    *
622    * @see Builder#addSetting(String, Object)
623    * @see #SETTING_collectionSuffix
624    */
625   public static final String SETTING_collectionPrefix = "collectionPrefix";
626
627   /**
628    * Setting key for the suffix character(s) used around collection content.
629    *
630    * <p>Default value: <js>"]"</js></p>
631    *
632    * <p>This setting defines the closing bracket or suffix used when displaying
633    * collection and array contents in string format.</p>
634    *
635    * @see Builder#addSetting(String, Object)
636    * @see #SETTING_collectionPrefix
637    */
638   public static final String SETTING_collectionSuffix = "collectionSuffix";
639
640   /**
641    * Setting key for the prefix character(s) used around map content.
642    *
643    * <p>Default value: <js>"{"</js></p>
644    *
645    * <p>This setting defines the opening brace or prefix used when displaying
646    * map contents in string format.</p>
647    *
648    * @see Builder#addSetting(String, Object)
649    * @see #SETTING_mapSuffix
650    */
651   public static final String SETTING_mapPrefix = "mapPrefix";
652
653   /**
654    * Setting key for the suffix character(s) used around map content.
655    *
656    * <p>Default value: <js>"}"</js></p>
657    *
658    * <p>This setting defines the closing brace or suffix used when displaying
659    * map contents in string format.</p>
660    *
661    * @see Builder#addSetting(String, Object)
662    * @see #SETTING_mapPrefix
663    */
664   public static final String SETTING_mapSuffix = "mapSuffix";
665
666   /**
667    * Setting key for the separator between map keys and values.
668    *
669    * <p>Default value: <js>"="</js></p>
670    *
671    * <p>This setting controls the separator used between keys and values when
672    * converting map entries to string representations (e.g., "key=value").</p>
673    *
674    * @see Builder#addSetting(String, Object)
675    */
676   public static final String SETTING_mapEntrySeparator = "mapEntrySeparator";
677
678   /**
679    * Setting key for the DateTimeFormatter used for calendar objects.
680    *
681    * <p>Default value: <jsf>ISO_INSTANT</jsf></p>
682    *
683    * <p>This setting defines the date/time format used when converting Calendar
684    * objects to string representations. Must be a valid DateTimeFormatter instance.</p>
685    *
686    * @see Builder#addSetting(String, Object)
687    * @see java.time.format.DateTimeFormatter
688    */
689   public static final String SETTING_calendarFormat = "calendarFormat";
690
691   /**
692    * Setting key for the format used when displaying class names.
693    *
694    * <p>Default value: <js>"simple"</js></p>
695    *
696    * <p>This setting controls how class names are displayed in string representations.
697    * Valid values are:</p>
698    * <ul>
699    *    <li><js>"simple"</js> - Simple class name only (e.g., "String")</li>
700    *    <li><js>"canonical"</js> - Canonical class name (e.g., "java.lang.String")</li>
701    *    <li><js>"full"</js> - Full class name with package (e.g., "java.lang.String")</li>
702    * </ul>
703    *
704    * @see Builder#addSetting(String, Object)
705    */
706   public static final String SETTING_classNameFormat = "classNameFormat";
707
708   /**
709    * Creates a new builder for configuring a BasicBeanConverter instance.
710    *
711    * <p>The builder allows registration of custom stringifiers, listifiers, and swappers,
712    * as well as configuration of various formatting settings before building the converter.</p>
713    *
714    * @return A new Builder instance
715    */
716   public static Builder builder() {
717      return new Builder();
718   }
719
720   private final List<StringifierEntry<?>> stringifiers;
721   private final List<ListifierEntry<?>> listifiers;
722   private final List<SizerEntry<?>> sizers;
723   private final List<SwapperEntry<?>> swappers;
724
725   private final List<PropertyExtractor> propertyExtractors;
726
727   private final Map<String,Object> settings;
728
729   private final ConcurrentHashMap<Class,Optional<Stringifier<?>>> stringifierMap = new ConcurrentHashMap<>();
730
731   private final ConcurrentHashMap<Class,Optional<Listifier<?>>> listifierMap = new ConcurrentHashMap<>();
732
733   private final ConcurrentHashMap<Class,Optional<Sizer<?>>> sizerMap = new ConcurrentHashMap<>();
734
735   private final ConcurrentHashMap<Class,Optional<Swapper<?>>> swapperMap = new ConcurrentHashMap<>();
736
737   protected BasicBeanConverter(Builder b) {
738      stringifiers = copyOf(b.stringifiers);
739      listifiers = copyOf(b.listifiers);
740      sizers = copyOf(b.sizers);
741      swappers = copyOf(b.swappers);
742      propertyExtractors = copyOf(b.propertyExtractors);
743      settings = copyOf(b.settings);
744      Collections.reverse(stringifiers);
745      Collections.reverse(listifiers);
746      Collections.reverse(swappers);
747      Collections.reverse(propertyExtractors);
748   }
749
750   @Override
751   public boolean canListify(Object o) {
752      o = swap(o);
753      if (o == null)
754         return false;
755      var c = o.getClass();
756      return o instanceof List || c.isArray() || listifierMap.computeIfAbsent(c, this::findListifier).isPresent();
757   }
758
759   @Override
760   public String getNested(Object o, NestedTokenizer.Token token) {
761      assertArgNotNull("token", token);
762
763      if (o == null)
764         return getSetting(SETTING_nullValue, null);
765
766      // Handle #{...} syntax for iterating over collections/arrays
767      if (eq("#", token.getValue()) && canListify(o)) {
768         return listify(o).stream().map(x -> token.getNested().stream().map(x2 -> getNested(x, x2)).collect(joining(",", "{", "}"))).collect(joining(",", "[", "]"));
769      }
770
771      // Original logic for regular property access
772      var pn = token.getValue();
773      var selfValue = getSetting(SETTING_selfValue, "<self>");
774
775      // Handle special values
776      Object e;
777      if (pn.equals(selfValue)) {
778         e = o; // Return the object itself
779      } else {
780         e = opt(getProperty(o, pn)).orElse(null);
781      }
782      if (e == null || ! token.hasNested())
783         return stringify(e);
784      return token.getNested().stream().map(x -> getNested(e, x)).map(this::stringify).collect(joining(",", "{", "}"));
785   }
786
787   @Override
788   public Object getProperty(Object object, String name) {
789      var o = swap(object);
790      // @formatter:off
791      return propertyExtractors
792         .stream()
793         .filter(x -> x.canExtract(this, o, name))
794         .findFirst()
795         .orElseThrow(() -> rex("Could not find extractor for object of type {0}", cn(o))).extract(this, o, name);
796      // @formatter:on
797   }
798
799   @Override
800   @SuppressWarnings("unchecked")
801   public <T> T getSetting(String key, T def) {
802      return (T)settings.getOrDefault(key, def);
803   }
804
805   @Override
806   @SuppressWarnings("unchecked")
807   public List<Object> listify(Object o) {
808      assertArgNotNull("o", o);
809
810      o = swap(o);
811
812      if (o instanceof List)
813         return (List<Object>)o;
814      if (o.getClass().isArray())
815         return arrayToList(o);
816
817      var c = o.getClass();
818      var o2 = o;
819      // @formatter:off
820      return listifierMap
821         .computeIfAbsent(c, this::findListifier)
822         .map(x -> (Listifier)x)
823         .map(x -> (List<Object>)x.apply(this, o2))
824         .orElseThrow(() -> illegalArg("Object of type {0} could not be converted to a list.", cns(o2)));
825      // @formatter:on
826   }
827
828   @Override
829   @SuppressWarnings("unchecked")
830   public int size(Object o) {
831      assertArgNotNull("o", o);
832
833      // Checks for Optional before unpacking.
834      if (o instanceof Optional o2)
835         return o2.isEmpty() ? 0 : 1;
836
837      o = swap(o);
838
839      // Check standard object types.
840      if (o == null)
841         return 0;
842      if (o instanceof Collection o2)
843         return o2.size();
844      if (o instanceof Map o3)
845         return o3.size();
846      if (o.getClass().isArray())
847         return Array.getLength(o);
848      if (o instanceof String o2)
849         return o2.length();
850
851      // Check for registered custom Sizer
852      var c = o.getClass();
853      var o2 = o;
854      var sizer = sizerMap.computeIfAbsent(c, this::findSizer);
855      if (sizer.isPresent())
856         return ((Sizer)sizer.get()).size(o2, this);
857
858      // Try to find size() or length() method via reflection
859      // @formatter:off
860      var sizeResult = info(c).getPublicMethods().stream()
861         .filter(m -> ! m.hasParameters())
862         .filter(m -> m.hasAnyName("size", "length"))
863         .filter(m -> m.getReturnType().isAny(int.class, Integer.class))
864         .findFirst()
865         .map(m -> safe(() -> (int) m.invoke(o2)))
866         .filter(Objects::nonNull);
867      // @formatter:on
868      if (sizeResult.isPresent())
869         return sizeResult.get();
870
871      // Fall back to listify
872      if (canListify(o))
873         return listify(o).size();
874
875      // Try to find isEmpty() method via reflection
876      // @formatter:off
877      var isEmpty = info(o).getPublicMethods().stream()
878         .filter(m -> ! m.hasParameters())
879         .filter(m -> m.hasName("isEmpty"))
880         .filter(m -> m.getReturnType().isAny(boolean.class, Boolean.class))
881         .map(m -> safe(() -> (Boolean)m.invoke(o2)))
882         .findFirst();
883      // @formatter:on
884      if (isEmpty.isPresent())
885         return isEmpty.get() ? 0 : 1;
886
887      throw illegalArg("Object of type {0} does not have a determinable size.", cns(o));
888   }
889
890   @Override
891   @SuppressWarnings("unchecked")
892   public String stringify(Object o) {
893
894      o = swap(o);
895
896      if (o == null)
897         return getSetting(SETTING_nullValue, null);
898
899      var c = o.getClass();
900      var stringifier = stringifierMap.computeIfAbsent(c, this::findStringifier);
901      if (stringifier.isEmpty()) {
902         stringifier = of(canListify(o) ? (bc, o2) -> bc.stringify(bc.listify(o2)) : (bc, o2) -> this.safeToString(o2));
903         stringifierMap.putIfAbsent(c, stringifier);
904      }
905      var o2 = o;
906      return stringifier.map(x -> (Stringifier)x).map(x -> x.apply(this, o2)).map(this::safeToString).orElse(null);
907   }
908
909   /**
910    * Builder class for configuring BasicBeanConverter instances.
911    *
912    * <p>This builder provides a fluent interface for registering custom type handlers
913    * and configuring conversion settings. All registration methods support method chaining
914    * for convenient configuration.</p>
915    *
916    * <h5 class='section'>Handler Registration:</h5>
917    * <ul>
918    *    <li><b>Stringifiers:</b> Custom string conversion logic for specific types</li>
919    *    <li><b>Listifiers:</b> Custom list conversion logic for collection-like types</li>
920    *    <li><b>Swappers:</b> Pre-processing transformation logic for wrapper types</li>
921    * </ul>
922    *
923    * <h5 class='section'>Registration Order:</h5>
924    * <p>Handlers are checked in reverse registration order (last registered wins).
925    * This allows overriding default handlers by registering more specific ones later.</p>
926    *
927    * <h5 class='section'>Inheritance Support:</h5>
928    * <p>All handlers support class inheritance and interface implementation.
929    * When looking up a handler, the system checks:</p>
930    * <ol>
931    *    <li>Exact class match</li>
932    *    <li>Interface matches (in order of interface declaration)</li>
933    *    <li>Superclass matches (walking up the inheritance hierarchy)</li>
934    * </ol>
935    *
936    * <h5 class='section'>Usage Example:</h5>
937    * <p class='bjava'>
938    *    <jk>var</jk> <jv>converter</jv> = BasicBeanConverter.<jsm>builder</jsm>()
939    *       .defaultSettings()
940    *       <jc>// Custom stringification for LocalDateTime</jc>
941    *       .addStringifier(LocalDateTime.<jk>class</jk>, (<jp>dt</jp>, <jp>conv</jp>) ->
942    *          <jp>dt</jp>.format(DateTimeFormatter.<jsf>ISO_LOCAL_DATE_TIME</jsf>))
943    *
944    *       <jc>// Custom collection handling for custom type</jc>
945    *       .addListifier(MyIterable.<jk>class</jk>, (<jp>iter</jp>, <jp>conv</jp>) ->
946    *          <jp>iter</jp>.stream().collect(toList()))
947    *
948    *       <jc>// Custom transformation for wrapper type</jc>
949    *       .addSwapper(LazyValue.<jk>class</jk>, (<jp>lazy</jp>, <jp>conv</jp>) ->
950    *          <jp>lazy</jp>.isComputed() ? <jp>lazy</jp>.get() : <jk>null</jk>)
951    *
952    *       <jc>// Configure settings</jc>
953    *       .addSetting(<jsf>SETTING_nullValue</jsf>, <js>"&lt;null&gt;"</js>)
954    *       .addSetting(<jsf>SETTING_fieldSeparator</jsf>, <js>" | "</js>)
955    *
956    *       <jc>// Add default handlers for common types</jc>
957    *       .defaultSettings()
958    *       .build();
959    * </p>
960    */
961
962   @Override
963   @SuppressWarnings("unchecked")
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 (Throwable t) { // NOSONAR
1026         return cns(t) + ": " + t.getMessage();
1027      }
1028   }
1029}