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}