Skip to content

Commit f6792c3

Browse files
Code Drop: Adding the Collection Package (#2658)
1 parent 6c56645 commit f6792c3

11 files changed

Lines changed: 1702 additions & 0 deletions
Lines changed: 301 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,301 @@
1+
/*
2+
* Copyright 2017 Google Inc. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.cloud.firestore.collection;
18+
19+
import com.google.api.core.InternalApi;
20+
import java.util.AbstractMap;
21+
import java.util.ArrayList;
22+
import java.util.Collections;
23+
import java.util.Comparator;
24+
import java.util.HashMap;
25+
import java.util.Iterator;
26+
import java.util.List;
27+
import java.util.Map;
28+
29+
/**
30+
* This is an array backed implementation of ImmutableSortedMap. It uses arrays and linear lookups
31+
* to achieve good memory efficiency while maintaining good performance for small collections. To
32+
* avoid degrading performance with increasing collection size it will automatically convert to a
33+
* RBTreeSortedMap after an insert call above a certain threshold.
34+
*
35+
* Note: This package is copied from https://github.com/firebase/firebase-admin-java/tree/master/
36+
* src/main/java/com/google/firebase/database/collection
37+
*/
38+
@InternalApi
39+
public class ArraySortedMap<K, V> extends ImmutableSortedMap<K, V> {
40+
41+
@SuppressWarnings("unchecked")
42+
public static <A, B, C> ArraySortedMap<A, C> buildFrom(
43+
List<A> keys,
44+
Map<B, C> values,
45+
Builder.KeyTranslator<A, B> translator,
46+
Comparator<A> comparator) {
47+
Collections.sort(keys, comparator);
48+
int size = keys.size();
49+
A[] keyArray = (A[]) new Object[size];
50+
C[] valueArray = (C[]) new Object[size];
51+
int pos = 0;
52+
for (A k : keys) {
53+
keyArray[pos] = k;
54+
C value = values.get(translator.translate(k));
55+
valueArray[pos] = value;
56+
pos++;
57+
}
58+
return new ArraySortedMap<A, C>(comparator, keyArray, valueArray);
59+
}
60+
61+
public static <K, V> ArraySortedMap<K, V> fromMap(Map<K, V> map, Comparator<K> comparator) {
62+
return buildFrom(
63+
new ArrayList<K>(map.keySet()), map, Builder.<K>identityTranslator(), comparator);
64+
}
65+
66+
private final K[] keys;
67+
private final V[] values;
68+
private final Comparator<K> comparator;
69+
70+
@SuppressWarnings("unchecked")
71+
public ArraySortedMap(Comparator<K> comparator) {
72+
this.keys = (K[]) new Object[0];
73+
this.values = (V[]) new Object[0];
74+
this.comparator = comparator;
75+
}
76+
77+
@SuppressWarnings("unchecked")
78+
private ArraySortedMap(Comparator<K> comparator, K[] keys, V[] values) {
79+
this.keys = keys;
80+
this.values = values;
81+
this.comparator = comparator;
82+
}
83+
84+
@Override
85+
public boolean containsKey(K key) {
86+
return findKey(key) != -1;
87+
}
88+
89+
@Override
90+
public V get(K key) {
91+
int pos = findKey(key);
92+
return pos != -1 ? this.values[pos] : null;
93+
}
94+
95+
@Override
96+
public ImmutableSortedMap<K, V> remove(K key) {
97+
int pos = findKey(key);
98+
if (pos == -1) {
99+
return this;
100+
} else {
101+
K[] keys = removeFromArray(this.keys, pos);
102+
V[] values = removeFromArray(this.values, pos);
103+
return new ArraySortedMap<K, V>(this.comparator, keys, values);
104+
}
105+
}
106+
107+
@Override
108+
public ImmutableSortedMap<K, V> insert(K key, V value) {
109+
int pos = findKey(key);
110+
if (pos != -1) {
111+
if (this.keys[pos] == key && this.values[pos] == value) {
112+
return this;
113+
} else {
114+
// The key and/or value might have changed, even though the comparison might still yield 0
115+
K[] newKeys = replaceInArray(this.keys, pos, key);
116+
V[] newValues = replaceInArray(this.values, pos, value);
117+
return new ArraySortedMap<K, V>(this.comparator, newKeys, newValues);
118+
}
119+
} else {
120+
if (this.keys.length > Builder.ARRAY_TO_RB_TREE_SIZE_THRESHOLD) {
121+
@SuppressWarnings("unchecked")
122+
Map<K, V> map = new HashMap<K, V>(this.keys.length + 1);
123+
for (int i = 0; i < this.keys.length; i++) {
124+
map.put(this.keys[i], this.values[i]);
125+
}
126+
map.put(key, value);
127+
return RBTreeSortedMap.fromMap(map, this.comparator);
128+
} else {
129+
int newPos = findKeyOrInsertPosition(key);
130+
K[] keys = addToArray(this.keys, newPos, key);
131+
V[] values = addToArray(this.values, newPos, value);
132+
return new ArraySortedMap<K, V>(this.comparator, keys, values);
133+
}
134+
}
135+
}
136+
137+
@Override
138+
public K getMinKey() {
139+
return this.keys.length > 0 ? this.keys[0] : null;
140+
}
141+
142+
@Override
143+
public K getMaxKey() {
144+
return this.keys.length > 0 ? this.keys[this.keys.length - 1] : null;
145+
}
146+
147+
@Override
148+
public int size() {
149+
return this.keys.length;
150+
}
151+
152+
@Override
153+
public boolean isEmpty() {
154+
return this.keys.length == 0;
155+
}
156+
157+
@Override
158+
public void inOrderTraversal(LLRBNode.NodeVisitor<K, V> visitor) {
159+
for (int i = 0; i < this.keys.length; i++) {
160+
visitor.visitEntry(this.keys[i], this.values[i]);
161+
}
162+
}
163+
164+
private Iterator<Map.Entry<K, V>> iterator(final int pos, final boolean reverse) {
165+
return new Iterator<Map.Entry<K, V>>() {
166+
int currentPos = pos;
167+
168+
@Override
169+
public boolean hasNext() {
170+
return reverse ? currentPos >= 0 : currentPos < keys.length;
171+
}
172+
173+
@Override
174+
public Map.Entry<K, V> next() {
175+
final K key = keys[currentPos];
176+
final V value = values[currentPos];
177+
currentPos = reverse ? currentPos - 1 : currentPos + 1;
178+
return new AbstractMap.SimpleImmutableEntry<K, V>(key, value);
179+
}
180+
181+
@Override
182+
public void remove() {
183+
throw new UnsupportedOperationException("Can't remove elements from ImmutableSortedMap");
184+
}
185+
};
186+
}
187+
188+
@Override
189+
public Iterator<Map.Entry<K, V>> iterator() {
190+
return iterator(0, false);
191+
}
192+
193+
@Override
194+
public Iterator<Map.Entry<K, V>> iteratorFrom(K key) {
195+
int pos = findKeyOrInsertPosition(key);
196+
return iterator(pos, false);
197+
}
198+
199+
@Override
200+
public Iterator<Map.Entry<K, V>> reverseIteratorFrom(K key) {
201+
int pos = findKeyOrInsertPosition(key);
202+
// if there's no exact match, findKeyOrInsertPosition will return the index *after* the closest
203+
// match, but
204+
// since this is a reverse iterator, we want to start just *before* the closest match.
205+
if (pos < this.keys.length && this.comparator.compare(this.keys[pos], key) == 0) {
206+
return iterator(pos, true);
207+
} else {
208+
return iterator(pos - 1, true);
209+
}
210+
}
211+
212+
@Override
213+
public Iterator<Map.Entry<K, V>> reverseIterator() {
214+
return iterator(this.keys.length - 1, true);
215+
}
216+
217+
@Override
218+
public K getPredecessorKey(K key) {
219+
int pos = findKey(key);
220+
if (pos == -1) {
221+
throw new IllegalArgumentException("Can't find predecessor of nonexistent key");
222+
} else {
223+
return (pos > 0) ? this.keys[pos - 1] : null;
224+
}
225+
}
226+
227+
@Override
228+
public K getSuccessorKey(K key) {
229+
int pos = findKey(key);
230+
if (pos == -1) {
231+
throw new IllegalArgumentException("Can't find successor of nonexistent key");
232+
} else {
233+
return (pos < this.keys.length - 1) ? this.keys[pos + 1] : null;
234+
}
235+
}
236+
237+
@Override
238+
public int indexOf(K key) {
239+
return findKey(key);
240+
}
241+
242+
@Override
243+
public Comparator<K> getComparator() {
244+
return comparator;
245+
}
246+
247+
@SuppressWarnings("unchecked")
248+
private static <T> T[] removeFromArray(T[] arr, int pos) {
249+
int newSize = arr.length - 1;
250+
T[] newArray = (T[]) new Object[newSize];
251+
System.arraycopy(arr, 0, newArray, 0, pos);
252+
System.arraycopy(arr, pos + 1, newArray, pos, newSize - pos);
253+
return newArray;
254+
}
255+
256+
@SuppressWarnings("unchecked")
257+
private static <T> T[] addToArray(T[] arr, int pos, T value) {
258+
int newSize = arr.length + 1;
259+
T[] newArray = (T[]) new Object[newSize];
260+
System.arraycopy(arr, 0, newArray, 0, pos);
261+
newArray[pos] = value;
262+
System.arraycopy(arr, pos, newArray, pos + 1, newSize - pos - 1);
263+
return newArray;
264+
}
265+
266+
@SuppressWarnings("unchecked")
267+
private static <T> T[] replaceInArray(T[] arr, int pos, T value) {
268+
int size = arr.length;
269+
T[] newArray = (T[]) new Object[size];
270+
System.arraycopy(arr, 0, newArray, 0, size);
271+
newArray[pos] = value;
272+
return newArray;
273+
}
274+
275+
/**
276+
* This does a linear scan which is simpler than a binary search. For a small collection size this
277+
* still should be as fast a as binary search.
278+
*/
279+
private int findKeyOrInsertPosition(K key) {
280+
int newPos = 0;
281+
while (newPos < this.keys.length && this.comparator.compare(this.keys[newPos], key) < 0) {
282+
newPos++;
283+
}
284+
return newPos;
285+
}
286+
287+
/**
288+
* This does a linear scan which is simpler than a binary search. For a small collection size this
289+
* still should be as fast a as binary search.
290+
*/
291+
private int findKey(K key) {
292+
int i = 0;
293+
for (K otherKey : this.keys) {
294+
if (this.comparator.compare(key, otherKey) == 0) {
295+
return i;
296+
}
297+
i++;
298+
}
299+
return -1;
300+
}
301+
}

0 commit comments

Comments
 (0)