1 /*
2  * Hunt - A refined core library for D programming language.
3  *
4  * Copyright (C) 2018-2019 HuntLabs
5  *
6  * Website: https://www.huntlabs.net/
7  *
8  * Licensed under the Apache-2.0 License.
9  *
10  */
11 
12 module hunt.collection.MultiMap;
13 
14 import hunt.collection.ArrayList;
15 import hunt.collection.HashMap;
16 import hunt.collection.List;
17 import hunt.collection.Map;
18 
19 import hunt.text;
20 
21 /**
22  * A multi valued Map.
23  * 
24  * @param !V
25  *            the entry type for multimap values
26  */
27 class MultiMap(V) : HashMap!(string, List!(V)) {
28 
29 	// TODO: Tasks pending completion -@zxp at 10/15/2018, 6:24:35 PM
30 	// replace this with hunt.collection.MultiValueMap
31 
32 	this() {
33 		super();
34 	}
35 
36 	this(Map!(string, List!(V)) map) {
37 		super(map);
38 	}
39 
40 	this(MultiMap!V map) {
41 		super(map);
42 	}
43 
44 	/**
45 	 * Get multiple values. Single valued entries are converted to singleton
46 	 * lists.
47 	 * 
48 	 * @param name
49 	 *            The entry key.
50 	 * @return Unmodifieable List of values.
51 	 */
52 	List!(V) getValues(string name) {
53 		List!(V) vals = super.get(name);
54 		if ((vals is null) || vals.isEmpty()) {
55 			return null;
56 		}
57 		return vals;
58 	}
59 
60 	/**
61 	 * Get a value from a multiple value. If the value is not a multivalue, then
62 	 * index 0 retrieves the value or null.
63 	 * 
64 	 * @param name
65 	 *            The entry key.
66 	 * @param i
67 	 *            Index of element to get.
68 	 * @return Unmodifieable List of values.
69 	 */
70 	V getValue(string name, int i=0) {
71 		List!(V) vals = getValues(name);
72 		if (vals is null) {
73 			return V.init;
74 		}
75 		if (i == 0 && vals.isEmpty()) {
76 			return V.init;
77 		}
78 		return vals.get(i);
79 	}
80 
81 	/**
82 	 * Get value as string. Single valued items are converted to a string with
83 	 * the toString() Object method. Multi valued entries are converted to a
84 	 * comma separated List. No quoting of commas within values is performed.
85 	 * 
86 	 * @param name
87 	 *            The entry key.
88 	 * @return string value.
89 	 */
90 	string getString(string name) {
91 		List!(V) vals = get(name);
92 		if ((vals is null) || (vals.isEmpty())) {
93 			return null;
94 		}
95 
96 		if (vals.size() == 1) {
97 			// simple form.
98 			static if(is(V == string))
99 				return vals.get(0);
100 			else
101 				return vals.get(0).toString();
102 		}
103 
104 		// delimited form
105 		StringBuilder values = new StringBuilder(128);
106 		foreach (V e ; vals) {
107 			if (e !is null) {
108 				if (values.length() > 0)
109 					values.append(',');
110 			static if(is(V == string))
111 				values.append(e);
112 			else
113 				values.append(e.toString());
114 			}
115 		}
116 		return values.toString();
117 	}
118 
119 	/**
120 	 * Put multi valued entry.
121 	 * 
122 	 * @param name
123 	 *            The entry key.
124 	 * @param value
125 	 *            The simple value
126 	 * @return The previous value or null.
127 	 */
128 	List!(V) put(string name, V value) {
129 		if (value is null) {
130 			return super.put(name, null);
131 		}
132 		List!(V) vals = new ArrayList!(V)();
133 		vals.add(value);
134 		return put(name, vals);
135 	}
136 
137 	alias put = HashMap!(string, List!(V)).put;
138 
139 	/**
140 	 * Shorthand version of putAll
141 	 * 
142 	 * @param input
143 	 *            the input map
144 	 */
145 	void putAllValues(Map!(string, V) input) {
146 		foreach (string key, V value ; input) {
147 			put(key, value);
148 		}
149 	}
150 
151 	/**
152 	 * Put multi valued entry.
153 	 * 
154 	 * @param name
155 	 *            The entry key.
156 	 * @param values
157 	 *            The List of multiple values.
158 	 * @return The previous value or null.
159 	 */
160 	List!(V) putValues(string name, List!(V) values) {
161 		return super.put(name, values);
162 	}
163 
164 	/**
165 	 * Put multi valued entry.
166 	 * 
167 	 * @param name
168 	 *            The entry key.
169 	 * @param values
170 	 *            The array of multiple values.
171 	 * @return The previous value or null.
172 	 */
173 	final List!(V) putValues(string name, V[] values...) {
174 		List!(V) list = new ArrayList!(V)();
175 		// list.addAll(Arrays.asList(values));
176 		foreach(V v; values)
177 			list.add(v);
178 		return super.put(name, list);
179 	}
180 
181 	/**
182 	 * Add value to multi valued entry. If the entry is single valued, it is
183 	 * converted to the first value of a multi valued entry.
184 	 * 
185 	 * @param name
186 	 *            The entry key.
187 	 * @param value
188 	 *            The entry value.
189 	 */
190 	void add(string name, V value) {
191 		List!(V) lo = get(name);
192 		if (lo is null) {
193 			lo = new ArrayList!(V)();
194 		}
195 		lo.add(value);
196 		super.put(name, lo);
197 	}
198 
199 	/**
200 	 * Add values to multi valued entry. If the entry is single valued, it is
201 	 * converted to the first value of a multi valued entry.
202 	 * 
203 	 * @param name
204 	 *            The entry key.
205 	 * @param values
206 	 *            The List of multiple values.
207 	 */
208 	void addValues(string name, List!(V) values) {
209 		List!(V) lo = get(name);
210 		if (lo is null) {
211 			lo = new ArrayList!(V)();
212 		}
213 		lo.addAll(values);
214 		put(name, lo);
215 	}
216 
217 	/**
218 	 * Add values to multi valued entry. If the entry is single valued, it is
219 	 * converted to the first value of a multi valued entry.
220 	 * 
221 	 * @param name
222 	 *            The entry key.
223 	 * @param values
224 	 *            The string array of multiple values.
225 	 */
226 	void addValues(string name, V[] values) {
227 		List!(V) lo = get(name);
228 		if (lo is null) {
229 			lo = new ArrayList!(V)();
230 		}
231 		// lo.addAll(Arrays.asList(values));
232 		foreach(V v; values)
233 			lo.add(v);
234 		put(name, lo);
235 	}
236 
237 	/**
238 	 * Merge values.
239 	 * 
240 	 * @param map
241 	 *            the map to overlay on top of this one, merging together values
242 	 *            if needed.
243 	 * @return true if an existing key was merged with potentially new values,
244 	 *         false if either no change was made, or there were only new keys.
245 	 */
246 	// bool addAllValues(MultiMap!V map) {
247 	// 	bool merged = false;
248 
249 	// 	if ((map is null) || (map.isEmpty())) {
250 	// 		// done
251 	// 		return merged;
252 	// 	}
253 
254 	// 	for (MapEntry!(string, List!(V)) entry : map.entrySet()) {
255 	// 		string name = entry.getKey();
256 	// 		List!(V) values = entry.getValue();
257 
258 	// 		if (this.containsKey(name)) {
259 	// 			merged = true;
260 	// 		}
261 
262 	// 		this.addValues(name, values);
263 	// 	}
264 
265 	// 	return merged;
266 	// }
267 
268 	/**
269 	 * Remove value.
270 	 * 
271 	 * @param name
272 	 *            The entry key.
273 	 * @param value
274 	 *            The entry value.
275 	 * @return true if it was removed.
276 	 */
277 	bool removeValue(string name, V value) {
278 		List!(V) lo = get(name);
279 		if ((lo is null) || (lo.isEmpty())) {
280 			return false;
281 		}
282 		bool ret = lo.remove(value);
283 		if (lo.isEmpty()) {
284 			remove(name);
285 		} else {
286 			put(name, lo);
287 		}
288 		return ret;
289 	}
290 
291 	/**
292 	 * Test for a specific single value in the map.
293 	 * <p>
294 	 * NOTE: This is a SLOW operation, and is actively discouraged.
295 	 * 
296 	 * @param value
297 	 *            the value to search for
298 	 * @return true if contains simple value
299 	 */
300 	bool containsSimpleValue(V value) {
301 		foreach (List!(V) vals ; byValue()) {
302 			if ((vals.size() == 1) && vals.contains(value)) {
303 				return true;
304 			}
305 		}
306 		return false;
307 	}
308 
309 	override
310 	string toString() {
311 		StringBuilder sb = new StringBuilder();
312 		sb.append('{');
313 		bool delim = false;
314 		import std.conv;
315 		foreach(string key, List!(V) vals; this) {
316 			if (delim) {
317 				sb.append(", ");
318 			}
319 			sb.append(key);
320 			sb.append('=');
321 			if (vals.size() == 1) {
322 				sb.append(vals.get(0).to!string());
323 			} else {
324 				sb.append(vals.toString());
325 			}
326 			delim = true;
327 		}
328 		sb.append('}');
329 		return sb.toString();
330 	}
331 
332 	/**
333 	 * @return Map of string arrays
334 	 */
335 	// Map!(string, string[]) toStringArrayMap() {
336 	// 	int s = size();
337 	// 	HashMap!(string, string[]) map = new (s * 3 / 2) class HashMap!(string, string[]) {
338 
339 	// 		// private enum long serialVersionUID = -6129887569971781626L;
340 	// 		this(int initialCapacity) {
341 	// 			super(initialCapacity)
342 	// 		}
343 
344 	// 		override
345 	// 		string toString() {
346 	// 			StringBuilder b = new StringBuilder();
347 	// 			b.append('{');
348 	// 			for (string k : super.keySet()) {
349 	// 				if (b.length() > 1)
350 	// 					b.append(',');
351 	// 				b.append(k);
352 	// 				b.append('=');
353 	// 				b.append(Arrays.asList(super.get(k)));
354 	// 			}
355 
356 	// 			b.append('}');
357 	// 			return b.toString();
358 	// 		}
359 	// 	};
360 
361 	// 	foreach (string key, List!(V) vals; this) {
362 	// 		string[] a = null;
363 	// 		if (vals !is null) {
364 	// 			a = new string[vals.size()];
365 	// 			a = vals.toArray(a);
366 	// 		}
367 	// 		map.put(key, a);
368 	// 	}
369 	// 	return map;
370 	// }
371 
372 }