1 // Protocol Buffers - Google's data interchange format 2 // Copyright 2008 Google Inc. All rights reserved. 3 // https://developers.google.com/protocol-buffers/ 4 // 5 // Redistribution and use in source and binary forms, with or without 6 // modification, are permitted provided that the following conditions are 7 // met: 8 // 9 // * Redistributions of source code must retain the above copyright 10 // notice, this list of conditions and the following disclaimer. 11 // * Redistributions in binary form must reproduce the above 12 // copyright notice, this list of conditions and the following disclaimer 13 // in the documentation and/or other materials provided with the 14 // distribution. 15 // * Neither the name of Google Inc. nor the names of its 16 // contributors may be used to endorse or promote products derived from 17 // this software without specific prior written permission. 18 // 19 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 31 package com.google.protobuf; 32 33 import java.util.AbstractMap; 34 import java.util.AbstractSet; 35 import java.util.ArrayList; 36 import java.util.Collections; 37 import java.util.Iterator; 38 import java.util.List; 39 import java.util.Map; 40 import java.util.NoSuchElementException; 41 import java.util.Set; 42 import java.util.SortedMap; 43 import java.util.TreeMap; 44 45 /** 46 * A custom map implementation from FieldDescriptor to Object optimized to minimize the number of 47 * memory allocations for instances with a small number of mappings. The implementation stores the 48 * first {@code k} mappings in an array for a configurable value of {@code k}, allowing direct 49 * access to the corresponding {@code Entry}s without the need to create an Iterator. The remaining 50 * entries are stored in an overflow map. Iteration over the entries in the map should be done as 51 * follows: 52 * 53 * <pre>{@code 54 * for (int i = 0; i < fieldMap.getNumArrayEntries(); i++) { 55 * process(fieldMap.getArrayEntryAt(i)); 56 * } 57 * for (Map.Entry<K, V> entry : fieldMap.getOverflowEntries()) { 58 * process(entry); 59 * } 60 * }</pre> 61 * 62 * The resulting iteration is in order of ascending field tag number. The object returned by {@link 63 * #entrySet()} adheres to the same contract but is less efficient as it necessarily involves 64 * creating an object for iteration. 65 * 66 * <p>The tradeoff for this memory efficiency is that the worst case running time of the {@code 67 * put()} operation is {@code O(k + lg n)}, which happens when entries are added in descending 68 * order. {@code k} should be chosen such that it covers enough common cases without adversely 69 * affecting larger maps. In practice, the worst case scenario does not happen for extensions 70 * because extension fields are serialized and deserialized in order of ascending tag number, but 71 * the worst case scenario can happen for DynamicMessages. 72 * 73 * <p>The running time for all other operations is similar to that of {@code TreeMap}. 74 * 75 * <p>Instances are not thread-safe until {@link #makeImmutable()} is called, after which any 76 * modifying operation will result in an {@link UnsupportedOperationException}. 77 * 78 * @author darick@google.com Darick Tong 79 */ 80 // This class is final for all intents and purposes because the constructor is 81 // private. However, the FieldDescriptor-specific logic is encapsulated in 82 // a subclass to aid testability of the core logic. 83 class SmallSortedMap<K extends Comparable<K>, V> extends AbstractMap<K, V> { 84 85 /** 86 * Creates a new instance for mapping FieldDescriptors to their values. The {@link 87 * #makeImmutable()} implementation will convert the List values of any repeated fields to 88 * unmodifiable lists. 89 * 90 * @param arraySize The size of the entry array containing the lexicographically smallest 91 * mappings. 92 */ 93 static <FieldDescriptorType extends FieldSet.FieldDescriptorLite<FieldDescriptorType>> newFieldMap(int arraySize)94 SmallSortedMap<FieldDescriptorType, Object> newFieldMap(int arraySize) { 95 return new SmallSortedMap<FieldDescriptorType, Object>(arraySize) { 96 @Override 97 @SuppressWarnings("unchecked") 98 public void makeImmutable() { 99 if (!isImmutable()) { 100 for (int i = 0; i < getNumArrayEntries(); i++) { 101 final Map.Entry<FieldDescriptorType, Object> entry = getArrayEntryAt(i); 102 if (entry.getKey().isRepeated()) { 103 final List value = (List) entry.getValue(); 104 entry.setValue(Collections.unmodifiableList(value)); 105 } 106 } 107 for (Map.Entry<FieldDescriptorType, Object> entry : getOverflowEntries()) { 108 if (entry.getKey().isRepeated()) { 109 final List value = (List) entry.getValue(); 110 entry.setValue(Collections.unmodifiableList(value)); 111 } 112 } 113 } 114 super.makeImmutable(); 115 } 116 }; 117 } 118 119 /** 120 * Creates a new instance for testing. 121 * 122 * @param arraySize The size of the entry array containing the lexicographically smallest 123 * mappings. 124 */ 125 static <K extends Comparable<K>, V> SmallSortedMap<K, V> newInstanceForTest(int arraySize) { 126 return new SmallSortedMap<K, V>(arraySize); 127 } 128 129 private final int maxArraySize; 130 // The "entry array" is actually a List because generic arrays are not 131 // allowed. ArrayList also nicely handles the entry shifting on inserts and 132 // removes. 133 private List<Entry> entryList; 134 private Map<K, V> overflowEntries; 135 private boolean isImmutable; 136 // The EntrySet is a stateless view of the Map. It's initialized the first 137 // time it is requested and reused henceforth. 138 private volatile EntrySet lazyEntrySet; 139 private Map<K, V> overflowEntriesDescending; 140 private volatile DescendingEntrySet lazyDescendingEntrySet; 141 142 /** 143 * @code arraySize Size of the array in which the lexicographically smallest mappings are stored. 144 * (i.e. the {@code k} referred to in the class documentation). 145 */ 146 private SmallSortedMap(int arraySize) { 147 this.maxArraySize = arraySize; 148 this.entryList = Collections.emptyList(); 149 this.overflowEntries = Collections.emptyMap(); 150 this.overflowEntriesDescending = Collections.emptyMap(); 151 } 152 153 /** Make this map immutable from this point forward. */ 154 public void makeImmutable() { 155 if (!isImmutable) { 156 // Note: There's no need to wrap the entryList in an unmodifiableList 157 // because none of the list's accessors are exposed. The iterator() of 158 // overflowEntries, on the other hand, is exposed so it must be made 159 // unmodifiable. 160 overflowEntries = 161 overflowEntries.isEmpty() 162 ? Collections.<K, V>emptyMap() 163 : Collections.unmodifiableMap(overflowEntries); 164 overflowEntriesDescending = 165 overflowEntriesDescending.isEmpty() 166 ? Collections.<K, V>emptyMap() 167 : Collections.unmodifiableMap(overflowEntriesDescending); 168 isImmutable = true; 169 } 170 } 171 172 /** @return Whether {@link #makeImmutable()} has been called. */ 173 public boolean isImmutable() { 174 return isImmutable; 175 } 176 177 /** @return The number of entries in the entry array. */ 178 public int getNumArrayEntries() { 179 return entryList.size(); 180 } 181 182 /** @return The array entry at the given {@code index}. */ 183 public Map.Entry<K, V> getArrayEntryAt(int index) { 184 return entryList.get(index); 185 } 186 187 /** @return There number of overflow entries. */ 188 public int getNumOverflowEntries() { 189 return overflowEntries.size(); 190 } 191 192 /** @return An iterable over the overflow entries. */ 193 public Iterable<Map.Entry<K, V>> getOverflowEntries() { 194 return overflowEntries.isEmpty() 195 ? EmptySet.<Map.Entry<K, V>>iterable() 196 : overflowEntries.entrySet(); 197 } 198 199 Iterable<Map.Entry<K, V>> getOverflowEntriesDescending() { 200 return overflowEntriesDescending.isEmpty() 201 ? EmptySet.<Map.Entry<K, V>>iterable() 202 : overflowEntriesDescending.entrySet(); 203 } 204 205 @Override 206 public int size() { 207 return entryList.size() + overflowEntries.size(); 208 } 209 210 /** 211 * The implementation throws a {@code ClassCastException} if o is not an object of type {@code K}. 212 * 213 * <p>{@inheritDoc} 214 */ 215 @Override 216 public boolean containsKey(Object o) { 217 @SuppressWarnings("unchecked") 218 final K key = (K) o; 219 return binarySearchInArray(key) >= 0 || overflowEntries.containsKey(key); 220 } 221 222 /** 223 * The implementation throws a {@code ClassCastException} if o is not an object of type {@code K}. 224 * 225 * <p>{@inheritDoc} 226 */ 227 @Override get(Object o)228 public V get(Object o) { 229 @SuppressWarnings("unchecked") 230 final K key = (K) o; 231 final int index = binarySearchInArray(key); 232 if (index >= 0) { 233 return entryList.get(index).getValue(); 234 } 235 return overflowEntries.get(key); 236 } 237 238 @Override put(K key, V value)239 public V put(K key, V value) { 240 checkMutable(); 241 final int index = binarySearchInArray(key); 242 if (index >= 0) { 243 // Replace existing array entry. 244 return entryList.get(index).setValue(value); 245 } 246 ensureEntryArrayMutable(); 247 final int insertionPoint = -(index + 1); 248 if (insertionPoint >= maxArraySize) { 249 // Put directly in overflow. 250 return getOverflowEntriesMutable().put(key, value); 251 } 252 // Insert new Entry in array. 253 if (entryList.size() == maxArraySize) { 254 // Shift the last array entry into overflow. 255 final Entry lastEntryInArray = entryList.remove(maxArraySize - 1); 256 getOverflowEntriesMutable().put(lastEntryInArray.getKey(), lastEntryInArray.getValue()); 257 } 258 entryList.add(insertionPoint, new Entry(key, value)); 259 return null; 260 } 261 262 @Override clear()263 public void clear() { 264 checkMutable(); 265 if (!entryList.isEmpty()) { 266 entryList.clear(); 267 } 268 if (!overflowEntries.isEmpty()) { 269 overflowEntries.clear(); 270 } 271 } 272 273 /** 274 * The implementation throws a {@code ClassCastException} if o is not an object of type {@code K}. 275 * 276 * <p>{@inheritDoc} 277 */ 278 @Override remove(Object o)279 public V remove(Object o) { 280 checkMutable(); 281 @SuppressWarnings("unchecked") 282 final K key = (K) o; 283 final int index = binarySearchInArray(key); 284 if (index >= 0) { 285 return removeArrayEntryAt(index); 286 } 287 // overflowEntries might be Collections.unmodifiableMap(), so only 288 // call remove() if it is non-empty. 289 if (overflowEntries.isEmpty()) { 290 return null; 291 } else { 292 return overflowEntries.remove(key); 293 } 294 } 295 removeArrayEntryAt(int index)296 private V removeArrayEntryAt(int index) { 297 checkMutable(); 298 final V removed = entryList.remove(index).getValue(); 299 if (!overflowEntries.isEmpty()) { 300 // Shift the first entry in the overflow to be the last entry in the 301 // array. 302 final Iterator<Map.Entry<K, V>> iterator = getOverflowEntriesMutable().entrySet().iterator(); 303 entryList.add(new Entry(iterator.next())); 304 iterator.remove(); 305 } 306 return removed; 307 } 308 309 /** 310 * @param key The key to find in the entry array. 311 * @return The returned integer position follows the same semantics as the value returned by 312 * {@link java.util.Arrays#binarySearch()}. 313 */ binarySearchInArray(K key)314 private int binarySearchInArray(K key) { 315 int left = 0; 316 int right = entryList.size() - 1; 317 318 // Optimization: For the common case in which entries are added in 319 // ascending tag order, check the largest element in the array before 320 // doing a full binary search. 321 if (right >= 0) { 322 int cmp = key.compareTo(entryList.get(right).getKey()); 323 if (cmp > 0) { 324 return -(right + 2); // Insert point is after "right". 325 } else if (cmp == 0) { 326 return right; 327 } 328 } 329 330 while (left <= right) { 331 int mid = (left + right) / 2; 332 int cmp = key.compareTo(entryList.get(mid).getKey()); 333 if (cmp < 0) { 334 right = mid - 1; 335 } else if (cmp > 0) { 336 left = mid + 1; 337 } else { 338 return mid; 339 } 340 } 341 return -(left + 1); 342 } 343 344 /** 345 * Similar to the AbstractMap implementation of {@code keySet()} and {@code values()}, the entry 346 * set is created the first time this method is called, and returned in response to all subsequent 347 * calls. 348 * 349 * <p>{@inheritDoc} 350 */ 351 @Override entrySet()352 public Set<Map.Entry<K, V>> entrySet() { 353 if (lazyEntrySet == null) { 354 lazyEntrySet = new EntrySet(); 355 } 356 return lazyEntrySet; 357 } 358 descendingEntrySet()359 Set<Map.Entry<K, V>> descendingEntrySet() { 360 if (lazyDescendingEntrySet == null) { 361 lazyDescendingEntrySet = new DescendingEntrySet(); 362 } 363 return lazyDescendingEntrySet; 364 } 365 366 /** @throws UnsupportedOperationException if {@link #makeImmutable()} has has been called. */ checkMutable()367 private void checkMutable() { 368 if (isImmutable) { 369 throw new UnsupportedOperationException(); 370 } 371 } 372 373 /** 374 * @return a {@link SortedMap} to which overflow entries mappings can be added or removed. 375 * @throws UnsupportedOperationException if {@link #makeImmutable()} has been called. 376 */ 377 @SuppressWarnings("unchecked") getOverflowEntriesMutable()378 private SortedMap<K, V> getOverflowEntriesMutable() { 379 checkMutable(); 380 if (overflowEntries.isEmpty() && !(overflowEntries instanceof TreeMap)) { 381 overflowEntries = new TreeMap<K, V>(); 382 overflowEntriesDescending = ((TreeMap<K, V>) overflowEntries).descendingMap(); 383 } 384 return (SortedMap<K, V>) overflowEntries; 385 } 386 387 /** Lazily creates the entry list. Any code that adds to the list must first call this method. */ ensureEntryArrayMutable()388 private void ensureEntryArrayMutable() { 389 checkMutable(); 390 if (entryList.isEmpty() && !(entryList instanceof ArrayList)) { 391 entryList = new ArrayList<Entry>(maxArraySize); 392 } 393 } 394 395 /** 396 * Entry implementation that implements Comparable in order to support binary search within the 397 * entry array. Also checks mutability in {@link #setValue()}. 398 */ 399 private class Entry implements Map.Entry<K, V>, Comparable<Entry> { 400 401 private final K key; 402 private V value; 403 Entry(Map.Entry<K, V> copy)404 Entry(Map.Entry<K, V> copy) { 405 this(copy.getKey(), copy.getValue()); 406 } 407 Entry(K key, V value)408 Entry(K key, V value) { 409 this.key = key; 410 this.value = value; 411 } 412 413 @Override getKey()414 public K getKey() { 415 return key; 416 } 417 418 @Override getValue()419 public V getValue() { 420 return value; 421 } 422 423 @Override compareTo(Entry other)424 public int compareTo(Entry other) { 425 return getKey().compareTo(other.getKey()); 426 } 427 428 @Override setValue(V newValue)429 public V setValue(V newValue) { 430 checkMutable(); 431 final V oldValue = this.value; 432 this.value = newValue; 433 return oldValue; 434 } 435 436 @Override equals(Object o)437 public boolean equals(Object o) { 438 if (o == this) { 439 return true; 440 } 441 if (!(o instanceof Map.Entry)) { 442 return false; 443 } 444 @SuppressWarnings("unchecked") 445 Map.Entry<?, ?> other = (Map.Entry<?, ?>) o; 446 return equals(key, other.getKey()) && equals(value, other.getValue()); 447 } 448 449 @Override hashCode()450 public int hashCode() { 451 return (key == null ? 0 : key.hashCode()) ^ (value == null ? 0 : value.hashCode()); 452 } 453 454 @Override toString()455 public String toString() { 456 return key + "=" + value; 457 } 458 459 /** equals() that handles null values. */ equals(Object o1, Object o2)460 private boolean equals(Object o1, Object o2) { 461 return o1 == null ? o2 == null : o1.equals(o2); 462 } 463 } 464 465 /** Stateless view of the entries in the field map. */ 466 private class EntrySet extends AbstractSet<Map.Entry<K, V>> { 467 468 @Override iterator()469 public Iterator<Map.Entry<K, V>> iterator() { 470 return new EntryIterator(); 471 } 472 473 @Override size()474 public int size() { 475 return SmallSortedMap.this.size(); 476 } 477 478 /** 479 * Throws a {@link ClassCastException} if o is not of the expected type. 480 * 481 * <p>{@inheritDoc} 482 */ 483 @Override contains(Object o)484 public boolean contains(Object o) { 485 @SuppressWarnings("unchecked") 486 final Map.Entry<K, V> entry = (Map.Entry<K, V>) o; 487 final V existing = get(entry.getKey()); 488 final V value = entry.getValue(); 489 return existing == value || (existing != null && existing.equals(value)); 490 } 491 492 @Override add(Map.Entry<K, V> entry)493 public boolean add(Map.Entry<K, V> entry) { 494 if (!contains(entry)) { 495 put(entry.getKey(), entry.getValue()); 496 return true; 497 } 498 return false; 499 } 500 501 /** 502 * Throws a {@link ClassCastException} if o is not of the expected type. 503 * 504 * <p>{@inheritDoc} 505 */ 506 @Override remove(Object o)507 public boolean remove(Object o) { 508 @SuppressWarnings("unchecked") 509 final Map.Entry<K, V> entry = (Map.Entry<K, V>) o; 510 if (contains(entry)) { 511 SmallSortedMap.this.remove(entry.getKey()); 512 return true; 513 } 514 return false; 515 } 516 517 @Override clear()518 public void clear() { 519 SmallSortedMap.this.clear(); 520 } 521 } 522 523 private class DescendingEntrySet extends EntrySet { 524 @Override iterator()525 public Iterator<java.util.Map.Entry<K, V>> iterator() { 526 return new DescendingEntryIterator(); 527 } 528 } 529 530 /** 531 * Iterator implementation that switches from the entry array to the overflow entries 532 * appropriately. 533 */ 534 private class EntryIterator implements Iterator<Map.Entry<K, V>> { 535 536 private int pos = -1; 537 private boolean nextCalledBeforeRemove; 538 private Iterator<Map.Entry<K, V>> lazyOverflowIterator; 539 540 @Override hasNext()541 public boolean hasNext() { 542 return (pos + 1) < entryList.size() 543 || (!overflowEntries.isEmpty() && getOverflowIterator().hasNext()); 544 } 545 546 @Override next()547 public Map.Entry<K, V> next() { 548 nextCalledBeforeRemove = true; 549 // Always increment pos so that we know whether the last returned value 550 // was from the array or from overflow. 551 if (++pos < entryList.size()) { 552 return entryList.get(pos); 553 } 554 return getOverflowIterator().next(); 555 } 556 557 @Override remove()558 public void remove() { 559 if (!nextCalledBeforeRemove) { 560 throw new IllegalStateException("remove() was called before next()"); 561 } 562 nextCalledBeforeRemove = false; 563 checkMutable(); 564 565 if (pos < entryList.size()) { 566 removeArrayEntryAt(pos--); 567 } else { 568 getOverflowIterator().remove(); 569 } 570 } 571 572 /** 573 * It is important to create the overflow iterator only after the array entries have been 574 * iterated over because the overflow entry set changes when the client calls remove() on the 575 * array entries, which invalidates any existing iterators. 576 */ getOverflowIterator()577 private Iterator<Map.Entry<K, V>> getOverflowIterator() { 578 if (lazyOverflowIterator == null) { 579 lazyOverflowIterator = overflowEntries.entrySet().iterator(); 580 } 581 return lazyOverflowIterator; 582 } 583 } 584 585 /** 586 * Reverse Iterator implementation that switches from the entry array to the overflow entries 587 * appropriately. 588 */ 589 private class DescendingEntryIterator implements Iterator<Map.Entry<K, V>> { 590 591 private int pos = entryList.size(); 592 private Iterator<Map.Entry<K, V>> lazyOverflowIterator; 593 594 @Override hasNext()595 public boolean hasNext() { 596 return (pos > 0 && pos <= entryList.size()) || getOverflowIterator().hasNext(); 597 } 598 599 @Override next()600 public Map.Entry<K, V> next() { 601 if (getOverflowIterator().hasNext()) { 602 return getOverflowIterator().next(); 603 } 604 return entryList.get(--pos); 605 } 606 607 @Override remove()608 public void remove() { 609 throw new UnsupportedOperationException(); 610 } 611 612 /** 613 * It is important to create the overflow iterator only after the array entries have been 614 * iterated over because the overflow entry set changes when the client calls remove() on the 615 * array entries, which invalidates any existing iterators. 616 */ getOverflowIterator()617 private Iterator<Map.Entry<K, V>> getOverflowIterator() { 618 if (lazyOverflowIterator == null) { 619 lazyOverflowIterator = overflowEntriesDescending.entrySet().iterator(); 620 } 621 return lazyOverflowIterator; 622 } 623 } 624 625 /** 626 * Helper class that holds immutable instances of an Iterable/Iterator that we return when the 627 * overflow entries is empty. This eliminates the creation of an Iterator object when there is 628 * nothing to iterate over. 629 */ 630 private static class EmptySet { 631 632 private static final Iterator<Object> ITERATOR = 633 new Iterator<Object>() { 634 @Override 635 public boolean hasNext() { 636 return false; 637 } 638 639 @Override 640 public Object next() { 641 throw new NoSuchElementException(); 642 } 643 644 @Override 645 public void remove() { 646 throw new UnsupportedOperationException(); 647 } 648 }; 649 650 private static final Iterable<Object> ITERABLE = 651 new Iterable<Object>() { 652 @Override 653 public Iterator<Object> iterator() { 654 return ITERATOR; 655 } 656 }; 657 658 @SuppressWarnings("unchecked") iterable()659 static <T> Iterable<T> iterable() { 660 return (Iterable<T>) ITERABLE; 661 } 662 } 663 664 @Override equals(Object o)665 public boolean equals(Object o) { 666 if (this == o) { 667 return true; 668 } 669 670 if (!(o instanceof SmallSortedMap)) { 671 return super.equals(o); 672 } 673 674 SmallSortedMap<?, ?> other = (SmallSortedMap<?, ?>) o; 675 final int size = size(); 676 if (size != other.size()) { 677 return false; 678 } 679 680 // Best effort try to avoid allocating an entry set. 681 final int numArrayEntries = getNumArrayEntries(); 682 if (numArrayEntries != other.getNumArrayEntries()) { 683 return entrySet().equals(other.entrySet()); 684 } 685 686 for (int i = 0; i < numArrayEntries; i++) { 687 if (!getArrayEntryAt(i).equals(other.getArrayEntryAt(i))) { 688 return false; 689 } 690 } 691 692 if (numArrayEntries != size) { 693 return overflowEntries.equals(other.overflowEntries); 694 } 695 696 return true; 697 } 698 699 @Override hashCode()700 public int hashCode() { 701 int h = 0; 702 final int listSize = getNumArrayEntries(); 703 for (int i = 0; i < listSize; i++) { 704 h += entryList.get(i).hashCode(); 705 } 706 // Avoid the iterator allocation if possible. 707 if (getNumOverflowEntries() > 0) { 708 h += overflowEntries.hashCode(); 709 } 710 return h; 711 } 712 } 713