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