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<>(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<>(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<>(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<>(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> -> <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<>(); 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}