1 /*
2  * Copyright (c) 2009-2016 Petri Lehtinen <petri@digip.org>
3  *
4  * This library is free software; you can redistribute it and/or modify
5  * it under the terms of the MIT license. See LICENSE for details.
6  */
7 /**
8  * License: MIT
9  */
10 module jansson_d.hashtable;
11 
12 
13 package:
14 
15 private static import core.stdc.string;
16 private static import jansson_d.hashtable_seed;
17 private static import jansson_d.jansson;
18 private static import jansson_d.jansson_private;
19 private static import jansson_d.lookup3;
20 
21 enum INITIAL_HASHTABLE_ORDER = 3;
22 
23 struct hashtable_list
24 {
25 	.hashtable_list* prev;
26 	.hashtable_list* next;
27 }
28 
29 /*
30  * "pair" may be a bit confusing a name, but think of it as a
31  * key-value pair. In this case, it just encodes some extra data,
32  * too
33  */
34 struct hashtable_pair
35 {
36 	.hashtable_list list;
37 	.hashtable_list ordered_list;
38 	size_t hash;
39 	jansson_d.jansson.json_t* value;
40 	size_t key_len;
41 
42 	/* dynamic array */
43 	char key = '\0';
44 }
45 
46 struct hashtable_bucket
47 {
48 	.hashtable_list* first;
49 	.hashtable_list* last;
50 }
51 
52 package struct hashtable
53 {
54 	size_t size;
55 	.hashtable_bucket* buckets;
56 
57 	/* hashtable has pow(2, order) buckets */
58 	size_t order;
59 
60 	.hashtable_list list;
61 	.hashtable_list ordered_list;
62 }
63 
64 alias hashtable_t = .hashtable;
65 
66 package template hashtable_key_to_iter(string key_)
67 {
68 	enum hashtable_key_to_iter = "(&(mixin (jansson_d.jansson_private.container_of!(\"" ~ key_ ~ "\", \"jansson_d.hashtable.hashtable_pair\", \"key\")).ordered_list))";
69 }
70 
71 alias list_t = .hashtable_list;
72 alias pair_t = .hashtable_pair;
73 alias bucket_t = .hashtable_bucket;
74 
75 template list_to_pair(string list_)
76 {
77 	enum list_to_pair = "(mixin (jansson_d.jansson_private.container_of!(\"" ~ list_ ~ "\", \"jansson_d.hashtable.pair_t\", \"list\")))";
78 }
79 
80 template ordered_list_to_pair(string list_)
81 {
82 	enum ordered_list_to_pair = "(mixin (jansson_d.jansson_private.container_of!(\"" ~ list_ ~ "\", \"jansson_d.hashtable.pair_t\", \"ordered_list\")))";
83 }
84 
85 template hash_str(string key, string len)
86 {
87 	enum hash_str = "(cast(size_t)(jansson_d.lookup3.hashlittle((" ~ key ~ "), (" ~ len ~ "), jansson_d.hashtable_seed.hashtable_seed)))";
88 }
89 
90 pragma(inline, true)
91 pure nothrow @trusted @nogc @live
92 private void list_init(scope .list_t* list)
93 
94 	in
95 	{
96 		assert(list != null);
97 	}
98 
99 	do
100 	{
101 		list.next = list;
102 		list.prev = list;
103 	}
104 
105 pragma(inline, true)
106 pure nothrow @trusted @nogc @live
107 private void list_insert(scope .list_t* list, scope .list_t* node)
108 
109 	in
110 	{
111 		assert(list != null);
112 		assert(node != null);
113 	}
114 
115 	do
116 	{
117 		node.next = list;
118 		node.prev = list.prev;
119 		list.prev.next = node;
120 		list.prev = node;
121 	}
122 
123 pragma(inline, true)
124 pure nothrow @trusted @nogc @live
125 private void list_remove(scope .list_t* list)
126 
127 	in
128 	{
129 		assert(list != null);
130 	}
131 
132 	do
133 	{
134 		list.prev.next = list.next;
135 		list.next.prev = list.prev;
136 	}
137 
138 pragma(inline, true)
139 pure nothrow @trusted @nogc @live
140 private int bucket_is_empty(scope .hashtable_t* hashtable_, scope .bucket_t* bucket)
141 
142 	in
143 	{
144 		assert(hashtable_ != null);
145 		assert(bucket != null);
146 	}
147 
148 	do
149 	{
150 		return (bucket.first == &hashtable_.list) && (bucket.first == bucket.last);
151 	}
152 
153 pure nothrow @trusted @nogc @live
154 private void insert_to_bucket(scope .hashtable_t* hashtable_, scope .bucket_t* bucket, scope .list_t* list)
155 
156 	in
157 	{
158 		assert(bucket != null);
159 	}
160 
161 	do
162 	{
163 		if (.bucket_is_empty(hashtable_, bucket)) {
164 			.list_insert(&hashtable_.list, list);
165 			bucket.first = bucket.last = list;
166 		} else {
167 			.list_insert(bucket.first, list);
168 			bucket.first = list;
169 		}
170 	}
171 
172 pure nothrow @trusted @nogc @live
173 private .pair_t* hashtable_find_pair(scope .hashtable_t* hashtable_, scope .bucket_t* bucket, scope const char* key, size_t key_len, size_t hash)
174 
175 	in
176 	{
177 		assert(bucket != null);
178 	}
179 
180 	do
181 	{
182 		if (.bucket_is_empty(hashtable_, bucket)) {
183 			return null;
184 		}
185 
186 		.list_t* list = bucket.first;
187 
188 		while (true) {
189 			assert(list != null);
190 			.pair_t* pair = mixin (.list_to_pair!("list"));
191 
192 			if ((pair.hash == hash) && (pair.key_len == key_len) && (core.stdc..string.memcmp(&(pair.key), key, key_len) == 0)) {
193 				return pair;
194 			}
195 
196 			if (list == bucket.last) {
197 				break;
198 			}
199 
200 			list = list.next;
201 		}
202 
203 		return null;
204 	}
205 
206 /* returns 0 on success, -1 if key was not found */
207 nothrow @trusted @nogc
208 private int hashtable_do_del(scope .hashtable_t* hashtable_, scope const char* key, size_t key_len, size_t hash)
209 
210 	in
211 	{
212 		assert(hashtable_ != null);
213 	}
214 
215 	do
216 	{
217 		size_t index = hash & mixin (jansson_d.lookup3.hashmask!("hashtable_.order"));
218 		.bucket_t* bucket = &hashtable_.buckets[index];
219 
220 		.pair_t* pair = .hashtable_find_pair(hashtable_, bucket, key, key_len, hash);
221 
222 		if (pair == null) {
223 			return -1;
224 		}
225 
226 		if ((&pair.list == bucket.first) && (&pair.list == bucket.last)) {
227 			bucket.first = bucket.last = &hashtable_.list;
228 		} else if (&pair.list == bucket.first) {
229 			bucket.first = pair.list.next;
230 		} else if (&pair.list == bucket.last) {
231 			bucket.last = pair.list.prev;
232 		}
233 
234 		.list_remove(&pair.list);
235 		.list_remove(&pair.ordered_list);
236 		jansson_d.jansson.json_decref(pair.value);
237 
238 		jansson_d.jansson_private.jsonp_free(pair);
239 		hashtable_.size--;
240 
241 		return 0;
242 	}
243 
244 nothrow @trusted @nogc
245 private void hashtable_do_clear(scope .hashtable_t* hashtable_)
246 
247 	in
248 	{
249 		assert(hashtable_ != null);
250 	}
251 
252 	do
253 	{
254 		.list_t* next = void;
255 
256 		for (.list_t* list = hashtable_.list.next; list != &hashtable_.list; list = next) {
257 			assert(list != null);
258 			next = list.next;
259 			.pair_t* pair = mixin (.list_to_pair!("list"));
260 			jansson_d.jansson.json_decref(pair.value);
261 			jansson_d.jansson_private.jsonp_free(pair);
262 		}
263 	}
264 
265 nothrow @trusted @nogc
266 private int hashtable_do_rehash(scope .hashtable_t* hashtable_)
267 
268 	in
269 	{
270 		assert(hashtable_ != null);
271 	}
272 
273 	do
274 	{
275 		size_t new_order = hashtable_.order + 1;
276 		size_t new_size = mixin (jansson_d.lookup3.hashsize!("new_order"));
277 
278 		.hashtable_bucket* new_buckets = cast(.hashtable_bucket*)(jansson_d.jansson_private.jsonp_malloc(new_size * .bucket_t.sizeof));
279 
280 		if (new_buckets == null) {
281 			return -1;
282 		}
283 
284 		jansson_d.jansson_private.jsonp_free(hashtable_.buckets);
285 		hashtable_.buckets = new_buckets;
286 		hashtable_.order = new_order;
287 
288 		for (size_t i = 0; i < mixin (jansson_d.lookup3.hashsize!("hashtable_.order")); i++) {
289 			hashtable_.buckets[i].last = &hashtable_.list;
290 			hashtable_.buckets[i].first = &hashtable_.list;
291 		}
292 
293 		.list_t* list = hashtable_.list.next;
294 		.list_init(&hashtable_.list);
295 
296 		for (.list_t* next = void; list != &hashtable_.list; list = next) {
297 			assert(list != null);
298 			next = list.next;
299 			.pair_t* pair = mixin (.list_to_pair!("list"));
300 			size_t index = pair.hash % new_size;
301 			.insert_to_bucket(hashtable_, &hashtable_.buckets[index], &pair.list);
302 		}
303 
304 		return 0;
305 	}
306 
307 /**
308  * Initialize a hashtable object
309  *
310  * Params:
311  *      hashtable_ = The (statically allocated) hashtable object
312  *
313  * Initializes a statically allocated hashtable object. The object
314  * should be cleared with hashtable_close when it's no longer used.
315  *
316  * Returns: 0 on success, -1 on error (out of memory).
317  */
318 nothrow @trusted @nogc //ToDo: @nodiscard
319 int hashtable_init(scope .hashtable_t* hashtable_)
320 
321 	in
322 	{
323 		assert(hashtable_ != null);
324 	}
325 
326 	do
327 	{
328 		hashtable_.size = 0;
329 		hashtable_.order = .INITIAL_HASHTABLE_ORDER;
330 		hashtable_.buckets = cast(.hashtable_bucket*)(jansson_d.jansson_private.jsonp_malloc(mixin (jansson_d.lookup3.hashsize!("hashtable_.order")) * .bucket_t.sizeof));
331 
332 		if (hashtable_.buckets == null) {
333 			return -1;
334 		}
335 
336 		.list_init(&hashtable_.list);
337 		.list_init(&hashtable_.ordered_list);
338 
339 		for (size_t i = 0; i < mixin (jansson_d.lookup3.hashsize!("hashtable_.order")); i++) {
340 			hashtable_.buckets[i].last = &hashtable_.list;
341 			hashtable_.buckets[i].first = &hashtable_.list;
342 		}
343 
344 		return 0;
345 	}
346 
347 /**
348  * Release all resources used by a hashtable object
349  *
350  * Params:
351  *      hashtable_ = The hashtable
352  *
353  * Destroys a statically allocated hashtable object.
354  */
355 nothrow @trusted @nogc
356 void hashtable_close(scope .hashtable_t* hashtable_)
357 
358 	in
359 	{
360 		assert(hashtable_ != null);
361 	}
362 
363 	do
364 	{
365 		.hashtable_do_clear(hashtable_);
366 		jansson_d.jansson_private.jsonp_free(hashtable_.buckets);
367 		hashtable_.buckets = null;
368 	}
369 
370 nothrow @trusted @nogc
371 private .pair_t* init_pair(scope jansson_d.jansson.json_t* value, scope const char* key, size_t key_len, size_t hash)
372 
373 	do
374 	{
375 		/*
376 		 * offsetof(...) returns the size of pair_t without the last,
377 		 * flexible member. This way, the correct amount is
378 		 * allocated.
379 		 */
380 
381 		if (key_len >= (size_t.max - .pair_t.key.offsetof)) {
382 			/* Avoid an overflow if the key is very long */
383 			return null;
384 		}
385 
386 		.pair_t* pair = cast(.pair_t*)(jansson_d.jansson_private.jsonp_malloc(.pair_t.key.offsetof + key_len + 1));
387 
388 		if (pair == null) {
389 			return null;
390 		}
391 
392 		pair.hash = hash;
393 		core.stdc..string.memcpy(&(pair.key), key, key_len);
394 		*(cast(char*)((&(pair.key))) + key_len) = '\0';
395 		pair.key_len = key_len;
396 		pair.value = value;
397 
398 		.list_init(&pair.list);
399 		.list_init(&pair.ordered_list);
400 
401 		return pair;
402 	}
403 
404 /**
405  * Add/modify value in hashtable
406  *
407  * Params:
408  *      hashtable_ = The hashtable object
409  *      key = The key
410  *      key_len = The length of key
411  *      serial = For addition order of keys
412  *      value = The value
413  *
414  * If a value with the given key already exists, its value is replaced
415  * with the new value. Value is "stealed" in the sense that hashtable
416  * doesn't increment its refcount but decreases the refcount when the
417  * value is no longer needed.
418  *
419  * Returns: 0 on success, -1 on failure (out of memory).
420  */
421 nothrow @trusted @nogc
422 int hashtable_set(scope .hashtable_t* hashtable_, scope const char* key, size_t key_len, scope jansson_d.jansson.json_t* value)
423 
424 	in
425 	{
426 		assert(hashtable_ != null);
427 	}
428 
429 	do
430 	{
431 		/* rehash if the load ratio exceeds 1 */
432 		if (hashtable_.size >= mixin (jansson_d.lookup3.hashsize!("hashtable_.order"))) {
433 			if (.hashtable_do_rehash(hashtable_)) {
434 				return -1;
435 			}
436 		}
437 
438 		size_t hash = mixin (.hash_str!("key", "key_len"));
439 		size_t index = hash & mixin (jansson_d.lookup3.hashmask!("hashtable_.order"));
440 		.bucket_t* bucket = &hashtable_.buckets[index];
441 		.pair_t* pair = .hashtable_find_pair(hashtable_, bucket, key, key_len, hash);
442 
443 		if (pair != null) {
444 			jansson_d.jansson.json_decref(pair.value);
445 			pair.value = value;
446 		} else {
447 			pair = .init_pair(value, key, key_len, hash);
448 
449 			if (pair == null) {
450 				return -1;
451 			}
452 
453 			.insert_to_bucket(hashtable_, bucket, &pair.list);
454 			.list_insert(&hashtable_.ordered_list, &pair.ordered_list);
455 
456 			hashtable_.size++;
457 		}
458 
459 		return 0;
460 	}
461 
462 /**
463  * Get a value associated with a key
464  *
465  * Params:
466  *      hashtable_ = The hashtable object
467  *      key = The key
468  *      key_len = The length of key
469  *
470  * Returns: value if it is found, or null otherwise.
471  */
472 nothrow @trusted @nogc @live
473 void* hashtable_get(scope .hashtable_t* hashtable_, scope const char* key, size_t key_len)
474 
475 	in
476 	{
477 		assert(hashtable_ != null);
478 	}
479 
480 	do
481 	{
482 		size_t hash = mixin (.hash_str!("key", "key_len"));
483 		.bucket_t* bucket = &hashtable_.buckets[hash & mixin (jansson_d.lookup3.hashmask!("hashtable_.order"))];
484 
485 		.pair_t* pair = .hashtable_find_pair(hashtable_, bucket, key, key_len, hash);
486 
487 		if (pair == null) {
488 			return null;
489 		}
490 
491 		return pair.value;
492 	}
493 
494 /**
495  * Remove a value from the hashtable
496  *
497  * Params:
498  *      hashtable_ = The hashtable object
499  *      key = The key
500  *      key_len = The length of key
501  *
502  * Returns: 0 on success, or -1 if the key was not found.
503  */
504 nothrow @trusted @nogc
505 int hashtable_del(scope .hashtable_t* hashtable_, scope const char* key, size_t key_len)
506 
507 	do
508 	{
509 		size_t hash = mixin (.hash_str!("key", "key_len"));
510 
511 		return .hashtable_do_del(hashtable_, key, key_len, hash);
512 	}
513 
514 /**
515  * Clear hashtable
516  *
517  * Params:
518  *      hashtable_ = The hashtable object
519  *
520  * Removes all items from the hashtable.
521  */
522 nothrow @trusted @nogc
523 void hashtable_clear(scope .hashtable_t* hashtable_)
524 
525 	in
526 	{
527 		assert(hashtable_ != null);
528 	}
529 
530 	do
531 	{
532 		.hashtable_do_clear(hashtable_);
533 
534 		for (size_t i = 0; i < mixin (jansson_d.lookup3.hashsize!("hashtable_.order")); i++) {
535 			hashtable_.buckets[i].first = hashtable_.buckets[i].last = &hashtable_.list;
536 		}
537 
538 		.list_init(&hashtable_.list);
539 		.list_init(&hashtable_.ordered_list);
540 		hashtable_.size = 0;
541 	}
542 
543 /**
544  * Iterate over hashtable
545  *
546  * Params:
547  *      hashtable_ = The hashtable object
548  *
549  * Returns an opaque iterator to the first element in the hashtable.
550  * The iterator should be passed to hashtable_iter_* functions.
551  * The hashtable items are not iterated over in any particular order.
552  *
553  * There's no need to free the iterator in any way. The iterator is
554  * valid as long as the item that is referenced by the iterator is not
555  * deleted. Other values may be added or deleted. In particular,
556  * hashtable_iter_next() may be called on an iterator, and after that
557  * the key/value pair pointed by the old iterator may be deleted.
558  */
559 pure nothrow @trusted @nogc @live
560 void* hashtable_iter(scope .hashtable_t* hashtable_)
561 
562 	in
563 	{
564 		assert(hashtable_ != null);
565 	}
566 
567 	do
568 	{
569 		return .hashtable_iter_next(hashtable_, &hashtable_.ordered_list);
570 	}
571 
572 /**
573  * Return an iterator at a specific key
574  *
575  * Params:
576  *      hashtable_ = The hashtable object
577  *      key = The key that the iterator should point to
578  *      key_len = The length of key
579  *
580  * Like hashtable_iter() but returns an iterator pointing to a
581  * specific key.
582  */
583 nothrow @trusted @nogc @live
584 void* hashtable_iter_at(scope .hashtable_t* hashtable_, scope const char* key, size_t key_len)
585 
586 	in
587 	{
588 		assert(hashtable_ != null);
589 	}
590 
591 	do
592 	{
593 		size_t hash = mixin (.hash_str!("key", "key_len"));
594 		.bucket_t* bucket = &hashtable_.buckets[hash & mixin (jansson_d.lookup3.hashmask!("hashtable_.order"))];
595 
596 		.pair_t* pair = .hashtable_find_pair(hashtable_, bucket, key, key_len, hash);
597 
598 		if (pair == null) {
599 			return null;
600 		}
601 
602 		return &pair.ordered_list;
603 	}
604 
605 /**
606  * Advance an iterator
607  *
608  * Params:
609  *      hashtable_ = The hashtable object
610  *      iter = The iterator
611  *
612  * Returns: a new iterator pointing to the next element in the hashtable or null if the whole hastable has been iterated over.
613  */
614 pure nothrow @trusted @nogc @live
615 void* hashtable_iter_next(scope .hashtable_t* hashtable_, scope void* iter)
616 
617 	in
618 	{
619 		assert(hashtable_ != null);
620 		assert(iter != null);
621 	}
622 
623 	do
624 	{
625 		.list_t* list = cast(.list_t*)(iter);
626 
627 		if (list.next == &hashtable_.ordered_list) {
628 			return null;
629 		}
630 
631 		return list.next;
632 	}
633 
634 /**
635  * Retrieve the key pointed by an iterator
636  *
637  * Params:
638  *      iter = The iterator
639  */
640 pure nothrow @trusted @nogc @live
641 void* hashtable_iter_key(scope void* iter)
642 
643 	in
644 	{
645 		assert(iter != null);
646 	}
647 
648 	do
649 	{
650 		.pair_t* pair = mixin (.ordered_list_to_pair!("cast(.list_t*)(iter)"));
651 
652 		return &(pair.key);
653 	}
654 
655 /**
656  * Retrieve the key length pointed by an iterator
657  *
658  * Params:
659  *      iter = The iterator
660  */
661 pure nothrow @trusted @nogc @live
662 size_t hashtable_iter_key_len(scope void* iter)
663 
664 	in
665 	{
666 		assert(iter != null);
667 	}
668 
669 	do
670 	{
671 		.pair_t* pair = mixin (.ordered_list_to_pair!("cast(.list_t*)(iter)"));
672 
673 		return pair.key_len;
674 	}
675 
676 /**
677  * Retrieve the value pointed by an iterator
678  *
679  * Params:
680  *      iter = The iterator
681  */
682 pure nothrow @trusted @nogc @live
683 void* hashtable_iter_value(scope void* iter)
684 
685 	in
686 	{
687 		assert(iter != null);
688 	}
689 
690 	do
691 	{
692 		.pair_t* pair = mixin (.ordered_list_to_pair!("cast(.list_t*)(iter)"));
693 
694 		return pair.value;
695 	}
696 
697 /**
698  * Set the value pointed by an iterator
699  *
700  * Params:
701  *      iter = The iterator
702  *      value = The value to set
703  */
704 nothrow @trusted @nogc
705 void hashtable_iter_set(scope void* iter, scope jansson_d.jansson.json_t* value)
706 
707 	in
708 	{
709 		assert(iter != null);
710 	}
711 
712 	do
713 	{
714 		.pair_t* pair = mixin (.ordered_list_to_pair!("cast(.list_t*)(iter)"));
715 
716 		jansson_d.jansson.json_decref(pair.value);
717 		pair.value = value;
718 	}