com.google.firebase.database.collection.ArraySortedMap Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of firebase-admin Show documentation
Show all versions of firebase-admin Show documentation
This is the official Firebase Admin Java SDK. Build extraordinary native JVM apps in
minutes with Firebase. The Firebase platform can power your app’s backend, user
authentication, static hosting, and more.
/*
* Copyright 2017 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.firebase.database.collection;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
/**
* This is an array backed implementation of ImmutableSortedMap. It uses arrays and linear lookups
* to achieve good memory efficiency while maintaining good performance for small collections. To
* avoid degrading performance with increasing collection size it will automatically convert to a
* RBTreeSortedMap after an insert call above a certain threshold.
*/
public class ArraySortedMap extends ImmutableSortedMap {
private final K[] keys;
private final V[] values;
private final Comparator comparator;
@SuppressWarnings("unchecked")
public ArraySortedMap(Comparator comparator) {
this.keys = (K[]) new Object[0];
this.values = (V[]) new Object[0];
this.comparator = comparator;
}
@SuppressWarnings("unchecked")
private ArraySortedMap(Comparator comparator, K[] keys, V[] values) {
this.keys = keys;
this.values = values;
this.comparator = comparator;
}
@SuppressWarnings("unchecked")
public static ArraySortedMap buildFrom(
List keys,
Map values,
Builder.KeyTranslator translator,
Comparator comparator) {
Collections.sort(keys, comparator);
int size = keys.size();
A[] keyArray = (A[]) new Object[size];
C[] valueArray = (C[]) new Object[size];
int pos = 0;
for (A k : keys) {
keyArray[pos] = k;
C value = values.get(translator.translate(k));
valueArray[pos] = value;
pos++;
}
return new ArraySortedMap<>(comparator, keyArray, valueArray);
}
public static ArraySortedMap fromMap(Map map, Comparator comparator) {
return buildFrom(
new ArrayList<>(map.keySet()), map, Builder.identityTranslator(), comparator);
}
@SuppressWarnings("unchecked")
private static T[] removeFromArray(T[] arr, int pos) {
int newSize = arr.length - 1;
T[] newArray = (T[]) new Object[newSize];
System.arraycopy(arr, 0, newArray, 0, pos);
System.arraycopy(arr, pos + 1, newArray, pos, newSize - pos);
return newArray;
}
@SuppressWarnings("unchecked")
private static T[] addToArray(T[] arr, int pos, T value) {
int newSize = arr.length + 1;
T[] newArray = (T[]) new Object[newSize];
System.arraycopy(arr, 0, newArray, 0, pos);
newArray[pos] = value;
System.arraycopy(arr, pos, newArray, pos + 1, newSize - pos - 1);
return newArray;
}
@SuppressWarnings("unchecked")
private static T[] replaceInArray(T[] arr, int pos, T value) {
int size = arr.length;
T[] newArray = (T[]) new Object[size];
System.arraycopy(arr, 0, newArray, 0, size);
newArray[pos] = value;
return newArray;
}
@Override
public boolean containsKey(K key) {
return findKey(key) != -1;
}
@Override
public V get(K key) {
int pos = findKey(key);
return pos != -1 ? this.values[pos] : null;
}
@Override
public ImmutableSortedMap remove(K key) {
int pos = findKey(key);
if (pos == -1) {
return this;
} else {
K[] keys = removeFromArray(this.keys, pos);
V[] values = removeFromArray(this.values, pos);
return new ArraySortedMap<>(this.comparator, keys, values);
}
}
@Override
public ImmutableSortedMap insert(K key, V value) {
int pos = findKey(key);
if (pos != -1) {
if (this.keys[pos] == key && this.values[pos] == value) {
return this;
} else {
// The key and/or value might have changed, even though the comparison might
// still yield 0
K[] newKeys = replaceInArray(this.keys, pos, key);
V[] newValues = replaceInArray(this.values, pos, value);
return new ArraySortedMap<>(this.comparator, newKeys, newValues);
}
} else {
if (this.keys.length > Builder.ARRAY_TO_RB_TREE_SIZE_THRESHOLD) {
@SuppressWarnings("unchecked")
Map map = new HashMap<>(this.keys.length + 1);
for (int i = 0; i < this.keys.length; i++) {
map.put(this.keys[i], this.values[i]);
}
map.put(key, value);
return RBTreeSortedMap.fromMap(map, this.comparator);
} else {
int newPos = findKeyOrInsertPosition(key);
K[] keys = addToArray(this.keys, newPos, key);
V[] values = addToArray(this.values, newPos, value);
return new ArraySortedMap<>(this.comparator, keys, values);
}
}
}
@Override
public K getMinKey() {
return this.keys.length > 0 ? this.keys[0] : null;
}
@Override
public K getMaxKey() {
return this.keys.length > 0 ? this.keys[this.keys.length - 1] : null;
}
@Override
public int size() {
return this.keys.length;
}
@Override
public boolean isEmpty() {
return this.keys.length == 0;
}
@Override
public void inOrderTraversal(LLRBNode.NodeVisitor visitor) {
for (int i = 0; i < this.keys.length; i++) {
visitor.visitEntry(this.keys[i], this.values[i]);
}
}
private Iterator> iterator(final int pos, final boolean reverse) {
return new Iterator>() {
int currentPos = pos;
@Override
public boolean hasNext() {
return reverse ? currentPos >= 0 : currentPos < keys.length;
}
@Override
public Map.Entry next() {
final K key = keys[currentPos];
final V value = values[currentPos];
currentPos = reverse ? currentPos - 1 : currentPos + 1;
return new AbstractMap.SimpleImmutableEntry<>(key, value);
}
@Override
public void remove() {
throw new UnsupportedOperationException("Can't remove elements from ImmutableSortedMap");
}
};
}
@Override
public Iterator> iterator() {
return iterator(0, false);
}
@Override
public Iterator> iteratorFrom(K key) {
int pos = findKeyOrInsertPosition(key);
return iterator(pos, false);
}
@Override
public Iterator> reverseIteratorFrom(K key) {
int pos = findKeyOrInsertPosition(key);
// if there's no exact match, findKeyOrInsertPosition will return the index *after* the
// closest match, but
// since this is a reverse iterator, we want to start just *before* the closest match.
if (pos < this.keys.length && this.comparator.compare(this.keys[pos], key) == 0) {
return iterator(pos, true);
} else {
return iterator(pos - 1, true);
}
}
@Override
public Iterator> reverseIterator() {
return iterator(this.keys.length - 1, true);
}
@Override
public K getPredecessorKey(K key) {
int pos = findKey(key);
if (pos == -1) {
throw new IllegalArgumentException("Can't find predecessor of nonexistent key");
} else {
return (pos > 0) ? this.keys[pos - 1] : null;
}
}
@Override
public K getSuccessorKey(K key) {
int pos = findKey(key);
if (pos == -1) {
throw new IllegalArgumentException("Can't find successor of nonexistent key");
} else {
return (pos < this.keys.length - 1) ? this.keys[pos + 1] : null;
}
}
@Override
public Comparator getComparator() {
return comparator;
}
/**
* This does a linear scan which is simpler than a binary search. For a small collection size this
* still should be as fast a as binary search.
*/
private int findKeyOrInsertPosition(K key) {
int newPos = 0;
while (newPos < this.keys.length && this.comparator.compare(this.keys[newPos], key) < 0) {
newPos++;
}
return newPos;
}
/**
* This does a linear scan which is simpler than a binary search. For a small collection size this
* still should be as fast a as binary search.
*/
private int findKey(K key) {
int i = 0;
for (K otherKey : this.keys) {
if (this.comparator.compare(key, otherKey) == 0) {
return i;
}
i++;
}
return -1;
}
}