| /* |
| * Copyright 2018 The Android Open Source Project |
| * |
| * 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 androidx.collection; |
| |
| import java.lang.reflect.Array; |
| import java.util.Collection; |
| import java.util.Iterator; |
| import java.util.Map; |
| import java.util.NoSuchElementException; |
| import java.util.Set; |
| |
| /** |
| * Helper for writing standard Java collection interfaces to a data |
| * structure like {@link ArrayMap}. |
| */ |
| abstract class MapCollections<K, V> { |
| EntrySet mEntrySet; |
| KeySet mKeySet; |
| ValuesCollection mValues; |
| |
| final class ArrayIterator<T> implements Iterator<T> { |
| final int mOffset; |
| int mSize; |
| int mIndex; |
| boolean mCanRemove = false; |
| |
| ArrayIterator(int offset) { |
| mOffset = offset; |
| mSize = colGetSize(); |
| } |
| |
| @Override |
| public boolean hasNext() { |
| return mIndex < mSize; |
| } |
| |
| @Override |
| public T next() { |
| if (!hasNext()) throw new NoSuchElementException(); |
| Object res = colGetEntry(mIndex, mOffset); |
| mIndex++; |
| mCanRemove = true; |
| return (T)res; |
| } |
| |
| @Override |
| public void remove() { |
| if (!mCanRemove) { |
| throw new IllegalStateException(); |
| } |
| mIndex--; |
| mSize--; |
| mCanRemove = false; |
| colRemoveAt(mIndex); |
| } |
| } |
| |
| final class MapIterator implements Iterator<Map.Entry<K, V>>, Map.Entry<K, V> { |
| int mEnd; |
| int mIndex; |
| boolean mEntryValid = false; |
| |
| MapIterator() { |
| mEnd = colGetSize() - 1; |
| mIndex = -1; |
| } |
| |
| @Override |
| public boolean hasNext() { |
| return mIndex < mEnd; |
| } |
| |
| @Override |
| public Map.Entry<K, V> next() { |
| if (!hasNext()) throw new NoSuchElementException(); |
| mIndex++; |
| mEntryValid = true; |
| return this; |
| } |
| |
| @Override |
| public void remove() { |
| if (!mEntryValid) { |
| throw new IllegalStateException(); |
| } |
| colRemoveAt(mIndex); |
| mIndex--; |
| mEnd--; |
| mEntryValid = false; |
| } |
| |
| @Override |
| public K getKey() { |
| if (!mEntryValid) { |
| throw new IllegalStateException( |
| "This container does not support retaining Map.Entry objects"); |
| } |
| return (K)colGetEntry(mIndex, 0); |
| } |
| |
| @Override |
| public V getValue() { |
| if (!mEntryValid) { |
| throw new IllegalStateException( |
| "This container does not support retaining Map.Entry objects"); |
| } |
| return (V)colGetEntry(mIndex, 1); |
| } |
| |
| @Override |
| public V setValue(V object) { |
| if (!mEntryValid) { |
| throw new IllegalStateException( |
| "This container does not support retaining Map.Entry objects"); |
| } |
| return colSetValue(mIndex, object); |
| } |
| |
| @Override |
| public boolean equals(Object o) { |
| if (!mEntryValid) { |
| throw new IllegalStateException( |
| "This container does not support retaining Map.Entry objects"); |
| } |
| if (!(o instanceof Map.Entry)) { |
| return false; |
| } |
| Map.Entry<?, ?> e = (Map.Entry<?, ?>) o; |
| return ContainerHelpers.equal(e.getKey(), colGetEntry(mIndex, 0)) |
| && ContainerHelpers.equal(e.getValue(), colGetEntry(mIndex, 1)); |
| } |
| |
| @Override |
| public int hashCode() { |
| if (!mEntryValid) { |
| throw new IllegalStateException( |
| "This container does not support retaining Map.Entry objects"); |
| } |
| final Object key = colGetEntry(mIndex, 0); |
| final Object value = colGetEntry(mIndex, 1); |
| return (key == null ? 0 : key.hashCode()) ^ |
| (value == null ? 0 : value.hashCode()); |
| } |
| |
| @Override |
| public String toString() { |
| return getKey() + "=" + getValue(); |
| } |
| } |
| |
| final class EntrySet implements Set<Map.Entry<K, V>> { |
| @Override |
| public boolean add(Map.Entry<K, V> object) { |
| throw new UnsupportedOperationException(); |
| } |
| |
| @Override |
| public boolean addAll(Collection<? extends Map.Entry<K, V>> collection) { |
| int oldSize = colGetSize(); |
| for (Map.Entry<K, V> entry : collection) { |
| colPut(entry.getKey(), entry.getValue()); |
| } |
| return oldSize != colGetSize(); |
| } |
| |
| @Override |
| public void clear() { |
| colClear(); |
| } |
| |
| @Override |
| public boolean contains(Object o) { |
| if (!(o instanceof Map.Entry)) |
| return false; |
| Map.Entry<?, ?> e = (Map.Entry<?, ?>) o; |
| int index = colIndexOfKey(e.getKey()); |
| if (index < 0) { |
| return false; |
| } |
| Object foundVal = colGetEntry(index, 1); |
| return ContainerHelpers.equal(foundVal, e.getValue()); |
| } |
| |
| @Override |
| public boolean containsAll(Collection<?> collection) { |
| Iterator<?> it = collection.iterator(); |
| while (it.hasNext()) { |
| if (!contains(it.next())) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| @Override |
| public boolean isEmpty() { |
| return colGetSize() == 0; |
| } |
| |
| @Override |
| public Iterator<Map.Entry<K, V>> iterator() { |
| return new MapIterator(); |
| } |
| |
| @Override |
| public boolean remove(Object object) { |
| throw new UnsupportedOperationException(); |
| } |
| |
| @Override |
| public boolean removeAll(Collection<?> collection) { |
| throw new UnsupportedOperationException(); |
| } |
| |
| @Override |
| public boolean retainAll(Collection<?> collection) { |
| throw new UnsupportedOperationException(); |
| } |
| |
| @Override |
| public int size() { |
| return colGetSize(); |
| } |
| |
| @Override |
| public Object[] toArray() { |
| throw new UnsupportedOperationException(); |
| } |
| |
| @Override |
| public <T> T[] toArray(T[] array) { |
| throw new UnsupportedOperationException(); |
| } |
| |
| @Override |
| public boolean equals(Object object) { |
| return equalsSetHelper(this, object); |
| } |
| |
| @Override |
| public int hashCode() { |
| int result = 0; |
| for (int i=colGetSize()-1; i>=0; i--) { |
| final Object key = colGetEntry(i, 0); |
| final Object value = colGetEntry(i, 1); |
| result += ( (key == null ? 0 : key.hashCode()) ^ |
| (value == null ? 0 : value.hashCode()) ); |
| } |
| return result; |
| } |
| }; |
| |
| final class KeySet implements Set<K> { |
| |
| @Override |
| public boolean add(K object) { |
| throw new UnsupportedOperationException(); |
| } |
| |
| @Override |
| public boolean addAll(Collection<? extends K> collection) { |
| throw new UnsupportedOperationException(); |
| } |
| |
| @Override |
| public void clear() { |
| colClear(); |
| } |
| |
| @Override |
| public boolean contains(Object object) { |
| return colIndexOfKey(object) >= 0; |
| } |
| |
| @Override |
| public boolean containsAll(Collection<?> collection) { |
| return containsAllHelper(colGetMap(), collection); |
| } |
| |
| @Override |
| public boolean isEmpty() { |
| return colGetSize() == 0; |
| } |
| |
| @Override |
| public Iterator<K> iterator() { |
| return new ArrayIterator<K>(0); |
| } |
| |
| @Override |
| public boolean remove(Object object) { |
| int index = colIndexOfKey(object); |
| if (index >= 0) { |
| colRemoveAt(index); |
| return true; |
| } |
| return false; |
| } |
| |
| @Override |
| public boolean removeAll(Collection<?> collection) { |
| return removeAllHelper(colGetMap(), collection); |
| } |
| |
| @Override |
| public boolean retainAll(Collection<?> collection) { |
| return retainAllHelper(colGetMap(), collection); |
| } |
| |
| @Override |
| public int size() { |
| return colGetSize(); |
| } |
| |
| @Override |
| public Object[] toArray() { |
| return toArrayHelper(0); |
| } |
| |
| @Override |
| public <T> T[] toArray(T[] array) { |
| return toArrayHelper(array, 0); |
| } |
| |
| @Override |
| public boolean equals(Object object) { |
| return equalsSetHelper(this, object); |
| } |
| |
| @Override |
| public int hashCode() { |
| int result = 0; |
| for (int i=colGetSize()-1; i>=0; i--) { |
| Object obj = colGetEntry(i, 0); |
| result += obj == null ? 0 : obj.hashCode(); |
| } |
| return result; |
| } |
| }; |
| |
| final class ValuesCollection implements Collection<V> { |
| |
| @Override |
| public boolean add(V object) { |
| throw new UnsupportedOperationException(); |
| } |
| |
| @Override |
| public boolean addAll(Collection<? extends V> collection) { |
| throw new UnsupportedOperationException(); |
| } |
| |
| @Override |
| public void clear() { |
| colClear(); |
| } |
| |
| @Override |
| public boolean contains(Object object) { |
| return colIndexOfValue(object) >= 0; |
| } |
| |
| @Override |
| public boolean containsAll(Collection<?> collection) { |
| Iterator<?> it = collection.iterator(); |
| while (it.hasNext()) { |
| if (!contains(it.next())) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| @Override |
| public boolean isEmpty() { |
| return colGetSize() == 0; |
| } |
| |
| @Override |
| public Iterator<V> iterator() { |
| return new ArrayIterator<V>(1); |
| } |
| |
| @Override |
| public boolean remove(Object object) { |
| int index = colIndexOfValue(object); |
| if (index >= 0) { |
| colRemoveAt(index); |
| return true; |
| } |
| return false; |
| } |
| |
| @Override |
| public boolean removeAll(Collection<?> collection) { |
| int N = colGetSize(); |
| boolean changed = false; |
| for (int i=0; i<N; i++) { |
| Object cur = colGetEntry(i, 1); |
| if (collection.contains(cur)) { |
| colRemoveAt(i); |
| i--; |
| N--; |
| changed = true; |
| } |
| } |
| return changed; |
| } |
| |
| @Override |
| public boolean retainAll(Collection<?> collection) { |
| int N = colGetSize(); |
| boolean changed = false; |
| for (int i=0; i<N; i++) { |
| Object cur = colGetEntry(i, 1); |
| if (!collection.contains(cur)) { |
| colRemoveAt(i); |
| i--; |
| N--; |
| changed = true; |
| } |
| } |
| return changed; |
| } |
| |
| @Override |
| public int size() { |
| return colGetSize(); |
| } |
| |
| @Override |
| public Object[] toArray() { |
| return toArrayHelper(1); |
| } |
| |
| @Override |
| public <T> T[] toArray(T[] array) { |
| return toArrayHelper(array, 1); |
| } |
| }; |
| |
| public static <K, V> boolean containsAllHelper(Map<K, V> map, Collection<?> collection) { |
| Iterator<?> it = collection.iterator(); |
| while (it.hasNext()) { |
| if (!map.containsKey(it.next())) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| public static <K, V> boolean removeAllHelper(Map<K, V> map, Collection<?> collection) { |
| int oldSize = map.size(); |
| Iterator<?> it = collection.iterator(); |
| while (it.hasNext()) { |
| map.remove(it.next()); |
| } |
| return oldSize != map.size(); |
| } |
| |
| public static <K, V> boolean retainAllHelper(Map<K, V> map, Collection<?> collection) { |
| int oldSize = map.size(); |
| Iterator<K> it = map.keySet().iterator(); |
| while (it.hasNext()) { |
| if (!collection.contains(it.next())) { |
| it.remove(); |
| } |
| } |
| return oldSize != map.size(); |
| } |
| |
| |
| public Object[] toArrayHelper(int offset) { |
| final int N = colGetSize(); |
| Object[] result = new Object[N]; |
| for (int i=0; i<N; i++) { |
| result[i] = colGetEntry(i, offset); |
| } |
| return result; |
| } |
| |
| public <T> T[] toArrayHelper(T[] array, int offset) { |
| final int N = colGetSize(); |
| if (array.length < N) { |
| @SuppressWarnings("unchecked") T[] newArray |
| = (T[]) Array.newInstance(array.getClass().getComponentType(), N); |
| array = newArray; |
| } |
| for (int i=0; i<N; i++) { |
| array[i] = (T)colGetEntry(i, offset); |
| } |
| if (array.length > N) { |
| array[N] = null; |
| } |
| return array; |
| } |
| |
| public static <T> boolean equalsSetHelper(Set<T> set, Object object) { |
| if (set == object) { |
| return true; |
| } |
| if (object instanceof Set) { |
| Set<?> s = (Set<?>) object; |
| |
| try { |
| return set.size() == s.size() && set.containsAll(s); |
| } catch (NullPointerException ignored) { |
| return false; |
| } catch (ClassCastException ignored) { |
| return false; |
| } |
| } |
| return false; |
| } |
| |
| public Set<Map.Entry<K, V>> getEntrySet() { |
| if (mEntrySet == null) { |
| mEntrySet = new EntrySet(); |
| } |
| return mEntrySet; |
| } |
| |
| public Set<K> getKeySet() { |
| if (mKeySet == null) { |
| mKeySet = new KeySet(); |
| } |
| return mKeySet; |
| } |
| |
| public Collection<V> getValues() { |
| if (mValues == null) { |
| mValues = new ValuesCollection(); |
| } |
| return mValues; |
| } |
| |
| protected abstract int colGetSize(); |
| protected abstract Object colGetEntry(int index, int offset); |
| protected abstract int colIndexOfKey(Object key); |
| protected abstract int colIndexOfValue(Object key); |
| protected abstract Map<K, V> colGetMap(); |
| protected abstract void colPut(K key, V value); |
| protected abstract V colSetValue(int index, V value); |
| protected abstract void colRemoveAt(int index); |
| protected abstract void colClear(); |
| } |