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&lt;String&gt; <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&lt;Integer&gt; <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&lt;String&gt; <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&lt;String&gt; <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&lt;String&gt; <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&lt;Integer&gt; <jv>existing</jv> = l(1, 2, 3);
082 *    List&lt;Integer&gt; <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&lt;String&gt; <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&lt;Integer&gt; <jv>filtered</jv> = Lists.<jsm>create</jsm>(Integer.<jk>class</jk>)
094 *       .filtered(v -&gt; v &gt; 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&lt;String&gt; <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&lt;Integer&gt; <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&lt;String&gt; <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&lt;Integer&gt; <jv>list</jv> = Lists.<jsm>create</jsm>(Integer.<jk>class</jk>)
341    *       .filtered(v -&gt; v != <jk>null</jk> &amp;&amp; v &gt; 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&lt;Object&gt; <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&lt;Integer&gt; <jv>list</jv> = Lists.<jsm>create</jsm>(Integer.<jk>class</jk>)
452    *       .filtered(v -&gt; v != <jk>null</jk> &amp;&amp; v &gt; 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&lt;Integer&gt; <jv>list2</jv> = Lists.<jsm>create</jsm>(Integer.<jk>class</jk>)
460    *       .filtered(v -&gt; v != <jk>null</jk>)           <jc>// First filter</jc>
461    *       .filtered(v -&gt; v &gt; 0)                    <jc>// Second filter (ANDed with first)</jc>
462    *       .filtered(v -&gt; v &lt; 100);                  <jc>// Third filter (ANDed with previous)</jc>
463    *       .add(5)
464    *       .add(150)  <jc>// Filtered out (not &lt; 100)</jc>
465    *       .add(-1)   <jc>// Filtered out (not &gt; 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&lt;String&gt; <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&lt;String&gt; <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}