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.Collections.*;
020import static java.util.Spliterators.*;
021import static java.util.stream.StreamSupport.*;
022import static org.apache.juneau.commons.utils.CollectionUtils.*;
023import static org.apache.juneau.commons.utils.Utils.*;
024import static org.apache.juneau.junit.bct.BctConfiguration.*;
025
026import java.util.*;
027import java.util.stream.*;
028
029/**
030 * Collection of standard listifier implementations for the Bean-Centric Testing framework.
031 *
032 * <p>This class provides built-in list conversion strategies that handle common Java collection
033 * and iterable types. These listifiers are automatically registered when using
034 * {@link BasicBeanConverter.Builder#defaultSettings()}.</p>
035 *
036 * <h5 class='section'>Purpose:</h5>
037 * <p>Listifiers convert various collection-like objects to {@link List} instances for uniform
038 * processing in BCT assertions. This enables consistent iteration and element access across
039 * different data structures like arrays, iterators, streams, and custom collections.</p>
040 *
041 * <h5 class='section'>Built-in Listifiers:</h5>
042 * <ul>
043 *    <li><b>{@link #collectionListifier()}</b> - Converts {@link Collection} objects to {@link ArrayList}</li>
044 *    <li><b>{@link #iterableListifier()}</b> - Converts {@link Iterable} objects using streams</li>
045 *    <li><b>{@link #iteratorListifier()}</b> - Converts {@link Iterator} objects to lists (consumes iterator)</li>
046 *    <li><b>{@link #enumerationListifier()}</b> - Converts {@link Enumeration} objects to lists</li>
047 *    <li><b>{@link #streamListifier()}</b> - Converts {@link Stream} objects to lists (terminates stream)</li>
048 *    <li><b>{@link #mapListifier()}</b> - Converts {@link Map} to list of {@link java.util.Map.Entry} objects</li>
049 * </ul>
050 *
051 * <h5 class='section'>Usage Example:</h5>
052 * <p class='bjava'>
053 *    <jc>// Register listifiers using builder</jc>
054 *    <jk>var</jk> <jv>converter</jv> = BasicBeanConverter.<jsm>builder</jsm>()
055 *       .defaultSettings()
056 *       .addListifier(Collection.<jk>class</jk>, Listifiers.<jsm>collectionListifier</jsm>())
057 *       .addListifier(Stream.<jk>class</jk>, Listifiers.<jsm>streamListifier</jsm>())
058 *       .build();
059 * </p>
060 *
061 * <h5 class='section'>Iterator Consumption:</h5>
062 * <p><b>Warning:</b> Some listifiers consume their input objects during conversion:</p>
063 * <ul>
064 *    <li><b>{@link Iterator}:</b> Elements are consumed and iterator becomes exhausted</li>
065 *    <li><b>{@link Enumeration}:</b> Elements are consumed and enumeration becomes exhausted</li>
066 *    <li><b>{@link Stream}:</b> Stream is terminated and cannot be reused</li>
067 * </ul>
068 *
069 * <h5 class='section'>Custom Listifier Development:</h5>
070 * <p>When creating custom listifiers, follow these patterns:</p>
071 * <ul>
072 *    <li><b>Null Safety:</b> Handle <jk>null</jk> inputs gracefully</li>
073 *    <li><b>Immutability:</b> Return new lists rather than modifying inputs</li>
074 *    <li><b>Type Safety:</b> Ensure proper generic type handling</li>
075 *    <li><b>Performance:</b> Consider memory usage for large collections</li>
076 * </ul>
077 *
078 * @see Listifier
079 * @see BasicBeanConverter.Builder#addListifier(Class, Listifier)
080 * @see BasicBeanConverter.Builder#defaultSettings()
081 */
082@SuppressWarnings("rawtypes")
083public class Listifiers {
084
085   /**
086    * Returns a listifier for {@link Collection} objects that converts them to {@link ArrayList}.
087    *
088    * <p>This listifier creates a new ArrayList containing all elements from the source collection.
089    * It works with any Collection subtype including List, Set, Queue, and Deque.</p>
090    *
091    * <h5 class='section'>Behavior:</h5>
092    * <ul>
093    *    <li><b>Non-empty collections:</b> Returns new ArrayList with all elements in iteration order</li>
094    *    <li><b>Empty collections:</b> Returns new empty ArrayList</li>
095    *    <li><b>Preserves order:</b> Maintains the iteration order of the source collection</li>
096    *    <li><b>Set ordering:</b> Converts unordered Sets (HashSet, etc.) to TreeSet for deterministic ordering</li>
097    * </ul>
098    *
099    * <h5 class='section'>Set Ordering Behavior:</h5>
100    * <p>To ensure predictable test results, this listifier handles Sets with unreliable ordering:</p>
101    * <ul>
102    *    <li><b>{@link SortedSet} (TreeSet, etc.):</b> Preserves existing sort order</li>
103    *    <li><b>{@link LinkedHashSet}:</b> Preserves insertion order</li>
104    *    <li><b>{@link HashSet} and other unordered Sets:</b> Converts to {@link TreeSet} for natural ordering</li>
105    * </ul>
106    *
107    * <h5 class='section'>Usage Examples:</h5>
108    * <p class='bjava'>
109    *    <jc>// Test with different collection types</jc>
110    *    <jk>var</jk> <jv>list</jv> = List.<jsm>of</jsm>(<js>"a"</js>, <js>"b"</js>, <js>"c"</js>);
111    *    <jsm>assertList</jsm>(<jv>list</jv>, <js>"a"</js>, <js>"b"</js>, <js>"c"</js>);
112    *
113    *    <jc>// HashSet converted to TreeSet for predictable ordering</jc>
114    *    <jk>var</jk> <jv>set</jv> = Set.<jsm>of</jsm>(<js>"z"</js>, <js>"a"</js>, <js>"m"</js>);
115    *    <jsm>assertList</jsm>(<jv>set</jv>, <js>"a"</js>, <js>"m"</js>, <js>"z"</js>); <jc>// Natural ordering</jc>
116    *
117    *    <jc>// LinkedHashSet preserves insertion order</jc>
118    *    <jk>var</jk> <jv>linkedSet</jv> = <jk>new</jk> LinkedHashSet&lt;&gt;(Arrays.<jsm>asList</jsm>(<js>"first"</js>, <js>"second"</js>));
119    *    <jsm>assertList</jsm>(<jv>linkedSet</jv>, <js>"first"</js>, <js>"second"</js>);
120    *
121    *    <jk>var</jk> <jv>queue</jv> = <jk>new</jk> LinkedList&lt;&gt;(Arrays.<jsm>asList</jsm>(<js>"first"</js>, <js>"second"</js>));
122    *    <jsm>assertList</jsm>(<jv>queue</jv>, <js>"first"</js>, <js>"second"</js>);
123    * </p>
124    *
125    * <h5 class='section'>Performance:</h5>
126    * <p>This listifier creates a new ArrayList and copies all elements, so it has O(n) time
127    * and space complexity. For unordered Sets, an additional TreeSet conversion adds O(n log n)
128    * sorting overhead. For large collections, consider the memory implications.</p>
129    *
130    * @return A {@link Listifier} for {@link Collection} objects
131    * @see Collection
132    * @see ArrayList
133    * @see TreeSet
134    * @see LinkedHashSet
135    */
136   @SuppressWarnings("unchecked")
137   public static Listifier<Collection> collectionListifier() {
138      return (bc, collection) -> {
139         if (collection instanceof Set && ! (collection instanceof SortedSet) && ! (collection instanceof LinkedHashSet) && BctConfiguration.get(BCT_SORT_COLLECTIONS, false)) {
140            // TODO - This is too unreliable.
141            var collection2 = new TreeSet<>(flexibleComparator(bc));
142            collection2.addAll(collection);
143            collection = collection2;
144         }
145         return toList(collection);
146      };
147   }
148
149   /**
150    * Returns a listifier for {@link Enumeration} objects that converts them to lists.
151    *
152    * <p><b>Warning:</b> This listifier consumes the enumeration during conversion. After listification,
153    * the enumeration will be exhausted and cannot be used again.</p>
154    *
155    * <h5 class='section'>Behavior:</h5>
156    * <ul>
157    *    <li><b>Element extraction:</b> Consumes all remaining elements from the enumeration</li>
158    *    <li><b>Order preservation:</b> Maintains the enumeration's order in the resulting list</li>
159    *    <li><b>Enumeration exhaustion:</b> The enumeration becomes unusable after conversion</li>
160    * </ul>
161    *
162    * <h5 class='section'>Usage Examples:</h5>
163    * <p class='bjava'>
164    *    <jc>// Test with Vector enumeration</jc>
165    *    <jk>var</jk> <jv>vector</jv> = <jk>new</jk> Vector&lt;&gt;(List.<jsm>of</jsm>(<js>"x"</js>, <js>"y"</js>, <js>"z"</js>));
166    *    <jk>var</jk> <jv>enumeration</jv> = <jv>vector</jv>.elements();
167    *    <jsm>assertList</jsm>(<jv>enumeration</jv>, <js>"x"</js>, <js>"y"</js>, <js>"z"</js>);
168    *
169    *    <jc>// Test with Hashtable enumeration</jc>
170    *    <jk>var</jk> <jv>table</jv> = <jk>new</jk> Hashtable&lt;&gt;(Map.<jsm>of</jsm>(<js>"key1"</js>, <js>"value1"</js>));
171    *    <jk>var</jk> <jv>keys</jv> = <jv>table</jv>.keys();
172    *    <jsm>assertList</jsm>(<jv>keys</jv>, <js>"key1"</js>);
173    * </p>
174    *
175    * @return A {@link Listifier} for {@link Enumeration} objects
176    * @see Enumeration
177    */
178   @SuppressWarnings("unchecked")
179   public static Listifier<Enumeration> enumerationListifier() {
180      return (bc, enumeration) -> list(enumeration);
181   }
182
183   /**
184    * Returns a listifier for {@link Iterable} objects that converts them using streams.
185    *
186    * <p>This listifier handles any Iterable implementation by creating a stream from its
187    * spliterator and collecting elements to a list. It works with custom iterables and
188    * collection types not covered by more specific listifiers.</p>
189    *
190    * <h5 class='section'>Behavior:</h5>
191    * <ul>
192    *    <li><b>Standard iterables:</b> Converts elements to list maintaining iteration order</li>
193    *    <li><b>Custom iterables:</b> Works with any object implementing Iterable interface</li>
194    *    <li><b>Large iterables:</b> Processes all elements regardless of size</li>
195    * </ul>
196    *
197    * <h5 class='section'>Usage Examples:</h5>
198    * <p class='bjava'>
199    *    <jc>// Test with custom iterable</jc>
200    *    <jk>var</jk> <jv>range</jv> = IntStream.<jsm>range</jsm>(<jv>1</jv>, <jv>4</jv>).<jsm>boxed</jsm>().<jsm>collect</jsm>(toList());
201    *    <jsm>assertList</jsm>(<jv>range</jv>, <jv>1</jv>, <jv>2</jv>, <jv>3</jv>);
202    *
203    *    <jc>// Test with custom Iterable implementation</jc>
204    *    <jk>var</jk> <jv>fibonacci</jv> = <jk>new</jk> FibonacciIterable(<jv>5</jv>);
205    *    <jsm>assertList</jsm>(<jv>fibonacci</jv>, <jv>1</jv>, <jv>1</jv>, <jv>2</jv>, <jv>3</jv>, <jv>5</jv>);
206    * </p>
207    *
208    * @return A {@link Listifier} for {@link Iterable} objects
209    * @see Iterable
210    */
211   @SuppressWarnings("unchecked")
212   public static Listifier<Iterable> iterableListifier() {
213      return (bc, iterable) -> stream(iterable.spliterator(), false).toList();
214   }
215
216   /**
217    * Returns a listifier for {@link Iterator} objects that converts them to lists.
218    *
219    * <p><b>Warning:</b> This listifier consumes the iterator during conversion. After listification,
220    * the iterator will be exhausted and cannot be used again.</p>
221    *
222    * <h5 class='section'>Behavior:</h5>
223    * <ul>
224    *    <li><b>Element extraction:</b> Consumes all remaining elements from the iterator</li>
225    *    <li><b>Order preservation:</b> Maintains the iterator's order in the resulting list</li>
226    *    <li><b>Iterator exhaustion:</b> The iterator becomes unusable after conversion</li>
227    * </ul>
228    *
229    * <h5 class='section'>Usage Examples:</h5>
230    * <p class='bjava'>
231    *    <jc>// Test with list iterator</jc>
232    *    <jk>var</jk> <jv>list</jv> = List.<jsm>of</jsm>(<js>"a"</js>, <js>"b"</js>, <js>"c"</js>);
233    *    <jk>var</jk> <jv>iterator</jv> = <jv>list</jv>.iterator();
234    *    <jsm>assertList</jsm>(<jv>iterator</jv>, <js>"a"</js>, <js>"b"</js>, <js>"c"</js>);
235    *    <jc>// iterator is now exhausted</jc>
236    *
237    *    <jc>// Test with custom iterator</jc>
238    *    <jk>var</jk> <jv>numbers</jv> = IntStream.<jsm>range</jsm>(<jv>1</jv>, <jv>4</jv>).iterator();
239    *    <jsm>assertList</jsm>(<jv>numbers</jv>, <jv>1</jv>, <jv>2</jv>, <jv>3</jv>);
240    * </p>
241    *
242    * <h5 class='section'>Important Notes:</h5>
243    * <ul>
244    *    <li><b>One-time use:</b> The iterator becomes exhausted and unusable after conversion</li>
245    *    <li><b>Side effects:</b> Any side effects in the iterator's next() method will occur</li>
246    *    <li><b>Exception handling:</b> Exceptions from the iterator are propagated</li>
247    * </ul>
248    *
249    * @return A {@link Listifier} for {@link Iterator} objects
250    * @see Iterator
251    */
252   @SuppressWarnings("unchecked")
253   public static Listifier<Iterator> iteratorListifier() {
254      return (bc, iterator) -> stream(spliteratorUnknownSize(iterator, 0), false).toList();
255   }
256
257   /**
258    * Returns a listifier for {@link Map} objects that converts them to lists of {@link java.util.Map.Entry} objects.
259    *
260    * <p>This listifier enables maps to be processed as lists in BCT assertions, making it easy
261    * to test map contents using list-based assertion methods.</p>
262    *
263    * <h5 class='section'>Behavior:</h5>
264    * <ul>
265    *    <li><b>Entry conversion:</b> Each key-value pair becomes a Map.Entry in the list</li>
266    *    <li><b>Order preservation:</b> Maintains the map's iteration order</li>
267    *    <li><b>Empty maps:</b> Returns empty list for empty maps</li>
268    *    <li><b>Map ordering:</b> Converts unordered Maps (HashMap, etc.) to TreeMap for deterministic ordering</li>
269    * </ul>
270    *
271    * <h5 class='section'>Map Ordering Behavior:</h5>
272    * <p>To ensure predictable test results, this listifier handles Maps with unreliable ordering:</p>
273    * <ul>
274    *    <li><b>{@link SortedMap} (TreeMap, etc.):</b> Preserves existing sort order</li>
275    *    <li><b>{@link LinkedHashMap}:</b> Preserves insertion order</li>
276    *    <li><b>{@link HashMap} and other unordered Maps:</b> Converts to {@link TreeMap} for natural key ordering</li>
277    * </ul>
278    *
279    * <h5 class='section'>Usage Examples:</h5>
280    * <p class='bjava'>
281    *    <jc>// Test map contents with deterministic ordering</jc>
282    *    <jk>var</jk> <jv>map</jv> = Map.<jsm>of</jsm>(<js>"z"</js>, <js>"value1"</js>, <js>"a"</js>, <js>"value2"</js>);
283    *    <jsm>assertSize</jsm>(<jv>map</jv>, <jv>2</jv>);
284    *    <jc>// Entries will be ordered by key: [a=value2, z=value1]</jc>
285    *
286    *    <jc>// LinkedHashMap preserves insertion order</jc>
287    *    <jk>var</jk> <jv>linkedMap</jv> = <jk>new</jk> LinkedHashMap&lt;&gt;();
288    *    <jv>linkedMap</jv>.put(<js>"first"</js>, <js>"1"</js>);
289    *    <jv>linkedMap</jv>.put(<js>"second"</js>, <js>"2"</js>);
290    *    <jc>// Entries will maintain insertion order: [first=1, second=2]</jc>
291    *
292    *    <jc>// Test empty map</jc>
293    *    <jk>var</jk> <jv>emptyMap</jv> = Map.<jsm>of</jsm>();
294    *    <jsm>assertEmpty</jsm>(<jv>emptyMap</jv>);
295    *
296    *    <jc>// Test map in object property</jc>
297    *    <jk>var</jk> <jv>config</jv> = <jk>new</jk> Configuration().setProperties(<jv>map</jv>);
298    *    <jsm>assertSize</jsm>(<jv>config</jv>, <js>"properties"</js>, <jv>2</jv>);
299    * </p>
300    *
301    * <h5 class='section'>Entry Processing:</h5>
302    * <p>The resulting Map.Entry objects can be further processed by other parts of the
303    * conversion system, typically being stringified to <js>"key=value"</js> format.</p>
304    *
305    * <h5 class='section'>Performance:</h5>
306    * <p>This listifier creates a new ArrayList from the map's entrySet. For unordered Maps,
307    * an additional TreeMap conversion adds O(n log n) sorting overhead based on key ordering.
308    * For large maps, consider the memory implications.</p>
309    *
310    * @return A {@link Listifier} for {@link Map} objects
311    * @see Map
312    * @see java.util.Map.Entry
313    * @see TreeMap
314    * @see LinkedHashMap
315    */
316   @SuppressWarnings("unchecked")
317   public static Listifier<Map> mapListifier() {
318      return (bc, map) -> {
319         if (! (map instanceof SortedMap) && ! (map instanceof LinkedHashMap) && BctConfiguration.get(BCT_SORT_MAPS, false)) {
320            var map2 = new TreeMap<>(flexibleComparator(bc));
321            map2.putAll(map);
322            map = map2;
323         }
324         return toList(map.entrySet());
325      };
326   }
327
328   /**
329    * Returns a listifier for {@link Stream} objects that converts them to lists.
330    *
331    * <p><b>Warning:</b> This listifier terminates the stream during conversion. After listification,
332    * the stream is closed and cannot be used again.</p>
333    *
334    * <h5 class='section'>Behavior:</h5>
335    * <ul>
336    *    <li><b>Stream termination:</b> Calls toList() to collect all stream elements</li>
337    *    <li><b>Order preservation:</b> Maintains stream order in the resulting list</li>
338    *    <li><b>Stream closure:</b> The stream becomes unusable after conversion</li>
339    * </ul>
340    *
341    * <h5 class='section'>Usage Examples:</h5>
342    * <p class='bjava'>
343    *    <jc>// Test with filtered stream</jc>
344    *    <jk>var</jk> <jv>numbers</jv> = IntStream.<jsm>range</jsm>(<jv>1</jv>, <jv>10</jv>)
345    *       .filter(<jv>n</jv> -&gt; <jv>n</jv> % <jv>2</jv> == <jv>0</jv>)
346    *       .boxed();
347    *    <jsm>assertList</jsm>(<jv>numbers</jv>, <jv>2</jv>, <jv>4</jv>, <jv>6</jv>, <jv>8</jv>);
348    *
349    *    <jc>// Test with mapped stream</jc>
350    *    <jk>var</jk> <jv>words</jv> = Stream.<jsm>of</jsm>(<js>"hello"</js>, <js>"world"</js>)
351    *       .map(String::toUpperCase);
352    *    <jsm>assertList</jsm>(<jv>words</jv>, <js>"HELLO"</js>, <js>"WORLD"</js>);
353    * </p>
354    *
355    * <h5 class='section'>Important Notes:</h5>
356    * <ul>
357    *    <li><b>One-time use:</b> The stream is terminated and cannot be reused</li>
358    *    <li><b>Lazy evaluation:</b> Stream operations are executed during listification</li>
359    *    <li><b>Exception handling:</b> Stream operation exceptions are propagated</li>
360    * </ul>
361    *
362    * @return A {@link Listifier} for {@link Stream} objects
363    * @see Stream
364    */
365   @SuppressWarnings("unchecked")
366   public static Listifier<Stream> streamListifier() {
367      return (bc, stream) -> stream.toList();
368   }
369
370   /**
371    * Creates a comparator that can handle any object type, including non-Comparable objects.
372    *
373    * <p>This comparator provides a flexible comparison strategy that handles various scenarios:</p>
374    * <ul>
375    *    <li><b>Null values:</b> Sorted to the beginning (nulls first)</li>
376    *    <li><b>Comparable objects:</b> Uses natural ordering via {@link Comparable#compareTo(Object)}</li>
377    *    <li><b>Non-Comparable objects:</b> Falls back to string-based comparison using the converter's stringify method</li>
378    *    <li><b>Mixed types:</b> Handles comparison between different types gracefully</li>
379    * </ul>
380    *
381    * <p>This comparator is used internally by {@link #collectionListifier()} and {@link #mapListifier()}
382    * to ensure predictable ordering in test assertions, even when dealing with objects that don't
383    * implement {@link Comparable}.</p>
384    *
385    * <h5 class='section'>Comparison Strategy:</h5>
386    * <ol>
387    *    <li>If both objects are null, they are equal</li>
388    *    <li>If one object is null, it comes first</li>
389    *    <li>If both objects are Comparable of compatible types, use natural ordering</li>
390    *    <li>Otherwise, stringify both objects and compare the string representations</li>
391    * </ol>
392    *
393    * @param converter The bean converter to use for stringification
394    * @return A comparator that can handle any object type
395    */
396   private static Comparator<Object> flexibleComparator(BeanConverter converter) {
397      // Use default converter if null is passed
398      BeanConverter conv = converter != null ? converter : BasicBeanConverter.DEFAULT;
399      return (o1, o2) -> {
400         // Try natural ordering using cmp() (handles nulls and Comparable objects)
401         int result = cmp(o1, o2);
402         if (result != 0)
403            return result;
404
405         // Fall back to string comparison
406         var s1 = conv.stringify(o1);
407         var s2 = conv.stringify(o2);
408         return cmp(s1, s2);
409      };
410   }
411
412   /**
413    * Constructor.
414    */
415   private Listifiers() {}
416}