001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.juneau.bct;
018
019import static java.util.Collections.list;
020import static java.util.Spliterators.*;
021import static java.util.stream.StreamSupport.stream;
022import static org.apache.juneau.commons.utils.CollectionUtils.*;
023import static org.apache.juneau.commons.utils.Shorts.*;
024import static org.apache.juneau.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({
083   "rawtypes", // Raw types necessary for generic listifier handling
084   "unchecked" // Listifiers are registered for raw Collection/Iterable/Map/Stream types, so adding and converting their elements is unchecked.
085})
086public class Listifiers {
087
088   /**
089    * Returns a listifier for {@link Collection} objects that converts them to {@link ArrayList}.
090    *
091    * <p>This listifier creates a new ArrayList containing all elements from the source collection.
092    * It works with any Collection subtype including List, Set, Queue, and Deque.</p>
093    *
094    * <h5 class='section'>Behavior:</h5>
095    * <ul>
096    *    <li><b>Non-empty collections:</b> Returns new ArrayList with all elements in iteration order</li>
097    *    <li><b>Empty collections:</b> Returns new empty ArrayList</li>
098    *    <li><b>Preserves order:</b> Maintains the iteration order of the source collection</li>
099    *    <li><b>Set ordering:</b> Converts unordered Sets (HashSet, etc.) to TreeSet for deterministic ordering</li>
100    * </ul>
101    *
102    * <h5 class='section'>Set Ordering Behavior:</h5>
103    * <p>To ensure predictable test results, this listifier handles Sets with unreliable ordering:</p>
104    * <ul>
105    *    <li><b>{@link SortedSet} (TreeSet, etc.):</b> Preserves existing sort order</li>
106    *    <li><b>{@link LinkedHashSet}:</b> Preserves insertion order</li>
107    *    <li><b>{@link HashSet} and other unordered Sets:</b> Converts to {@link TreeSet} for natural ordering</li>
108    * </ul>
109    *
110    * <h5 class='section'>Usage Examples:</h5>
111    * <p class='bjava'>
112    *    <jc>// Test with different collection types</jc>
113    *    <jk>var</jk> <jv>list</jv> = List.<jsm>of</jsm>(<js>"a"</js>, <js>"b"</js>, <js>"c"</js>);
114    *    <jsm>assertList</jsm>(<jv>list</jv>, <js>"a"</js>, <js>"b"</js>, <js>"c"</js>);
115    *
116    *    <jc>// HashSet converted to TreeSet for predictable ordering</jc>
117    *    <jk>var</jk> <jv>set</jv> = Set.<jsm>of</jsm>(<js>"z"</js>, <js>"a"</js>, <js>"m"</js>);
118    *    <jsm>assertList</jsm>(<jv>set</jv>, <js>"a"</js>, <js>"m"</js>, <js>"z"</js>); <jc>// Natural ordering</jc>
119    *
120    *    <jc>// LinkedHashSet preserves insertion order</jc>
121    *    <jk>var</jk> <jv>linkedSet</jv> = <jk>new</jk> LinkedHashSet&lt;&gt;(Arrays.<jsm>asList</jsm>(<js>"first"</js>, <js>"second"</js>));
122    *    <jsm>assertList</jsm>(<jv>linkedSet</jv>, <js>"first"</js>, <js>"second"</js>);
123    *
124    *    <jk>var</jk> <jv>queue</jv> = <jk>new</jk> LinkedList&lt;&gt;(Arrays.<jsm>asList</jsm>(<js>"first"</js>, <js>"second"</js>));
125    *    <jsm>assertList</jsm>(<jv>queue</jv>, <js>"first"</js>, <js>"second"</js>);
126    * </p>
127    *
128    * <h5 class='section'>Performance:</h5>
129    * <p>This listifier creates a new ArrayList and copies all elements, so it has O(n) time
130    * and space complexity. For unordered Sets, an additional TreeSet conversion adds O(n log n)
131    * sorting overhead. For large collections, consider the memory implications.</p>
132    *
133    * @return A {@link Listifier} for {@link Collection} objects
134    * @see Collection
135    * @see ArrayList
136    * @see TreeSet
137    * @see LinkedHashSet
138    */
139   public static Listifier<Collection> collectionListifier() {
140      return (bc, collection) -> {
141         if (collection instanceof Set && ! (collection instanceof SortedSet) && ! (collection instanceof LinkedHashSet) && BctConfiguration.get(BCT_SORT_COLLECTIONS, false)) {
142            var collection2 = new TreeSet<>(flexibleComparator(bc));
143            collection2.addAll(collection);
144            collection = collection2;
145         }
146         return toList(collection);
147      };
148   }
149
150   /**
151    * Returns a listifier for {@link Enumeration} objects that converts them to lists.
152    *
153    * <p><b>Warning:</b> This listifier consumes the enumeration during conversion. After listification,
154    * the enumeration will be exhausted and cannot be used again.</p>
155    *
156    * <h5 class='section'>Behavior:</h5>
157    * <ul>
158    *    <li><b>Element extraction:</b> Consumes all remaining elements from the enumeration</li>
159    *    <li><b>Order preservation:</b> Maintains the enumeration's order in the resulting list</li>
160    *    <li><b>Enumeration exhaustion:</b> The enumeration becomes unusable after conversion</li>
161    * </ul>
162    *
163    * <h5 class='section'>Usage Examples:</h5>
164    * <p class='bjava'>
165    *    <jc>// Test with Vector enumeration</jc>
166    *    <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>));
167    *    <jk>var</jk> <jv>enumeration</jv> = <jv>vector</jv>.elements();
168    *    <jsm>assertList</jsm>(<jv>enumeration</jv>, <js>"x"</js>, <js>"y"</js>, <js>"z"</js>);
169    *
170    *    <jc>// Test with Hashtable enumeration</jc>
171    *    <jk>var</jk> <jv>table</jv> = <jk>new</jk> Hashtable&lt;&gt;(Map.<jsm>of</jsm>(<js>"key1"</js>, <js>"value1"</js>));
172    *    <jk>var</jk> <jv>keys</jv> = <jv>table</jv>.keys();
173    *    <jsm>assertList</jsm>(<jv>keys</jv>, <js>"key1"</js>);
174    * </p>
175    *
176    * @return A {@link Listifier} for {@link Enumeration} objects
177    * @see Enumeration
178    */
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   public static Listifier<Iterable> iterableListifier() {
212      return (bc, iterable) -> stream(iterable.spliterator(), false).toList();
213   }
214
215   /**
216    * Returns a listifier for {@link Iterator} objects that converts them to lists.
217    *
218    * <p><b>Warning:</b> This listifier consumes the iterator during conversion. After listification,
219    * the iterator will be exhausted and cannot be used again.</p>
220    *
221    * <h5 class='section'>Behavior:</h5>
222    * <ul>
223    *    <li><b>Element extraction:</b> Consumes all remaining elements from the iterator</li>
224    *    <li><b>Order preservation:</b> Maintains the iterator's order in the resulting list</li>
225    *    <li><b>Iterator exhaustion:</b> The iterator becomes unusable after conversion</li>
226    * </ul>
227    *
228    * <h5 class='section'>Usage Examples:</h5>
229    * <p class='bjava'>
230    *    <jc>// Test with list iterator</jc>
231    *    <jk>var</jk> <jv>list</jv> = List.<jsm>of</jsm>(<js>"a"</js>, <js>"b"</js>, <js>"c"</js>);
232    *    <jk>var</jk> <jv>iterator</jv> = <jv>list</jv>.iterator();
233    *    <jsm>assertList</jsm>(<jv>iterator</jv>, <js>"a"</js>, <js>"b"</js>, <js>"c"</js>);
234    *    <jc>// iterator is now exhausted</jc>
235    *
236    *    <jc>// Test with custom iterator</jc>
237    *    <jk>var</jk> <jv>numbers</jv> = IntStream.<jsm>range</jsm>(<jv>1</jv>, <jv>4</jv>).iterator();
238    *    <jsm>assertList</jsm>(<jv>numbers</jv>, <jv>1</jv>, <jv>2</jv>, <jv>3</jv>);
239    * </p>
240    *
241    * <h5 class='section'>Important Notes:</h5>
242    * <ul>
243    *    <li><b>One-time use:</b> The iterator becomes exhausted and unusable after conversion</li>
244    *    <li><b>Side effects:</b> Any side effects in the iterator's next() method will occur</li>
245    *    <li><b>Exception handling:</b> Exceptions from the iterator are propagated</li>
246    * </ul>
247    *
248    * @return A {@link Listifier} for {@link Iterator} objects
249    * @see Iterator
250    */
251   public static Listifier<Iterator> iteratorListifier() {
252      return (bc, iterator) -> stream(spliteratorUnknownSize(iterator, 0), false).toList();
253   }
254
255   /**
256    * Returns a listifier for {@link Map} objects that converts them to lists of {@link java.util.Map.Entry} objects.
257    *
258    * <p>This listifier enables maps to be processed as lists in BCT assertions, making it easy
259    * to test map contents using list-based assertion methods.</p>
260    *
261    * <h5 class='section'>Behavior:</h5>
262    * <ul>
263    *    <li><b>Entry conversion:</b> Each key-value pair becomes a Map.Entry in the list</li>
264    *    <li><b>Order preservation:</b> Maintains the map's iteration order</li>
265    *    <li><b>Empty maps:</b> Returns empty list for empty maps</li>
266    *    <li><b>Map ordering:</b> Converts unordered Maps (HashMap, etc.) to TreeMap for deterministic ordering</li>
267    * </ul>
268    *
269    * <h5 class='section'>Map Ordering Behavior:</h5>
270    * <p>To ensure predictable test results, this listifier handles Maps with unreliable ordering:</p>
271    * <ul>
272    *    <li><b>{@link SortedMap} (TreeMap, etc.):</b> Preserves existing sort order</li>
273    *    <li><b>{@link LinkedHashMap}:</b> Preserves insertion order</li>
274    *    <li><b>{@link HashMap} and other unordered Maps:</b> Converts to {@link TreeMap} for natural key ordering</li>
275    * </ul>
276    *
277    * <h5 class='section'>Usage Examples:</h5>
278    * <p class='bjava'>
279    *    <jc>// Test map contents with deterministic ordering</jc>
280    *    <jk>var</jk> <jv>map</jv> = Map.<jsm>of</jsm>(<js>"z"</js>, <js>"value1"</js>, <js>"a"</js>, <js>"value2"</js>);
281    *    <jsm>assertSize</jsm>(<jv>map</jv>, <jv>2</jv>);
282    *    <jc>// Entries will be ordered by key: [a=value2, z=value1]</jc>
283    *
284    *    <jc>// LinkedHashMap preserves insertion order</jc>
285    *    <jk>var</jk> <jv>linkedMap</jv> = <jk>new</jk> LinkedHashMap&lt;&gt;();
286    *    <jv>linkedMap</jv>.put(<js>"first"</js>, <js>"1"</js>);
287    *    <jv>linkedMap</jv>.put(<js>"second"</js>, <js>"2"</js>);
288    *    <jc>// Entries will maintain insertion order: [first=1, second=2]</jc>
289    *
290    *    <jc>// Test empty map</jc>
291    *    <jk>var</jk> <jv>emptyMap</jv> = Map.<jsm>of</jsm>();
292    *    <jsm>assertEmpty</jsm>(<jv>emptyMap</jv>);
293    *
294    *    <jc>// Test map in object property</jc>
295    *    <jk>var</jk> <jv>config</jv> = <jk>new</jk> Configuration().setProperties(<jv>map</jv>);
296    *    <jsm>assertSize</jsm>(<jv>config</jv>, <js>"properties"</js>, <jv>2</jv>);
297    * </p>
298    *
299    * <h5 class='section'>Entry Processing:</h5>
300    * <p>The resulting Map.Entry objects can be further processed by other parts of the
301    * conversion system, typically being stringified to <js>"key=value"</js> format.</p>
302    *
303    * <h5 class='section'>Performance:</h5>
304    * <p>This listifier creates a new ArrayList from the map's entrySet. For unordered Maps,
305    * an additional TreeMap conversion adds O(n log n) sorting overhead based on key ordering.
306    * For large maps, consider the memory implications.</p>
307    *
308    * @return A {@link Listifier} for {@link Map} objects
309    * @see Map
310    * @see java.util.Map.Entry
311    * @see TreeMap
312    * @see LinkedHashMap
313    */
314   public static Listifier<Map> mapListifier() {
315      return (bc, map) -> {
316         if (! (map instanceof SortedMap) && ! (map instanceof LinkedHashMap) && BctConfiguration.get(BCT_SORT_MAPS, false)) {
317            var map2 = new TreeMap<>(flexibleComparator(bc));
318            map2.putAll(map);
319            map = map2;
320         }
321         return toList(map.entrySet());
322      };
323   }
324
325   /**
326    * Returns a listifier for {@link Stream} objects that converts them to lists.
327    *
328    * <p><b>Warning:</b> This listifier terminates the stream during conversion. After listification,
329    * the stream is closed and cannot be used again.</p>
330    *
331    * <h5 class='section'>Behavior:</h5>
332    * <ul>
333    *    <li><b>Stream termination:</b> Calls toList() to collect all stream elements</li>
334    *    <li><b>Order preservation:</b> Maintains stream order in the resulting list</li>
335    *    <li><b>Stream closure:</b> The stream becomes unusable after conversion</li>
336    * </ul>
337    *
338    * <h5 class='section'>Usage Examples:</h5>
339    * <p class='bjava'>
340    *    <jc>// Test with filtered stream</jc>
341    *    <jk>var</jk> <jv>numbers</jv> = IntStream.<jsm>range</jsm>(<jv>1</jv>, <jv>10</jv>)
342    *       .filter(<jv>n</jv> -&gt; <jv>n</jv> % <jv>2</jv> == <jv>0</jv>)
343    *       .boxed();
344    *    <jsm>assertList</jsm>(<jv>numbers</jv>, <jv>2</jv>, <jv>4</jv>, <jv>6</jv>, <jv>8</jv>);
345    *
346    *    <jc>// Test with mapped stream</jc>
347    *    <jk>var</jk> <jv>words</jv> = Stream.<jsm>of</jsm>(<js>"hello"</js>, <js>"world"</js>)
348    *       .map(String::toUpperCase);
349    *    <jsm>assertList</jsm>(<jv>words</jv>, <js>"HELLO"</js>, <js>"WORLD"</js>);
350    * </p>
351    *
352    * <h5 class='section'>Important Notes:</h5>
353    * <ul>
354    *    <li><b>One-time use:</b> The stream is terminated and cannot be reused</li>
355    *    <li><b>Lazy evaluation:</b> Stream operations are executed during listification</li>
356    *    <li><b>Exception handling:</b> Stream operation exceptions are propagated</li>
357    * </ul>
358    *
359    * @return A {@link Listifier} for {@link Stream} objects
360    * @see Stream
361    */
362   public static Listifier<Stream> streamListifier() {
363      return (bc, stream) -> stream.toList();
364   }
365
366   /**
367    * Creates a comparator that can handle any object type, including non-Comparable objects.
368    *
369    * <p>This comparator provides a flexible comparison strategy that handles various scenarios:</p>
370    * <ul>
371    *    <li><b>Null values:</b> Sorted to the beginning (nulls first)</li>
372    *    <li><b>Comparable objects:</b> Uses natural ordering via {@link Comparable#compareTo(Object)}</li>
373    *    <li><b>Non-Comparable objects:</b> Falls back to string-based comparison using the converter's stringify method</li>
374    *    <li><b>Mixed types:</b> Handles comparison between different types gracefully</li>
375    * </ul>
376    *
377    * <p>This comparator is used internally by {@link #collectionListifier()} and {@link #mapListifier()}
378    * to ensure predictable ordering in test assertions, even when dealing with objects that don't
379    * implement {@link Comparable}.</p>
380    *
381    * <h5 class='section'>Comparison Strategy:</h5>
382    * <ol>
383    *    <li>If both objects are null, they are equal</li>
384    *    <li>If one object is null, it comes first</li>
385    *    <li>If both objects are Comparable of compatible types, use natural ordering</li>
386    *    <li>Otherwise, stringify both objects and compare the string representations</li>
387    * </ol>
388    *
389    * @param converter The bean converter to use for stringification
390    * @return A comparator that can handle any object type
391    */
392   private static Comparator<Object> flexibleComparator(BeanConverter converter) {
393      // Use default converter if null is passed
394      BeanConverter conv = or(converter, BasicBeanConverter.DEFAULT);
395      return (o1, o2) -> {
396         // Try natural ordering using cmp() (handles nulls and Comparable objects)
397         int result = cmp(o1, o2);
398         if (result != 0)
399            return result;
400
401         // Fall back to string comparison
402         var s1 = conv.stringify(o1);
403         var s2 = conv.stringify(o2);
404         return cmp(s1, s2);
405      };
406   }
407
408   /**
409    * Constructor.
410    */
411   private Listifiers() {}
412}