• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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