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.ClassUtils.*;
018import static org.apache.juneau.internal.ConverterUtils.*;
019
020import java.lang.reflect.*;
021import java.util.*;
022
023import org.apache.juneau.*;
024import org.apache.juneau.collections.*;
025import org.apache.juneau.parser.*;
026
027/**
028 * Builder for maps.
029 *
030 * <h5 class='section'>See Also:</h5><ul>
031 * </ul>
032 *
033 * @param <K> Key type.
034 * @param <V> Value type.
035 */
036public final class MapBuilder<K,V> {
037
038   private Map<K,V> map;
039   private boolean unmodifiable = false, sparse = false;
040   private Comparator<K> comparator = null;
041
042   private Class<K> keyType;
043   private Class<V> valueType;
044   private Type[] valueTypeArgs;
045
046   /**
047    * Constructor.
048    *
049    * @param keyType The key type.
050    * @param valueType The value type.
051    * @param valueTypeArgs The value type generic arguments if there are any.
052    */
053   public MapBuilder(Class<K> keyType, Class<V> valueType, Type...valueTypeArgs) {
054      this.keyType = keyType;
055      this.valueType = valueType;
056      this.valueTypeArgs = valueTypeArgs;
057   }
058
059   /**
060    * Constructor.
061    *
062    * @param addTo The map to add to.
063    */
064   public MapBuilder(Map<K,V> addTo) {
065      this.map = addTo;
066   }
067
068   /**
069    * Builds the map.
070    *
071    * @return A map conforming to the settings on this builder.
072    */
073   public Map<K,V> build() {
074      if (sparse) {
075         if (map != null && map.isEmpty())
076            map = null;
077      } else {
078         if (map == null)
079            map = new LinkedHashMap<>();
080      }
081      if (map != null) {
082         if (comparator != null) {
083            Map<K,V> m2 = new TreeMap<>(comparator);
084            m2.putAll(map);
085            map = m2;
086         }
087         if (unmodifiable)
088            map = Collections.unmodifiableMap(map);
089      }
090      return map;
091   }
092
093   /**
094    * When specified, the {@link #build()} method will return <jk>null</jk> if the map is empty.
095    *
096    * <p>
097    * Otherwise {@link #build()} will never return <jk>null</jk>.
098    *
099    * @return This object.
100    */
101   public MapBuilder<K,V> sparse() {
102      this.sparse = true;
103      return this;
104   }
105
106   /**
107    * When specified, {@link #build()} will return an unmodifiable map.
108    *
109    * @return This object.
110    */
111   public MapBuilder<K,V> unmodifiable() {
112      this.unmodifiable = true;
113      return this;
114   }
115
116   /**
117    * Forces the existing set to be copied instead of appended to.
118    *
119    * @return This object.
120    */
121   public MapBuilder<K,V> copy() {
122      if (map != null)
123         map = new LinkedHashMap<>(map);
124      return this;
125   }
126
127   /**
128    * Converts the set into a {@link SortedMap}.
129    *
130    * @return This object.
131    */
132   @SuppressWarnings("unchecked")
133   public MapBuilder<K,V> sorted() {
134      return sorted((Comparator<K>)Comparator.naturalOrder());
135   }
136
137   /**
138    * Converts the set into a {@link SortedMap} using the specified comparator.
139    *
140    * @param comparator The comparator to use for sorting.
141    * @return This object.
142    */
143   public MapBuilder<K,V> sorted(Comparator<K> comparator) {
144      this.comparator = comparator;
145      return this;
146   }
147
148   /**
149    * Appends the contents of the specified map into this map.
150    *
151    * <p>
152    * This is a no-op if the value is <jk>null</jk>.
153    *
154    * @param value The map to add to this map.
155    * @return This object.
156    */
157   public MapBuilder<K,V> addAll(Map<K,V> value) {
158      if (value != null) {
159         if (map == null)
160            map = new LinkedHashMap<>(value);
161         else
162            map.putAll(value);
163      }
164      return this;
165   }
166
167   /**
168    * Adds a single entry to this map.
169    *
170    * @param key The map key.
171    * @param value The map value.
172    * @return This object.
173    */
174   public MapBuilder<K,V> add(K key, V value) {
175      if (map == null)
176         map = new LinkedHashMap<>();
177      map.put(key, value);
178      return this;
179   }
180
181   /**
182    * Adds entries to this list via JSON object strings.
183    *
184    * @param values The JSON object strings to parse and add to this list.
185    * @return This object.
186    */
187   public MapBuilder<K,V> addJson(String...values) {
188      return addAny((Object[])values);
189   }
190
191   /**
192    * Adds arbitrary values to this list.
193    *
194    * <p>
195    * Objects can be any of the following:
196    * <ul>
197    *    <li>Maps of key/value types convertible to the key/value types of this map.
198    *    <li>JSON object strings parsed and convertible to the key/value types of this map.
199    * </ul>
200    *
201    * @param values The values to add.
202    * @return This object.
203    */
204   @SuppressWarnings("unchecked")
205   public MapBuilder<K,V> addAny(Object...values) {
206      if (keyType == null || valueType == null)
207         throw new IllegalStateException("Unknown key and value types. Cannot use this method.");
208      try {
209         for (Object o : values) {
210            if (o != null) {
211               if (o instanceof Map) {
212                  ((Map<Object,Object>)o).forEach((k,v) -> add(toType(k, keyType), toType(v, valueType, valueTypeArgs)));
213               } else if (isJsonObject(o, false)) {
214                  JsonMap.ofJson(o.toString()).forEach((k,v) -> add(toType(k, keyType), toType(v, valueType, valueTypeArgs)));
215               } else {
216                  throw new BasicRuntimeException("Invalid object type {0} passed to addAny()", className(o));
217               }
218            }
219         }
220      } catch (ParseException e) {
221         throw asRuntimeException(e);
222      }
223      return this;
224   }
225
226   /**
227    * Adds a list of key/value pairs to this map.
228    *
229    * @param pairs The pairs to add.
230    * @return This object.
231    */
232   @SuppressWarnings("unchecked")
233   public MapBuilder<K,V> addPairs(Object...pairs) {
234      if (pairs.length % 2 != 0)
235         throw new IllegalArgumentException("Odd number of parameters passed into AMap.ofPairs()");
236      for (int i = 0; i < pairs.length; i+=2)
237         add((K)pairs[i], (V)pairs[i+1]);
238      return this;
239   }
240}