/**
* Copyright (c) 2017-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.litho.internal;
import javax.annotation.Nullable;
import java.lang.reflect.Array;
import java.util.Collection;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.Set;
import android.support.v4.util.SimpleArrayMap;
/**
* A simple Set implementation backed by an array. Currently implemented as a wrapper around
* ArrayMap because Google's ArraySet is API23+ and not yet available from a support library.
* Google's ArraySet should be a drop-in replacement for this class, and will be a little more
* efficient and complete.
*/
public class ArraySet<E> implements Set<E> {
// This could be any value other than null.
private static final Integer SENTINEL_MAP_VALUE = Integer.valueOf(0);
private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0];
private final SimpleArrayMap<E, Integer> mMap;
public ArraySet() {
mMap = new SimpleArrayMap<>();
}
public ArraySet(int capacity) {
mMap = new SimpleArrayMap<>(capacity);
}
public ArraySet(@Nullable Collection<? extends E> set) {
mMap = new SimpleArrayMap<>();
if (set != null) {
addAll(set);
}
}
@Override
public boolean add(E value) {
Object oldValue = mMap.put(value, SENTINEL_MAP_VALUE);
return oldValue == null;
}
@Override
public boolean addAll(Collection<? extends E> collection) {
ensureCapacity(size() + collection.size());
boolean added = false;
if (collection instanceof ArraySet) {
// Use a code path in SimpleArrayMap which avoids an iterator allocation. It is also optimized
// to run in O(N) time when this.size() == 0.
ArraySet<? extends E> arraySet = (ArraySet) collection;
int oldSize = size();
mMap.putAll(arraySet.mMap);
added = size() != oldSize;
} else {
for (E value : collection) {
added |= add(value);
}
}
return added;
}
@Override
public void clear() {
mMap.clear();
}
/**
* Equivalent to calling {@link #clear()} followed by {@link #addAll(Collection)}, but this should
* make it more apparent that this is an optimized code path. Instead of needing lots of O(log2 N)
* operations, this special case is optimized to perform in O(N) time, where N is the size of the
* incoming collection.
*/
public void clearAndAddAll(ArraySet<? extends E> collection) {
clear();
addAll(collection);
}
@Override
public boolean contains(Object value) {
return mMap.containsKey(value);
}
@Override
public boolean containsAll(Collection<?> collection) {
Iterator<?> it = collection.iterator();
while (it.hasNext()) {
if (!contains(it.next())) {
return false;
}
}
return true;
}
public void ensureCapacity(int minimumCapacity) {
mMap.ensureCapacity(minimumCapacity);
}
@Override
public boolean equals(Object object) {
// This implementation is borrowed from the real ArraySet<T>
if (object == this) {
return true;
}
if (object instanceof Set) {
Set<?> set = (Set<?>) object;
if (size() != set.size()) {
return false;
}
try {
for (int i = 0, size = size(); i < size; ++i) {
E mine = valueAt(i);
if (!set.contains(mine)) {
return false;
}
}
} catch (NullPointerException ignored) {
return false;
} catch (ClassCastException ignored) {
return false;
}
return true;
}
return false;
}
@Override
public int hashCode() {
// This algorithm is borrowed from the real ArraySet<T>
int result = 0;
for (int i = 0, size = size(); i < size; ++i) {
E value = valueAt(i);
if (value != null) {
result += value.hashCode();
}
}
return result;
}
public int indexOf(E value) {
return mMap.indexOfKey(value);
}
@Override
public boolean isEmpty() {
return mMap.isEmpty();
}
@Override
public Iterator<E> iterator() {
return new ArraySetIterator();
}
@Override
public boolean remove(Object value) {
int index = indexOf((E) value);
if (index >= 0) {
removeAt(index);
return true;
} else {
return false;
}
}
@Override
public boolean removeAll(Collection<?> collection) {
boolean removed = false;
for (Object value : collection) {
removed |= remove(value);
}
return removed;
}
public E removeAt(int index) {
E value = mMap.keyAt(index);
mMap.removeAt(index);
return value;
}
@Override
public boolean retainAll(Collection<?> collection) {
boolean removed = false;
for (int i = size() - 1; i >= 0; --i) {
if (!collection.contains(valueAt(i))) {
removeAt(i);
removed = true;
}
}
return removed;
}
@Override
public int size() {
return mMap.size();
}
@Override
public Object[] toArray() {
int size = mMap.size();
if (size == 0) {
// This ensures that ImmutableSet.copyOf() can be used without an extra Object[0] allocation
return EMPTY_OBJECT_ARRAY;
}
Object[] array = new Object[size];
for (int i = 0; i < size; ++i) {
array[i] = mMap.keyAt(i);
}
return array;
}
@Override
@SuppressWarnings("unchecked")
public <T> T[] toArray(T[] array) {
int size = size();
if (array.length < size) {
T[] newArray = (T[]) Array.newInstance(array.getClass().getComponentType(), size);
array = newArray;
}
for (int i = 0; i < size; ++i) {
array[i] = (T) valueAt(i);
}
if (array.length > size) {
array[size] = null;
}
return array;
}
@Override
public String toString() {
// This implementation is borrowed from the real ArraySet<T>
if (isEmpty()) {
return "{}";
}
int size = size();
StringBuilder buffer = new StringBuilder(size * 14);
buffer.append('{');
for (int i = 0; i < size; ++i) {
if (i > 0) {
buffer.append(", ");
}
Object value = valueAt(i);
if (value != this) {
buffer.append(value);
} else {
buffer.append("(this Set)");
}
}
buffer.append('}');
return buffer.toString();
}
public E valueAt(int index) {
return mMap.keyAt(index);
}
private final class ArraySetIterator implements Iterator<E> {
private int mIndex;
private boolean mRemoved;
public ArraySetIterator() {
mIndex = -1;
}
@Override
public boolean hasNext() {
return (mIndex + 1) < size();
}
@Override
public E next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
mRemoved = false;
++mIndex;
return valueAt(mIndex);
}
@Override
public void remove() {
if (mRemoved) {
throw new IllegalStateException();
}
removeAt(mIndex);
mRemoved = true;
--mIndex;
}
}
}