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.commons.collections; 018 019import static java.util.Collections.*; 020import static org.apache.juneau.commons.utils.AssertionUtils.*; 021import static org.apache.juneau.commons.utils.CollectionUtils.*; 022import static org.apache.juneau.commons.utils.ThrowableUtils.*; 023import static org.apache.juneau.commons.utils.Utils.*; 024 025import java.lang.reflect.*; 026import java.util.*; 027import java.util.function.*; 028 029/** 030 * A fluent builder for constructing {@link List} instances with various configuration options. 031 * 032 * <p> 033 * This builder provides a flexible and type-safe way to construct lists with support for adding elements, 034 * collections, arrays, sorting, and applying modifiers like unmodifiable or sparse modes. It's particularly 035 * useful when you need to construct lists dynamically with conditional elements or from multiple sources. 036 * 037 * <h5 class='section'>Features:</h5> 038 * <ul class='spaced-list'> 039 * <li>Fluent API - all methods return <c>this</c> for method chaining 040 * <li>Multiple add methods - single elements, varargs, collections, arrays 041 * <li>Arbitrary input support - automatic type conversion with {@link #addAny(Object...)} 042 * <li>Conditional adding - {@link #addIf(boolean, Object)} for conditional elements 043 * <li>Sorting support - natural order or custom {@link Comparator} 044 * <li>Sparse mode - return <jk>null</jk> for empty lists 045 * <li>Unmodifiable mode - create immutable lists 046 * <li>Filtering support - exclude unwanted elements via {@link #filtered()} or {@link #filtered(Predicate)} 047 * <li>Custom conversion functions - type conversion via {@link #elementFunction(Function)} 048 * </ul> 049 * 050 * <h5 class='section'>Examples:</h5> 051 * <p class='bjava'> 052 * <jc>// Basic usage</jc> 053 * List<String> <jv>list</jv> = Lists.<jsm>create</jsm>(String.<jk>class</jk>) 054 * .add(<js>"apple"</js>, <js>"banana"</js>, <js>"cherry"</js>) 055 * .build(); 056 * 057 * <jc>// With sorting</jc> 058 * List<Integer> <jv>sorted</jv> = Lists.<jsm>create</jsm>(Integer.<jk>class</jk>) 059 * .add(3, 1, 4, 1, 5, 9, 2, 6) 060 * .sorted() 061 * .build(); 062 * 063 * <jc>// Conditional elements</jc> 064 * List<String> <jv>filtered</jv> = Lists.<jsm>create</jsm>(String.<jk>class</jk>) 065 * .add(<js>"always"</js>) 066 * .addIf(<jv>includeOptional</jv>, <js>"optional"</js>) 067 * .build(); 068 * 069 * <jc>// Immutable list</jc> 070 * List<String> <jv>immutable</jv> = Lists.<jsm>create</jsm>(String.<jk>class</jk>) 071 * .add(<js>"read"</js>, <js>"only"</js>) 072 * .unmodifiable() 073 * .build(); 074 * 075 * <jc>// Sparse mode - returns null when empty</jc> 076 * List<String> <jv>maybeNull</jv> = Lists.<jsm>create</jsm>(String.<jk>class</jk>) 077 * .sparse() 078 * .build(); <jc>// Returns null, not empty list</jc> 079 * 080 * <jc>// From multiple sources</jc> 081 * List<Integer> <jv>existing</jv> = l(1, 2, 3); 082 * List<Integer> <jv>combined</jv> = Lists.<jsm>create</jsm>(Integer.<jk>class</jk>) 083 * .addAll(<jv>existing</jv>) 084 * .add(4, 5, 6) 085 * .build(); 086 * 087 * <jc>// FluentList wrapper - use buildFluent()</jc> 088 * FluentList<String> <jv>fluent</jv> = Lists.<jsm>create</jsm>(String.<jk>class</jk>) 089 * .add(<js>"one"</js>, <js>"two"</js>) 090 * .buildFluent(); 091 * 092 * <jc>// FilteredList - use buildFiltered()</jc> 093 * FilteredList<Integer> <jv>filtered</jv> = Lists.<jsm>create</jsm>(Integer.<jk>class</jk>) 094 * .filtered(v -> v > 0) 095 * .add(5) 096 * .add(-1) <jc>// Filtered out</jc> 097 * .buildFiltered(); 098 * </p> 099 * 100 * <h5 class='section'>Thread Safety:</h5> 101 * <p> 102 * This class is <b>not thread-safe</b>. Each builder instance should be used by a single thread or 103 * properly synchronized. 104 * 105 * <h5 class='section'>See Also:</h5><ul> 106 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauCommonsCollections">Collections Package</a> 107 * <li class='jc'>{@link Maps} 108 * <li class='jc'>{@link Sets} 109 * </ul> 110 * 111 * @param <E> The element type. 112 */ 113public class Lists<E> { 114 115 /** 116 * Creates a new list builder for the specified element type. 117 * 118 * <h5 class='section'>Example:</h5> 119 * <p class='bjava'> 120 * List<String> <jv>list</jv> = Lists.<jsm>create</jsm>(String.<jk>class</jk>) 121 * .add(<js>"a"</js>, <js>"b"</js>, <js>"c"</js>) 122 * .build(); 123 * </p> 124 * 125 * @param <E> The element type. 126 * @param elementType The element type class. Required for type-safe operations. Must not be <jk>null</jk>. 127 * @return A new list builder instance. 128 */ 129 public static <E> Lists<E> create(Class<E> elementType) { 130 return new Lists<>(assertArgNotNull("elementType", elementType)); 131 } 132 133 private List<E> list; 134 private boolean unmodifiable = false, sparse = false, concurrent = false; 135 private Comparator<E> comparator; 136 137 private Predicate<E> filter; 138 private Class<E> elementType; 139 private Function<Object,E> elementFunction; 140 141 /** 142 * Constructor. 143 * 144 * @param elementType The element type. Must not be <jk>null</jk>. 145 */ 146 public Lists(Class<E> elementType) { 147 this.elementType = assertArgNotNull("elementType", elementType); 148 } 149 150 /** 151 * Adds a single value to this list. 152 * 153 * <p> 154 * Note: Filtering is applied at build time, not when adding elements. 155 * 156 * @param value The value to add to this list. 157 * @return This object. 158 */ 159 public Lists<E> add(E value) { 160 if (list == null) 161 list = list(); 162 list.add(value); 163 return this; 164 } 165 166 /** 167 * Adds multiple values to this list. 168 * 169 * @param values The values to add to this list. 170 * @return This object. 171 */ 172 @SuppressWarnings("unchecked") 173 public Lists<E> add(E...values) { 174 assertArgNotNull("values", values); 175 for (var v : values) 176 add(v); 177 return this; 178 } 179 180 /** 181 * Appends the contents of the specified collection into this list. 182 * 183 * <p> 184 * This is a no-op if the value is <jk>null</jk>. 185 * 186 * @param value The collection to add to this list. 187 * @return This object. 188 */ 189 public Lists<E> addAll(Collection<E> value) { 190 if (nn(value)) { 191 if (list == null) 192 list = new LinkedList<>(value); 193 else 194 list.addAll(value); 195 } 196 return this; 197 } 198 199 /** 200 * Adds arbitrary values to this list with automatic type conversion. 201 * 202 * <p> 203 * This method provides flexible input handling by automatically converting and flattening various input types: 204 * <ul class='spaced-list'> 205 * <li>Direct instances of the element type - added as-is 206 * <li>Collections - recursively flattened and elements converted 207 * <li>Arrays - recursively flattened and elements converted 208 * <li>Convertible types - converted using {@link #elementFunction(Function)} 209 * </ul> 210 * 211 * <h5 class='section'>Example:</h5> 212 * <p class='bjava'> 213 * <jc>// Mix different input types</jc> 214 * List<Integer> <jv>list</jv> = Lists.<jsm>create</jsm>(Integer.<jk>class</jk>) 215 * .addAny(1, 2, 3) <jc>// Direct values</jc> 216 * .addAny(l(4, 5, 6)) <jc>// Collection</jc> 217 * .addAny(<jk>new int</jk>[]{7, 8, 9}) <jc>// Array</jc> 218 * .build(); 219 * </p> 220 * 221 * @param values The values to add. <jk>null</jk> values are ignored. 222 * @return This object for method chaining. 223 * @throws IllegalStateException if element type is unknown. 224 * @throws RuntimeException if a value cannot be converted to the element type. 225 */ 226 public Lists<E> addAny(Object...values) { 227 if (nn(values)) { 228 for (var o : values) { 229 if (nn(o)) { 230 if (o instanceof Collection<?> o2) { 231 o2.forEach(x -> addAny(x)); 232 } else if (isArray(o)) { 233 for (var i = 0; i < Array.getLength(o); i++) 234 addAny(Array.get(o, i)); 235 } else if (elementType.isInstance(o)) { 236 add(elementType.cast(o)); 237 } else { 238 E converted = convertElement(o); 239 if (converted != null) { 240 add(converted); 241 } else { 242 throw rex("Object of type {0} could not be converted to type {1}", cn(o), cn(elementType)); 243 } 244 } 245 } 246 } 247 } 248 return this; 249 } 250 251 /** 252 * Appends a value to this list of the flag is true. 253 * 254 * @param flag The flag. 255 * @param value The value. 256 * @return This object. 257 */ 258 public Lists<E> addIf(boolean flag, E value) { 259 if (flag) 260 add(value); 261 return this; 262 } 263 264 /** 265 * Builds the list. 266 * 267 * @return A list conforming to the settings on this builder. 268 */ 269 /** 270 * Builds the list. 271 * 272 * <p> 273 * Applies filtering, sorting, concurrent, unmodifiable, and sparse options. 274 * 275 * <p> 276 * If filtering is applied, the result is wrapped in a {@link FilteredList}. 277 * 278 * @return The built list, or {@code null} if {@link #sparse()} is set and the list is empty. 279 */ 280 public List<E> build() { 281 if (sparse && e(list)) 282 return null; 283 284 var list2 = (List<E>)null; 285 if (nn(comparator)) 286 list2 = new SortedArrayList<>(comparator); 287 else 288 list2 = new ArrayList<>(); 289 290 if (concurrent) 291 list2 = synchronizedList(list2); 292 293 if (nn(filter) || nn(elementFunction)) { 294 var list3b = FilteredList.create(elementType); 295 if (nn(filter)) 296 list3b.filter(filter); 297 if (nn(elementFunction)) 298 list3b.elementFunction(elementFunction); 299 list2 = list3b.inner(list2).build(); 300 } 301 302 if (nn(list)) 303 list2.addAll(list); 304 305 if (unmodifiable) 306 list2 = unmodifiableList(list2); 307 308 return list2; 309 } 310 311 /** 312 * Builds the list and wraps it in a {@link FluentList}. 313 * 314 * <p> 315 * This is a convenience method that calls {@link #build()} and wraps the result in a {@link FluentList}. 316 * 317 * <h5 class='section'>Example:</h5> 318 * <p class='bjava'> 319 * <jk>import static</jk> org.apache.juneau.commons.utils.CollectionUtils.*; 320 * 321 * FluentList<String> <jv>list</jv> = Lists.<jsm>create</jsm>(String.<jk>class</jk>) 322 * .add(<js>"one"</js>, <js>"two"</js>) 323 * .buildFluent(); 324 * </p> 325 * 326 * @return The built list wrapped in a {@link FluentList}, or {@code null} if {@link #sparse()} is set and the list is empty. 327 */ 328 public FluentList<E> buildFluent() { 329 List<E> result = build(); 330 return result == null ? null : new FluentList<>(result); 331 } 332 333 /** 334 * Builds the list as a {@link FilteredList}. 335 * 336 * <h5 class='section'>Example:</h5> 337 * <p class='bjava'> 338 * <jk>import static</jk> org.apache.juneau.commons.utils.CollectionUtils.*; 339 * 340 * FilteredList<Integer> <jv>list</jv> = Lists.<jsm>create</jsm>(Integer.<jk>class</jk>) 341 * .filtered(v -> v != <jk>null</jk> && v > 0) 342 * .add(5) 343 * .add(-1) <jc>// Will be filtered out</jc> 344 * .buildFiltered(); 345 * </p> 346 * 347 * <p> 348 * Note: If {@link #unmodifiable()} is set, the returned list will be wrapped in an unmodifiable view, 349 * which may cause issues if the FilteredList tries to modify it internally. It's recommended to avoid 350 * using {@link #unmodifiable()} when calling this method. 351 * 352 * @return The built list as a {@link FilteredList}, or {@code null} if {@link #sparse()} is set and the list is empty. 353 */ 354 public FilteredList<E> buildFiltered() { 355 var l = build(); 356 if (l == null) // sparse mode and empty 357 return null; 358 if (l instanceof FilteredList<E> l2) 359 return l2; 360 // Note that if unmodifiable is true, 'l' will be unmodifiable and will cause an error if you try 361 // to insert a value from within FilteredList. 362 return FilteredList.create(elementType).inner(l).build(); 363 } 364 365 /** 366 * Sets the element conversion function for converting elements in {@link #addAny(Object...)}. 367 * 368 * <p> 369 * The function is applied to each element when adding elements in {@link #addAny(Object...)}. 370 * 371 * @param elementFunction The function to convert elements. Must not be <jk>null</jk>. 372 * @return This object. 373 */ 374 public Lists<E> elementFunction(Function<Object,E> elementFunction) { 375 this.elementFunction = assertArgNotNull("elementFunction", elementFunction); 376 return this; 377 } 378 379 /** 380 * Specifies the element type on this list. 381 * 382 * @param value The element type. Must not be <jk>null</jk>. 383 * @return This object. 384 */ 385 public Lists<E> elementType(Class<E> value) { 386 elementType = assertArgNotNull("value", value); 387 return this; 388 } 389 390 /** 391 * Applies a default filter that excludes common "empty" or "unset" values from being added to the list. 392 * 393 * <p> 394 * The following values are filtered out: 395 * <ul> 396 * <li>{@code null} 397 * <li>{@link Boolean#FALSE} 398 * <li>Numbers with {@code intValue() == -1} 399 * <li>Empty arrays 400 * <li>Empty {@link Map Maps} 401 * <li>Empty {@link Collection Collections} 402 * </ul> 403 * 404 * <h5 class='section'>Example:</h5> 405 * <p class='bjava'> 406 * <jk>import static</jk> org.apache.juneau.commons.utils.CollectionUtils.*; 407 * 408 * List<Object> <jv>list</jv> = Lists.<jsm>create</jsm>(Object.<jk>class</jk>) 409 * .filtered() 410 * .add(<js>"name"</js>) 411 * .add(-1) <jc>// Filtered out at build time</jc> 412 * .add(<jk>false</jk>) <jc>// Filtered out at build time</jc> 413 * .add(<jk>new</jk> String[0]) <jc>// Filtered out at build time</jc> 414 * .build(); 415 * </p> 416 * 417 * @return This object. 418 */ 419 public Lists<E> filtered() { 420 // @formatter:off 421 return filtered(v -> ! ( 422 v == null 423 || (v instanceof Boolean v2 && v2.equals(false)) 424 || (v instanceof Number v3 && v3.intValue() == -1) 425 || (isArray(v) && Array.getLength(v) == 0) 426 || (v instanceof Map v2 && v2.isEmpty()) 427 || (v instanceof Collection v3 && v3.isEmpty()) 428 )); 429 // @formatter:on 430 } 431 432 /** 433 * Applies a filter predicate to elements when building the list. 434 * 435 * <p> 436 * The filter receives the element value. Elements where the predicate returns 437 * {@code true} will be kept; elements where it returns {@code false} will be filtered out. 438 * 439 * <p> 440 * This method can be called multiple times. When called multiple times, all filters are combined 441 * using AND logic - an element must pass all filters to be kept in the list. 442 * 443 * <p> 444 * Note: Filtering is applied at build time, not when adding elements. 445 * 446 * <h5 class='section'>Example:</h5> 447 * <p class='bjava'> 448 * <jk>import static</jk> org.apache.juneau.commons.utils.CollectionUtils.*; 449 * 450 * <jc>// Keep only non-null, positive integers</jc> 451 * List<Integer> <jv>list</jv> = Lists.<jsm>create</jsm>(Integer.<jk>class</jk>) 452 * .filtered(v -> v != <jk>null</jk> && v > 0) 453 * .add(5) 454 * .add(-1) <jc>// Filtered out at build time</jc> 455 * .add(<jk>null</jk>) <jc>// Filtered out at build time</jc> 456 * .build(); 457 * 458 * <jc>// Multiple filters combined with AND</jc> 459 * List<Integer> <jv>list2</jv> = Lists.<jsm>create</jsm>(Integer.<jk>class</jk>) 460 * .filtered(v -> v != <jk>null</jk>) <jc>// First filter</jc> 461 * .filtered(v -> v > 0) <jc>// Second filter (ANDed with first)</jc> 462 * .filtered(v -> v < 100); <jc>// Third filter (ANDed with previous)</jc> 463 * .add(5) 464 * .add(150) <jc>// Filtered out (not < 100)</jc> 465 * .add(-1) <jc>// Filtered out (not > 0)</jc> 466 * .build(); 467 * </p> 468 * 469 * @param filter The filter predicate. Must not be <jk>null</jk>. 470 * @return This object. 471 */ 472 public Lists<E> filtered(Predicate<E> filter) { 473 Predicate<E> newFilter = assertArgNotNull("filter", filter); 474 if (this.filter == null) 475 this.filter = newFilter; 476 else 477 this.filter = this.filter.and(newFilter); 478 return this; 479 } 480 481 /** 482 * Sorts the contents of the list. 483 * 484 * @return This object. 485 */ 486 @SuppressWarnings("unchecked") 487 public Lists<E> sorted() { 488 return sorted((Comparator<E>)Comparator.naturalOrder()); 489 } 490 491 /** 492 * Sorts the contents of the list using the specified comparator. 493 * 494 * @param comparator The comparator to use for sorting. Must not be <jk>null</jk>. 495 * @return This object. 496 */ 497 public Lists<E> sorted(Comparator<E> comparator) { 498 this.comparator = assertArgNotNull("comparator", comparator); 499 return this; 500 } 501 502 /** 503 * When specified, the {@link #build()} method will return <jk>null</jk> if the list is empty. 504 * 505 * <p> 506 * Otherwise {@link #build()} will never return <jk>null</jk>. 507 * 508 * @return This object. 509 */ 510 public Lists<E> sparse() { 511 sparse = true; 512 return this; 513 } 514 515 516 /** 517 * When specified, {@link #build()} will return an unmodifiable list. 518 * 519 * @return This object. 520 */ 521 public Lists<E> unmodifiable() { 522 this.unmodifiable = true; 523 return this; 524 } 525 526 /** 527 * When specified, {@link #build()} will return a thread-safe synchronized list. 528 * 529 * <p> 530 * The list will be wrapped using {@link Collections#synchronizedList(List)} to provide thread-safety. 531 * This is useful when the list needs to be accessed from multiple threads. 532 * 533 * <h5 class='section'>Example:</h5> 534 * <p class='bjava'> 535 * <jc>// Create a thread-safe list</jc> 536 * List<String> <jv>list</jv> = Lists.<jsm>create</jsm>(String.<jk>class</jk>) 537 * .add(<js>"one"</js>, <js>"two"</js>) 538 * .concurrent() 539 * .build(); 540 * </p> 541 * 542 * @return This object. 543 */ 544 public Lists<E> concurrent() { 545 concurrent = true; 546 return this; 547 } 548 549 /** 550 * Sets whether {@link #build()} should return a thread-safe synchronized list. 551 * 552 * <p> 553 * When <c>true</c>, the list will be wrapped using {@link Collections#synchronizedList(List)} to provide thread-safety. 554 * This is useful when the list needs to be accessed from multiple threads. 555 * 556 * <h5 class='section'>Example:</h5> 557 * <p class='bjava'> 558 * <jc>// Conditionally create a thread-safe list</jc> 559 * List<String> <jv>list</jv> = Lists.<jsm>create</jsm>(String.<jk>class</jk>) 560 * .add(<js>"one"</js>, <js>"two"</js>) 561 * .concurrent(<jv>needsThreadSafety</jv>) 562 * .build(); 563 * </p> 564 * 565 * @param value Whether to make the list thread-safe. 566 * @return This object. 567 */ 568 public Lists<E> concurrent(boolean value) { 569 concurrent = value; 570 return this; 571 } 572 573 /** 574 * Converts an element object to the element type. 575 * 576 * @param o The object to convert. 577 * @return The converted element, or <jk>null</jk> if conversion is not possible. 578 */ 579 @SuppressWarnings("unchecked") 580 private E convertElement(Object o) { 581 if (elementType.isInstance(o)) 582 return (E)o; 583 if (nn(elementFunction)) 584 return elementFunction.apply(o); 585 return null; 586 } 587}