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