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 }