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