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()
75 	const {
76 		auto ret = new FieldTuple[](this.length);
77 		size_t i = 0;
78 		foreach (k, ref v; this.byKeyValue) {
79 			// avoid bogus warning:
80 			// scope variable `k` assigned to non-scope `ret[i++]`
81 			// note that k is of type string and the bytes referred to by the
82 			// string should not be annotated scope anywhere
83 			() @trusted {
84 				ret[i++] = FieldTuple(k, v);
85 			} ();
86 		}
87 		return ret;
88 	}
89 
90 	/** Generates an associative-array equivalent string representation of the dictionary.
91 	*/
92 	void toString(scope void delegate(const(char)[] str) @safe sink)
93 	const {
94 		sink("[");
95 		bool first = true;
96 
97 		foreach (k, v; this.byKeyValue) {
98 			if (!first) sink(", ");
99 			else first = false;
100 			() @trusted {
101 				import std.format : formattedWrite;
102 				string[] ka = (&k)[0 .. 1];
103 				ValueType[] va = (&v)[0 .. 1];
104 				sink.formattedWrite("%(%s%): %(%s%)", ka, va);
105 			} ();
106 		}
107 		sink("]");
108 	}
109 	/// ditto
110 	void toString(scope void delegate(const(char)[] str) @system sink)
111 	const {
112 		toString(cast(void delegate(const(char)[]) @safe)sink);
113 	}
114 	/// ditto
115 	string toString()
116 	const {
117 		import std.array : appender;
118 		auto ret = appender!string();
119 		toString((s) { ret.put(s); });
120 		return ret.data;
121 	}
122 
123 	/** Removes the first field that matches the given key.
124 	*/
125 	void remove(string key)
126 	{
127 		static if (USE_HASHSUM) auto keysum = computeCheckSumI(key);
128 		enum keysum = 0;
129 		auto idx = getIndex(m_fields[0 .. m_fieldCount], key, keysum);
130 		if( idx >= 0 ){
131 			auto slice = m_fields[0 .. m_fieldCount];
132 			removeFromArrayIdx(slice, idx);
133 			m_fieldCount--;
134 		} else {
135 			idx = getIndex(m_extendedFields, key, keysum);
136 			enforce(idx >= 0);
137 			removeFromArrayIdx(m_extendedFields, idx);
138 		}
139 	}
140 
141 	/** Removes all fields that matches the given key.
142 	*/
143 	void removeAll(string key)
144 	{
145 		static if (USE_HASHSUM) auto keysum = computeCheckSumI(key);
146 		else enum keysum = 0;
147 		for (size_t i = 0; i < m_fieldCount;) {
148 			if (m_fields[i].keyCheckSum == keysum && matches(m_fields[i].key, key)) {
149 				auto slice = m_fields[0 .. m_fieldCount];
150 				removeFromArrayIdx(slice, i);
151 				m_fieldCount--;
152 			} else i++;
153 		}
154 
155 		for (size_t i = 0; i < m_extendedFields.length;) {
156 			if (m_fields[i].keyCheckSum == keysum && matches(m_fields[i].key, key))
157 				removeFromArrayIdx(m_extendedFields, i);
158 			else i++;
159 		}
160 	}
161 
162 	/// Used by `remove` and `removeAll`
163 	private static void removeFromArrayIdx(ref Field[] array, size_t idx)
164 	{
165 		foreach( j; idx+1 .. array.length)
166 			array[j-1] = array[j];
167 		array.length = array.length-1;
168 	}
169 
170 	/** Adds a new field to the map.
171 
172 		The new field will be added regardless of any existing fields that
173 		have the same key, possibly resulting in duplicates. Use opIndexAssign
174 		if you want to avoid duplicates.
175 	*/
176 	void addField(string key, ValueType value)
177 	{
178 		static if (USE_HASHSUM) auto keysum = computeCheckSumI(key);
179 		else enum keysum = 0;
180 		if (m_fieldCount < m_fields.length)
181 			m_fields[m_fieldCount++] = Field(keysum, key, value);
182 		else m_extendedFields ~= Field(keysum, key, value);
183 	}
184 
185 	void addField(T)(string key, T value)
186 	if (canAssign!T)
187 	{
188 		ValueType convertedValue = value;
189 		addField(key, convertedValue);
190 	}
191 
192 	/** Returns the first field that matches the given key.
193 
194 		If no field is found, def_val is returned.
195 	*/
196 	inout(ValueType) get(string key, lazy inout(ValueType) def_val = ValueType.init)
197 	inout {
198 		if (auto pv = key in this) return *pv;
199 		return def_val;
200 	}
201 
202 	// DMD bug: cannot set T.init as default value for def_val parameter,
203 	// because compilation fails with message:
204 	//      Error: undefined identifier 'T'
205 	/// ditto
206 	inout(T) get(T)(string key, lazy inout(T) def_val)
207 	inout if (typedGet!T) {
208 		if (auto pv = key in this) return (*pv).get!T;
209 		return def_val;
210 	}
211 
212 	/// ditto
213 	inout(T) get(T)(string key) // Work around DMD bug
214 	inout if (typedGet!T) {
215 		return get!T(key, inout(T).init);
216 	}
217 
218 	/** Returns all values matching the given key.
219 
220 		Note that the version returning an array will allocate for each call.
221 	*/
222 	const(ValueType)[] getAll(string key)
223 	const @trusted { // appender
224 		import std.array;
225 		auto ret = appender!(const(ValueType)[])();
226 		getAll(key, (v) @trusted { ret.put(v); });
227 		return ret.data;
228 	}
229 	/// ditto
230 	void getAll(string key, scope void delegate(const(ValueType)) @safe nothrow del)
231 	const {
232 		getAllImpl(key, del);
233 	}
234 	/// ditto
235 	void getAll(string key, scope void delegate(const(ValueType)) @safe del)
236 	const {
237 		getAllImpl(key, del);
238 	}
239 
240 	private void getAllImpl(C)(string key, scope C del)
241 	const {
242 		static if (USE_HASHSUM) uint keysum = computeCheckSumI(key);
243 		else enum keysum = 0;
244 		foreach (ref f; m_fields[0 .. m_fieldCount]) {
245 			static if (USE_HASHSUM)
246 				if (f.keyCheckSum != keysum) continue;
247 			if (matches(f.key, key)) del(f.value);
248 		}
249 		foreach (ref f; m_extendedFields) {
250 			static if (USE_HASHSUM)
251 				if (f.keyCheckSum != keysum) continue;
252 			if (matches(f.key, key)) del(f.value);
253 		}
254 	}
255 
256 	/** Returns the first value matching the given key.
257 	*/
258 	inout(ValueType) opIndex(string key)
259 	inout {
260 		auto pitm = key in this;
261 		enforce(pitm !is null, "Accessing non-existent key '"~key~"'.");
262 		return *pitm;
263 	}
264 
265 	/** Adds or replaces the given field with a new value.
266 	*/
267 	ValueType opIndexAssign(ValueType val, string key)
268 	{
269 		static if (USE_HASHSUM) auto keysum = computeCheckSumI(key);
270 		else enum keysum = 0;
271 		auto pitm = key in this;
272 		if (pitm) *pitm = val;
273 		else if (m_fieldCount < m_fields.length) m_fields[m_fieldCount++] = Field(keysum, key, val);
274 		else m_extendedFields ~= Field(keysum, key, val);
275 		return val;
276 	}
277 
278 	/// ditto
279 	ValueType opIndexAssign(T)(T val, string key)
280 	if (canAssign!T)
281 	{
282 		ValueType convertedVal = val;
283 		return opIndexAssign(convertedVal, key);
284 	}
285 
286 	/** Returns a pointer to the first field that matches the given key.
287 	*/
288 	inout(ValueType)* opBinaryRight(string op)(string key) inout if(op == "in")
289 	{
290 		static if (USE_HASHSUM) uint keysum = computeCheckSumI(key);
291 		else enum keysum = 0;
292 		auto idx = getIndex(m_fields[0 .. m_fieldCount], key, keysum);
293 		if (idx >= 0) return &m_fields[idx].tuple[1];
294 		idx = getIndex(m_extendedFields, key, keysum);
295 		if (idx >= 0) return &m_extendedFields[idx].tuple[1];
296 		return null;
297 	}
298 	/// ditto
299 	bool opBinaryRight(string op)(string key) inout if(op == "!in") {
300 		return !(key in this);
301 	}
302 
303 	/** Iterates over all fields, including duplicates.
304 	*/
305 	auto byKeyValue() {return Rng!false(&this, 0); }
306 	/// ditto
307 	auto byKeyValue() const { return Rng!true(&this, 0); }
308 	/// ditto
309 	auto byKey() inout { import std.algorithm.iteration : map; return byKeyValue().map!(p => p[0]); }
310 	/// ditto
311 	auto byValue() inout { import std.algorithm.iteration : map; return byKeyValue().map!(p => p[1]); }
312 
313 	static if (is(typeof({ const(ValueType) v; ValueType w; w = v; }))) {
314 		/** Duplicates the header map.
315 		*/
316 		@property DictionaryList dup()
317 		const {
318 			DictionaryList ret;
319 			ret.m_fields[0 .. m_fieldCount] = m_fields[0 .. m_fieldCount];
320 			ret.m_fieldCount = m_fieldCount;
321 			ret.m_extendedFields = m_extendedFields.dup;
322 			return ret;
323 		}
324 	}
325 
326 	private ptrdiff_t getIndex(in Field[] map, string key, uint keysum)
327 	const {
328 		foreach (i, ref const(Field) entry; map) {
329 			static if (USE_HASHSUM) if (entry.keyCheckSum != keysum) continue;
330 			if (matches(entry.key, key)) return i;
331 		}
332 		return -1;
333 	}
334 
335 	private static bool matches(string a, string b)
336 	{
337 		static if (case_sensitive) return a == b;
338 		else return a.length == b.length && icmp2(a, b) == 0;
339 	}
340 
341 	// very simple check sum function with a good chance to match
342 	// strings with different case equal
343 	static if (USE_HASHSUM) private static uint computeCheckSumI(string s)
344 	@trusted {
345 		uint csum = 0;
346 		immutable(char)* pc = s.ptr, pe = s.ptr + s.length;
347 		for (; pc != pe; pc++) {
348 			static if (case_sensitive) csum ^= *pc;
349 			else csum ^= *pc & 0x1101_1111;
350 			csum = (csum << 1) | (csum >> 31);
351 		}
352 		return csum;
353 	}
354 
355 	private static struct Rng(bool CONST) {
356 	@safe nothrow @nogc:
357 		static if (CONST) {
358 			alias KVT = const(KeyValue);
359 			const(DictionaryList)* list;
360 		} else {
361 			alias KVT = KeyValue;
362 			DictionaryList* list;
363 		}
364 		size_t idx;
365 
366 		@property Rng save() { return this; }
367 
368 		@property bool empty() const { return idx >= list.length; }
369 		@property ref KVT front() {
370 			if (idx < list.m_fieldCount)
371 				return list.m_fields[idx].tuple;
372 			return list.m_extendedFields[idx - list.m_fieldCount].tuple;
373 		}
374 		void popFront() { idx++; }
375 	}
376 }
377 
378 @safe unittest
379 {
380 	assert(DictionaryList!(string, true, 2).safeValueCopy);
381 }
382 
383 @safe unittest {
384 	DictionaryList!(int, true) a;
385 	a.addField("a", 1);
386 	a.addField("a", 2);
387 	assert(a["a"] == 1);
388 	assert(a.getAll("a") == [1, 2]);
389 	a["a"] = 3;
390 	assert(a["a"] == 3);
391 	assert(a.getAll("a") == [3, 2]);
392 	a.removeAll("a");
393 	assert(a.getAll("a").length == 0);
394 	assert(a.get("a", 4) == 4);
395 	a.addField("b", 2);
396 	a.addField("b", 1);
397 	a.remove("b");
398 	assert(a.getAll("b") == [1]);
399 
400 	DictionaryList!(int, false) b;
401 	b.addField("a", 1);
402 	b.addField("A", 2);
403 	assert(b["A"] == 1);
404 	assert(b.getAll("a") == [1, 2]);
405 }
406 
407 @safe nothrow unittest {
408 	DictionaryList!(int, true) a;
409 	a.addField("a", 1);
410 	a.addField("a", 2);
411 	assert(a.getAll("a") == [1, 2]);
412 	assert("a" in a);
413 	a["a"] = 3;
414 	assert(a.getAll("a") == [3, 2]);
415 	a.removeAll("a");
416 	assert(a.getAll("a").length == 0);
417 }
418 
419 unittest {
420 	import std.variant : Variant;
421 	DictionaryList!(Variant) c;
422 	c["a"] = true;
423 	c["b"] = "Hello";
424 	assert(c.get("a").type == typeid(bool));
425 	assert(c.get!string("b") == "Hello");
426 	assert(c.get!int("c") == int.init);
427 	c.addField("d", 9);
428 	c.addField("d", "bar");
429 	assert(c.getAll("d") == [ cast(Variant) 9, cast(Variant) "bar" ]);
430 }
431 
432 @safe unittest {
433 	import std.conv : text;
434 	DictionaryList!int l;
435 	l["foo"] = 42;
436 	l["bar"] = 43;
437 	assert(text(l) == `["foo": 42, "bar": 43]`, text(l));
438 	assert(l.toString() == `["foo": 42, "bar": 43]`, l.toString());
439 }
440 
441 // Issue 2004
442 unittest {
443 	import std.variant : Variant;
444 	DictionaryList!Variant l;
445 	class C {
446 		int x = 123;
447 	}
448 	l["foo"] = new C;
449 	auto c = l.get!C("foo");
450 	assert(c.x == 123);
451 }