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 sets.
028 *
029 * <h5 class='section'>See Also:</h5><ul>
030 * </ul>
031 *
032 * @param <E> Element type.
033 */
034public final class SetBuilder<E> {
035
036   private Set<E> set;
037   private boolean unmodifiable, sparse;
038   private Comparator<E> comparator;
039
040   private Class<E> elementType;
041   private Type[] elementTypeArgs;
042
043   /**
044    * Constructor.
045    *
046    * @param elementType The element type.
047    * @param elementTypeArgs The element type generic arguments if there are any.
048    */
049   public SetBuilder(Class<E> elementType, Type...elementTypeArgs) {
050      this.elementType = elementType;
051      this.elementTypeArgs = elementTypeArgs;
052   }
053
054   /**
055    * Constructor.
056    *
057    * @param addTo The set to add to.
058    */
059   public SetBuilder(Set<E> addTo) {
060      this.set = addTo;
061   }
062
063   /**
064    * Builds the set.
065    *
066    * @return A set conforming to the settings on this builder.
067    */
068   public Set<E> build() {
069      if (sparse) {
070         if (set != null && set.isEmpty())
071            set = null;
072      } else {
073         if (set == null)
074            set = new LinkedHashSet<>(0);
075      }
076      if (set != null) {
077         if (comparator != null) {
078            Set<E> s = new TreeSet<>(comparator);
079            s.addAll(set);
080            set = s;
081         }
082         if (unmodifiable)
083            set = unmodifiableSet(set);
084      }
085      return set;
086   }
087
088   /**
089    * When specified, the {@link #build()} method will return <jk>null</jk> if the set is empty.
090    *
091    * <p>
092    * Otherwise {@link #build()} will never return <jk>null</jk>.
093    *
094    * @return This object.
095    */
096   public SetBuilder<E> sparse() {
097      this.sparse = true;
098      return this;
099   }
100
101   /**
102    * When specified, {@link #build()} will return an unmodifiable set.
103    *
104    * @return This object.
105    */
106   public SetBuilder<E> unmodifiable() {
107      this.unmodifiable = true;
108      return this;
109   }
110
111   /**
112    * Forces the existing set to be copied instead of appended to.
113    *
114    * @return This object.
115    */
116   public SetBuilder<E> copy() {
117      if (set != null)
118         set = new LinkedHashSet<>(set);
119      return this;
120   }
121
122   /**
123    * Converts the set into a {@link SortedSet}.
124    *
125    * @return This object.
126    */
127   @SuppressWarnings("unchecked")
128   public SetBuilder<E> sorted() {
129      return sorted((Comparator<E>)Comparator.naturalOrder());
130   }
131
132   /**
133    * Converts the set into a {@link SortedSet} using the specified comparator.
134    *
135    * @param comparator The comparator to use for sorting.
136    * @return This object.
137    */
138   public SetBuilder<E> sorted(Comparator<E> comparator) {
139      this.comparator = comparator;
140      return this;
141   }
142
143   /**
144    * Appends the contents of the specified collection into this set.
145    *
146    * <p>
147    * This is a no-op if the value is <jk>null</jk>.
148    *
149    * @param value The collection to add to this set.
150    * @return This object.
151    */
152   public SetBuilder<E> addAll(Collection<E> value) {
153      if (value != null) {
154         if (set == null)
155            set = new LinkedHashSet<>(value);
156         else
157            set.addAll(value);
158      }
159      return this;
160   }
161
162   /**
163    * Adds a single value to this set.
164    *
165    * @param value The value to add to this set.
166    * @return This object.
167    */
168   public SetBuilder<E> add(E value) {
169      if (set == null)
170         set = new LinkedHashSet<>();
171      set.add(value);
172      return this;
173   }
174
175   /**
176    * Adds multiple values to this set.
177    *
178    * @param values The values to add to this set.
179    * @return This object.
180    */
181   @SuppressWarnings("unchecked")
182   public SetBuilder<E> add(E...values) {
183      for (E v : values)
184         add(v);
185      return this;
186   }
187
188   /**
189    * Adds entries to this set via JSON array strings.
190    *
191    * @param values The JSON array strings to parse and add to this set.
192    * @return This object.
193    */
194   public SetBuilder<E> addJson(String...values) {
195      return addAny((Object[])values);
196   }
197
198   /**
199    * Adds arbitrary values to this set.
200    *
201    * <p>
202    * Objects can be any of the following:
203    * <ul>
204    *    <li>The same type or convertible to the element type of this set.
205    *    <li>Collections or arrays of anything on this set.
206    *    <li>JSON array strings parsed and convertible to the element type of this set.
207    * </ul>
208    *
209    * @param values The values to add.
210    * @return This object.
211    */
212   public SetBuilder<E> addAny(Object...values) {
213      if (elementType == null)
214         throw new RuntimeException("Unknown element type.  Cannot use this method.");
215      try {
216         if (values != null) {
217            for (Object o : values) {
218               if (o != null) {
219                  if (o instanceof Collection) {
220                     ((Collection<?>)o).forEach(x -> addAny(x));
221                  } else if (o.getClass().isArray()) {
222                     for (int i = 0; i < Array.getLength(o); i++)
223                        addAny(Array.get(o, i));
224                  } else if (isJsonArray(o, false)) {
225                     new JsonList(o.toString()).forEach(x -> addAny(x));
226                  } else if (elementType.isInstance(o)) {
227                     add(elementType.cast(o));
228                  } else {
229                     add(toType(o, elementType, elementTypeArgs));
230                  }
231               }
232            }
233         }
234      } catch (ParseException e) {
235         throw asRuntimeException(e);
236      }
237      return this;
238   }
239
240   /**
241    * Adds a value to this set if the specified flag is true.
242    *
243    * @param flag The flag.
244    * @param value The value.
245    * @return This object.
246    */
247   public SetBuilder<E> addIf(boolean flag, E value) {
248      if (flag)
249         add(value);
250      return this;
251   }
252}