From edd1ec2cc10a6cce0aae9bc701fe66014efe0c71 Mon Sep 17 00:00:00 2001 From: tarsin Date: Mon, 13 Jun 2022 12:47:38 +0800 Subject: [PATCH] Inline hippo's yorozuya-collect --- app/build.gradle | 4 - .../yorozuya/collect/AbstractMultimap.java | 181 ++++++++ .../yorozuya/collect/CollectionUtils.java | 169 ++++++++ .../yorozuya/collect/ContainerHelpers.java | 81 ++++ .../com/hippo/yorozuya/collect/IntList.java | 219 ++++++++++ .../com/hippo/yorozuya/collect/LongList.java | 219 ++++++++++ .../com/hippo/yorozuya/collect/Multimap.java | 29 ++ .../hippo/yorozuya/collect/MultimapSet.java | 31 ++ .../hippo/yorozuya/collect/SparseIJArray.java | 254 +++++++++++ .../hippo/yorozuya/collect/SparseILArray.java | 408 ++++++++++++++++++ .../hippo/yorozuya/collect/SparseJBArray.java | 258 +++++++++++ .../hippo/yorozuya/collect/SparseJLArray.java | 408 ++++++++++++++++++ 12 files changed, 2257 insertions(+), 4 deletions(-) create mode 100644 app/src/main/java/com/hippo/yorozuya/collect/AbstractMultimap.java create mode 100644 app/src/main/java/com/hippo/yorozuya/collect/CollectionUtils.java create mode 100644 app/src/main/java/com/hippo/yorozuya/collect/ContainerHelpers.java create mode 100644 app/src/main/java/com/hippo/yorozuya/collect/IntList.java create mode 100644 app/src/main/java/com/hippo/yorozuya/collect/LongList.java create mode 100644 app/src/main/java/com/hippo/yorozuya/collect/Multimap.java create mode 100644 app/src/main/java/com/hippo/yorozuya/collect/MultimapSet.java create mode 100644 app/src/main/java/com/hippo/yorozuya/collect/SparseIJArray.java create mode 100644 app/src/main/java/com/hippo/yorozuya/collect/SparseILArray.java create mode 100644 app/src/main/java/com/hippo/yorozuya/collect/SparseJBArray.java create mode 100644 app/src/main/java/com/hippo/yorozuya/collect/SparseJLArray.java diff --git a/app/build.gradle b/app/build.gradle index 8833ae37e..b3c143641 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -108,7 +108,6 @@ dependencies { implementation 'com.github.seven332:streampipe:0.1.0' implementation 'com.github.seven332:tuxiang:0.1.6' implementation 'com.github.seven332:unifile:9ec57bcd8f' - implementation 'com.github.seven332:yorozuya-collect:0.1.4' implementation 'com.h6ah4i.android.widget.advrecyclerview:advrecyclerview:1.0.0' implementation 'com.squareup.okhttp3:okhttp:4.10.0' implementation 'com.squareup.okhttp3:okhttp-dnsoverhttps:4.10.0' @@ -124,9 +123,6 @@ dependencies { configurations.all { resolutionStrategy { - exclude group: 'com.github.seven332', module: 'okhttp' - exclude group: 'com.github.seven332', module: 'image' - exclude group: 'com.github.seven332', module: 'yorozuya' exclude group: 'androidx.appcompat', module: 'appcompat' } } diff --git a/app/src/main/java/com/hippo/yorozuya/collect/AbstractMultimap.java b/app/src/main/java/com/hippo/yorozuya/collect/AbstractMultimap.java new file mode 100644 index 000000000..32e0604df --- /dev/null +++ b/app/src/main/java/com/hippo/yorozuya/collect/AbstractMultimap.java @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2014-2015 Markus Junginger, greenrobot (http://greenrobot.de) + * + * 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.hippo.yorozuya.collect; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +/** + * Combines a Map with List values to provide simple way to store multiple values for a key (multimap). + *

+ * Threading note: All methods are synchronized + */ +public abstract class AbstractMultimap> implements Map { + + protected Map map; + protected final boolean threadSafeCollections; + + protected AbstractMultimap(Map map, boolean threadSafeCollections) { + this.map = map; + this.threadSafeCollections = threadSafeCollections; + } + + abstract protected C createNewCollection(); + + @Override + public void putAll(Map m) { + map.putAll(m); + } + + @Override + public synchronized int size() { + return map.size(); + } + + @Override + public synchronized boolean isEmpty() { + return map.isEmpty(); + } + + @Override + public synchronized boolean containsKey(Object key) { + return map.containsKey(key); + } + + @Override + public synchronized boolean containsValue(Object value) { + return map.containsValue(value); + } + + @Override + public synchronized C get(Object key) { + return map.get(key); + } + + @Override + public synchronized C remove(Object key) { + return map.remove(key); + } + + @Override + public synchronized void clear() { + map.clear(); + } + + @Override + public synchronized Set keySet() { + return map.keySet(); + } + + @Override + public synchronized Collection values() { + return map.values(); + } + + @Override + public synchronized boolean equals(Object o) { + return map.equals(o); + } + + @Override + public synchronized int hashCode() { + return map.hashCode(); + } + + /** + * @return number of elements stored for given key after storing the given value. + */ + public synchronized int putElement(K key, V value) { + C collection = map.get(key); + if (collection == null) { + collection = createNewCollection(); + map.put(key, collection); + } + collection.add(value); + return collection.size(); + } + + @Override + public synchronized C put(K key, C value) { + return map.put(key, value); + } + + @Override + public synchronized Set> entrySet() { + return map.entrySet(); + } + + /** @return true if the collection was changed. */ + public synchronized boolean putElements(K key, Collection values) { + C collection = map.get(key); + if (collection == null) { + collection = createNewCollection(); + map.put(key, collection); + } + return collection.addAll(values); + } + + /** @return true if the given element was removed. */ + public synchronized boolean removeElement(K key, V value) { + C collection = map.get(key); + if (collection == null) { + return false; + } else { + boolean removed = collection.remove(value); + if (collection.isEmpty()) { + map.remove(key); + } + return removed; + } + } + + public synchronized int countElements(K key) { + C collection = map.get(key); + if (collection == null) { + return 0; + } else { + return collection.size(); + } + } + + public synchronized boolean containsElement(K key, V value) { + C collection = map.get(key); + if (collection == null) { + return false; + } else { + return collection.contains(value); + } + } + + public synchronized boolean containsElement(V value) { + for (C collection : map.values()) { + if (collection.contains(value)) { + return true; + } + } + return false; + } + + public synchronized C valuesElements() { + C all = createNewCollection(); + for (C collection : map.values()) { + all.addAll(collection); + } + return all; + } +} diff --git a/app/src/main/java/com/hippo/yorozuya/collect/CollectionUtils.java b/app/src/main/java/com/hippo/yorozuya/collect/CollectionUtils.java new file mode 100644 index 000000000..4b69cb011 --- /dev/null +++ b/app/src/main/java/com/hippo/yorozuya/collect/CollectionUtils.java @@ -0,0 +1,169 @@ +package com.hippo.yorozuya.collect; + +public class CollectionUtils { + + /** + * An empty immutable {@code boolean} array. + */ + public static final boolean[] EMPTY_BOOLEAN_ARRAY = new boolean[0]; + + /** + * An empty immutable {@code int} array. + */ + public static final int[] EMPTY_INT_ARRAY = new int[0]; + + /** + * An empty immutable {@code long} array. + */ + public static final long[] EMPTY_LONG_ARRAY = new long[0]; + + /** + * An empty immutable {@code String} array. + */ + public static final Object[] EMPTY_OBJECT_ARRAY = new Object[0]; + + /** + * Appends an element to the end of the array, growing the array if there is no more room. + * @param array The array to which to append the element. This must NOT be null. + * @param currentSize The number of elements in the array. Must be less than or equal to + * array.length. + * @param element The element to append. + * @return the array to which the element was appended. This may be different than the given + * array. + */ + public static T[] append(T[] array, int currentSize, T element) { + if (currentSize + 1 > array.length) { + @SuppressWarnings("unchecked") + T[] newArray = (T[]) new Object[growSize(currentSize)]; + System.arraycopy(array, 0, newArray, 0, currentSize); + array = newArray; + } + array[currentSize] = element; + return array; + } + + /** + * Primitive boolean version of {@link #append(Object[], int, Object)}. + */ + public static boolean[] append(boolean[] array, int currentSize, boolean element) { + if (currentSize + 1 > array.length) { + boolean[] newArray = new boolean[growSize(currentSize)]; + System.arraycopy(array, 0, newArray, 0, currentSize); + array = newArray; + } + array[currentSize] = element; + return array; + } + + /** + * Primitive int version of {@link #append(Object[], int, Object)}. + */ + public static int[] append(int[] array, int currentSize, int element) { + if (currentSize + 1 > array.length) { + int[] newArray = new int[growSize(currentSize)]; + System.arraycopy(array, 0, newArray, 0, currentSize); + array = newArray; + } + array[currentSize] = element; + return array; + } + + /** + * Primitive long version of {@link #append(Object[], int, Object)}. + */ + public static long[] append(long[] array, int currentSize, long element) { + if (currentSize + 1 > array.length) { + long[] newArray = new long[growSize(currentSize)]; + System.arraycopy(array, 0, newArray, 0, currentSize); + array = newArray; + } + array[currentSize] = element; + return array; + } + + /** + * Inserts an element into the array at the specified index, growing the array if there is no + * more room. + * + * @param array The array to which to append the element. Must NOT be null. + * @param currentSize The number of elements in the array. Must be less than or equal to + * array.length. + * @param element The element to insert. + * @return the array to which the element was appended. This may be different than the given + * array. + */ + public static T[] insert(T[] array, int currentSize, int index, T element) { + if (currentSize + 1 <= array.length) { + System.arraycopy(array, index, array, index + 1, currentSize - index); + array[index] = element; + return array; + } + + @SuppressWarnings("unchecked") + T[] newArray = (T[]) new Object[growSize(currentSize)]; + System.arraycopy(array, 0, newArray, 0, index); + newArray[index] = element; + System.arraycopy(array, index, newArray, index + 1, array.length - index); + return newArray; + } + + /** + * Primitive boolean version of {@link #insert(Object[], int, int, Object)}. + */ + public static boolean[] insert(boolean[] array, int currentSize, int index, boolean element) { + if (currentSize + 1 <= array.length) { + System.arraycopy(array, index, array, index + 1, currentSize - index); + array[index] = element; + return array; + } + + boolean[] newArray = new boolean[growSize(currentSize)]; + System.arraycopy(array, 0, newArray, 0, index); + newArray[index] = element; + System.arraycopy(array, index, newArray, index + 1, array.length - index); + return newArray; + } + + /** + * Primitive int version of {@link #insert(Object[], int, int, Object)}. + */ + public static int[] insert(int[] array, int currentSize, int index, int element) { + if (currentSize + 1 <= array.length) { + System.arraycopy(array, index, array, index + 1, currentSize - index); + array[index] = element; + return array; + } + + int[] newArray = new int[growSize(currentSize)]; + System.arraycopy(array, 0, newArray, 0, index); + newArray[index] = element; + System.arraycopy(array, index, newArray, index + 1, array.length - index); + return newArray; + } + + /** + * Primitive long version of {@link #insert(Object[], int, int, Object)}. + */ + public static long[] insert(long[] array, int currentSize, int index, long element) { + if (currentSize + 1 <= array.length) { + System.arraycopy(array, index, array, index + 1, currentSize - index); + array[index] = element; + return array; + } + + long[] newArray = new long[growSize(currentSize)]; + System.arraycopy(array, 0, newArray, 0, index); + newArray[index] = element; + System.arraycopy(array, index, newArray, index + 1, array.length - index); + return newArray; + } + + /** + * Given the current size of an array, returns an ideal size to which the array should grow. + * This is typically double the given size, but should not be relied upon to do so in the + * future. + */ + public static int growSize(int currentSize) { + return currentSize <= 4 ? 8 : currentSize * 2; + } +} diff --git a/app/src/main/java/com/hippo/yorozuya/collect/ContainerHelpers.java b/app/src/main/java/com/hippo/yorozuya/collect/ContainerHelpers.java new file mode 100644 index 000000000..bbcae1afa --- /dev/null +++ b/app/src/main/java/com/hippo/yorozuya/collect/ContainerHelpers.java @@ -0,0 +1,81 @@ +/* + * 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 com.hippo.yorozuya.collect; + +import java.util.Comparator; + +public class ContainerHelpers { + + // This is Arrays.binarySearch(), but doesn't do any argument validation. + public static int binarySearch(int[] array, int size, int value) { + int lo = 0; + int hi = size - 1; + + while (lo <= hi) { + final int mid = (lo + hi) >>> 1; + final int midVal = array[mid]; + + if (midVal < value) { + lo = mid + 1; + } else if (midVal > value) { + hi = mid - 1; + } else { + return mid; // value found + } + } + return ~lo; // value not present + } + + public static int binarySearch(long[] array, int size, long value) { + int lo = 0; + int hi = size - 1; + + while (lo <= hi) { + final int mid = (lo + hi) >>> 1; + final long midVal = array[mid]; + + if (midVal < value) { + lo = mid + 1; + } else if (midVal > value) { + hi = mid - 1; + } else { + return mid; // value found + } + } + return ~lo; // value not present + } + + public static int binarySearch(T[] array, int size, T value, + Comparator comparator) { + int lo = 0; + int hi = size - 1; + + while (lo <= hi) { + final int mid = (lo + hi) >>> 1; + final int midValCmp = comparator.compare(array[mid], value); + + if (midValCmp < 0) { + lo = mid + 1; + } else if (midValCmp > 0) { + hi = mid - 1; + } else { + return mid; // value found + } + } + return ~lo; // value not present + } +} diff --git a/app/src/main/java/com/hippo/yorozuya/collect/IntList.java b/app/src/main/java/com/hippo/yorozuya/collect/IntList.java new file mode 100644 index 000000000..178d765a5 --- /dev/null +++ b/app/src/main/java/com/hippo/yorozuya/collect/IntList.java @@ -0,0 +1,219 @@ +/* + * Copyright 2015 Hippo Seven + * + * 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.hippo.yorozuya.collect; + +import android.os.Parcel; +import android.os.Parcelable; + +public class IntList implements Parcelable { + + private static final int MIN_CAPACITY_INCREMENT = 12; + + private int[] mArray; + private int mSize = 0; + + public IntList() { + mArray = CollectionUtils.EMPTY_INT_ARRAY; + } + + public IntList(int capacity) { + mArray = new int[capacity]; + } + + static void throwIndexOutOfBoundsException(int index, int size) { + throw new IndexOutOfBoundsException("Invalid index " + index + ", size is " + size); + } + + private static int newCapacity(int currentCapacity) { + int increment = (currentCapacity < (MIN_CAPACITY_INCREMENT / 2) ? + MIN_CAPACITY_INCREMENT : currentCapacity >> 1); + return currentCapacity + increment; + } + + public void add(int value) { + int[] a = mArray; + int s = mSize; + if (s == a.length) { + int[] newArray = new int[s + + (s < (MIN_CAPACITY_INCREMENT / 2) ? + MIN_CAPACITY_INCREMENT : s >> 1)]; + System.arraycopy(a, 0, newArray, 0, s); + mArray = a = newArray; + } + a[s] = value; + mSize = s + 1; + } + + public void add(int location, int value) throws IndexOutOfBoundsException { + int[] a = mArray; + int s = mSize; + if (location > s || location < 0) { + throwIndexOutOfBoundsException(location, s); + } + + if (s < a.length) { + System.arraycopy(a, location, a, location + 1, s - location); + } else { + // assert s == a.length; + int[] newArray = new int[newCapacity(s)]; + System.arraycopy(a, 0, newArray, 0, location); + System.arraycopy(a, location, newArray, location + 1, s - location); + mArray = a = newArray; + } + a[location] = value; + mSize = s + 1; + } + + public void clear() { + mSize = 0; + } + + public boolean contains(int value) { + int[] a = mArray; + int s = mSize; + for (int i = 0; i < s; i++) { + if (a[i] == value) { + return true; + } + } + return false; + } + + public int get(int location) { + if (location >= mSize) { + throwIndexOutOfBoundsException(location, mSize); + } + return mArray[location]; + } + + public int indexOf(int value) { + int[] a = mArray; + int s = mSize; + for (int i = 0; i < s; i++) { + if (a[i] == value) { + return i; + } + } + return -1; + } + + public boolean isEmpty() { + return mSize == 0; + } + + public int removeAt(int location) { + int[] a = mArray; + int s = mSize; + if (location >= s) { + throwIndexOutOfBoundsException(location, s); + } + int result = a[location]; + System.arraycopy(a, location + 1, a, location, --s - location); + mSize = s; + return result; + } + + public boolean remove(int value) { + int[] a = mArray; + int s = mSize; + for (int i = 0; i < s; i++) { + if (a[i] == value) { + System.arraycopy(a, i + 1, a, i, --s - i); + mSize = s; + return true; + } + } + return false; + } + + public int set(int location, int value) { + int[] a = mArray; + if (location >= mSize) { + throwIndexOutOfBoundsException(location, mSize); + } + int result = a[location]; + a[location] = value; + return result; + } + + public int size() { + return mSize; + } + + public int[] getInternalArray() { + return mArray; + } + + @Override + public String toString() { + int[] a = mArray; + if (a == null) { + return "null"; + } + if (mSize == 0) { + return "[]"; + } + StringBuilder sb = new StringBuilder(mSize * 6); + sb.append('['); + sb.append(a[0]); + for (int i = 1; i < mSize; i++) { + sb.append(", "); + sb.append(a[i]); + } + sb.append(']'); + return sb.toString(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + int s = mSize; + int[] a = mArray; + dest.writeInt(a.length); + dest.writeInt(s); + for (int i = 0; i < s; i++) { + dest.writeInt(a[i]); + } + } + + protected IntList(Parcel in) { + this(in.readInt()); + mSize = in.readInt(); + int s = mSize; + int[] a = mArray; + for (int i = 0; i < s; i++) { + a[i] = in.readInt(); + } + } + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + + @Override + public IntList createFromParcel(Parcel source) { + return new IntList(source); + } + + @Override + public IntList[] newArray(int size) { + return new IntList[size]; + } + }; +} diff --git a/app/src/main/java/com/hippo/yorozuya/collect/LongList.java b/app/src/main/java/com/hippo/yorozuya/collect/LongList.java new file mode 100644 index 000000000..6486a74c0 --- /dev/null +++ b/app/src/main/java/com/hippo/yorozuya/collect/LongList.java @@ -0,0 +1,219 @@ +/* + * Copyright 2016 Hippo Seven + * + * 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.hippo.yorozuya.collect; + +import android.os.Parcel; +import android.os.Parcelable; + +public class LongList implements Parcelable { + + private static final int MIN_CAPACITY_INCREMENT = 12; + + private long[] mArray; + private int mSize = 0; + + public LongList() { + mArray = CollectionUtils.EMPTY_LONG_ARRAY; + } + + public LongList(int capacity) { + mArray = new long[capacity]; + } + + static void throwIndexOutOfBoundsException(int index, int size) { + throw new IndexOutOfBoundsException("Invalid index " + index + ", size is " + size); + } + + private static int newCapacity(int currentCapacity) { + int increment = (currentCapacity < (MIN_CAPACITY_INCREMENT / 2) ? + MIN_CAPACITY_INCREMENT : currentCapacity >> 1); + return currentCapacity + increment; + } + + public void add(long value) { + long[] a = mArray; + int s = mSize; + if (s == a.length) { + long[] newArray = new long[s + + (s < (MIN_CAPACITY_INCREMENT / 2) ? + MIN_CAPACITY_INCREMENT : s >> 1)]; + System.arraycopy(a, 0, newArray, 0, s); + mArray = a = newArray; + } + a[s] = value; + mSize = s + 1; + } + + public void add(int location, long value) throws IndexOutOfBoundsException { + long[] a = mArray; + int s = mSize; + if (location > s || location < 0) { + throwIndexOutOfBoundsException(location, s); + } + + if (s < a.length) { + System.arraycopy(a, location, a, location + 1, s - location); + } else { + // assert s == a.length; + long[] newArray = new long[newCapacity(s)]; + System.arraycopy(a, 0, newArray, 0, location); + System.arraycopy(a, location, newArray, location + 1, s - location); + mArray = a = newArray; + } + a[location] = value; + mSize = s + 1; + } + + public void clear() { + mSize = 0; + } + + public boolean contains(long value) { + long[] a = mArray; + int s = mSize; + for (int i = 0; i < s; i++) { + if (a[i] == value) { + return true; + } + } + return false; + } + + public long get(int location) { + if (location >= mSize) { + throwIndexOutOfBoundsException(location, mSize); + } + return mArray[location]; + } + + public int indexOf(long value) { + long[] a = mArray; + int s = mSize; + for (int i = 0; i < s; i++) { + if (a[i] == value) { + return i; + } + } + return -1; + } + + public boolean isEmpty() { + return mSize == 0; + } + + public long removeAt(int location) { + long[] a = mArray; + int s = mSize; + if (location >= s) { + throwIndexOutOfBoundsException(location, s); + } + long result = a[location]; + System.arraycopy(a, location + 1, a, location, --s - location); + mSize = s; + return result; + } + + public boolean remove(long value) { + long[] a = mArray; + int s = mSize; + for (int i = 0; i < s; i++) { + if (a[i] == value) { + System.arraycopy(a, i + 1, a, i, --s - i); + mSize = s; + return true; + } + } + return false; + } + + public long set(int location, long value) { + long[] a = mArray; + if (location >= mSize) { + throwIndexOutOfBoundsException(location, mSize); + } + long result = a[location]; + a[location] = value; + return result; + } + + public int size() { + return mSize; + } + + public long[] getInternalArray() { + return mArray; + } + + @Override + public String toString() { + long[] a = mArray; + if (a == null) { + return "null"; + } + if (mSize == 0) { + return "[]"; + } + StringBuilder sb = new StringBuilder(mSize * 6); + sb.append('['); + sb.append(a[0]); + for (int i = 1; i < mSize; i++) { + sb.append(", "); + sb.append(a[i]); + } + sb.append(']'); + return sb.toString(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + int s = mSize; + long[] a = mArray; + dest.writeInt(a.length); + dest.writeInt(s); + for (int i = 0; i < s; i++) { + dest.writeLong(a[i]); + } + } + + protected LongList(Parcel in) { + this(in.readInt()); + mSize = in.readInt(); + int s = mSize; + long[] a = mArray; + for (int i = 0; i < s; i++) { + a[i] = in.readLong(); + } + } + + public static final Creator CREATOR = new Creator() { + + @Override + public LongList createFromParcel(Parcel source) { + return new LongList(source); + } + + @Override + public LongList[] newArray(int size) { + return new LongList[size]; + } + }; +} diff --git a/app/src/main/java/com/hippo/yorozuya/collect/Multimap.java b/app/src/main/java/com/hippo/yorozuya/collect/Multimap.java new file mode 100644 index 000000000..584eafc64 --- /dev/null +++ b/app/src/main/java/com/hippo/yorozuya/collect/Multimap.java @@ -0,0 +1,29 @@ +package com.hippo.yorozuya.collect; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CopyOnWriteArrayList; + +/** + * Combines a Map with List values to provide simple way to store multiple values for a key. + */ +// Top level class to get rid of 3rd generic collection parameter for more convenient usage. +public class Multimap extends AbstractMultimap> { + public static Multimap create() { + return new Multimap<>(new HashMap>(), false); + } + + public static Multimap createWithThreadSafeLists() { + return new Multimap<>(new HashMap>(), true); + } + + protected Multimap(Map> map, boolean threadSafeCollections) { + super(map, threadSafeCollections); + } + + protected List createNewCollection() { + return threadSafeCollections ? new CopyOnWriteArrayList() : new ArrayList(); + } +} diff --git a/app/src/main/java/com/hippo/yorozuya/collect/MultimapSet.java b/app/src/main/java/com/hippo/yorozuya/collect/MultimapSet.java new file mode 100644 index 000000000..d85dfccd4 --- /dev/null +++ b/app/src/main/java/com/hippo/yorozuya/collect/MultimapSet.java @@ -0,0 +1,31 @@ +package com.hippo.yorozuya.collect; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArraySet; + +/** + * Combines a Map with Set values to provide simple way to store multiple values for a key. + * Like {@link com.hippo.yorozuya.collect.Multimap}, but element values are stored in Sets. + */ +// Top level class to get rid of 3rd generic collection parameter for more convenient usage. +public class MultimapSet extends AbstractMultimap> { + + public static MultimapSet create() { + return new MultimapSet<>(new HashMap>(), false); + } + + public static MultimapSet createWithThreadSafeLists() { + return new MultimapSet<>(new HashMap>(), true); + } + + protected MultimapSet(Map> map, boolean threadSafeSets) { + super(map, threadSafeSets); + } + + protected Set createNewCollection() { + return threadSafeCollections ? new CopyOnWriteArraySet() : new HashSet(); + } +} diff --git a/app/src/main/java/com/hippo/yorozuya/collect/SparseIJArray.java b/app/src/main/java/com/hippo/yorozuya/collect/SparseIJArray.java new file mode 100644 index 000000000..6b8891592 --- /dev/null +++ b/app/src/main/java/com/hippo/yorozuya/collect/SparseIJArray.java @@ -0,0 +1,254 @@ +/* + * Copyright 2016 Hippo Seven + * + * 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.hippo.yorozuya.collect; + +/** + * SparseIJArrays map integers to longs. Unlike a normal array of longs, + * there can be gaps in the indices. It is intended to be more memory efficient + * than using a HashMap to map Integers to Longs, both because it avoids + * auto-boxing keys and values and its data structure doesn't rely on an extra entry object + * for each mapping. + * + *

Note that this container keeps its mappings in an array data structure, + * using a binary search to find keys. The implementation is not intended to be appropriate for + * data structures + * that may contain large numbers of items. It is generally slower than a traditional + * HashMap, since lookups require a binary search and adds and removes require inserting + * and deleting entries in the array. For containers holding up to hundreds of items, + * the performance difference is not significant, less than 50%.

+ * + *

It is possible to iterate over the items in this container using + * {@link #keyAt(int)} and {@link #valueAt(int)}. Iterating over the keys using + * keyAt(int) with ascending values of the index will return the + * keys in ascending order, or the values corresponding to the keys in ascending + * order in the case of valueAt(int).

+ */ +public class SparseIJArray { + private int[] mKeys; + private long[] mValues; + private int mSize; + + /** + * Creates a new SparseIJArray containing no mappings. + */ + public SparseIJArray() { + this(10); + } + + /** + * Creates a new SparseIJArray containing no mappings that will not + * require any additional memory allocation to store the specified + * number of mappings. If you supply an initial capacity of 0, the + * sparse array will be initialized with a light-weight representation + * not requiring any additional array allocations. + */ + public SparseIJArray(int initialCapacity) { + if (initialCapacity == 0) { + mKeys = CollectionUtils.EMPTY_INT_ARRAY; + mValues = CollectionUtils.EMPTY_LONG_ARRAY; + } else { + mKeys = new int[initialCapacity]; + mValues = new long[initialCapacity]; + } + mSize = 0; + } + + @Override + public SparseIJArray clone() { + SparseIJArray clone = null; + try { + clone = (SparseIJArray) super.clone(); + clone.mKeys = mKeys.clone(); + clone.mValues = mValues.clone(); + } catch (CloneNotSupportedException e) { + /* ignore */ + } + return clone; + } + + /** + * Gets the long mapped from the specified key, or 0 + * if no such mapping has been made. + */ + public long get(int key) { + return get(key, 0); + } + + /** + * Gets the long mapped from the specified key, or the specified value + * if no such mapping has been made. + */ + public long get(int key, long valueIfKeyNotFound) { + int i = ContainerHelpers.binarySearch(mKeys, mSize, key); + + if (i < 0) { + return valueIfKeyNotFound; + } else { + return mValues[i]; + } + } + + /** + * Removes the mapping from the specified key, if there was any. + */ + public void delete(int key) { + int i = ContainerHelpers.binarySearch(mKeys, mSize, key); + + if (i >= 0) { + removeAt(i); + } + } + + /** + * Removes the mapping at the given index. + */ + public void removeAt(int index) { + System.arraycopy(mKeys, index + 1, mKeys, index, mSize - (index + 1)); + System.arraycopy(mValues, index + 1, mValues, index, mSize - (index + 1)); + mSize--; + } + + /** + * Adds a mapping from the specified key to the specified value, + * replacing the previous mapping from the specified key if there + * was one. + */ + public void put(int key, long value) { + int i = ContainerHelpers.binarySearch(mKeys, mSize, key); + + if (i >= 0) { + mValues[i] = value; + } else { + i = ~i; + + mKeys = CollectionUtils.insert(mKeys, mSize, i, key); + mValues = CollectionUtils.insert(mValues, mSize, i, value); + mSize++; + } + } + + /** + * Returns the number of key-value mappings that this SparseIntArray + * currently stores. + */ + public int size() { + return mSize; + } + + /** + * Given an index in the range 0...size()-1, returns + * the key from the indexth key-value mapping that this + * SparseIJArray stores. + * + *

The keys corresponding to indices in ascending order are guaranteed to + * be in ascending order, e.g., keyAt(0) will return the + * smallest key and keyAt(size()-1) will return the largest + * key.

+ */ + public int keyAt(int index) { + return mKeys[index]; + } + + /** + * Given an index in the range 0...size()-1, returns + * the value from the indexth key-value mapping that this + * SparseIJArray stores. + * + *

The values corresponding to indices in ascending order are guaranteed + * to be associated with keys in ascending order, e.g., + * valueAt(0) will return the value associated with the + * smallest key and valueAt(size()-1) will return the value + * associated with the largest key.

+ */ + public long valueAt(int index) { + return mValues[index]; + } + + /** + * Returns the index for which {@link #keyAt} would return the + * specified key, or a negative number if the specified + * key is not mapped. + */ + public int indexOfKey(int key) { + return ContainerHelpers.binarySearch(mKeys, mSize, key); + } + + /** + * Returns an index for which {@link #valueAt} would return the + * specified key, or a negative number if no keys map to the + * specified value. + * Beware that this is a linear search, unlike lookups by key, + * and that multiple keys can map to the same value and this will + * find only one of them. + */ + public int indexOfValue(long value) { + for (int i = 0; i < mSize; i++) + if (mValues[i] == value) + return i; + + return -1; + } + + /** + * Removes all key-value mappings from this SparseIntArray. + */ + public void clear() { + mSize = 0; + } + + /** + * Puts a key/value pair into the array, optimizing for the case where + * the key is greater than all existing keys in the array. + */ + public void append(int key, long value) { + if (mSize != 0 && key <= mKeys[mSize - 1]) { + put(key, value); + return; + } + + mKeys = CollectionUtils.append(mKeys, mSize, key); + mValues = CollectionUtils.append(mValues, mSize, value); + mSize++; + } + + /** + * {@inheritDoc} + * + *

This implementation composes a string by iterating over its mappings. + */ + @Override + public String toString() { + if (size() <= 0) { + return "{}"; + } + + StringBuilder buffer = new StringBuilder(mSize * 28); + buffer.append('{'); + for (int i=0; i 0) { + buffer.append(", "); + } + int key = keyAt(i); + buffer.append(key); + buffer.append('='); + long value = valueAt(i); + buffer.append(value); + } + buffer.append('}'); + return buffer.toString(); + } +} diff --git a/app/src/main/java/com/hippo/yorozuya/collect/SparseILArray.java b/app/src/main/java/com/hippo/yorozuya/collect/SparseILArray.java new file mode 100644 index 000000000..8be12bc01 --- /dev/null +++ b/app/src/main/java/com/hippo/yorozuya/collect/SparseILArray.java @@ -0,0 +1,408 @@ +/* + * Copyright (C) 2006 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 com.hippo.yorozuya.collect; + +/** + * SparseILArrays map integers to Objects. Unlike a normal array of Objects, + * there can be gaps in the indices. It is intended to be more memory efficient + * than using a HashMap to map Integers to Objects, both because it avoids + * auto-boxing keys and its data structure doesn't rely on an extra entry object + * for each mapping. + * + *

Note that this container keeps its mappings in an array data structure, + * using a binary search to find keys. The implementation is not intended to be appropriate for + * data structures + * that may contain large numbers of items. It is generally slower than a traditional + * HashMap, since lookups require a binary search and adds and removes require inserting + * and deleting entries in the array. For containers holding up to hundreds of items, + * the performance difference is not significant, less than 50%.

+ * + *

To help with performance, the container includes an optimization when removing + * keys: instead of compacting its array immediately, it leaves the removed entry marked + * as deleted. The entry can then be re-used for the same key, or compacted later in + * a single garbage collection step of all removed entries. This garbage collection will + * need to be performed at any time the array needs to be grown or the the map size or + * entry values are retrieved.

+ * + *

It is possible to iterate over the items in this container using + * {@link #keyAt(int)} and {@link #valueAt(int)}. Iterating over the keys using + * keyAt(int) with ascending values of the index will return the + * keys in ascending order, or the values corresponding to the keys in ascending + * order in the case of valueAt(int).

+ */ +public class SparseILArray implements Cloneable { + private static final Object DELETED = new Object(); + private boolean mGarbage = false; + + private int[] mKeys; + private Object[] mValues; + private int mSize; + + /** + * Creates a new SparseILArray containing no mappings. + */ + public SparseILArray() { + this(10); + } + + /** + * Creates a new SparseILArray containing no mappings that will not + * require any additional memory allocation to store the specified + * number of mappings. If you supply an initial capacity of 0, the + * sparse array will be initialized with a light-weight representation + * not requiring any additional array allocations. + */ + public SparseILArray(int initialCapacity) { + if (initialCapacity == 0) { + mKeys = CollectionUtils.EMPTY_INT_ARRAY; + mValues = CollectionUtils.EMPTY_OBJECT_ARRAY; + } else { + mKeys = new int[initialCapacity]; + mValues = new Object[initialCapacity]; + } + mSize = 0; + } + + @Override + @SuppressWarnings("unchecked") + public SparseILArray clone() { + SparseILArray clone = null; + try { + clone = (SparseILArray) super.clone(); + clone.mKeys = mKeys.clone(); + clone.mValues = mValues.clone(); + } catch (CloneNotSupportedException e) { + /* ignore */ + } + return clone; + } + + /** + * Gets the Object mapped from the specified key, or null + * if no such mapping has been made. + */ + public E get(int key) { + return get(key, null); + } + + /** + * Gets the Object mapped from the specified key, or the specified Object + * if no such mapping has been made. + */ + @SuppressWarnings("unchecked") + public E get(int key, E valueIfKeyNotFound) { + int i = ContainerHelpers.binarySearch(mKeys, mSize, key); + + if (i < 0 || mValues[i] == DELETED) { + return valueIfKeyNotFound; + } else { + return (E) mValues[i]; + } + } + + /** + * Removes the mapping from the specified key, if there was any. + */ + @SuppressWarnings("unchecked") + public E delete(int key) { + int i = ContainerHelpers.binarySearch(mKeys, mSize, key); + + if (i >= 0) { + E deleted = (E) mValues[i]; + if (deleted != DELETED) { + mValues[i] = DELETED; + mGarbage = true; + return deleted; + } + } + + return null; + } + + /** + * Removes the mapping from the specified key, if there was any, returning the old value. + */ + @SuppressWarnings("unchecked") + public E removeReturnOld(int key) { + int i = ContainerHelpers.binarySearch(mKeys, mSize, key); + + if (i >= 0) { + if (mValues[i] != DELETED) { + final E old = (E) mValues[i]; + mValues[i] = DELETED; + mGarbage = true; + return old; + } + } + return null; + } + + /** + * Alias for {@link #delete(int)}. + */ + public E remove(int key) { + return delete(key); + } + + /** + * Removes the mapping at the specified index. + */ + public void removeAt(int index) { + if (mValues[index] != DELETED) { + mValues[index] = DELETED; + mGarbage = true; + } + } + + /** + * Remove a range of mappings as a batch. + * + * @param index Index to begin at + * @param size Number of mappings to remove + */ + public void removeAtRange(int index, int size) { + final int end = Math.min(mSize, index + size); + for (int i = index; i < end; i++) { + removeAt(i); + } + } + + private void gc() { + // Log.e("SparseILArray", "gc start with " + mSize); + + int n = mSize; + int o = 0; + int[] keys = mKeys; + Object[] values = mValues; + + for (int i = 0; i < n; i++) { + Object val = values[i]; + + if (val != DELETED) { + if (i != o) { + keys[o] = keys[i]; + values[o] = val; + values[i] = null; + } + + o++; + } + } + + mGarbage = false; + mSize = o; + + // Log.e("SparseILArray", "gc end with " + mSize); + } + + /** + * Adds a mapping from the specified key to the specified value, + * replacing the previous mapping from the specified key if there + * was one. + */ + public void put(int key, E value) { + int i = ContainerHelpers.binarySearch(mKeys, mSize, key); + + if (i >= 0) { + mValues[i] = value; + } else { + i = ~i; + + if (i < mSize && mValues[i] == DELETED) { + mKeys[i] = key; + mValues[i] = value; + return; + } + + if (mGarbage && mSize >= mKeys.length) { + gc(); + + // Search again because indices may have changed. + i = ~ContainerHelpers.binarySearch(mKeys, mSize, key); + } + + mKeys = CollectionUtils.insert(mKeys, mSize, i, key); + mValues = CollectionUtils.insert(mValues, mSize, i, value); + mSize++; + } + } + + /** + * Returns the number of key-value mappings that this SparseILArray + * currently stores. + */ + public int size() { + if (mGarbage) { + gc(); + } + + return mSize; + } + + /** + * Given an index in the range 0...size()-1, returns + * the key from the indexth key-value mapping that this + * SparseILArray stores. + * + *

The keys corresponding to indices in ascending order are guaranteed to + * be in ascending order, e.g., keyAt(0) will return the + * smallest key and keyAt(size()-1) will return the largest + * key.

+ */ + public int keyAt(int index) { + if (mGarbage) { + gc(); + } + + return mKeys[index]; + } + + /** + * Given an index in the range 0...size()-1, returns + * the value from the indexth key-value mapping that this + * SparseILArray stores. + * + *

The values corresponding to indices in ascending order are guaranteed + * to be associated with keys in ascending order, e.g., + * valueAt(0) will return the value associated with the + * smallest key and valueAt(size()-1) will return the value + * associated with the largest key.

+ */ + @SuppressWarnings("unchecked") + public E valueAt(int index) { + if (mGarbage) { + gc(); + } + + return (E) mValues[index]; + } + + /** + * Given an index in the range 0...size()-1, sets a new + * value for the indexth key-value mapping that this + * SparseILArray stores. + */ + public void setValueAt(int index, E value) { + if (mGarbage) { + gc(); + } + + mValues[index] = value; + } + + /** + * Returns the index for which {@link #keyAt} would return the + * specified key, or a negative number if the specified + * key is not mapped. + */ + public int indexOfKey(int key) { + if (mGarbage) { + gc(); + } + + return ContainerHelpers.binarySearch(mKeys, mSize, key); + } + + /** + * Returns an index for which {@link #valueAt} would return the + * specified key, or a negative number if no keys map to the + * specified value. + *

Beware that this is a linear search, unlike lookups by key, + * and that multiple keys can map to the same value and this will + * find only one of them. + *

Note also that unlike most collections' {@code indexOf} methods, + * this method compares values using {@code ==} rather than {@code equals}. + */ + public int indexOfValue(E value) { + if (mGarbage) { + gc(); + } + + for (int i = 0; i < mSize; i++) + if (mValues[i] == value) + return i; + + return -1; + } + + /** + * Removes all key-value mappings from this SparseILArray. + */ + public void clear() { + int n = mSize; + Object[] values = mValues; + + for (int i = 0; i < n; i++) { + values[i] = null; + } + + mSize = 0; + mGarbage = false; + } + + /** + * Puts a key/value pair into the array, optimizing for the case where + * the key is greater than all existing keys in the array. + */ + public void append(int key, E value) { + if (mSize != 0 && key <= mKeys[mSize - 1]) { + put(key, value); + return; + } + + if (mGarbage && mSize >= mKeys.length) { + gc(); + } + + mKeys = CollectionUtils.append(mKeys, mSize, key); + mValues = CollectionUtils.append(mValues, mSize, value); + mSize++; + } + + /** + * {@inheritDoc} + * + *

This implementation composes a string by iterating over its mappings. If + * this map contains itself as a value, the string "(this Map)" + * will appear in its place. + */ + @Override + public String toString() { + if (size() <= 0) { + return "{}"; + } + + StringBuilder buffer = new StringBuilder(mSize * 28); + buffer.append('{'); + for (int i=0; i 0) { + buffer.append(", "); + } + int key = keyAt(i); + buffer.append(key); + buffer.append('='); + Object value = valueAt(i); + if (value != this) { + buffer.append(value); + } else { + buffer.append("(this Map)"); + } + } + buffer.append('}'); + return buffer.toString(); + } +} diff --git a/app/src/main/java/com/hippo/yorozuya/collect/SparseJBArray.java b/app/src/main/java/com/hippo/yorozuya/collect/SparseJBArray.java new file mode 100644 index 000000000..b836399c6 --- /dev/null +++ b/app/src/main/java/com/hippo/yorozuya/collect/SparseJBArray.java @@ -0,0 +1,258 @@ +/* + * Copyright (C) 2006 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 com.hippo.yorozuya.collect; + +/** + * SparseJBArrays map longs to booleans. + * Unlike a normal array of booleans + * there can be gaps in the indices. It is intended to be more memory efficient + * than using a HashMap to map Integers to Booleans, both because it avoids + * auto-boxing keys and values and its data structure doesn't rely on an extra entry object + * for each mapping. + * + *

Note that this container keeps its mappings in an array data structure, + * using a binary search to find keys. The implementation is not intended to be appropriate for + * data structures + * that may contain large numbers of items. It is generally slower than a traditional + * HashMap, since lookups require a binary search and adds and removes require inserting + * and deleting entries in the array. For containers holding up to hundreds of items, + * the performance difference is not significant, less than 50%.

+ * + *

It is possible to iterate over the items in this container using + * {@link #keyAt(int)} and {@link #valueAt(int)}. Iterating over the keys using + * keyAt(int) with ascending values of the index will return the + * keys in ascending order, or the values corresponding to the keys in ascending + * order in the case of valueAt(int).

+ */ +public class SparseJBArray implements Cloneable { + /** + * Creates a new SparseJBArray containing no mappings. + */ + public SparseJBArray() { + this(10); + } + + /** + * Creates a new SparseJBArray containing no mappings that will not + * require any additional memory allocation to store the specified + * number of mappings. If you supply an initial capacity of 0, the + * sparse array will be initialized with a light-weight representation + * not requiring any additional array allocations. + */ + public SparseJBArray(int initialCapacity) { + if (initialCapacity == 0) { + mKeys = CollectionUtils.EMPTY_LONG_ARRAY; + mValues = CollectionUtils.EMPTY_BOOLEAN_ARRAY; + } else { + mKeys = new long[initialCapacity]; + mValues = new boolean[initialCapacity]; + } + mSize = 0; + } + + @Override + public SparseJBArray clone() { + SparseJBArray clone = null; + try { + clone = (SparseJBArray) super.clone(); + clone.mKeys = mKeys.clone(); + clone.mValues = mValues.clone(); + } catch (CloneNotSupportedException e) { + /* ignore */ + } + return clone; + } + + /** + * Gets the boolean mapped from the specified key, or false + * if no such mapping has been made. + */ + public boolean get(long key) { + return get(key, false); + } + + /** + * Gets the boolean mapped from the specified key, or the specified value + * if no such mapping has been made. + */ + public boolean get(long key, boolean valueIfKeyNotFound) { + int i = ContainerHelpers.binarySearch(mKeys, mSize, key); + + if (i < 0) { + return valueIfKeyNotFound; + } else { + return mValues[i]; + } + } + + /** + * Removes the mapping from the specified key, if there was any. + */ + public void delete(long key) { + int i = ContainerHelpers.binarySearch(mKeys, mSize, key); + + if (i >= 0) { + System.arraycopy(mKeys, i + 1, mKeys, i, mSize - (i + 1)); + System.arraycopy(mValues, i + 1, mValues, i, mSize - (i + 1)); + mSize--; + } + } + + public void removeAt(int index) { + System.arraycopy(mKeys, index + 1, mKeys, index, mSize - (index + 1)); + System.arraycopy(mValues, index + 1, mValues, index, mSize - (index + 1)); + mSize--; + } + + /** + * Adds a mapping from the specified key to the specified value, + * replacing the previous mapping from the specified key if there + * was one. + */ + public void put(long key, boolean value) { + int i = ContainerHelpers.binarySearch(mKeys, mSize, key); + + if (i >= 0) { + mValues[i] = value; + } else { + i = ~i; + + mKeys = CollectionUtils.insert(mKeys, mSize, i, key); + mValues = CollectionUtils.insert(mValues, mSize, i, value); + mSize++; + } + } + + /** + * Returns the number of key-value mappings that this SparseJBArray + * currently stores. + */ + public int size() { + return mSize; + } + + /** + * Given an index in the range 0...size()-1, returns + * the key from the indexth key-value mapping that this + * SparseJBArray stores. + * + *

The keys corresponding to indices in ascending order are guaranteed to + * be in ascending order, e.g., keyAt(0) will return the + * smallest key and keyAt(size()-1) will return the largest + * key.

+ */ + public long keyAt(int index) { + return mKeys[index]; + } + + /** + * Given an index in the range 0...size()-1, returns + * the value from the indexth key-value mapping that this + * SparseJBArray stores. + * + *

The values corresponding to indices in ascending order are guaranteed + * to be associated with keys in ascending order, e.g., + * valueAt(0) will return the value associated with the + * smallest key and valueAt(size()-1) will return the value + * associated with the largest key.

+ */ + public boolean valueAt(int index) { + return mValues[index]; + } + + public void setValueAt(int index, boolean value) { + mValues[index] = value; + } + + /** + * Returns the index for which {@link #keyAt} would return the + * specified key, or a negative number if the specified + * key is not mapped. + */ + public int indexOfKey(long key) { + return ContainerHelpers.binarySearch(mKeys, mSize, key); + } + + /** + * Returns an index for which {@link #valueAt} would return the + * specified key, or a negative number if no keys map to the + * specified value. + * Beware that this is a linear search, unlike lookups by key, + * and that multiple keys can map to the same value and this will + * find only one of them. + */ + public int indexOfValue(boolean value) { + for (int i = 0; i < mSize; i++) + if (mValues[i] == value) + return i; + + return -1; + } + + /** + * Removes all key-value mappings from this SparseJBArray. + */ + public void clear() { + mSize = 0; + } + + /** + * Puts a key/value pair into the array, optimizing for the case where + * the key is greater than all existing keys in the array. + */ + public void append(long key, boolean value) { + if (mSize != 0 && key <= mKeys[mSize - 1]) { + put(key, value); + return; + } + + mKeys = CollectionUtils.append(mKeys, mSize, key); + mValues = CollectionUtils.append(mValues, mSize, value); + mSize++; + } + + /** + * {@inheritDoc} + * + *

This implementation composes a string by iterating over its mappings. + */ + @Override + public String toString() { + if (size() <= 0) { + return "{}"; + } + + StringBuilder buffer = new StringBuilder(mSize * 28); + buffer.append('{'); + for (int i=0; i 0) { + buffer.append(", "); + } + long key = keyAt(i); + buffer.append(key); + buffer.append('='); + boolean value = valueAt(i); + buffer.append(value); + } + buffer.append('}'); + return buffer.toString(); + } + + private long[] mKeys; + private boolean[] mValues; + private int mSize; +} diff --git a/app/src/main/java/com/hippo/yorozuya/collect/SparseJLArray.java b/app/src/main/java/com/hippo/yorozuya/collect/SparseJLArray.java new file mode 100644 index 000000000..9af7b8073 --- /dev/null +++ b/app/src/main/java/com/hippo/yorozuya/collect/SparseJLArray.java @@ -0,0 +1,408 @@ +/* + * Copyright (C) 2006 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 com.hippo.yorozuya.collect; + +/** + * SparseArrays map longs to Objects. Unlike a normal array of Objects, + * there can be gaps in the indices. It is intended to be more memory efficient + * than using a HashMap to map Integers to Objects, both because it avoids + * auto-boxing keys and its data structure doesn't rely on an extra entry object + * for each mapping. + * + *

Note that this container keeps its mappings in an array data structure, + * using a binary search to find keys. The implementation is not intended to be appropriate for + * data structures + * that may contain large numbers of items. It is generally slower than a traditional + * HashMap, since lookups require a binary search and adds and removes require inserting + * and deleting entries in the array. For containers holding up to hundreds of items, + * the performance difference is not significant, less than 50%.

+ * + *

To help with performance, the container includes an optimization when removing + * keys: instead of compacting its array immediately, it leaves the removed entry marked + * as deleted. The entry can then be re-used for the same key, or compacted later in + * a single garbage collection step of all removed entries. This garbage collection will + * need to be performed at any time the array needs to be grown or the the map size or + * entry values are retrieved.

+ * + *

It is possible to iterate over the items in this container using + * {@link #keyAt(int)} and {@link #valueAt(int)}. Iterating over the keys using + * keyAt(int) with ascending values of the index will return the + * keys in ascending order, or the values corresponding to the keys in ascending + * order in the case of valueAt(int).

+ */ +public class SparseJLArray { + private static final Object DELETED = new Object(); + private boolean mGarbage = false; + + private long[] mKeys; + private Object[] mValues; + private int mSize; + + /** + * Creates a new SparseJLArray containing no mappings. + */ + public SparseJLArray() { + this(10); + } + + /** + * Creates a new SparseArray containing no mappings that will not + * require any additional memory allocation to store the specified + * number of mappings. If you supply an initial capacity of 0, the + * sparse array will be initialized with a light-weight representation + * not requiring any additional array allocations. + */ + public SparseJLArray(int initialCapacity) { + if (initialCapacity == 0) { + mKeys = CollectionUtils.EMPTY_LONG_ARRAY; + mValues = CollectionUtils.EMPTY_OBJECT_ARRAY; + } else { + mKeys = new long[initialCapacity]; + mValues = new Object[initialCapacity]; + } + mSize = 0; + } + + @Override + @SuppressWarnings("unchecked") + public SparseJLArray clone() { + SparseJLArray clone = null; + try { + clone = (SparseJLArray) super.clone(); + clone.mKeys = mKeys.clone(); + clone.mValues = mValues.clone(); + } catch (CloneNotSupportedException e) { + /* ignore */ + } + return clone; + } + + /** + * Gets the Object mapped from the specified key, or null + * if no such mapping has been made. + */ + public E get(long key) { + return get(key, null); + } + + /** + * Gets the Object mapped from the specified key, or the specified Object + * if no such mapping has been made. + */ + @SuppressWarnings("unchecked") + public E get(long key, E valueIfKeyNotFound) { + int i = ContainerHelpers.binarySearch(mKeys, mSize, key); + + if (i < 0 || mValues[i] == DELETED) { + return valueIfKeyNotFound; + } else { + return (E) mValues[i]; + } + } + + /** + * Removes the mapping from the specified key, if there was any. + */ + @SuppressWarnings("unchecked") + public E delete(long key) { + int i = ContainerHelpers.binarySearch(mKeys, mSize, key); + + if (i >= 0) { + E deleted = (E) mValues[i]; + if (deleted != DELETED) { + mValues[i] = DELETED; + mGarbage = true; + return deleted; + } + } + + return null; + } + + /** + * Removes the mapping from the specified key, if there was any, returning the old value. + */ + @SuppressWarnings("unchecked") + public E removeReturnOld(long key) { + int i = ContainerHelpers.binarySearch(mKeys, mSize, key); + + if (i >= 0) { + if (mValues[i] != DELETED) { + final E old = (E) mValues[i]; + mValues[i] = DELETED; + mGarbage = true; + return old; + } + } + return null; + } + + /** + * Alias for {@link #delete(long)}. + */ + public E remove(long key) { + return delete(key); + } + + /** + * Removes the mapping at the specified index. + */ + public void removeAt(int index) { + if (mValues[index] != DELETED) { + mValues[index] = DELETED; + mGarbage = true; + } + } + + /** + * Remove a range of mappings as a batch. + * + * @param index Index to begin at + * @param size Number of mappings to remove + */ + public void removeAtRange(int index, int size) { + final int end = Math.min(mSize, index + size); + for (int i = index; i < end; i++) { + removeAt(i); + } + } + + private void gc() { + // Log.e("SparseJLArray", "gc start with " + mSize); + + int n = mSize; + int o = 0; + long[] keys = mKeys; + Object[] values = mValues; + + for (int i = 0; i < n; i++) { + Object val = values[i]; + + if (val != DELETED) { + if (i != o) { + keys[o] = keys[i]; + values[o] = val; + values[i] = null; + } + + o++; + } + } + + mGarbage = false; + mSize = o; + + // Log.e("SparseJLArray", "gc end with " + mSize); + } + + /** + * Adds a mapping from the specified key to the specified value, + * replacing the previous mapping from the specified key if there + * was one. + */ + public void put(long key, E value) { + int i = ContainerHelpers.binarySearch(mKeys, mSize, key); + + if (i >= 0) { + mValues[i] = value; + } else { + i = ~i; + + if (i < mSize && mValues[i] == DELETED) { + mKeys[i] = key; + mValues[i] = value; + return; + } + + if (mGarbage && mSize >= mKeys.length) { + gc(); + + // Search again because indices may have changed. + i = ~ContainerHelpers.binarySearch(mKeys, mSize, key); + } + + mKeys = CollectionUtils.insert(mKeys, mSize, i, key); + mValues = CollectionUtils.insert(mValues, mSize, i, value); + mSize++; + } + } + + /** + * Returns the number of key-value mappings that this SparseJLArray + * currently stores. + */ + public int size() { + if (mGarbage) { + gc(); + } + + return mSize; + } + + /** + * Given an index in the range 0...size()-1, returns + * the key from the indexth key-value mapping that this + * SparseJLArray stores. + * + *

The keys corresponding to indices in ascending order are guaranteed to + * be in ascending order, e.g., keyAt(0) will return the + * smallest key and keyAt(size()-1) will return the largest + * key.

+ */ + public long keyAt(int index) { + if (mGarbage) { + gc(); + } + + return mKeys[index]; + } + + /** + * Given an index in the range 0...size()-1, returns + * the value from the indexth key-value mapping that this + * SparseJLArray stores. + * + *

The values corresponding to indices in ascending order are guaranteed + * to be associated with keys in ascending order, e.g., + * valueAt(0) will return the value associated with the + * smallest key and valueAt(size()-1) will return the value + * associated with the largest key.

+ */ + @SuppressWarnings("unchecked") + public E valueAt(int index) { + if (mGarbage) { + gc(); + } + + return (E) mValues[index]; + } + + /** + * Given an index in the range 0...size()-1, sets a new + * value for the indexth key-value mapping that this + * SparseJLArray stores. + */ + public void setValueAt(int index, E value) { + if (mGarbage) { + gc(); + } + + mValues[index] = value; + } + + /** + * Returns the index for which {@link #keyAt} would return the + * specified key, or a negative number if the specified + * key is not mapped. + */ + public int indexOfKey(long key) { + if (mGarbage) { + gc(); + } + + return ContainerHelpers.binarySearch(mKeys, mSize, key); + } + + /** + * Returns an index for which {@link #valueAt} would return the + * specified key, or a negative number if no keys map to the + * specified value. + *

Beware that this is a linear search, unlike lookups by key, + * and that multiple keys can map to the same value and this will + * find only one of them. + *

Note also that unlike most collections' {@code indexOf} methods, + * this method compares values using {@code ==} rather than {@code equals}. + */ + public int indexOfValue(E value) { + if (mGarbage) { + gc(); + } + + for (int i = 0; i < mSize; i++) + if (mValues[i] == value) + return i; + + return -1; + } + + /** + * Removes all key-value mappings from this SparseJLArray. + */ + public void clear() { + int n = mSize; + Object[] values = mValues; + + for (int i = 0; i < n; i++) { + values[i] = null; + } + + mSize = 0; + mGarbage = false; + } + + /** + * Puts a key/value pair into the array, optimizing for the case where + * the key is greater than all existing keys in the array. + */ + public void append(long key, E value) { + if (mSize != 0 && key <= mKeys[mSize - 1]) { + put(key, value); + return; + } + + if (mGarbage && mSize >= mKeys.length) { + gc(); + } + + mKeys = CollectionUtils.append(mKeys, mSize, key); + mValues = CollectionUtils.append(mValues, mSize, value); + mSize++; + } + + /** + * {@inheritDoc} + * + *

This implementation composes a string by iterating over its mappings. If + * this map contains itself as a value, the string "(this Map)" + * will appear in its place. + */ + @Override + public String toString() { + if (size() <= 0) { + return "{}"; + } + + StringBuilder buffer = new StringBuilder(mSize * 28); + buffer.append('{'); + for (int i=0; i 0) { + buffer.append(", "); + } + long key = keyAt(i); + buffer.append(key); + buffer.append('='); + Object value = valueAt(i); + if (value != this) { + buffer.append(value); + } else { + buffer.append("(this Map)"); + } + } + buffer.append('}'); + return buffer.toString(); + } +}