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&lt;String&gt; <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&lt;Integer&gt; <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&lt;String&gt; <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&lt;String&gt; <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&lt;String&gt; <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&lt;Integer&gt; <jv>existing</jv> = l(1, 2, 3);
081 *    List&lt;Integer&gt; <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&lt;String&gt; <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&lt;Integer&gt; <jv>filtered</jv> = Lists.<jsm>create</jsm>(Integer.<jk>class</jk>)
093 *       .filtered(v -&gt; v &gt; 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&lt;String&gt; <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&lt;Integer&gt; <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&lt;String&gt; <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&lt;Integer&gt; <jv>list</jv> = Lists.<jsm>create</jsm>(Integer.<jk>class</jk>)
348    *       .filtered(v -&gt; v != <jk>null</jk> &amp;&amp; v &gt; 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&lt;Object&gt; <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&lt;Integer&gt; <jv>list</jv> = Lists.<jsm>create</jsm>(Integer.<jk>class</jk>)
459    *       .filtered(v -&gt; v != <jk>null</jk> &amp;&amp; v &gt; 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&lt;Integer&gt; <jv>list2</jv> = Lists.<jsm>create</jsm>(Integer.<jk>class</jk>)
467    *       .filtered(v -&gt; v != <jk>null</jk>)           <jc>// First filter</jc>
468    *       .filtered(v -&gt; v &gt; 0)                    <jc>// Second filter (ANDed with first)</jc>
469    *       .filtered(v -&gt; v &lt; 100);                  <jc>// Third filter (ANDed with previous)</jc>
470    *       .add(5)
471    *       .add(150)  <jc>// Filtered out (not &lt; 100)</jc>
472    *       .add(-1)   <jc>// Filtered out (not &gt; 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&lt;String&gt; <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&lt;String&gt; <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}