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 }