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