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