1 /**
2 	Defines a string based multi-map with conserved insertion order.
3 
4 	Copyright: © 2012-2014 Sönke Ludwig
5 	License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file.
6 	Authors: Sönke Ludwig
7 */
8 module vibe.utils.dictionarylist;
9 
10 import vibe.utils.string : icmp2;
11 import std.exception : enforce;
12 
13 
14 /**
15 	Behaves similar to $(D VALUE[string]) but the insertion order is not changed
16 	and multiple values per key are supported.
17 
18 	This kind of map is used for MIME headers (e.g. for HTTP, see
19 	vibe.inet.message.InetHeaderMap), or for form data
20 	(vibe.inet.webform.FormFields). Note that the map can contain fields with
21 	the same key multiple times if addField is used for insertion. Insertion
22 	order is preserved.
23 
24 	Note that despite case not being relevant for matching keyse, iterating
25 	over the map will yield	the original case of the key that was put in.
26 
27 	Insertion and lookup has O(n) complexity.
28 */
29 struct DictionaryList(VALUE, bool case_sensitive = true, size_t NUM_STATIC_FIELDS = 32, bool USE_HASHSUM = false) {
30 	import std.typecons : Tuple;
31 
32 	private {
33 		alias KeyValue = Tuple!(string, "key", ValueType, "value");
34 
35 		static struct Field {
36 			static if (USE_HASHSUM) uint keyCheckSum;
37 			else {
38 				enum keyCheckSum = 0;
39 				this(uint, string key, VALUE value) { this.key = key; this.value = value; }
40 			}
41 			KeyValue tuple;
42 			@property ref inout(string) key() inout { return tuple.key; }
43 			@property ref inout(VALUE) value() inout { return tuple.value; }
44 		}
45 		Field[NUM_STATIC_FIELDS] m_fields;
46 		size_t m_fieldCount = 0;
47 		Field[] m_extendedFields;
48 
49 		enum bool safeValueCopy = __traits(compiles, (VALUE v) @safe { VALUE vc; vc = v; });
50 		template typedGet(T) {
51 			enum typedGet = __traits(compiles, (VALUE v) { return v.get!T; });
52 		}
53 		template canAssign(T) {
54 			enum canAssign = __traits(compiles, (T t) { VALUE v = t; });
55 		}
56 	}
57 
58 	alias ValueType = VALUE;
59 
60 	struct FieldTuple { string key; ValueType value; }
61 
62 	/** The number of fields present in the map.
63 	*/
64 	@property size_t length() const { return m_fieldCount + m_extendedFields.length; }
65 
66 	/// Supports serialization using vibe.data.serialization.
67 	static DictionaryList fromRepresentation(FieldTuple[] array)
68 	{
69 		DictionaryList ret;
70 		foreach (ref v; array) ret.addField(v.key, v.value);
71 		return ret;
72 	}
73 	/// ditto
74 	FieldTuple[] toRepresentation() const {
75 		FieldTuple[] ret;
76 		foreach (k, ref v; this.byKeyValue) ret ~= FieldTuple(k, v);
77 		return ret;
78 	}
79 
80 	/** Generates an associative-array equivalent string representation of the dictionary.
81 	*/
82 	void toString(scope void delegate(const(char)[] str) @safe sink)
83 	const {
84 		sink("[");
85 		bool first = true;
86 
87 		foreach (k, v; this.byKeyValue) {
88 			if (!first) sink(", ");
89 			else first = false;
90 			() @trusted {
91 				import std.format : formattedWrite;
92 				string[] ka = (&k)[0 .. 1];
93 				ValueType[] va = (&v)[0 .. 1];
94 				sink.formattedWrite("%(%s%): %(%s%)", ka, va);
95 			} ();
96 		}
97 		sink("]");
98 	}
99 	/// ditto
100 	void toString(scope void delegate(const(char)[] str) @system sink)
101 	const {
102 		toString(cast(void delegate(const(char)[]) @safe)sink);
103 	}
104 	/// ditto
105 	string toString()
106 	const {
107 		import std.array : appender;
108 		auto ret = appender!string();
109 		toString((s) { ret.put(s); });
110 		return ret.data;
111 	}
112 
113 	/** Removes the first field that matches the given key.
114 	*/
115 	void remove(string key)
116 	{
117 		static if (USE_HASHSUM) auto keysum = computeCheckSumI(key);
118 		enum keysum = 0;
119 		auto idx = getIndex(m_fields[0 .. m_fieldCount], key, keysum);
120 		if( idx >= 0 ){
121 			auto slice = m_fields[0 .. m_fieldCount];
122 			removeFromArrayIdx(slice, idx);
123 			m_fieldCount--;
124 		} else {
125 			idx = getIndex(m_extendedFields, key, keysum);
126 			enforce(idx >= 0);
127 			removeFromArrayIdx(m_extendedFields, idx);
128 		}
129 	}
130 
131 	/** Removes all fields that matches the given key.
132 	*/
133 	void removeAll(string key)
134 	{
135 		static if (USE_HASHSUM) auto keysum = computeCheckSumI(key);
136 		else enum keysum = 0;
137 		for (size_t i = 0; i < m_fieldCount;) {
138 			if (m_fields[i].keyCheckSum == keysum && matches(m_fields[i].key, key)) {
139 				auto slice = m_fields[0 .. m_fieldCount];
140 				removeFromArrayIdx(slice, i);
141 				m_fieldCount--;
142 			} else i++;
143 		}
144 
145 		for (size_t i = 0; i < m_extendedFields.length;) {
146 			if (m_fields[i].keyCheckSum == keysum && matches(m_fields[i].key, key))
147 				removeFromArrayIdx(m_extendedFields, i);
148 			else i++;
149 		}
150 	}
151 
152 	/// Used by `remove` and `removeAll`
153 	private static void removeFromArrayIdx(ref Field[] array, size_t idx)
154 	{
155 		foreach( j; idx+1 .. array.length)
156 			array[j-1] = array[j];
157 		array.length = array.length-1;
158 	}
159 
160 	/** Adds a new field to the map.
161 
162 		The new field will be added regardless of any existing fields that
163 		have the same key, possibly resulting in duplicates. Use opIndexAssign
164 		if you want to avoid duplicates.
165 	*/
166 	void addField(string key, ValueType value)
167 	{
168 		static if (USE_HASHSUM) auto keysum = computeCheckSumI(key);
169 		else enum keysum = 0;
170 		if (m_fieldCount < m_fields.length)
171 			m_fields[m_fieldCount++] = Field(keysum, key, value);
172 		else m_extendedFields ~= Field(keysum, key, value);
173 	}
174 
175 	void addField(T)(string key, T value)
176 	if (canAssign!T)
177 	{
178 		ValueType convertedValue = value;
179 		addField(key, convertedValue);
180 	}
181 
182 	/** Returns the first field that matches the given key.
183 
184 		If no field is found, def_val is returned.
185 	*/
186 	inout(ValueType) get(string key, lazy inout(ValueType) def_val = ValueType.init)
187 	inout {
188 		if (auto pv = key in this) return *pv;
189 		return def_val;
190 	}
191 
192 	// DMD bug: cannot set T.init as default value for def_val parameter,
193 	// because compilation fails with message:
194 	//      Error: undefined identifier 'T'
195 	/// ditto
196 	inout(T) get(T)(string key, lazy inout(T) def_val)
197 	inout if (typedGet!T) {
198 		if (auto pv = key in this) return (*pv).get!T;
199 		return def_val;
200 	}
201 
202 	/// ditto
203 	inout(T) get(T)(string key) // Work around DMD bug
204 	inout if (typedGet!T) {
205 		return get!T(key, inout(T).init);
206 	}
207 
208 	/** Returns all values matching the given key.
209 
210 		Note that the version returning an array will allocate for each call.
211 	*/
212 	const(ValueType)[] getAll(string key)
213 	const @trusted { // appender
214 		import std.array;
215 		auto ret = appender!(const(ValueType)[])();
216 		getAll(key, (v) @trusted { ret.put(v); });
217 		return ret.data;
218 	}
219 	/// ditto
220 	void getAll(string key, scope void delegate(const(ValueType)) @safe nothrow del)
221 	const {
222 		getAllImpl(key, del);
223 	}
224 	/// ditto
225 	void getAll(string key, scope void delegate(const(ValueType)) @safe del)
226 	const {
227 		getAllImpl(key, del);
228 	}
229 
230 	private void getAllImpl(C)(string key, scope C del)
231 	const {
232 		static if (USE_HASHSUM) uint keysum = computeCheckSumI(key);
233 		else enum keysum = 0;
234 		foreach (ref f; m_fields[0 .. m_fieldCount]) {
235 			static if (USE_HASHSUM)
236 				if (f.keyCheckSum != keysum) continue;
237 			if (matches(f.key, key)) del(f.value);
238 		}
239 		foreach (ref f; m_extendedFields) {
240 			static if (USE_HASHSUM)
241 				if (f.keyCheckSum != keysum) continue;
242 			if (matches(f.key, key)) del(f.value);
243 		}
244 	}
245 
246 	/** Returns the first value matching the given key.
247 	*/
248 	inout(ValueType) opIndex(string key)
249 	inout {
250 		auto pitm = key in this;
251 		enforce(pitm !is null, "Accessing non-existent key '"~key~"'.");
252 		return *pitm;
253 	}
254 
255 	/** Adds or replaces the given field with a new value.
256 	*/
257 	ValueType opIndexAssign(ValueType val, string key)
258 	{
259 		static if (USE_HASHSUM) auto keysum = computeCheckSumI(key);
260 		else enum keysum = 0;
261 		auto pitm = key in this;
262 		if (pitm) *pitm = val;
263 		else if (m_fieldCount < m_fields.length) m_fields[m_fieldCount++] = Field(keysum, key, val);
264 		else m_extendedFields ~= Field(keysum, key, val);
265 		return val;
266 	}
267 
268 	/// ditto
269 	ValueType opIndexAssign(T)(T val, string key)
270 	if (canAssign!T)
271 	{
272 		ValueType convertedVal = val;
273 		return opIndexAssign(convertedVal, key);
274 	}
275 
276 	/** Returns a pointer to the first field that matches the given key.
277 	*/
278 	inout(ValueType)* opBinaryRight(string op)(string key) inout if(op == "in")
279 	{
280 		static if (USE_HASHSUM) uint keysum = computeCheckSumI(key);
281 		else enum keysum = 0;
282 		auto idx = getIndex(m_fields[0 .. m_fieldCount], key, keysum);
283 		if (idx >= 0) return &m_fields[idx].tuple[1];
284 		idx = getIndex(m_extendedFields, key, keysum);
285 		if (idx >= 0) return &m_extendedFields[idx].tuple[1];
286 		return null;
287 	}
288 	/// ditto
289 	bool opBinaryRight(string op)(string key) inout if(op == "!in") {
290 		return !(key in this);
291 	}
292 
293 	/** Iterates over all fields, including duplicates.
294 	*/
295 	auto byKeyValue() {return Rng!false(&this, 0); }
296 	/// ditto
297 	auto byKeyValue() const { return Rng!true(&this, 0); }
298 	/// ditto
299 	auto byKey() inout { import std.algorithm.iteration : map; return byKeyValue().map!(p => p[0]); }
300 	/// ditto
301 	auto byValue() inout { import std.algorithm.iteration : map; return byKeyValue().map!(p => p[1]); }
302 
303 	static if (is(typeof({ const(ValueType) v; ValueType w; w = v; }))) {
304 		/** Duplicates the header map.
305 		*/
306 		@property DictionaryList dup()
307 		const {
308 			DictionaryList ret;
309 			ret.m_fields[0 .. m_fieldCount] = m_fields[0 .. m_fieldCount];
310 			ret.m_fieldCount = m_fieldCount;
311 			ret.m_extendedFields = m_extendedFields.dup;
312 			return ret;
313 		}
314 	}
315 
316 	private ptrdiff_t getIndex(in Field[] map, string key, uint keysum)
317 	const {
318 		foreach (i, ref const(Field) entry; map) {
319 			static if (USE_HASHSUM) if (entry.keyCheckSum != keysum) continue;
320 			if (matches(entry.key, key)) return i;
321 		}
322 		return -1;
323 	}
324 
325 	private static bool matches(string a, string b)
326 	{
327 		static if (case_sensitive) return a == b;
328 		else return a.length == b.length && icmp2(a, b) == 0;
329 	}
330 
331 	// very simple check sum function with a good chance to match
332 	// strings with different case equal
333 	static if (USE_HASHSUM) private static uint computeCheckSumI(string s)
334 	@trusted {
335 		uint csum = 0;
336 		immutable(char)* pc = s.ptr, pe = s.ptr + s.length;
337 		for (; pc != pe; pc++) {
338 			static if (case_sensitive) csum ^= *pc;
339 			else csum ^= *pc & 0x1101_1111;
340 			csum = (csum << 1) | (csum >> 31);
341 		}
342 		return csum;
343 	}
344 
345 	private static struct Rng(bool CONST) {
346 	@safe nothrow @nogc:
347 		static if (CONST) {
348 			alias KVT = const(KeyValue);
349 			const(DictionaryList)* list;
350 		} else {
351 			alias KVT = KeyValue;
352 			DictionaryList* list;
353 		}
354 		size_t idx;
355 
356 		@property Rng save() { return this; }
357 
358 		@property bool empty() const { return idx >= list.length; }
359 		@property ref KVT front() {
360 			if (idx < list.m_fieldCount)
361 				return list.m_fields[idx].tuple;
362 			return list.m_extendedFields[idx - list.m_fieldCount].tuple;
363 		}
364 		void popFront() { idx++; }
365 	}
366 }
367 
368 @safe unittest
369 {
370 	assert(DictionaryList!(string, true, 2).safeValueCopy);
371 }
372 
373 @safe unittest {
374 	DictionaryList!(int, true) a;
375 	a.addField("a", 1);
376 	a.addField("a", 2);
377 	assert(a["a"] == 1);
378 	assert(a.getAll("a") == [1, 2]);
379 	a["a"] = 3;
380 	assert(a["a"] == 3);
381 	assert(a.getAll("a") == [3, 2]);
382 	a.removeAll("a");
383 	assert(a.getAll("a").length == 0);
384 	assert(a.get("a", 4) == 4);
385 	a.addField("b", 2);
386 	a.addField("b", 1);
387 	a.remove("b");
388 	assert(a.getAll("b") == [1]);
389 
390 	DictionaryList!(int, false) b;
391 	b.addField("a", 1);
392 	b.addField("A", 2);
393 	assert(b["A"] == 1);
394 	assert(b.getAll("a") == [1, 2]);
395 }
396 
397 @safe nothrow unittest {
398 	DictionaryList!(int, true) a;
399 	a.addField("a", 1);
400 	a.addField("a", 2);
401 	assert(a.getAll("a") == [1, 2]);
402 	assert("a" in a);
403 	a["a"] = 3;
404 	assert(a.getAll("a") == [3, 2]);
405 	a.removeAll("a");
406 	assert(a.getAll("a").length == 0);
407 }
408 
409 unittest {
410 	import std.variant : Variant;
411 	DictionaryList!(Variant) c;
412 	c["a"] = true;
413 	c["b"] = "Hello";
414 	assert(c.get("a").type == typeid(bool));
415 	assert(c.get!string("b") == "Hello");
416 	assert(c.get!int("c") == int.init);
417 	c.addField("d", 9);
418 	c.addField("d", "bar");
419 	assert(c.getAll("d") == [ cast(Variant) 9, cast(Variant) "bar" ]);
420 }
421 
422 @safe unittest {
423 	import std.conv : text;
424 	DictionaryList!int l;
425 	l["foo"] = 42;
426 	l["bar"] = 43;
427 	assert(text(l) == `["foo": 42, "bar": 43]`, text(l));
428 	assert(l.toString() == `["foo": 42, "bar": 43]`, l.toString());
429 }
430 
431 // Issue 2004
432 unittest {
433 	import std.variant : Variant;
434 	DictionaryList!Variant l;
435 	class C {
436 		int x = 123;
437 	}
438 	l["foo"] = new C;
439 	auto c = l.get!C("foo");
440 	assert(c.x == 123);
441 }