001// ***************************************************************************************************************************
002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
003// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
005// * with the License.  You may obtain a copy of the License at                                                              *
006// *                                                                                                                         *
007// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
008// *                                                                                                                         *
009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
011// * specific language governing permissions and limitations under the License.                                              *
012// ***************************************************************************************************************************
013package org.apache.juneau.internal;
014
015import static org.apache.juneau.common.internal.StringUtils.*;
016import static org.apache.juneau.common.internal.ThrowableUtils.*;
017import static org.apache.juneau.internal.ConverterUtils.*;
018import static java.util.Collections.*;
019
020import java.lang.reflect.*;
021import java.util.*;
022
023import org.apache.juneau.collections.*;
024import org.apache.juneau.parser.*;
025
026/**
027 * Builder for lists.
028 *
029 * <h5 class='section'>See Also:</h5><ul>
030 * </ul>
031 *
032 * @param <E> Element type.
033 */
034public final class ListBuilder<E> {
035
036   //-----------------------------------------------------------------------------------------------------------------
037   // Static
038   //-----------------------------------------------------------------------------------------------------------------
039
040   /**
041    * Static creator.
042    *
043    * @param <E> The element type.
044    * @param elementType The element type.
045    * @param elementTypeArgs Optional element type arguments.
046    * @return A new builder.
047    */
048   public static <E> ListBuilder<E> create(Class<E> elementType, Type...elementTypeArgs) {
049      return new ListBuilder<>(elementType, elementTypeArgs);
050   }
051
052   //-----------------------------------------------------------------------------------------------------------------
053   // Instance
054   //-----------------------------------------------------------------------------------------------------------------
055
056   private List<E> list;
057   private boolean unmodifiable = false, sparse = false;
058   private Comparator<E> comparator;
059
060   private Class<E> elementType;
061   private Type[] elementTypeArgs;
062
063   /**
064    * Constructor.
065    *
066    * @param elementType The element type.
067    * @param elementTypeArgs The element type generic arguments if there are any.
068    */
069   public ListBuilder(Class<E> elementType, Type...elementTypeArgs) {
070      this.elementType = elementType;
071      this.elementTypeArgs = elementTypeArgs;
072   }
073
074   /**
075    * Constructor.
076    *
077    * @param addTo The list to add to.
078    */
079   public ListBuilder(List<E> addTo) {
080      this.list = addTo;
081   }
082
083   /**
084    * Builds the list.
085    *
086    * @return A list conforming to the settings on this builder.
087    */
088   public List<E> build() {
089      if (sparse) {
090         if (list != null && list.isEmpty())
091            list = null;
092      } else {
093         if (list == null)
094            list = new ArrayList<>(0);
095      }
096      if (list != null) {
097         if (comparator != null)
098            Collections.sort(list, comparator);
099         if (unmodifiable)
100            list = unmodifiableList(list);
101      }
102      return list;
103   }
104
105   /**
106    * When specified, the {@link #build()} method will return <jk>null</jk> if the list is empty.
107    *
108    * <p>
109    * Otherwise {@link #build()} will never return <jk>null</jk>.
110    *
111    * @return This object.
112    */
113   public ListBuilder<E> sparse() {
114      this.sparse = true;
115      return this;
116   }
117
118   /**
119    * When specified, {@link #build()} will return an unmodifiable list.
120    *
121    * @return This object.
122    */
123   public ListBuilder<E> unmodifiable() {
124      this.unmodifiable = true;
125      return this;
126   }
127
128   /**
129    * Forces the existing list to be copied instead of appended to.
130    *
131    * @return This object.
132    */
133   public ListBuilder<E> copy() {
134      if (list != null)
135         list = new ArrayList<>(list);
136      return this;
137   }
138
139   /**
140    * Sorts the contents of the list.
141    *
142    * @return This object.
143    */
144   @SuppressWarnings("unchecked")
145   public ListBuilder<E> sorted() {
146      return sorted((Comparator<E>)Comparator.naturalOrder());
147   }
148
149   /**
150    * Sorts the contents of the list using the specified comparator.
151    *
152    * @param comparator The comparator to use for sorting.
153    * @return This object.
154    */
155   public ListBuilder<E> sorted(Comparator<E> comparator) {
156      this.comparator = comparator;
157      return this;
158   }
159
160   /**
161    * Appends the contents of the specified collection into this list.
162    *
163    * <p>
164    * This is a no-op if the value is <jk>null</jk>.
165    *
166    * @param value The collection to add to this list.
167    * @return This object.
168    */
169   public ListBuilder<E> addAll(Collection<E> value) {
170      if (value != null) {
171         if (list == null)
172            list = new LinkedList<>(value);
173         else
174            list.addAll(value);
175      }
176      return this;
177   }
178
179   /**
180    * Adds a single value to this list.
181    *
182    * @param value The value to add to this list.
183    * @return This object.
184    */
185   public ListBuilder<E> add(E value) {
186      if (list == null)
187         list = new ArrayList<>();
188      list.add(value);
189      return this;
190   }
191
192   /**
193    * Adds multiple values to this list.
194    *
195    * @param values The values to add to this list.
196    * @return This object.
197    */
198   @SuppressWarnings("unchecked")
199   public ListBuilder<E> add(E...values) {
200      for (E v : values)
201         add(v);
202      return this;
203   }
204
205   /**
206    * Adds entries to this list via JSON array strings.
207    *
208    * @param values The JSON array strings to parse and add to this list.
209    * @return This object.
210    */
211   public ListBuilder<E> addJson(String...values) {
212      return addAny((Object[])values);
213   }
214
215   /**
216    * Adds arbitrary values to this list.
217    *
218    * <p>
219    * Objects can be any of the following:
220    * <ul>
221    *    <li>The same type or convertible to the element type of this list.
222    *    <li>Collections or arrays of anything on this list.
223    *    <li>JSON array strings parsed and convertible to the element type of this list.
224    * </ul>
225    *
226    * @param values The values to add.
227    * @return This object.
228    */
229   public ListBuilder<E> addAny(Object...values) {
230      if (elementType == null)
231         throw new RuntimeException("Unknown element type.  Cannot use this method.");
232      try {
233         if (values != null) {
234            for (Object o : values) {
235               if (o != null) {
236                  if (o instanceof Collection) {
237                     ((Collection<?>)o).forEach(x -> addAny(x));
238                  } else if (o.getClass().isArray()) {
239                     for (int i = 0; i < Array.getLength(o); i++)
240                        addAny(Array.get(o, i));
241                  } else if (isJsonArray(o, false)) {
242                     new JsonList(o.toString()).forEach(x -> addAny(x));
243                  } else if (elementType.isInstance(o)) {
244                     add(elementType.cast(o));
245                  } else {
246                     add(toType(o, elementType, elementTypeArgs));
247                  }
248               }
249            }
250         }
251      } catch (ParseException e) {
252         throw asRuntimeException(e);
253      }
254      return this;
255   }
256
257   /**
258    * Appends a value to this list of the flag is true.
259    *
260    * @param flag The flag.
261    * @param value The value.
262    * @return This object.
263    */
264   public ListBuilder<E> addIf(boolean flag, E value) {
265      if (flag)
266         add(value);
267      return this;
268   }
269}