1 /* 2 * Hunt - A refined core library for D programming language. 3 * 4 * Copyright (C) 2018-2019 HuntLabs 5 * 6 * Website: https://www.huntlabs.net/ 7 * 8 * Licensed under the Apache-2.0 License. 9 * 10 */ 11 12 module hunt.collection.AbstractMap; 13 14 import hunt.collection.Collection; 15 import hunt.collection.Iterator; 16 import hunt.collection.Map; 17 import hunt.collection.Set; 18 19 import hunt.Exceptions; 20 import hunt.Object; 21 import hunt.util.ObjectUtils; 22 23 import std.array; 24 import std.container.array; 25 // import hunt.collection.Array; 26 import std.conv; 27 import std.exception; 28 import std.range; 29 30 /** 31 */ 32 abstract class AbstractMap(K, V) : Map!(K, V) { 33 34 /** 35 * The number of key-value mappings contained in this map. 36 */ 37 protected int _size; 38 39 /** 40 * Sole constructor. (For invocation by subclass constructors, typically 41 * implicit.) 42 */ 43 protected this() { 44 } 45 46 // Query Operations 47 48 /** 49 * Returns the number of key-value mappings in this map. 50 * 51 * @return the number of key-value mappings in this map 52 */ 53 int size() { 54 return _size; 55 } 56 57 /** 58 * Returns <tt>true</tt> if this map contains no key-value mappings. 59 * 60 * @return <tt>true</tt> if this map contains no key-value mappings 61 */ 62 bool isEmpty() { 63 return _size == 0; 64 } 65 66 /** 67 * {@inheritDoc} 68 * 69 * @implSpec 70 * This implementation iterates over <tt>entrySet()</tt> searching 71 * for an entry with the specified value. If such an entry is found, 72 * <tt>true</tt> is returned. If the iteration terminates without 73 * finding such an entry, <tt>false</tt> is returned. Note that this 74 * implementation requires linear time in the size of the map. 75 * 76 * @throws ClassCastException {@inheritDoc} 77 * @throws NullPointerException {@inheritDoc} 78 */ 79 bool containsKey(K key) { 80 throw new UnsupportedOperationException(); 81 } 82 /** 83 * {@inheritDoc} 84 * 85 * @implSpec 86 * This implementation iterates over <tt>entrySet()</tt> searching 87 * for an entry with the specified value. If such an entry is found, 88 * <tt>true</tt> is returned. If the iteration terminates without 89 * finding such an entry, <tt>false</tt> is returned. Note that this 90 * implementation requires linear time in the size of the map. 91 * 92 * @throws ClassCastException {@inheritDoc} 93 * @throws NullPointerException {@inheritDoc} 94 */ 95 bool containsValue(V value) { 96 throw new UnsupportedOperationException(); 97 } 98 99 V opIndex(K key) { 100 return get(key); 101 } 102 103 /** 104 * {@inheritDoc} 105 * 106 * @implSpec 107 * This implementation iterates over <tt>entrySet()</tt> searching 108 * for an entry with the specified key. If such an entry is found, 109 * the entry's value is returned. If the iteration terminates without 110 * finding such an entry, <tt>null</tt> is returned. Note that this 111 * implementation requires linear time in the size of the map; many 112 * implementations will override this method. 113 * 114 * @throws ClassCastException {@inheritDoc} 115 * @throws NullPointerException {@inheritDoc} 116 */ 117 V get(K key) { 118 throw new UnsupportedOperationException(); 119 } 120 121 /** 122 * Returns the value to which the specified key is mapped, or 123 * {@code defaultValue} if this map contains no mapping for the key. 124 * 125 * @implSpec 126 * The default implementation makes no guarantees about synchronization 127 * or atomicity properties of this method. Any implementation providing 128 * atomicity guarantees must override this method and document its 129 * concurrency properties. 130 * 131 * @param key the key whose associated value is to be returned 132 * @param defaultValue the default mapping of the key 133 * @return the value to which the specified key is mapped, or 134 * {@code defaultValue} if this map contains no mapping for the key 135 * @throws ClassCastException if the key is of an inappropriate type for 136 * this map 137 * (<a href="{@docRoot}/java/util/Collection.html#optional-restrictions">optional</a>) 138 * @throws NullPointerException if the specified key is null and this map 139 * does not permit null keys 140 * (<a href="{@docRoot}/java/util/Collection.html#optional-restrictions">optional</a>) 141 */ 142 V getOrDefault(K k, V value) { 143 throw new UnsupportedOperationException(); 144 } 145 146 // Modification Operations 147 148 /** 149 * {@inheritDoc} 150 * 151 * @implSpec 152 * This implementation always throws an 153 * <tt>UnsupportedOperationException</tt>. 154 * 155 * @throws UnsupportedOperationException {@inheritDoc} 156 * @throws ClassCastException {@inheritDoc} 157 * @throws NullPointerException {@inheritDoc} 158 * @throws IllegalArgumentException {@inheritDoc} 159 */ 160 V put(K key, V value) { 161 throw new UnsupportedOperationException(); 162 } 163 164 V putIfAbsent(K key, V value) { 165 V v = V.init; 166 167 if (!containsKey(key)) 168 v = put(key, value); 169 170 return v; 171 } 172 173 /** 174 * {@inheritDoc} 175 * 176 * @implSpec 177 * This implementation iterates over <tt>entrySet()</tt> searching for an 178 * entry with the specified key. If such an entry is found, its value is 179 * obtained with its <tt>getValue</tt> operation, the entry is removed 180 * from the collection (and the backing map) with the iterator's 181 * <tt>remove</tt> operation, and the saved value is returned. If the 182 * iteration terminates without finding such an entry, <tt>null</tt> is 183 * returned. Note that this implementation requires linear time in the 184 * size of the map; many implementations will override this method. 185 * 186 * <p>Note that this implementation throws an 187 * <tt>UnsupportedOperationException</tt> if the <tt>entrySet</tt> 188 * iterator does not support the <tt>remove</tt> method and this map 189 * contains a mapping for the specified key. 190 * 191 * @throws UnsupportedOperationException {@inheritDoc} 192 * @throws ClassCastException {@inheritDoc} 193 * @throws NullPointerException {@inheritDoc} 194 */ 195 V remove(K key) { 196 throw new UnsupportedOperationException(); 197 } 198 199 bool remove(K key, V value) { 200 V curValue = get(key); 201 if (curValue != value || !containsKey(key)) 202 return false; 203 remove(key); 204 return true; 205 } 206 207 // Bulk Operations 208 209 /** 210 * {@inheritDoc} 211 * 212 * @implSpec 213 * This implementation iterates over the specified map's 214 * <tt>entrySet()</tt> collection, and calls this map's <tt>put</tt> 215 * operation once for each entry returned by the iteration. 216 * 217 * <p>Note that this implementation throws an 218 * <tt>UnsupportedOperationException</tt> if this map does not support 219 * the <tt>put</tt> operation and the specified map is nonempty. 220 * 221 * @throws UnsupportedOperationException {@inheritDoc} 222 * @throws ClassCastException {@inheritDoc} 223 * @throws NullPointerException {@inheritDoc} 224 * @throws IllegalArgumentException {@inheritDoc} 225 */ 226 void putAll(Map!(K, V) m) { 227 foreach (K k, V v; m) 228 put(k, v); 229 } 230 231 /** 232 * {@inheritDoc} 233 * 234 * @implSpec 235 * This implementation calls <tt>entrySet().clear()</tt>. 236 * 237 * <p>Note that this implementation throws an 238 * <tt>UnsupportedOperationException</tt> if the <tt>entrySet</tt> 239 * does not support the <tt>clear</tt> operation. 240 * 241 * @throws UnsupportedOperationException {@inheritDoc} 242 */ 243 void clear() { 244 throw new NotImplementedException(""); 245 } 246 247 bool replace(K key, V oldValue, V newValue) { 248 V curValue = get(key); 249 if (curValue != oldValue || !containsKey(key)) { 250 return false; 251 } 252 put(key, newValue); 253 return true; 254 } 255 256 V replace(K key, V value) { 257 V curValue = V.init; 258 if (containsKey(key)) { 259 curValue = put(key, value); 260 } 261 return curValue; 262 } 263 264 int opApply(scope int delegate(ref K, ref V) dg) { 265 throw new NotImplementedException(); 266 } 267 268 int opApply(scope int delegate(MapEntry!(K, V) entry) dg) { 269 throw new NotImplementedException(); 270 } 271 272 InputRange!K byKey() { 273 throw new NotImplementedException(); 274 } 275 276 InputRange!V byValue() { 277 throw new NotImplementedException(); 278 } 279 280 // Views 281 282 /** 283 * Each of these fields are initialized to contain an instance of the 284 * appropriate view the first time this view is requested. The views are 285 * stateless, so there's no reason to create more than one of each. 286 * 287 * <p>Since there is no synchronization performed while accessing these fields, 288 * it is expected that java.util.Map view classes using these fields have 289 * no non-final fields (or any fields at all except for outer-this). Adhering 290 * to this rule would make the races on these fields benign. 291 * 292 * <p>It is also imperative that implementations read the field only once, 293 * as in: 294 * 295 * <pre> {@code 296 * public Set!K keySet() { 297 * Set!K ks = keySet; // single racy read 298 * if (ks is null) { 299 * ks = new KeySet(); 300 * keySet = ks; 301 * } 302 * return ks; 303 * } 304 *}</pre> 305 */ 306 // protected Set!K _keySet; 307 // protected Collection!V _values; 308 309 /** 310 * {@inheritDoc} 311 * 312 * @implSpec 313 * This implementation returns a set that subclasses {@link AbstractSet}. 314 * The subclass's iterator method returns a "wrapper object" over this 315 * map's <tt>entrySet()</tt> iterator. The <tt>size</tt> method 316 * delegates to this map's <tt>size</tt> method and the 317 * <tt>contains</tt> method delegates to this map's 318 * <tt>containsKey</tt> method. 319 * 320 * <p>The set is created the first time this method is called, 321 * and returned in response to all subsequent calls. No synchronization 322 * is performed, so there is a slight chance that multiple calls to this 323 * method will not all return the same set. 324 */ 325 K[] keySet() { 326 Array!K arr; 327 foreach (K key; byKey()) { 328 arr.insertBack(key); 329 } 330 return arr.array(); 331 } 332 333 /** 334 * {@inheritDoc} 335 * 336 * @implSpec 337 * This implementation returns a collection that subclasses {@link 338 * AbstractCollection}. The subclass's iterator method returns a 339 * "wrapper object" over this map's <tt>entrySet()</tt> iterator. 340 * The <tt>size</tt> method delegates to this map's <tt>size</tt> 341 * method and the <tt>contains</tt> method delegates to this map's 342 * <tt>containsValue</tt> method. 343 * 344 * <p>The collection is created the first time this method is called, and 345 * returned in response to all subsequent calls. No synchronization is 346 * performed, so there is a slight chance that multiple calls to this 347 * method will not all return the same collection. 348 */ 349 V[] values() { 350 // FIXME: Needing refactor or cleanup -@zxp at 9/26/2018, 6:00:54 PM 351 // 352 // Array!V arr; 353 // foreach(V value; byValue()) { 354 // arr.insertBack(value); 355 // } 356 // return arr.array(); 357 // V[] arr; 358 // foreach (V value; byValue()) { 359 // arr ~= value; 360 // } 361 // return arr; 362 return byValue().array(); 363 } 364 365 // Comparison and hashing 366 367 /** 368 * Compares the specified object with this map for equality. Returns 369 * <tt>true</tt> if the given object is also a map and the two maps 370 * represent the same mappings. More formally, two maps <tt>m1</tt> and 371 * <tt>m2</tt> represent the same mappings if 372 * <tt>m1.entrySet().equals(m2.entrySet())</tt>. This ensures that the 373 * <tt>equals</tt> method works properly across different implementations 374 * of the <tt>Map</tt> interface. 375 * 376 * @implSpec 377 * This implementation first checks if the specified object is this map; 378 * if so it returns <tt>true</tt>. Then, it checks if the specified 379 * object is a map whose size is identical to the size of this map; if 380 * not, it returns <tt>false</tt>. If so, it iterates over this map's 381 * <tt>entrySet</tt> collection, and checks that the specified map 382 * contains each mapping that this map contains. If the specified map 383 * fails to contain such a mapping, <tt>false</tt> is returned. If the 384 * iteration completes, <tt>true</tt> is returned. 385 * 386 * @param o object to be compared for equality with this map 387 * @return <tt>true</tt> if the specified object is equal to this map 388 */ 389 override bool opEquals(Object o) { 390 if(o is null) return false; 391 if (o is this) return true; 392 393 auto m = cast(Map!(K, V)) o; 394 if(m is null) return false; 395 if (m.size() != size()) 396 return false; 397 398 try { 399 foreach(K key, V value; this) { 400 if(value != m.get(key)) 401 return false; 402 } 403 } catch (Exception) { 404 return false; 405 } 406 407 return true; 408 } 409 410 bool opEquals(IObject o) { 411 return opEquals(cast(Object) o); 412 } 413 414 // Iterator!(MapEntry!(K,V)) iterator() 415 // { 416 // throw new UnsupportedOperationException(); 417 // } 418 419 /** 420 * Returns the hash code value for this map. The hash code of a map is 421 * defined to be the sum of the hash codes of each entry in the map's 422 * <tt>entrySet()</tt> view. This ensures that <tt>m1.equals(m2)</tt> 423 * implies that <tt>m1.toHash()==m2.toHash()</tt> for any two maps 424 * <tt>m1</tt> and <tt>m2</tt>, as required by the general contract of 425 * {@link Object#toHash}. 426 * 427 * @implSpec 428 * This implementation iterates over <tt>entrySet()</tt>, calling 429 * {@link MapEntry#toHash toHash()} on each element (entry) in the 430 * set, and adding up the results. 431 * 432 * @return the hash code value for this map 433 * @see MapEntry#toHash() 434 * @see Object#equals(Object) 435 * @see Set#equals(Object) 436 */ 437 override size_t toHash() @trusted nothrow { 438 size_t h = 0; 439 try { 440 foreach (MapEntry!(K, V) i; this) { 441 h += i.toHash(); 442 } 443 } catch (Exception ex) { 444 } 445 return h; 446 } 447 448 /** 449 * Returns a string representation of this map. The string representation 450 * consists of a list of key-value mappings in the order returned by the 451 * map's <tt>entrySet</tt> view's iterator, enclosed in braces 452 * (<tt>"{}"</tt>). Adjacent mappings are separated by the characters 453 * <tt>", "</tt> (comma and space). Each key-value mapping is rendered as 454 * the key followed by an equals sign (<tt>"="</tt>) followed by the 455 * associated value. Keys and values are converted to strings as by 456 * {@link string#valueOf(Object)}. 457 * 458 * @return a string representation of this map 459 */ 460 override string toString() { 461 if (isEmpty()) 462 return "{}"; 463 464 Appender!string sb; 465 sb.put("{"); 466 bool isFirst = true; 467 foreach (K key, V value; this) { 468 if (!isFirst) { 469 sb.put(", "); 470 } 471 sb.put(key.to!string() ~ "=" ~ value.to!string()); 472 isFirst = false; 473 } 474 sb.put("}"); 475 476 return sb.data; 477 } 478 479 /** 480 * Returns a shallow copy of this <tt>AbstractMap</tt> instance: the keys 481 * and values themselves are not cloned. 482 * 483 * @return a shallow copy of this map 484 */ 485 mixin CloneMemberTemplate!(typeof(this)); 486 487 /** 488 * Utility method for SimpleEntry and SimpleImmutableEntry. 489 * Test for equality, checking for nulls. 490 * 491 * NB: Do not replace with Object.equals until JDK-8015417 is resolved. 492 */ 493 // private static bool eq(Object o1, Object o2) { 494 // return o1 is null ? o2 is null : o1.equals(o2); 495 // } 496 497 } 498 499 /** 500 * An Entry maintaining an immutable key and value. This class 501 * does not support method <tt>setValue</tt>. This class may be 502 * convenient in methods that return thread-safe snapshots of 503 * key-value mappings. 504 * 505 * @since 1.6 506 */ 507 class SimpleImmutableEntry(K, V) : AbstractMapEntry!(K, V) { 508 509 /** 510 * Creates an entry representing a mapping from the specified 511 * key to the specified value. 512 * 513 * @param key the key represented by this entry 514 * @param value the value represented by this entry 515 */ 516 this(K key, V value) { 517 super(key, value); 518 } 519 520 /** 521 * Creates an entry representing the same mapping as the 522 * specified entry. 523 * 524 * @param entry the entry to copy 525 */ 526 this(MapEntry!(K, V) entry) { 527 super(entry.getKey(), entry.getValue()); 528 } 529 530 /** 531 * Replaces the value corresponding to this entry with the specified 532 * value (optional operation). This implementation simply throws 533 * <tt>UnsupportedOperationException</tt>, as this class implements 534 * an <i>immutable</i> map entry. 535 * 536 * @param value new value to be stored in this entry 537 * @return (Does not return) 538 * @throws UnsupportedOperationException always 539 */ 540 override V setValue(V value) { 541 throw new UnsupportedOperationException(); 542 } 543 544 /** 545 * Returns the hash code value for this map entry. The hash code 546 * of a map entry {@code e} is defined to be: <pre> 547 * (e.getKey()==null ? 0 : e.getKey().toHash()) ^ 548 * (e.getValue()==null ? 0 : e.getValue().toHash())</pre> 549 * This ensures that {@code e1.equals(e2)} implies that 550 * {@code e1.toHash()==e2.toHash()} for any two Entries 551 * {@code e1} and {@code e2}, as required by the general 552 * contract of {@link Object#toHash}. 553 * 554 * @return the hash code value for this map entry 555 * @see #equals 556 */ 557 override size_t toHash() @trusted nothrow { 558 static if (is(K == class)) { 559 size_t kHash = 0; 560 if (key !is null) 561 kHash = key.toHash(); 562 } else { 563 size_t kHash = hashOf(key); 564 } 565 566 static if (is(V == class)) { 567 size_t vHash = 0; 568 if (value !is null) 569 vHash = value.toHash(); 570 } else { 571 size_t vHash = hashOf(value); 572 } 573 574 return kHash ^ vHash; 575 } 576 } 577 578 /** 579 */ 580 class EmptyMap(K, V) : AbstractMap!(K, V) { 581 582 override int size() { 583 return 0; 584 } 585 586 override bool isEmpty() { 587 return true; 588 } 589 590 override bool containsKey(K key) { 591 return false; 592 } 593 594 // override 595 // bool containsValue(V value) {return false;} 596 597 override V get(K key) { 598 return V.init; 599 } 600 601 override K[] keySet() { 602 return null; 603 } 604 605 override V[] values() { 606 return null; 607 } 608 // Collection!(V) values() {return emptySet();} 609 // Set!(MapEntry!(K,V)) entrySet() {return emptySet();} 610 611 override bool opEquals(Object o) { 612 return (typeid(o) == typeid(Map!(K, V))) && (cast(Map!(K, V)) o).isEmpty(); 613 } 614 615 override size_t toHash() { 616 return 0; 617 } 618 619 // Override default methods in Map 620 override V getOrDefault(K k, V defaultValue) { 621 return defaultValue; 622 } 623 624 override int opApply(scope int delegate(ref K, ref V) dg) { 625 return 0; 626 } 627 628 // override 629 // void replaceAll(BiFunction!(K, V, V) function) { 630 // Objects.requireNonNull(function); 631 // } 632 633 // override 634 // V putIfAbsent(K key, V value) { 635 // throw new UnsupportedOperationException(); 636 // } 637 638 // override 639 // bool remove(Object key, Object value) { 640 // throw new UnsupportedOperationException(); 641 // } 642 643 // override 644 // bool replace(K key, V oldValue, V newValue) { 645 // throw new UnsupportedOperationException(); 646 // } 647 648 // override 649 // V replace(K key, V value) { 650 // throw new UnsupportedOperationException(); 651 // } 652 653 // override 654 // V computeIfAbsent(K key, 655 // Function!(K, V) mappingFunction) { 656 // throw new UnsupportedOperationException(); 657 // } 658 659 // override 660 // V computeIfPresent(K key, 661 // BiFunction!(K, V, V) remappingFunction) { 662 // throw new UnsupportedOperationException(); 663 // } 664 665 // override 666 // V compute(K key, 667 // BiFunction!(K, V, V) remappingFunction) { 668 // throw new UnsupportedOperationException(); 669 // } 670 671 // override 672 // V merge(K key, V value, 673 // BiFunction!(V, V, V) remappingFunction) { 674 // throw new UnsupportedOperationException(); 675 // } 676 677 // // Preserves singleton property 678 // private Object readResolve() { 679 // return EMPTY_MAP; 680 // } 681 } 682 683 684 685 // Implementation Note: SimpleEntry and SimpleImmutableEntry 686 // are distinct unrelated classes, even though they share 687 // some code. Since you can't add or subtract final-ness 688 // of a field in a subclass, they can't share representations, 689 // and the amount of duplicated code is too small to warrant 690 // exposing a common abstract class. 691 692 /** 693 * An Entry maintaining a key and a value. The value may be 694 * changed using the <tt>setValue</tt> method. This class 695 * facilitates the process of building custom map 696 * implementations. For example, it may be convenient to return 697 * arrays of <tt>SimpleEntry</tt> instances in method 698 * <tt>Map.entrySet().toArray</tt>. 699 * 700 */ 701 class SimpleEntry(K,V) : MapEntry!(K,V) 702 { 703 704 private K key; 705 private V value; 706 707 /** 708 * Creates an entry representing a mapping from the specified 709 * key to the specified value. 710 * 711 * @param key the key represented by this entry 712 * @param value the value represented by this entry 713 */ 714 this(K key, V value) { 715 this.key = key; 716 this.value = value; 717 } 718 719 /** 720 * Creates an entry representing the same mapping as the 721 * specified entry. 722 * 723 * @param entry the entry to copy 724 */ 725 this(MapEntry!(K, V) entry) { 726 this.key = entry.getKey(); 727 this.value = entry.getValue(); 728 } 729 730 /** 731 * Returns the key corresponding to this entry. 732 * 733 * @return the key corresponding to this entry 734 */ 735 K getKey() { 736 return key; 737 } 738 739 /** 740 * Returns the value corresponding to this entry. 741 * 742 * @return the value corresponding to this entry 743 */ 744 V getValue() { 745 return value; 746 } 747 748 /** 749 * Replaces the value corresponding to this entry with the specified 750 * value. 751 * 752 * @param value new value to be stored in this entry 753 * @return the old value corresponding to the entry 754 */ 755 V setValue(V value) { 756 V oldValue = this.value; 757 this.value = value; 758 return oldValue; 759 } 760 761 /** 762 * Compares the specified object with this entry for equality. 763 * Returns {@code true} if the given object is also a map entry and 764 * the two entries represent the same mapping. More formally, two 765 * entries {@code e1} and {@code e2} represent the same mapping 766 * if<pre> 767 * (e1.getKey()==null ? 768 * e2.getKey()==null : 769 * e1.getKey().equals(e2.getKey())) 770 * && 771 * (e1.getValue()==null ? 772 * e2.getValue()==null : 773 * e1.getValue().equals(e2.getValue()))</pre> 774 * This ensures that the {@code equals} method works properly across 775 * different implementations of the {@code MapEntry} interface. 776 * 777 * @param o object to be compared for equality with this map entry 778 * @return {@code true} if the specified object is equal to this map 779 * entry 780 * @see #toHash 781 */ 782 override bool opEquals(Object o) { 783 if (o is this) 784 return true; 785 786 MapEntry!(K, V) e = cast(MapEntry!(K, V))o; 787 if (e !is null) { 788 if (key == e.getKey() && value == e.getValue()) 789 return true; 790 } 791 return false; 792 } 793 794 bool opEquals(IObject o) { 795 return opEquals(cast(Object) o); 796 } 797 798 int opCmp(MapEntry!(K, V) o) { 799 throw new NotImplementedException("opCmp"); 800 } 801 802 alias opCmp = Object.opCmp; 803 804 /** 805 * Returns the hash code value for this map entry. The hash code 806 * of a map entry {@code e} is defined to be: <pre> 807 * (e.getKey()==null ? 0 : e.getKey().toHash()) ^ 808 * (e.getValue()==null ? 0 : e.getValue().toHash())</pre> 809 * This ensures that {@code e1.equals(e2)} implies that 810 * {@code e1.toHash()==e2.toHash()} for any two Entries 811 * {@code e1} and {@code e2}, as required by the general 812 * contract of {@link Object#toHash}. 813 * 814 * @return the hash code value for this map entry 815 * @see #equals 816 */ 817 override size_t toHash() @trusted nothrow { 818 return hashOf(key) ^ hashOf(value); 819 } 820 821 /** 822 * Returns a string representation of this map entry. This 823 * implementation returns the string representation of this 824 * entry's key followed by the equals character ("<tt>=</tt>") 825 * followed by the string representation of this entry's value. 826 * 827 * @return a string representation of this map entry 828 */ 829 override string toString() { 830 return key.to!string() ~ "=" ~ value.to!string(); 831 } 832 833 }