/* * Copyright (C) 2013 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 android.util; import android.annotation.Nullable; import java.lang.reflect.Array; import java.util.Collection; import java.util.Iterator; import java.util.Map; import java.util.NoSuchElementException; import java.util.Objects; import java.util.Set; /** * Helper for writing standard Java collection interfaces to a data * structure like {@link ArrayMap}. * @hide */ abstract class MapCollections { EntrySet mEntrySet; KeySet mKeySet; ValuesCollection mValues; final class ArrayIterator implements Iterator { 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 { int mEnd; int mIndex; boolean mEntryValid = false; MapIterator() { mEnd = colGetSize() - 1; mIndex = -1; } @Override public boolean hasNext() { return mIndex < mEnd; } @Override public Map.Entry 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 final 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 Objects.equals(e.getKey(), colGetEntry(mIndex, 0)) && Objects.equals(e.getValue(), colGetEntry(mIndex, 1)); } @Override public final 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 final String toString() { return getKey() + "=" + getValue(); } } final class EntrySet implements Set> { @Override public boolean add(Map.Entry object) { throw new UnsupportedOperationException(); } @Override public boolean addAll(Collection> collection) { int oldSize = colGetSize(); for (Map.Entry 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 Objects.equals(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> 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[] toArray(T[] array) { throw new UnsupportedOperationException(); } @Override public boolean equals(@Nullable 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 { @Override public boolean add(K object) { throw new UnsupportedOperationException(); } @Override public boolean addAll(Collection 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 iterator() { return new ArrayIterator(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[] toArray(T[] array) { return toArrayHelper(array, 0); } @Override public boolean equals(@Nullable 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 { @Override public boolean add(V object) { throw new UnsupportedOperationException(); } @Override public boolean addAll(Collection 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 iterator() { return new ArrayIterator(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 collection) { int N = colGetSize(); boolean changed = false; for (int i=0; i T[] toArray(T[] array) { return toArrayHelper(array, 1); } }; public static boolean containsAllHelper(Map map, Collection collection) { Iterator it = collection.iterator(); while (it.hasNext()) { if (!map.containsKey(it.next())) { return false; } } return true; } public static boolean removeAllHelper(Map map, Collection collection) { int oldSize = map.size(); Iterator it = collection.iterator(); while (it.hasNext()) { map.remove(it.next()); } return oldSize != map.size(); } public static boolean retainAllHelper(Map map, Collection collection) { int oldSize = map.size(); Iterator 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 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) { array[N] = null; } return array; } public static boolean equalsSetHelper(Set 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> getEntrySet() { if (mEntrySet == null) { mEntrySet = new EntrySet(); } return mEntrySet; } public Set getKeySet() { if (mKeySet == null) { mKeySet = new KeySet(); } return mKeySet; } public Collection 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 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(); }