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 org.apache.juneau.junit.bct.Utils.*;
021import static java.util.stream.Collectors.*;
022
023import java.io.*;
024import java.lang.reflect.*;
025import java.util.*;
026import java.util.concurrent.*;
027import java.util.function.*;
028import java.util.stream.*;
029
030/**
031 * Default implementation of {@link BeanConverter} for Bean-Centric Test (BCT) object conversion.
032 *
033 * <p>This class provides a comprehensive, extensible framework for converting Java objects to strings
034 * and lists, with sophisticated property access capabilities. It's the core engine behind BCT testing
035 * assertions, handling complex object introspection and value extraction with high performance through
036 * intelligent caching and optimized lookup strategies.</p>
037 *
038 * <h5 class='section'>Key Features:</h5>
039 * <ul>
040 *    <li><b>Extensible Type Handlers:</b> Pluggable stringifiers, listifiers, and swappers for custom types</li>
041 *    <li><b>Performance Optimization:</b> ConcurrentHashMap caching for type-to-handler mappings</li>
042 *    <li><b>Comprehensive Defaults:</b> Built-in support for all common Java types and structures</li>
043 *    <li><b>Configurable Settings:</b> Customizable formatting, delimiters, and display options</li>
044 *    <li><b>Thread Safety:</b> Fully thread-safe implementation suitable for concurrent testing</li>
045 * </ul>
046 *
047 * <h5 class='section'>Architecture Overview:</h5>
048 * <p>The converter uses four types of pluggable handlers:</p>
049 * <dl>
050 *    <dt><b>Stringifiers:</b></dt>
051 *    <dd>Convert objects to string representations with custom formatting rules</dd>
052 *
053 *    <dt><b>Listifiers:</b></dt>
054 *    <dd>Convert collection-like objects to List&lt;Object&gt; for uniform iteration</dd>
055 *
056 *    <dt><b>Swappers:</b></dt>
057 *    <dd>Pre-process objects before conversion (unwrap Optional, call Supplier, etc.)</dd>
058 *
059 *    <dt><b>PropertyExtractors:</b></dt>
060 *    <dd>Define custom property access strategies for nested field navigation (e.g., <js>"user.address.city"</js>)</dd>
061 * </dl>
062 *
063 * <p>PropertyExtractors use a chain-of-responsibility pattern, where each extractor in the chain
064 * is tried until one can handle the property access. The framework includes built-in extractors for:</p>
065 * <ul>
066 *    <li><b>JavaBean properties:</b> Standard getter methods and public fields</li>
067 *    <li><b>Collection/Array access:</b> Numeric indices and size/length properties</li>
068 *    <li><b>Map access:</b> Key-based property retrieval and size property</li>
069 * </ul>
070 *
071 * <h5 class='section'>Default Type Support:</h5>
072 * <p>Out-of-the-box stringification support includes:</p>
073 * <ul>
074 *    <li><b>Collections:</b> List, Set, Queue → <js>"[item1,item2,item3]"</js> format</li>
075 *    <li><b>Maps:</b> Map, Properties → <js>"{key1=value1,key2=value2}"</js> format</li>
076 *    <li><b>Map Entries:</b> Map.Entry → <js>"key=value"</js> format</li>
077 *    <li><b>Arrays:</b> All array types → <js>"[element1,element2]"</js> format</li>
078 *    <li><b>Dates:</b> Date, Calendar → ISO-8601 format</li>
079 *    <li><b>Files/Streams:</b> File, InputStream, Reader → content as hex or text</li>
080 *    <li><b>Reflection:</b> Class, Method, Constructor → human-readable signatures</li>
081 *    <li><b>Enums:</b> Enum values → name() format</li>
082 * </ul>
083 *
084 * <p>Default listification support includes:</p>
085 * <ul>
086 *    <li><b>Collection types:</b> List, Set, Queue, and all subtypes</li>
087 *    <li><b>Iterable objects:</b> Any Iterable implementation</li>
088 *    <li><b>Iterators:</b> Iterator and Enumeration (consumed to list)</li>
089 *    <li><b>Streams:</b> Stream objects (terminated to list)</li>
090 *    <li><b>Optional:</b> Empty list or single-element list</li>
091 *    <li><b>Maps:</b> Converted to list of Map.Entry objects</li>
092 * </ul>
093 *
094 * <p>Default swapping support includes:</p>
095 * <ul>
096 *    <li><b>Optional:</b> Unwrapped to contained value or <jk>null</jk></li>
097 *    <li><b>Supplier:</b> Called to get supplied value</li>
098 *    <li><b>Future:</b> Extracts completed result or returns <js>"&lt;pending&gt;"</js> for incomplete futures (via {@link Swappers#futureSwapper()})</li>
099 * </ul>
100 *
101 * <h5 class='section'>Configuration Settings:</h5>
102 * <p>The converter supports extensive customization via settings:</p>
103 * <dl>
104 *    <dt><code>nullValue</code></dt>
105 *    <dd>String representation for null values (default: <js>"&lt;null&gt;"</js>)</dd>
106 *
107 *    <dt><code>selfValue</code></dt>
108 *    <dd>Special property name that returns the object itself (default: <js>"&lt;self&gt;"</js>)</dd>
109 *
110 *    <dt><code>emptyValue</code></dt>
111 *    <dd>String representation for empty collections (default: <js>"&lt;empty&gt;"</js>)</dd>
112 *
113 *    <dt><code>fieldSeparator</code></dt>
114 *    <dd>Delimiter between collection elements and map entries (default: <js>","</js>)</dd>
115 *
116 *    <dt><code>collectionPrefix/Suffix</code></dt>
117 *    <dd>Brackets around collection content (default: <js>"["</js> and <js>"]"</js>)</dd>
118 *
119 *    <dt><code>mapPrefix/Suffix</code></dt>
120 *    <dd>Brackets around map content (default: <js>"{"</js> and <js>"}"</js>)</dd>
121 *
122 *    <dt><code>mapEntrySeparator</code></dt>
123 *    <dd>Separator between map keys and values (default: <js>"="</js>)</dd>
124 *
125 *    <dt><code>calendarFormat</code></dt>
126 *    <dd>DateTimeFormatter for calendar objects (default: <jsf>ISO_INSTANT</jsf>)</dd>
127 *
128 *    <dt><code>classNameFormat</code></dt>
129 *    <dd>Format for class names: <js>"simple"</js>, <js>"canonical"</js>, or <js>"full"</js> (default: <js>"simple"</js>)</dd>
130 * </dl>
131 *
132 * <h5 class='section'>Usage Examples:</h5>
133 *
134 * <p><b>Basic Usage with Defaults:</b></p>
135 * <p class='bjava'>
136 *    <jc>// Use default converter</jc>
137 *    <jk>var</jk> <jv>converter</jv> = BasicBeanConverter.<jsf>DEFAULT</jsf>;
138 *    <jk>var</jk> <jv>result</jv> = <jv>converter</jv>.stringify(<jv>myObject</jv>);
139 * </p>
140 *
141 * <p><b>Custom Configuration:</b></p>
142 * <p class='bjava'>
143 *    <jc>// Build custom converter</jc>
144 *    <jk>var</jk> <jv>converter</jv> = BasicBeanConverter.<jsm>builder</jsm>()
145 *       .defaultSettings()
146 *       .addSetting(<jsf>SETTING_nullValue</jsf>, <js>"&lt;null&gt;"</js>)
147 *       .addSetting(<jsf>SETTING_fieldSeparator</jsf>, <js>" | "</js>)
148 *       .addStringifier(MyClass.<jk>class</jk>, (<jp>obj</jp>, <jp>conv</jp>) -> <js>"MyClass["</js> + <jp>obj</jp>.getName() + <js>"]"</js>)
149 *       .addListifier(MyIterable.<jk>class</jk>, (<jp>obj</jp>, <jp>conv</jp>) -> <jp>obj</jp>.toList())
150 *       .addSwapper(MyWrapper.<jk>class</jk>, (<jp>obj</jp>, <jp>conv</jp>) -> <jp>obj</jp>.getWrapped())
151 *       .build();
152 * </p>
153 *
154 * <p><b>Complex Property Access:</b></p>
155 * <p class='bjava'>
156 *    <jc>// Extract nested properties</jc>
157 *    <jk>var</jk> <jv>name</jv> = <jv>converter</jv>.getEntry(<jv>user</jv>, <js>"name"</js>);
158 *    <jk>var</jk> <jv>city</jv> = <jv>converter</jv>.getEntry(<jv>user</jv>, <js>"address.city"</js>);
159 *    <jk>var</jk> <jv>firstOrder</jv> = <jv>converter</jv>.getEntry(<jv>user</jv>, <js>"orders.0.id"</js>);
160 *    <jk>var</jk> <jv>orderCount</jv> = <jv>converter</jv>.getEntry(<jv>user</jv>, <js>"orders.length"</js>);
161 * </p>
162 *
163 * <p><b>Special Property Values:</b></p>
164 * <p class='bjava'>
165 *    <jc>// Use special property names</jc>
166 *    <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>
167 *    <jk>var</jk> <jv>nullValue</jv> = <jv>converter</jv>.getEntry(<jv>user</jv>, <js>"&lt;null&gt;"</js>); <jc>// Returns null</jc>
168 *
169 *    <jc>// Custom self value</jc>
170 *    <jk>var</jk> <jv>customConverter</jv> = BasicBeanConverter.<jsm>builder</jsm>()
171 *       .defaultSettings()
172 *       .addSetting(<jsf>SETTING_selfValue</jsf>, <js>"this"</js>)
173 *       .build();
174 *    <jk>var</jk> <jv>selfRef</jv> = <jv>customConverter</jv>.getEntry(<jv>user</jv>, <js>"this"</js>); <jc>// Returns user object</jc>
175 * </p>
176 *
177 * <h5 class='section'>Performance Characteristics:</h5>
178 * <ul>
179 *    <li><b>Handler Lookup:</b> O(1) average case via ConcurrentHashMap caching</li>
180 *    <li><b>Type Registration:</b> Handlers checked in reverse registration order (last wins)</li>
181 *    <li><b>Inheritance Support:</b> Handlers support class inheritance and interface implementation</li>
182 *    <li><b>Thread Safety:</b> Full concurrency support with no locking overhead after initialization</li>
183 *    <li><b>Memory Efficiency:</b> Minimal object allocation during normal operation</li>
184 * </ul>
185 *
186 * <h5 class='section'>Extension Patterns:</h5>
187 *
188 * <p><b>Custom Type Stringification:</b></p>
189 * <p class='bjava'>
190 *    <jv>builder</jv>.addStringifier(LocalDateTime.<jk>class</jk>, (<jp>dt</jp>, <jp>conv</jp>) ->
191 *       <jp>dt</jp>.format(DateTimeFormatter.<jsf>ISO_LOCAL_DATE_TIME</jsf>));
192 * </p>
193 *
194 * <p><b>Custom Collection Handling:</b></p>
195 * <p class='bjava'>
196 *    <jv>builder</jv>.addListifier(MyCustomCollection.<jk>class</jk>, (<jp>coll</jp>, <jp>conv</jp>) ->
197 *       <jp>coll</jp>.stream().map(<jp>conv</jp>::swap).toList());
198 * </p>
199 *
200 * <p><b>Custom Object Transformation:</b></p>
201 * <p class='bjava'>
202 *    <jv>builder</jv>.addSwapper(LazyValue.<jk>class</jk>, (<jp>lazy</jp>, <jp>conv</jp>) ->
203 *       <jp>lazy</jp>.isEvaluated() ? <jp>lazy</jp>.getValue() : "&lt;unevaluated&gt;");
204 * </p>
205 *
206 * <h5 class='section'>Integration with BCT:</h5>
207 * <p>This class is used internally by all BCT assertion methods in {@link BctAssertions}:</p>
208 * <ul>
209 *    <li>{@link BctAssertions#assertBean(Object, String, String)} - Uses getEntry() for property access</li>
210 *    <li>{@link BctAssertions#assertList(List, Object...)} - Uses stringify() for element comparison</li>
211 *    <li>{@link BctAssertions#assertBeans(Collection, String, String...)} - Uses both getEntry() and stringify()</li>
212 * </ul>
213 *
214 * @see BeanConverter
215 */
216@SuppressWarnings("rawtypes")
217public class BasicBeanConverter implements BeanConverter {
218
219   public static final BasicBeanConverter DEFAULT = builder().defaultSettings().build();
220
221   public static final String
222      SETTING_nullValue = "nullValue",
223      SETTING_selfValue = "selfValue",
224      SETTING_fieldSeparator = "fieldSeparator",
225      SETTING_collectionPrefix = "collectionPrefix",
226      SETTING_collectionSuffix = "collectionSuffix",
227      SETTING_mapPrefix = "mapPrefix",
228      SETTING_mapSuffix = "mapSuffix",
229      SETTING_mapEntrySeparator = "mapEntrySeparator",
230      SETTING_calendarFormat = "calendarFormat",
231      SETTING_classNameFormat = "classNameFormat";
232
233   private final List<StringifierEntry<?>> stringifiers;
234   private final List<ListifierEntry<?>> listifiers;
235   private final List<SwapperEntry<?>> swappers;
236   private final List<PropertyExtractor> propertyExtractors;
237   private final Map<String,Object> settings;
238
239   private final ConcurrentHashMap<Class,Optional<Stringifier<?>>> stringifierMap = new ConcurrentHashMap<>();
240   private final ConcurrentHashMap<Class,Optional<Listifier<?>>> listifierMap = new ConcurrentHashMap<>();
241   private final ConcurrentHashMap<Class,Optional<Swapper<?>>> swapperMap = new ConcurrentHashMap<>();
242
243   protected BasicBeanConverter(Builder b) {
244      stringifiers = new ArrayList<>(b.stringifiers);
245      listifiers = new ArrayList<>(b.listifiers);
246      swappers = new ArrayList<>(b.swappers);
247      propertyExtractors = new ArrayList<>(b.propertyExtractors);
248      settings = new HashMap<>(b.settings);
249      Collections.reverse(stringifiers);
250      Collections.reverse(listifiers);
251      Collections.reverse(swappers);
252      Collections.reverse(propertyExtractors);
253   }
254
255   /**
256    * Creates a new builder for configuring a BasicBeanConverter instance.
257    *
258    * <p>The builder allows registration of custom stringifiers, listifiers, and swappers,
259    * as well as configuration of various formatting settings before building the converter.</p>
260    *
261    * @return A new Builder instance
262    */
263   public static Builder builder() {
264      return new Builder();
265   }
266
267   @Override
268   public String stringify(Object o) {
269      o = swap(o);
270      if (o == null)
271         return getSetting(SETTING_nullValue, null);
272      var c = o.getClass();
273      var stringifier = stringifierMap.computeIfAbsent(c, this::findStringifier);
274      if (stringifier.isEmpty()) {
275         stringifier = of(canListify(o) ? (bc, o2) -> bc.stringify(bc.listify(o2)) : (bc, o2) -> safeToString(o2));
276         stringifierMap.putIfAbsent(c, stringifier);
277      }
278      var o2 = o;
279      return stringifier.map(x -> (Stringifier)x).map(x -> x.apply(this, o2)).map(Utils::safeToString).orElse(null);
280   }
281
282   @Override
283   public Object swap(Object o) {
284      if (o == null) return null;
285      var c = o.getClass();
286      var swapper = swapperMap.computeIfAbsent(c, this::findSwapper);
287      if (swapper.isPresent())
288         return swap(swapper.map(x -> (Swapper)x).map(x -> x.apply(this, o)).orElse(null));
289      return o;
290   }
291
292   @Override
293   public List<Object> listify(Object o) {
294      assertArgNotNull("o", o);
295      o = swap(o);
296      if (o instanceof List)
297         return (List<Object>)o;
298      var c = o.getClass();
299      if (c.isArray())
300         return arrayToList(o);
301      var o2 = o;
302      return listifierMap.computeIfAbsent(c, this::findListifier).map(x -> (Listifier)x).map(x -> (List<Object>)x.apply(this, o2)).orElseThrow(()->new IllegalArgumentException(f("Object of type {0} could not be converted to a list.", t(o2))));
303   }
304
305   @Override
306   public boolean canListify(Object o) {
307      o = swap(o);
308      if (o == null)
309         return false;
310      var c = o.getClass();
311      return o instanceof List || c.isArray() || listifierMap.computeIfAbsent(c, this::findListifier).isPresent();
312   }
313
314   @Override
315   public Object getProperty(Object object, String name) {
316      var o = swap(object);
317      return propertyExtractors
318         .stream()
319         .filter(x -> x.canExtract(this, o, name))
320         .findFirst()
321         .orElseThrow(()->new RuntimeException(f("Could not find extractor for object of type {0}", o.getClass().getName())))
322         .extract(this, o, name);
323   }
324
325   @Override
326   public String getNested(Object o, NestedTokenizer.Token token) {
327      assertArgNotNull("token", token);
328
329      if (o == null) return getSetting(SETTING_nullValue, null);
330
331      // Handle #{...} syntax for iterating over collections/arrays
332      if (eq("#", token.getValue()) && canListify(o)) {
333         return listify(o).stream().map(x -> token.getNested().stream().map(x2 -> getNested(x, x2)).collect(joining(",","{","}"))).collect(joining(",","[","]"));
334      }
335
336      // Original logic for regular property access
337      var pn = token.getValue();
338      var selfValue = getSetting(SETTING_selfValue, "<self>");
339
340      // Handle special values
341      Object e;
342      if (pn.equals(selfValue)) {
343         e = o; // Return the object itself
344      } else {
345         e = ofNullable(getProperty(o, pn)).orElse(null);
346      }
347      if (e == null || ! token.hasNested()) return stringify(e);
348      return token.getNested().stream().map(x -> getNested(e, x)).map(this::stringify).collect(joining(",","{","}"));
349   }
350
351   private Optional<Stringifier<?>> findStringifier(Class<?> c) {
352      if (c == null)
353         return empty();
354      var s = stringifiers.stream().filter(x -> x.forClass.isAssignableFrom(c)).findFirst().orElse(null);
355      if (s != null)
356         return of(s.function);
357      return findStringifier(c.getSuperclass());
358   }
359
360   private Optional<Listifier<?>> findListifier(Class<?> c) {
361      if (c == null)
362         return empty();
363      var l = listifiers.stream().filter(x -> x.forClass.isAssignableFrom(c)).findFirst().orElse(null);
364      if (l != null)
365         return of(l.function);
366      return findListifier(c.getSuperclass());
367   }
368
369   private Optional<Swapper<?>> findSwapper(Class<?> c) {
370      if (c == null)
371         return empty();
372      var s = swappers.stream().filter(x -> x.forClass.isAssignableFrom(c)).findFirst().orElse(null);
373      if (s != null)
374         return of(s.function);
375      return findSwapper(c.getSuperclass());
376   }
377
378   @Override
379   public <T> T getSetting(String key, T def) {
380      return (T)settings.getOrDefault(key, def);
381   }
382
383   /**
384    * Builder class for configuring BasicBeanConverter instances.
385    *
386    * <p>This builder provides a fluent interface for registering custom type handlers
387    * and configuring conversion settings. All registration methods support method chaining
388    * for convenient configuration.</p>
389    *
390    * <h5 class='section'>Handler Registration:</h5>
391    * <ul>
392    *    <li><b>Stringifiers:</b> Custom string conversion logic for specific types</li>
393    *    <li><b>Listifiers:</b> Custom list conversion logic for collection-like types</li>
394    *    <li><b>Swappers:</b> Pre-processing transformation logic for wrapper types</li>
395    * </ul>
396    *
397    * <h5 class='section'>Registration Order:</h5>
398    * <p>Handlers are checked in reverse registration order (last registered wins).
399    * This allows overriding default handlers by registering more specific ones later.</p>
400    *
401    * <h5 class='section'>Inheritance Support:</h5>
402    * <p>All handlers support class inheritance and interface implementation.
403    * When looking up a handler, the system checks:</p>
404    * <ol>
405    *    <li>Exact class match</li>
406    *    <li>Interface matches (in order of interface declaration)</li>
407    *    <li>Superclass matches (walking up the inheritance hierarchy)</li>
408    * </ol>
409    *
410    * <h5 class='section'>Usage Example:</h5>
411    * <p class='bjava'>
412    *    <jk>var</jk> <jv>converter</jv> = BasicBeanConverter.<jsm>builder</jsm>()
413    *       .defaultSettings()
414    *       <jc>// Custom stringification for LocalDateTime</jc>
415    *       .addStringifier(LocalDateTime.<jk>class</jk>, (<jp>dt</jp>, <jp>conv</jp>) ->
416    *          <jp>dt</jp>.format(DateTimeFormatter.<jsf>ISO_LOCAL_DATE_TIME</jsf>))
417    *
418    *       <jc>// Custom collection handling for custom type</jc>
419    *       .addListifier(MyIterable.<jk>class</jk>, (<jp>iter</jp>, <jp>conv</jp>) ->
420    *          <jp>iter</jp>.stream().collect(toList()))
421    *
422    *       <jc>// Custom transformation for wrapper type</jc>
423    *       .addSwapper(LazyValue.<jk>class</jk>, (<jp>lazy</jp>, <jp>conv</jp>) ->
424    *          <jp>lazy</jp>.isComputed() ? <jp>lazy</jp>.get() : <jk>null</jk>)
425    *
426    *       <jc>// Configure settings</jc>
427    *       .addSetting(<jsf>SETTING_nullValue</jsf>, <js>"&lt;null&gt;"</js>)
428    *       .addSetting(<jsf>SETTING_fieldSeparator</jsf>, <js>" | "</js>)
429    *
430    *       <jc>// Add default handlers for common types</jc>
431    *       .defaultSettings()
432    *       .build();
433    * </p>
434    */
435
436   /**
437    * Builder for creating customized BasicBeanConverter instances.
438    *
439    * <p>This builder provides a fluent API for configuring custom type handlers, settings,
440    * and property extraction logic. The builder supports registration of four main types
441    * of customizations:</p>
442    *
443    * <h5 class='section'>Type Handlers:</h5>
444    * <ul>
445    *    <li><b>{@link #addStringifier(Class, Stringifier)}</b> - Custom string conversion logic</li>
446    *    <li><b>{@link #addListifier(Class, Listifier)}</b> - Custom list conversion for collection-like objects</li>
447    *    <li><b>{@link #addSwapper(Class, Swapper)}</b> - Pre-processing and object transformation</li>
448    *    <li><b>{@link #addPropertyExtractor(PropertyExtractor)}</b> - Custom property access strategies</li>
449    * </ul>
450    *
451    * <h5 class='section'>PropertyExtractors:</h5>
452    * <p>Property extractors define how the converter accesses object properties during nested
453    * field access (e.g., {@code "user.address.city"}). The converter uses a chain-of-responsibility
454    * pattern, trying each registered extractor until one succeeds:</p>
455    *
456    * <ul>
457    *    <li><b>{@link PropertyExtractors.ObjectPropertyExtractor}</b> - JavaBean-style properties via reflection</li>
458    *    <li><b>{@link PropertyExtractors.ListPropertyExtractor}</b> - Numeric indices and size/length for arrays/collections</li>
459    *    <li><b>{@link PropertyExtractors.MapPropertyExtractor}</b> - Key-based access for Map objects</li>
460    * </ul>
461    *
462    * <p>Custom extractors can be added to handle specialized property access patterns:</p>
463    * <p class='bjava'>
464    *    <jk>var</jk> <jv>converter</jv> = BasicBeanConverter.<jsm>builder</jsm>()
465    *       .defaultSettings()
466    *       .addPropertyExtractor(<jk>new</jk> MyCustomExtractor())
467    *       .addPropertyExtractor((<jp>obj</jp>, <jp>prop</jp>) -&gt; {
468    *          <jk>if</jk> (<jp>obj</jp> <jk>instanceof</jk> MySpecialType <jv>special</jv>) {
469    *             <jk>return</jk> <jv>special</jv>.getCustomProperty(<jp>prop</jp>);
470    *          }
471    *          <jk>return</jk> <jk>null</jk>; <jc>// Try next extractor</jc>
472    *       })
473    *       .build();
474    * </p>
475    *
476    * <h5 class='section'>Default Configuration:</h5>
477    * <p>The {@link #defaultSettings()} method pre-registers comprehensive type handlers
478    * and property extractors for common Java types, providing out-of-the-box functionality
479    * for most use cases while still allowing full customization.</p>
480    *
481    * @see PropertyExtractors
482    * @see PropertyExtractor
483    */
484   public static class Builder {
485      private Map<String,Object> settings = new HashMap<>();
486      private List<StringifierEntry<?>> stringifiers = new ArrayList<>();
487      private List<ListifierEntry<?>> listifiers = new ArrayList<>();
488      private List<SwapperEntry<?>> swappers = new ArrayList<>();
489      private List<PropertyExtractor> propertyExtractors = new ArrayList<>();
490
491      /**
492       * Adds a configuration setting to the converter.
493       *
494       * @param key The setting key (use SETTING_* constants)
495       * @param value The setting value
496       * @return This builder for method chaining
497       */
498      public Builder addSetting(String key, Object value) { settings.put(key, value); return this; }
499
500      /**
501       * Registers a custom stringifier for a specific type.
502       *
503       * <p>Stringifiers convert objects to their string representations. The BiFunction
504       * receives the object to convert and the converter instance for recursive calls.</p>
505       *
506       * @param <T> The type to handle
507       * @param c The class to register the stringifier for
508       * @param s The stringification function
509       * @return This builder for method chaining
510       */
511      public <T> Builder addStringifier(Class<T> c, Stringifier<T> s) { stringifiers.add(new StringifierEntry<>(c, s)); return this; }
512
513      /**
514       * Registers a custom listifier for a specific type.
515       *
516       * <p>Listifiers convert collection-like objects to List&lt;Object&gt;. The BiFunction
517       * receives the object to convert and the converter instance for recursive calls.</p>
518       *
519       * @param <T> The type to handle
520       * @param c The class to register the listifier for
521       * @param l The listification function
522       * @return This builder for method chaining
523       */
524      public <T> Builder addListifier(Class<T> c, Listifier<T> l) { listifiers.add(new ListifierEntry<>(c, l)); return this; }
525
526      /**
527       * Registers a custom swapper for a specific type.
528       *
529       * <p>Swappers pre-process objects before conversion. Common uses include
530       * unwrapping Optional values, calling Supplier methods, or extracting values
531       * from wrapper objects.</p>
532       *
533       * @param <T> The type to handle
534       * @param c The class to register the swapper for
535       * @param s The swapping function
536       * @return This builder for method chaining
537       */
538      public <T> Builder addSwapper(Class<T> c, Swapper<T> s) { swappers.add(new SwapperEntry<>(c, s)); return this; }
539
540      /**
541       * Registers a custom property extractor for specialized property access logic.
542       *
543       * <p>Property extractors enable custom property access patterns beyond standard JavaBean
544       * conventions. The converter tries extractors in registration order until one returns
545       * a non-<jk>null</jk> value. This allows for:</p>
546       * <ul>
547       *    <li><b>Custom data structures:</b> Special property access for non-standard objects</li>
548       *    <li><b>Database entities:</b> Property access via entity-specific methods</li>
549       *    <li><b>Dynamic properties:</b> Computed or cached property values</li>
550       *    <li><b>Legacy objects:</b> Bridging older APIs with modern property access</li>
551       * </ul>
552       *
553       * <h5 class='section'>Implementation Example:</h5>
554       * <p class='bjava'>
555       *    <jc>// Custom extractor for a specialized data class</jc>
556       *    PropertyExtractor <jv>customExtractor</jv> = (<jp>obj</jp>, <jp>property</jp>) -&gt; {
557       *       <jk>if</jk> (<jp>obj</jp> <jk>instanceof</jk> DatabaseEntity <jv>entity</jv>) {
558       *          <jk>switch</jk> (<jp>property</jp>) {
559       *             <jk>case</jk> <js>"id"</js>: <jk>return</jk> <jv>entity</jv>.getPrimaryKey();
560       *             <jk>case</jk> <js>"displayName"</js>: <jk>return</jk> <jv>entity</jv>.computeDisplayName();
561       *             <jk>case</jk> <js>"metadata"</js>: <jk>return</jk> <jv>entity</jv>.getMetadataAsMap();
562       *          }
563       *       }
564       *       <jk>return</jk> <jk>null</jk>; <jc>// Let next extractor try</jc>
565       *    };
566       *
567       *    <jk>var</jk> <jv>converter</jv> = BasicBeanConverter.<jsm>builder</jsm>()
568       *       .addPropertyExtractor(<jv>customExtractor</jv>)
569       *       .defaultSettings() <jc>// Adds standard extractors</jc>
570       *       .build();
571       * </p>
572       *
573       * <p><b>Execution Order:</b> Custom extractors are tried before the default extractors
574       * added by {@link #defaultSettings()}, allowing overrides of standard behavior.</p>
575       *
576       * @param e The property extractor to register
577       * @return This builder for method chaining
578       * @see PropertyExtractor
579       * @see PropertyExtractors
580       */
581      public Builder addPropertyExtractor(PropertyExtractor e) { propertyExtractors.add(e); return this; }
582
583      /**
584       * Adds default handlers and settings for common Java types.
585       *
586       * <p>This method registers comprehensive support for:</p>
587       * <ul>
588       *    <li><b>Collections:</b> List, Set, Collection → bracket format</li>
589       *    <li><b>Maps:</b> Map, Properties → brace format with key=value pairs</li>
590       *    <li><b>Map Entries:</b> Map.Entry → <js>"key=value"</js> format</li>
591       *    <li><b>Dates:</b> Date, Calendar → ISO-8601 format</li>
592       *    <li><b>Files/Streams:</b> File, InputStream, Reader → content extraction</li>
593       *    <li><b>Reflection:</b> Class, Method, Constructor → readable signatures</li>
594       *    <li><b>Enums:</b> All enum types → name() format</li>
595       *    <li><b>Iterables:</b> Iterable, Iterator, Enumeration, Stream → list conversion</li>
596       *    <li><b>Wrappers:</b> Optional, Supplier → unwrapping/evaluation</li>
597       * </ul>
598       *
599       * <p>Default settings include:</p>
600       * <ul>
601       *    <li><code>nullValue</code> = <js>"&lt;null&gt;"</js></li>
602       *    <li><code>emptyValue</code> = <js>"&lt;empty&gt;"</js></li>
603       *    <li><code>classNameFormat</code> = <js>"simple"</js></li>
604       * </ul>
605       *
606       * <p><b>Note:</b> This should typically be called after custom handlers to avoid
607       * overriding your custom configurations, since handlers are processed in reverse order.</p>
608       *
609       * @return This builder for method chaining
610       */
611      public Builder defaultSettings() {
612         addSetting(SETTING_nullValue, "<null>");
613         addSetting(SETTING_selfValue, "<self>");
614         addSetting(SETTING_classNameFormat, "simple");
615
616         addStringifier(Map.Entry.class, Stringifiers.mapEntryStringifier());
617         addStringifier(GregorianCalendar.class, Stringifiers.calendarStringifier());
618         addStringifier(Date.class, Stringifiers.dateStringifier());
619         addStringifier(InputStream.class, Stringifiers.inputStreamStringifier());
620         addStringifier(byte[].class, Stringifiers.byteArrayStringifier());
621         addStringifier(Reader.class, Stringifiers.readerStringifier());
622         addStringifier(File.class, Stringifiers.fileStringifier());
623         addStringifier(Enum.class, Stringifiers.enumStringifier());
624         addStringifier(Class.class, Stringifiers.classStringifier());
625         addStringifier(Constructor.class, Stringifiers.constructorStringifier());
626         addStringifier(Method.class, Stringifiers.methodStringifier());
627         addStringifier(List.class, Stringifiers.listStringifier());
628         addStringifier(Map.class, Stringifiers.mapStringifier());
629
630         // Note: Listifiers are processed in reverse registration order (last registered wins).
631         // Collection must be registered after Iterable so it takes precedence for Sets,
632         // ensuring TreeSet conversion for deterministic ordering.
633         addListifier(Iterable.class, Listifiers.iterableListifier());
634         addListifier(Collection.class, Listifiers.collectionListifier());
635         addListifier(Iterator.class, Listifiers.iteratorListifier());
636         addListifier(Enumeration.class, Listifiers.enumerationListifier());
637         addListifier(Stream.class, Listifiers.streamListifier());
638         addListifier(Map.class, Listifiers.mapListifier());
639
640         addSwapper(Optional.class, Swappers.optionalSwapper());
641         addSwapper(Supplier.class, Swappers.supplierSwapper());
642         addSwapper(Future.class, Swappers.futureSwapper());
643
644         addPropertyExtractor(new PropertyExtractors.ObjectPropertyExtractor());
645         addPropertyExtractor(new PropertyExtractors.ListPropertyExtractor());
646         addPropertyExtractor(new PropertyExtractors.MapPropertyExtractor());
647
648         return this;
649      }
650
651      /**
652       * Builds the configured BasicBeanConverter instance.
653       *
654       * <p>This method creates a new BasicBeanConverter with all registered handlers
655       * and settings. The builder can be reused to create multiple converters with
656       * the same configuration.</p>
657       *
658       * @return A new BasicBeanConverter instance
659       */
660      public BasicBeanConverter build() {
661         return new BasicBeanConverter(this);
662      }
663   }
664
665   static class StringifierEntry<T> {
666      private Class<T> forClass;
667      private Stringifier<T> function;
668
669      private StringifierEntry(Class<T> forClass, Stringifier function) {
670         this.forClass = forClass;
671         this.function = function;
672      }
673   }
674
675   static class ListifierEntry<T> {
676      private Class<T> forClass;
677      private Listifier<T> function;
678
679      private ListifierEntry(Class<T> forClass, Listifier<T> function) {
680         this.forClass = forClass;
681         this.function = function;
682      }
683   }
684
685   static class SwapperEntry<T> {
686      private Class<T> forClass;
687      private Swapper<T> function;
688
689      private SwapperEntry(Class<T> forClass, Swapper<T> function) {
690         this.forClass = forClass;
691         this.function = function;
692      }
693   }
694}