1 // Protocol Buffers - Google's data interchange format
2 // Copyright 2008 Google Inc. All rights reserved.
3 // http://code.google.com/p/protobuf/
5 // Redistribution and use in source and binary forms, with or without
6 // modification, are permitted provided that the following conditions are
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
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.
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.
31 package com.google.protobuf;
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.TreeMap;
39 import java.util.List;
41 import java.util.NoSuchElementException;
43 import java.util.SortedMap;
46 * A custom map implementation from FieldDescriptor to Object optimized to
47 * minimize the number of memory allocations for instances with a small number
48 * of mappings. The implementation stores the first {@code k} mappings in an
49 * array for a configurable value of {@code k}, allowing direct access to the
50 * corresponding {@code Entry}s without the need to create an Iterator. The
51 * remaining entries are stored in an overflow map. Iteration over the entries
52 * in the map should be done as follows:
55 * for (int i = 0; i < fieldMap.getNumArrayEntries(); i++) {
56 * process(fieldMap.getArrayEntryAt(i));
58 * for (Map.Entry<K, V> entry : fieldMap.getOverflowEntries()) {
63 * The resulting iteration is in order of ascending field tag number. The
64 * object returned by {@link #entrySet()} adheres to the same contract but is
65 * less efficient as it necessarily involves creating an object for iteration.
67 * The tradeoff for this memory efficiency is that the worst case running time
68 * of the {@code put()} operation is {@code O(k + lg n)}, which happens when
69 * entries are added in descending order. {@code k} should be chosen such that
70 * it covers enough common cases without adversely affecting larger maps. In
71 * practice, the worst case scenario does not happen for extensions because
72 * extension fields are serialized and deserialized in order of ascending tag
73 * number, but the worst case scenario can happen for DynamicMessages.
75 * The running time for all other operations is similar to that of
78 * Instances are not thread-safe until {@link #makeImmutable()} is called,
79 * after which any modifying operation will result in an
80 * {@link UnsupportedOperationException}.
82 * @author darick@google.com Darick Tong
84 // This class is final for all intents and purposes because the constructor is
85 // private. However, the FieldDescriptor-specific logic is encapsulated in
86 // a subclass to aid testability of the core logic.
87 class SmallSortedMap<K extends Comparable<K>, V> extends AbstractMap<K, V> {
90 * Creates a new instance for mapping FieldDescriptors to their values.
91 * The {@link #makeImmutable()} implementation will convert the List values
92 * of any repeated fields to unmodifiable lists.
94 * @param arraySize The size of the entry array containing the
95 * lexicographically smallest mappings.
97 static <FieldDescriptorType extends
98 FieldSet.FieldDescriptorLite<FieldDescriptorType>>
99 SmallSortedMap<FieldDescriptorType, Object> newFieldMap(int arraySize) {
100 return new SmallSortedMap<FieldDescriptorType, Object>(arraySize) {
102 @SuppressWarnings("unchecked")
103 public void makeImmutable() {
104 if (!isImmutable()) {
105 for (int i = 0; i < getNumArrayEntries(); i++) {
106 final Map.Entry<FieldDescriptorType, Object> entry =
108 if (entry.getKey().isRepeated()) {
109 final List value = (List) entry.getValue();
110 entry.setValue(Collections.unmodifiableList(value));
113 for (Map.Entry<FieldDescriptorType, Object> entry :
114 getOverflowEntries()) {
115 if (entry.getKey().isRepeated()) {
116 final List value = (List) entry.getValue();
117 entry.setValue(Collections.unmodifiableList(value));
121 super.makeImmutable();
127 * Creates a new instance for testing.
129 * @param arraySize The size of the entry array containing the
130 * lexicographically smallest mappings.
132 static <K extends Comparable<K>, V> SmallSortedMap<K, V> newInstanceForTest(
134 return new SmallSortedMap<K, V>(arraySize);
137 private final int maxArraySize;
138 // The "entry array" is actually a List because generic arrays are not
139 // allowed. ArrayList also nicely handles the entry shifting on inserts and
141 private List<Entry> entryList;
142 private Map<K, V> overflowEntries;
143 private boolean isImmutable;
144 // The EntrySet is a stateless view of the Map. It's initialized the first
145 // time it is requested and reused henceforth.
146 private volatile EntrySet lazyEntrySet;
149 * @code arraySize Size of the array in which the lexicographically smallest
150 * mappings are stored. (i.e. the {@code k} referred to in the class
153 private SmallSortedMap(int arraySize) {
154 this.maxArraySize = arraySize;
155 this.entryList = Collections.emptyList();
156 this.overflowEntries = Collections.emptyMap();
159 /** Make this map immutable from this point forward. */
160 public void makeImmutable() {
162 // Note: There's no need to wrap the entryList in an unmodifiableList
163 // because none of the list's accessors are exposed. The iterator() of
164 // overflowEntries, on the other hand, is exposed so it must be made
166 overflowEntries = overflowEntries.isEmpty() ?
167 Collections.<K, V>emptyMap() :
168 Collections.unmodifiableMap(overflowEntries);
173 /** @return Whether {@link #makeImmutable()} has been called. */
174 public boolean isImmutable() {
178 /** @return The number of entries in the entry array. */
179 public int getNumArrayEntries() {
180 return entryList.size();
183 /** @return The array entry at the given {@code index}. */
184 public Map.Entry<K, V> getArrayEntryAt(int index) {
185 return entryList.get(index);
188 /** @return There number of overflow entries. */
189 public int getNumOverflowEntries() {
190 return overflowEntries.size();
193 /** @return An iterable over the overflow entries. */
194 public Iterable<Map.Entry<K, V>> getOverflowEntries() {
195 return overflowEntries.isEmpty() ?
196 EmptySet.<Map.Entry<K, V>>iterable() :
197 overflowEntries.entrySet();
202 return entryList.size() + overflowEntries.size();
206 * The implementation throws a {@code ClassCastException} if o is not an
207 * object of type {@code K}.
212 public boolean containsKey(Object o) {
213 @SuppressWarnings("unchecked")
215 return binarySearchInArray(key) >= 0 || overflowEntries.containsKey(key);
219 * The implementation throws a {@code ClassCastException} if o is not an
220 * object of type {@code K}.
225 public V get(Object o) {
226 @SuppressWarnings("unchecked")
228 final int index = binarySearchInArray(key);
230 return entryList.get(index).getValue();
232 return overflowEntries.get(key);
236 public V put(K key, V value) {
238 final int index = binarySearchInArray(key);
240 // Replace existing array entry.
241 return entryList.get(index).setValue(value);
243 ensureEntryArrayMutable();
244 final int insertionPoint = -(index + 1);
245 if (insertionPoint >= maxArraySize) {
246 // Put directly in overflow.
247 return getOverflowEntriesMutable().put(key, value);
249 // Insert new Entry in array.
250 if (entryList.size() == maxArraySize) {
251 // Shift the last array entry into overflow.
252 final Entry lastEntryInArray = entryList.remove(maxArraySize - 1);
253 getOverflowEntriesMutable().put(lastEntryInArray.getKey(),
254 lastEntryInArray.getValue());
256 entryList.add(insertionPoint, new Entry(key, value));
261 public void clear() {
263 if (!entryList.isEmpty()) {
266 if (!overflowEntries.isEmpty()) {
267 overflowEntries.clear();
272 * The implementation throws a {@code ClassCastException} if o is not an
273 * object of type {@code K}.
278 public V remove(Object o) {
280 @SuppressWarnings("unchecked")
282 final int index = binarySearchInArray(key);
284 return removeArrayEntryAt(index);
286 // overflowEntries might be Collections.unmodifiableMap(), so only
287 // call remove() if it is non-empty.
288 if (overflowEntries.isEmpty()) {
291 return overflowEntries.remove(key);
295 private V removeArrayEntryAt(int index) {
297 final V removed = entryList.remove(index).getValue();
298 if (!overflowEntries.isEmpty()) {
299 // Shift the first entry in the overflow to be the last entry in the
301 final Iterator<Map.Entry<K, V>> iterator =
302 getOverflowEntriesMutable().entrySet().iterator();
303 entryList.add(new Entry(iterator.next()));
310 * @param key The key to find in the entry array.
311 * @return The returned integer position follows the same semantics as the
312 * value returned by {@link java.util.Arrays#binarySearch()}.
314 private int binarySearchInArray(K key) {
316 int right = entryList.size() - 1;
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.
322 int cmp = key.compareTo(entryList.get(right).getKey());
324 return -(right + 2); // Insert point is after "right".
325 } else if (cmp == 0) {
330 while (left <= right) {
331 int mid = (left + right) / 2;
332 int cmp = key.compareTo(entryList.get(mid).getKey());
335 } else if (cmp > 0) {
345 * Similar to the AbstractMap implementation of {@code keySet()} and
346 * {@code values()}, the entry set is created the first time this method is
347 * called, and returned in response to all subsequent calls.
352 public Set<Map.Entry<K, V>> entrySet() {
353 if (lazyEntrySet == null) {
354 lazyEntrySet = new EntrySet();
360 * @throws UnsupportedOperationException if {@link #makeImmutable()} has
363 private void checkMutable() {
365 throw new UnsupportedOperationException();
370 * @return a {@link SortedMap} to which overflow entries mappings can be
372 * @throws UnsupportedOperationException if {@link #makeImmutable()} has been
375 @SuppressWarnings("unchecked")
376 private SortedMap<K, V> getOverflowEntriesMutable() {
378 if (overflowEntries.isEmpty() && !(overflowEntries instanceof TreeMap)) {
379 overflowEntries = new TreeMap<K, V>();
381 return (SortedMap<K, V>) overflowEntries;
385 * Lazily creates the entry list. Any code that adds to the list must first
388 private void ensureEntryArrayMutable() {
390 if (entryList.isEmpty() && !(entryList instanceof ArrayList)) {
391 entryList = new ArrayList<Entry>(maxArraySize);
396 * Entry implementation that implements Comparable in order to support
397 * binary search within the entry array. Also checks mutability in
398 * {@link #setValue()}.
400 private class Entry implements Map.Entry<K, V>, Comparable<Entry> {
405 Entry(Map.Entry<K, V> copy) {
406 this(copy.getKey(), copy.getValue());
409 Entry(K key, V value) {
414 //@Override (Java 1.6 override semantics, but we must support 1.5)
419 //@Override (Java 1.6 override semantics, but we must support 1.5)
420 public V getValue() {
424 //@Override (Java 1.6 override semantics, but we must support 1.5)
425 public int compareTo(Entry other) {
426 return getKey().compareTo(other.getKey());
429 //@Override (Java 1.6 override semantics, but we must support 1.5)
430 public V setValue(V newValue) {
432 final V oldValue = this.value;
433 this.value = newValue;
438 public boolean equals(Object o) {
442 if (!(o instanceof Map.Entry)) {
445 @SuppressWarnings("unchecked")
446 Map.Entry<?, ?> other = (Map.Entry<?, ?>) o;
447 return equals(key, other.getKey()) && equals(value, other.getValue());
451 public int hashCode() {
452 return (key == null ? 0 : key.hashCode()) ^
453 (value == null ? 0 : value.hashCode());
457 public String toString() {
458 return key + "=" + value;
461 /** equals() that handles null values. */
462 private boolean equals(Object o1, Object o2) {
463 return o1 == null ? o2 == null : o1.equals(o2);
468 * Stateless view of the entries in the field map.
470 private class EntrySet extends AbstractSet<Map.Entry<K, V>> {
473 public Iterator<Map.Entry<K, V>> iterator() {
474 return new EntryIterator();
479 return SmallSortedMap.this.size();
483 * Throws a {@link ClassCastException} if o is not of the expected type.
488 public boolean contains(Object o) {
489 @SuppressWarnings("unchecked")
490 final Map.Entry<K, V> entry = (Map.Entry<K, V>) o;
491 final V existing = get(entry.getKey());
492 final V value = entry.getValue();
493 return existing == value ||
494 (existing != null && existing.equals(value));
498 public boolean add(Map.Entry<K, V> entry) {
499 if (!contains(entry)) {
500 put(entry.getKey(), entry.getValue());
507 * Throws a {@link ClassCastException} if o is not of the expected type.
512 public boolean remove(Object o) {
513 @SuppressWarnings("unchecked")
514 final Map.Entry<K, V> entry = (Map.Entry<K, V>) o;
515 if (contains(entry)) {
516 SmallSortedMap.this.remove(entry.getKey());
523 public void clear() {
524 SmallSortedMap.this.clear();
529 * Iterator implementation that switches from the entry array to the overflow
530 * entries appropriately.
532 private class EntryIterator implements Iterator<Map.Entry<K, V>> {
534 private int pos = -1;
535 private boolean nextCalledBeforeRemove;
536 private Iterator<Map.Entry<K, V>> lazyOverflowIterator;
538 //@Override (Java 1.6 override semantics, but we must support 1.5)
539 public boolean hasNext() {
540 return (pos + 1) < entryList.size() ||
541 getOverflowIterator().hasNext();
544 //@Override (Java 1.6 override semantics, but we must support 1.5)
545 public Map.Entry<K, V> next() {
546 nextCalledBeforeRemove = true;
547 // Always increment pos so that we know whether the last returned value
548 // was from the array or from overflow.
549 if (++pos < entryList.size()) {
550 return entryList.get(pos);
552 return getOverflowIterator().next();
555 //@Override (Java 1.6 override semantics, but we must support 1.5)
556 public void remove() {
557 if (!nextCalledBeforeRemove) {
558 throw new IllegalStateException("remove() was called before next()");
560 nextCalledBeforeRemove = false;
563 if (pos < entryList.size()) {
564 removeArrayEntryAt(pos--);
566 getOverflowIterator().remove();
571 * It is important to create the overflow iterator only after the array
572 * entries have been iterated over because the overflow entry set changes
573 * when the client calls remove() on the array entries, which invalidates
574 * any existing iterators.
576 private Iterator<Map.Entry<K, V>> getOverflowIterator() {
577 if (lazyOverflowIterator == null) {
578 lazyOverflowIterator = overflowEntries.entrySet().iterator();
580 return lazyOverflowIterator;
585 * Helper class that holds immutable instances of an Iterable/Iterator that
586 * we return when the overflow entries is empty. This eliminates the creation
587 * of an Iterator object when there is nothing to iterate over.
589 private static class EmptySet {
591 private static final Iterator<Object> ITERATOR = new Iterator<Object>() {
592 //@Override (Java 1.6 override semantics, but we must support 1.5)
593 public boolean hasNext() {
596 //@Override (Java 1.6 override semantics, but we must support 1.5)
597 public Object next() {
598 throw new NoSuchElementException();
600 //@Override (Java 1.6 override semantics, but we must support 1.5)
601 public void remove() {
602 throw new UnsupportedOperationException();
606 private static final Iterable<Object> ITERABLE = new Iterable<Object>() {
607 //@Override (Java 1.6 override semantics, but we must support 1.5)
608 public Iterator<Object> iterator() {
613 @SuppressWarnings("unchecked")
614 static <T> Iterable<T> iterable() {
615 return (Iterable<T>) ITERABLE;