From 73892165606f5f66f81ad68d85c3c02d3b3fc467 Mon Sep 17 00:00:00 2001 From: zzz <zzz@mail.i2p> Date: Thu, 24 Oct 2019 13:46:32 +0000 Subject: [PATCH] Crypto: SparseArray from AOSP Latest as pulled from android.googlesource.com Copyright (C) 2006 The Android Open Source Project Licensed under the Apache License, Version 2.0 Unmodified, as reference for future merges, will not compile, mods to follow --- LICENSE.txt | 4 + .../i2p/router/crypto/ratchet/ArrayUtils.java | 763 ++++++++++++++++++ .../crypto/ratchet/ContainerHelpers.java | 59 ++ .../crypto/ratchet/GrowingArrayUtils.java | 215 +++++ .../router/crypto/ratchet/SparseArray.java | 489 +++++++++++ 5 files changed, 1530 insertions(+) create mode 100644 router/java/src/net/i2p/router/crypto/ratchet/ArrayUtils.java create mode 100644 router/java/src/net/i2p/router/crypto/ratchet/ContainerHelpers.java create mode 100644 router/java/src/net/i2p/router/crypto/ratchet/GrowingArrayUtils.java create mode 100644 router/java/src/net/i2p/router/crypto/ratchet/SparseArray.java diff --git a/LICENSE.txt b/LICENSE.txt index c1d2ecff40..c48b335163 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -121,6 +121,10 @@ Public domain except as listed below: Copyright (C) 2016 Southern Storm Software, Pty Ltd. See licenses/LICENSE-Noise.txt + SparseArray: + Copyright (C) 2006 The Android Open Source Project + See licenses/LICENSE-Apache2.0.txt + Installer: (not included in distribution packages) diff --git a/router/java/src/net/i2p/router/crypto/ratchet/ArrayUtils.java b/router/java/src/net/i2p/router/crypto/ratchet/ArrayUtils.java new file mode 100644 index 0000000000..b73ecd1974 --- /dev/null +++ b/router/java/src/net/i2p/router/crypto/ratchet/ArrayUtils.java @@ -0,0 +1,763 @@ +/* + * 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.android.internal.util; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.UnsupportedAppUsage; +import android.util.ArraySet; + +import dalvik.system.VMRuntime; + +import libcore.util.EmptyArray; + +import java.io.File; +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.function.IntFunction; + +/** + * ArrayUtils contains some methods that you can call to find out + * the most efficient increments by which to grow arrays. + */ +public class ArrayUtils { + private static final int CACHE_SIZE = 73; + private static Object[] sCache = new Object[CACHE_SIZE]; + + public static final File[] EMPTY_FILE = new File[0]; + + private ArrayUtils() { /* cannot be instantiated */ } + + public static byte[] newUnpaddedByteArray(int minLen) { + return (byte[])VMRuntime.getRuntime().newUnpaddedArray(byte.class, minLen); + } + + public static char[] newUnpaddedCharArray(int minLen) { + return (char[])VMRuntime.getRuntime().newUnpaddedArray(char.class, minLen); + } + + @UnsupportedAppUsage + public static int[] newUnpaddedIntArray(int minLen) { + return (int[])VMRuntime.getRuntime().newUnpaddedArray(int.class, minLen); + } + + public static boolean[] newUnpaddedBooleanArray(int minLen) { + return (boolean[])VMRuntime.getRuntime().newUnpaddedArray(boolean.class, minLen); + } + + public static long[] newUnpaddedLongArray(int minLen) { + return (long[])VMRuntime.getRuntime().newUnpaddedArray(long.class, minLen); + } + + public static float[] newUnpaddedFloatArray(int minLen) { + return (float[])VMRuntime.getRuntime().newUnpaddedArray(float.class, minLen); + } + + public static Object[] newUnpaddedObjectArray(int minLen) { + return (Object[])VMRuntime.getRuntime().newUnpaddedArray(Object.class, minLen); + } + + @UnsupportedAppUsage + @SuppressWarnings("unchecked") + public static <T> T[] newUnpaddedArray(Class<T> clazz, int minLen) { + return (T[])VMRuntime.getRuntime().newUnpaddedArray(clazz, minLen); + } + + /** + * Checks if the beginnings of two byte arrays are equal. + * + * @param array1 the first byte array + * @param array2 the second byte array + * @param length the number of bytes to check + * @return true if they're equal, false otherwise + */ + public static boolean equals(byte[] array1, byte[] array2, int length) { + if (length < 0) { + throw new IllegalArgumentException(); + } + + if (array1 == array2) { + return true; + } + if (array1 == null || array2 == null || array1.length < length || array2.length < length) { + return false; + } + for (int i = 0; i < length; i++) { + if (array1[i] != array2[i]) { + return false; + } + } + return true; + } + + /** + * Returns an empty array of the specified type. The intent is that + * it will return the same empty array every time to avoid reallocation, + * although this is not guaranteed. + */ + @UnsupportedAppUsage + @SuppressWarnings("unchecked") + public static <T> T[] emptyArray(Class<T> kind) { + if (kind == Object.class) { + return (T[]) EmptyArray.OBJECT; + } + + int bucket = (kind.hashCode() & 0x7FFFFFFF) % CACHE_SIZE; + Object cache = sCache[bucket]; + + if (cache == null || cache.getClass().getComponentType() != kind) { + cache = Array.newInstance(kind, 0); + sCache[bucket] = cache; + + // Log.e("cache", "new empty " + kind.getName() + " at " + bucket); + } + + return (T[]) cache; + } + + /** + * Checks if given array is null or has zero elements. + */ + public static boolean isEmpty(@Nullable Collection<?> array) { + return array == null || array.isEmpty(); + } + + /** + * Checks if given map is null or has zero elements. + */ + public static boolean isEmpty(@Nullable Map<?, ?> map) { + return map == null || map.isEmpty(); + } + + /** + * Checks if given array is null or has zero elements. + */ + @UnsupportedAppUsage + public static <T> boolean isEmpty(@Nullable T[] array) { + return array == null || array.length == 0; + } + + /** + * Checks if given array is null or has zero elements. + */ + public static boolean isEmpty(@Nullable int[] array) { + return array == null || array.length == 0; + } + + /** + * Checks if given array is null or has zero elements. + */ + public static boolean isEmpty(@Nullable long[] array) { + return array == null || array.length == 0; + } + + /** + * Checks if given array is null or has zero elements. + */ + public static boolean isEmpty(@Nullable byte[] array) { + return array == null || array.length == 0; + } + + /** + * Checks if given array is null or has zero elements. + */ + public static boolean isEmpty(@Nullable boolean[] array) { + return array == null || array.length == 0; + } + + /** + * Length of the given array or 0 if it's null. + */ + public static int size(@Nullable Object[] array) { + return array == null ? 0 : array.length; + } + + /** + * Length of the given collection or 0 if it's null. + */ + public static int size(@Nullable Collection<?> collection) { + return collection == null ? 0 : collection.size(); + } + + /** + * Checks that value is present as at least one of the elements of the array. + * @param array the array to check in + * @param value the value to check for + * @return true if the value is present in the array + */ + @UnsupportedAppUsage + public static <T> boolean contains(@Nullable T[] array, T value) { + return indexOf(array, value) != -1; + } + + /** + * Return first index of {@code value} in {@code array}, or {@code -1} if + * not found. + */ + @UnsupportedAppUsage + public static <T> int indexOf(@Nullable T[] array, T value) { + if (array == null) return -1; + for (int i = 0; i < array.length; i++) { + if (Objects.equals(array[i], value)) return i; + } + return -1; + } + + /** + * Test if all {@code check} items are contained in {@code array}. + */ + public static <T> boolean containsAll(@Nullable T[] array, T[] check) { + if (check == null) return true; + for (T checkItem : check) { + if (!contains(array, checkItem)) { + return false; + } + } + return true; + } + + /** + * Test if any {@code check} items are contained in {@code array}. + */ + public static <T> boolean containsAny(@Nullable T[] array, T[] check) { + if (check == null) return false; + for (T checkItem : check) { + if (contains(array, checkItem)) { + return true; + } + } + return false; + } + + @UnsupportedAppUsage + public static boolean contains(@Nullable int[] array, int value) { + if (array == null) return false; + for (int element : array) { + if (element == value) { + return true; + } + } + return false; + } + + public static boolean contains(@Nullable long[] array, long value) { + if (array == null) return false; + for (long element : array) { + if (element == value) { + return true; + } + } + return false; + } + + public static boolean contains(@Nullable char[] array, char value) { + if (array == null) return false; + for (char element : array) { + if (element == value) { + return true; + } + } + return false; + } + + /** + * Test if all {@code check} items are contained in {@code array}. + */ + public static <T> boolean containsAll(@Nullable char[] array, char[] check) { + if (check == null) return true; + for (char checkItem : check) { + if (!contains(array, checkItem)) { + return false; + } + } + return true; + } + + public static long total(@Nullable long[] array) { + long total = 0; + if (array != null) { + for (long value : array) { + total += value; + } + } + return total; + } + + public static int[] convertToIntArray(List<Integer> list) { + int[] array = new int[list.size()]; + for (int i = 0; i < list.size(); i++) { + array[i] = list.get(i); + } + return array; + } + + public static @Nullable long[] convertToLongArray(@Nullable int[] intArray) { + if (intArray == null) return null; + long[] array = new long[intArray.length]; + for (int i = 0; i < intArray.length; i++) { + array[i] = (long) intArray[i]; + } + return array; + } + + @SuppressWarnings("unchecked") + public static @NonNull <T> T[] concatElements(Class<T> kind, @Nullable T[] a, @Nullable T[] b) { + final int an = (a != null) ? a.length : 0; + final int bn = (b != null) ? b.length : 0; + if (an == 0 && bn == 0) { + if (kind == String.class) { + return (T[]) EmptyArray.STRING; + } else if (kind == Object.class) { + return (T[]) EmptyArray.OBJECT; + } + } + final T[] res = (T[]) Array.newInstance(kind, an + bn); + if (an > 0) System.arraycopy(a, 0, res, 0, an); + if (bn > 0) System.arraycopy(b, 0, res, an, bn); + return res; + } + + /** + * Adds value to given array if not already present, providing set-like + * behavior. + */ + @UnsupportedAppUsage + @SuppressWarnings("unchecked") + public static @NonNull <T> T[] appendElement(Class<T> kind, @Nullable T[] array, T element) { + return appendElement(kind, array, element, false); + } + + /** + * Adds value to given array. + */ + @SuppressWarnings("unchecked") + public static @NonNull <T> T[] appendElement(Class<T> kind, @Nullable T[] array, T element, + boolean allowDuplicates) { + final T[] result; + final int end; + if (array != null) { + if (!allowDuplicates && contains(array, element)) return array; + end = array.length; + result = (T[])Array.newInstance(kind, end + 1); + System.arraycopy(array, 0, result, 0, end); + } else { + end = 0; + result = (T[])Array.newInstance(kind, 1); + } + result[end] = element; + return result; + } + + /** + * Removes value from given array if present, providing set-like behavior. + */ + @UnsupportedAppUsage + @SuppressWarnings("unchecked") + public static @Nullable <T> T[] removeElement(Class<T> kind, @Nullable T[] array, T element) { + if (array != null) { + if (!contains(array, element)) return array; + final int length = array.length; + for (int i = 0; i < length; i++) { + if (Objects.equals(array[i], element)) { + if (length == 1) { + return null; + } + T[] result = (T[])Array.newInstance(kind, length - 1); + System.arraycopy(array, 0, result, 0, i); + System.arraycopy(array, i + 1, result, i, length - i - 1); + return result; + } + } + } + return array; + } + + /** + * Adds value to given array. + */ + public static @NonNull int[] appendInt(@Nullable int[] cur, int val, + boolean allowDuplicates) { + if (cur == null) { + return new int[] { val }; + } + final int N = cur.length; + if (!allowDuplicates) { + for (int i = 0; i < N; i++) { + if (cur[i] == val) { + return cur; + } + } + } + int[] ret = new int[N + 1]; + System.arraycopy(cur, 0, ret, 0, N); + ret[N] = val; + return ret; + } + + /** + * Adds value to given array if not already present, providing set-like + * behavior. + */ + @UnsupportedAppUsage + public static @NonNull int[] appendInt(@Nullable int[] cur, int val) { + return appendInt(cur, val, false); + } + + /** + * Removes value from given array if present, providing set-like behavior. + */ + public static @Nullable int[] removeInt(@Nullable int[] cur, int val) { + if (cur == null) { + return null; + } + final int N = cur.length; + for (int i = 0; i < N; i++) { + if (cur[i] == val) { + int[] ret = new int[N - 1]; + if (i > 0) { + System.arraycopy(cur, 0, ret, 0, i); + } + if (i < (N - 1)) { + System.arraycopy(cur, i + 1, ret, i, N - i - 1); + } + return ret; + } + } + return cur; + } + + /** + * Removes value from given array if present, providing set-like behavior. + */ + public static @Nullable String[] removeString(@Nullable String[] cur, String val) { + if (cur == null) { + return null; + } + final int N = cur.length; + for (int i = 0; i < N; i++) { + if (Objects.equals(cur[i], val)) { + String[] ret = new String[N - 1]; + if (i > 0) { + System.arraycopy(cur, 0, ret, 0, i); + } + if (i < (N - 1)) { + System.arraycopy(cur, i + 1, ret, i, N - i - 1); + } + return ret; + } + } + return cur; + } + + /** + * Adds value to given array if not already present, providing set-like + * behavior. + */ + public static @NonNull long[] appendLong(@Nullable long[] cur, long val, + boolean allowDuplicates) { + if (cur == null) { + return new long[] { val }; + } + final int N = cur.length; + if (!allowDuplicates) { + for (int i = 0; i < N; i++) { + if (cur[i] == val) { + return cur; + } + } + } + long[] ret = new long[N + 1]; + System.arraycopy(cur, 0, ret, 0, N); + ret[N] = val; + return ret; + } + + /** + * Adds value to given array if not already present, providing set-like + * behavior. + */ + public static @NonNull long[] appendLong(@Nullable long[] cur, long val) { + return appendLong(cur, val, false); + } + + /** + * Removes value from given array if present, providing set-like behavior. + */ + public static @Nullable long[] removeLong(@Nullable long[] cur, long val) { + if (cur == null) { + return null; + } + final int N = cur.length; + for (int i = 0; i < N; i++) { + if (cur[i] == val) { + long[] ret = new long[N - 1]; + if (i > 0) { + System.arraycopy(cur, 0, ret, 0, i); + } + if (i < (N - 1)) { + System.arraycopy(cur, i + 1, ret, i, N - i - 1); + } + return ret; + } + } + return cur; + } + + public static @Nullable long[] cloneOrNull(@Nullable long[] array) { + return (array != null) ? array.clone() : null; + } + + /** + * Clones an array or returns null if the array is null. + */ + public static @Nullable <T> T[] cloneOrNull(@Nullable T[] array) { + return (array != null) ? array.clone() : null; + } + + public static @Nullable <T> ArraySet<T> cloneOrNull(@Nullable ArraySet<T> array) { + return (array != null) ? new ArraySet<T>(array) : null; + } + + public static @NonNull <T> ArraySet<T> add(@Nullable ArraySet<T> cur, T val) { + if (cur == null) { + cur = new ArraySet<>(); + } + cur.add(val); + return cur; + } + + public static @Nullable <T> ArraySet<T> remove(@Nullable ArraySet<T> cur, T val) { + if (cur == null) { + return null; + } + cur.remove(val); + if (cur.isEmpty()) { + return null; + } else { + return cur; + } + } + + public static @NonNull <T> ArrayList<T> add(@Nullable ArrayList<T> cur, T val) { + if (cur == null) { + cur = new ArrayList<>(); + } + cur.add(val); + return cur; + } + + public static @Nullable <T> ArrayList<T> remove(@Nullable ArrayList<T> cur, T val) { + if (cur == null) { + return null; + } + cur.remove(val); + if (cur.isEmpty()) { + return null; + } else { + return cur; + } + } + + public static <T> boolean contains(@Nullable Collection<T> cur, T val) { + return (cur != null) ? cur.contains(val) : false; + } + + public static @Nullable <T> T[] trimToSize(@Nullable T[] array, int size) { + if (array == null || size == 0) { + return null; + } else if (array.length == size) { + return array; + } else { + return Arrays.copyOf(array, size); + } + } + + /** + * Returns true if the two ArrayLists are equal with respect to the objects they contain. + * The objects must be in the same order and be reference equal (== not .equals()). + */ + public static <T> boolean referenceEquals(ArrayList<T> a, ArrayList<T> b) { + if (a == b) { + return true; + } + + final int sizeA = a.size(); + final int sizeB = b.size(); + if (a == null || b == null || sizeA != sizeB) { + return false; + } + + boolean diff = false; + for (int i = 0; i < sizeA && !diff; i++) { + diff |= a.get(i) != b.get(i); + } + return !diff; + } + + /** + * Removes elements that match the predicate in an efficient way that alters the order of + * elements in the collection. This should only be used if order is not important. + * @param collection The ArrayList from which to remove elements. + * @param predicate The predicate that each element is tested against. + * @return the number of elements removed. + */ + public static <T> int unstableRemoveIf(@Nullable ArrayList<T> collection, + @NonNull java.util.function.Predicate<T> predicate) { + if (collection == null) { + return 0; + } + + final int size = collection.size(); + int leftIdx = 0; + int rightIdx = size - 1; + while (leftIdx <= rightIdx) { + // Find the next element to remove moving left to right. + while (leftIdx < size && !predicate.test(collection.get(leftIdx))) { + leftIdx++; + } + + // Find the next element to keep moving right to left. + while (rightIdx > leftIdx && predicate.test(collection.get(rightIdx))) { + rightIdx--; + } + + if (leftIdx >= rightIdx) { + // Done. + break; + } + + Collections.swap(collection, leftIdx, rightIdx); + leftIdx++; + rightIdx--; + } + + // leftIdx is now at the end. + for (int i = size - 1; i >= leftIdx; i--) { + collection.remove(i); + } + return size - leftIdx; + } + + public static @NonNull int[] defeatNullable(@Nullable int[] val) { + return (val != null) ? val : EmptyArray.INT; + } + + public static @NonNull String[] defeatNullable(@Nullable String[] val) { + return (val != null) ? val : EmptyArray.STRING; + } + + public static @NonNull File[] defeatNullable(@Nullable File[] val) { + return (val != null) ? val : EMPTY_FILE; + } + + /** + * Throws {@link ArrayIndexOutOfBoundsException} if the index is out of bounds. + * + * @param len length of the array. Must be non-negative + * @param index the index to check + * @throws ArrayIndexOutOfBoundsException if the {@code index} is out of bounds of the array + */ + public static void checkBounds(int len, int index) { + if (index < 0 || len <= index) { + throw new ArrayIndexOutOfBoundsException("length=" + len + "; index=" + index); + } + } + + /** + * Returns an array with values from {@code val} minus {@code null} values + * + * @param arrayConstructor typically {@code T[]::new} e.g. {@code String[]::new} + */ + public static <T> T[] filterNotNull(T[] val, IntFunction<T[]> arrayConstructor) { + int nullCount = 0; + int size = size(val); + for (int i = 0; i < size; i++) { + if (val[i] == null) { + nullCount++; + } + } + if (nullCount == 0) { + return val; + } + T[] result = arrayConstructor.apply(size - nullCount); + int outIdx = 0; + for (int i = 0; i < size; i++) { + if (val[i] != null) { + result[outIdx++] = val[i]; + } + } + return result; + } + + public static boolean startsWith(byte[] cur, byte[] val) { + if (cur == null || val == null) return false; + if (cur.length < val.length) return false; + for (int i = 0; i < val.length; i++) { + if (cur[i] != val[i]) return false; + } + return true; + } + + /** + * Returns the first element from the array for which + * condition {@code predicate} is true, or null if there is no such element + */ + public static @Nullable <T> T find(@Nullable T[] items, + @NonNull java.util.function.Predicate<T> predicate) { + if (isEmpty(items)) return null; + for (final T item : items) { + if (predicate.test(item)) return item; + } + return null; + } + + public static String deepToString(Object value) { + if (value != null && value.getClass().isArray()) { + if (value.getClass() == boolean[].class) { + return Arrays.toString((boolean[]) value); + } else if (value.getClass() == byte[].class) { + return Arrays.toString((byte[]) value); + } else if (value.getClass() == char[].class) { + return Arrays.toString((char[]) value); + } else if (value.getClass() == double[].class) { + return Arrays.toString((double[]) value); + } else if (value.getClass() == float[].class) { + return Arrays.toString((float[]) value); + } else if (value.getClass() == int[].class) { + return Arrays.toString((int[]) value); + } else if (value.getClass() == long[].class) { + return Arrays.toString((long[]) value); + } else if (value.getClass() == short[].class) { + return Arrays.toString((short[]) value); + } else { + return Arrays.deepToString((Object[]) value); + } + } else { + return String.valueOf(value); + } + } + + public static @Nullable <T> T firstOrNull(T[] items) { + return items.length > 0 ? items[0] : null; + } +} diff --git a/router/java/src/net/i2p/router/crypto/ratchet/ContainerHelpers.java b/router/java/src/net/i2p/router/crypto/ratchet/ContainerHelpers.java new file mode 100644 index 0000000000..4e5fefb9de --- /dev/null +++ b/router/java/src/net/i2p/router/crypto/ratchet/ContainerHelpers.java @@ -0,0 +1,59 @@ +/* + * 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; + +class ContainerHelpers { + + // This is Arrays.binarySearch(), but doesn't do any argument validation. + 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 + } + + 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 + } +} diff --git a/router/java/src/net/i2p/router/crypto/ratchet/GrowingArrayUtils.java b/router/java/src/net/i2p/router/crypto/ratchet/GrowingArrayUtils.java new file mode 100644 index 0000000000..6a12f5a30f --- /dev/null +++ b/router/java/src/net/i2p/router/crypto/ratchet/GrowingArrayUtils.java @@ -0,0 +1,215 @@ +/* + * Copyright (C) 2014 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 net.i2p.router.crypto.ratchet; + +import android.annotation.UnsupportedAppUsage; + +/** + * A helper class that aims to provide comparable growth performance to ArrayList, but on primitive + * arrays. Common array operations are implemented for efficient use in dynamic containers. + * + * All methods in this class assume that the length of an array is equivalent to its capacity and + * NOT the number of elements in the array. The current size of the array is always passed in as a + * parameter. + * + * @hide + */ +public final class GrowingArrayUtils { + + /** + * 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. + */ + @UnsupportedAppUsage + public static <T> T[] append(T[] array, int currentSize, T element) { + assert currentSize <= array.length; + + if (currentSize + 1 > array.length) { + @SuppressWarnings("unchecked") + T[] newArray = ArrayUtils.newUnpaddedArray( + (Class<T>) array.getClass().getComponentType(), 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)}. + */ + @UnsupportedAppUsage + public static int[] append(int[] array, int currentSize, int element) { + assert currentSize <= array.length; + + if (currentSize + 1 > array.length) { + int[] newArray = ArrayUtils.newUnpaddedIntArray(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) { + assert currentSize <= array.length; + + if (currentSize + 1 > array.length) { + long[] newArray = ArrayUtils.newUnpaddedLongArray(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) { + assert currentSize <= array.length; + + if (currentSize + 1 > array.length) { + boolean[] newArray = ArrayUtils.newUnpaddedBooleanArray(growSize(currentSize)); + System.arraycopy(array, 0, newArray, 0, currentSize); + array = newArray; + } + array[currentSize] = element; + return array; + } + + /** + * Primitive float version of {@link #append(Object[], int, Object)}. + */ + public static float[] append(float[] array, int currentSize, float element) { + assert currentSize <= array.length; + + if (currentSize + 1 > array.length) { + float[] newArray = ArrayUtils.newUnpaddedFloatArray(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> T[] insert(T[] array, int currentSize, int index, T element) { + assert currentSize <= array.length; + + if (currentSize + 1 <= array.length) { + System.arraycopy(array, index, array, index + 1, currentSize - index); + array[index] = element; + return array; + } + + @SuppressWarnings("unchecked") + T[] newArray = ArrayUtils.newUnpaddedArray((Class<T>)array.getClass().getComponentType(), + 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) { + assert currentSize <= array.length; + + if (currentSize + 1 <= array.length) { + System.arraycopy(array, index, array, index + 1, currentSize - index); + array[index] = element; + return array; + } + + int[] newArray = ArrayUtils.newUnpaddedIntArray(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) { + assert currentSize <= array.length; + + if (currentSize + 1 <= array.length) { + System.arraycopy(array, index, array, index + 1, currentSize - index); + array[index] = element; + return array; + } + + long[] newArray = ArrayUtils.newUnpaddedLongArray(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) { + assert currentSize <= array.length; + + if (currentSize + 1 <= array.length) { + System.arraycopy(array, index, array, index + 1, currentSize - index); + array[index] = element; + return array; + } + + boolean[] newArray = ArrayUtils.newUnpaddedBooleanArray(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; + } + + // Uninstantiable + private GrowingArrayUtils() {} +} diff --git a/router/java/src/net/i2p/router/crypto/ratchet/SparseArray.java b/router/java/src/net/i2p/router/crypto/ratchet/SparseArray.java new file mode 100644 index 0000000000..0a15db2278 --- /dev/null +++ b/router/java/src/net/i2p/router/crypto/ratchet/SparseArray.java @@ -0,0 +1,489 @@ +/* + * 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 android.util; + +import android.annotation.UnsupportedAppUsage; + +import com.android.internal.util.ArrayUtils; +import com.android.internal.util.GrowingArrayUtils; + +import libcore.util.EmptyArray; + +/** + * <code>SparseArray</code> maps integers to Objects and, unlike a normal array of Objects, + * its indices can contain gaps. <code>SparseArray</code> is intended to be more memory-efficient + * than a + * <a href="/reference/java/util/HashMap"><code>HashMap</code></a>, because it avoids + * auto-boxing keys and its data structure doesn't rely on an extra entry object + * for each mapping. + * + * <p>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 + * <code>HashMap</code> because 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 less than 50%. + * + * <p>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 of all removed entries. This garbage collection + * must be performed whenever the array needs to be grown, or when the map size or + * entry values are retrieved. + * + * <p>It is possible to iterate over the items in this container using + * {@link #keyAt(int)} and {@link #valueAt(int)}. Iterating over the keys using + * <code>keyAt(int)</code> with ascending values of the index returns the + * keys in ascending order. In the case of <code>valueAt(int)</code>, the + * values corresponding to the keys are returned in ascending order. + */ +public class SparseArray<E> implements Cloneable { + private static final Object DELETED = new Object(); + private boolean mGarbage = false; + + @UnsupportedAppUsage(maxTargetSdk = 28) // Use keyAt(int) + private int[] mKeys; + @UnsupportedAppUsage(maxTargetSdk = 28) // Use valueAt(int), setValueAt(int, E) + private Object[] mValues; + @UnsupportedAppUsage(maxTargetSdk = 28) // Use size() + private int mSize; + + /** + * Creates a new SparseArray containing no mappings. + */ + public SparseArray() { + 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 SparseArray(int initialCapacity) { + if (initialCapacity == 0) { + mKeys = EmptyArray.INT; + mValues = EmptyArray.OBJECT; + } else { + mValues = ArrayUtils.newUnpaddedObjectArray(initialCapacity); + mKeys = new int[mValues.length]; + } + mSize = 0; + } + + @Override + @SuppressWarnings("unchecked") + public SparseArray<E> clone() { + SparseArray<E> clone = null; + try { + clone = (SparseArray<E>) super.clone(); + clone.mKeys = mKeys.clone(); + clone.mValues = mValues.clone(); + } catch (CloneNotSupportedException cnse) { + /* ignore */ + } + return clone; + } + + /** + * Gets the Object mapped from the specified key, or <code>null</code> + * 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. + */ + public void delete(int key) { + int i = ContainerHelpers.binarySearch(mKeys, mSize, key); + + if (i >= 0) { + if (mValues[i] != DELETED) { + mValues[i] = DELETED; + mGarbage = true; + } + } + } + + /** + * @hide + * Removes the mapping from the specified key, if there was any, returning the old value. + */ + 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 void remove(int key) { + delete(key); + } + + /** + * Removes the mapping at the specified index. + * + * <p>For indices outside of the range <code>0...size()-1</code>, + * the behavior is undefined for apps targeting {@link android.os.Build.VERSION_CODES#P} and + * earlier, and an {@link ArrayIndexOutOfBoundsException} is thrown for apps targeting + * {@link android.os.Build.VERSION_CODES#Q} and later.</p> + */ + public void removeAt(int index) { + if (index >= mSize && UtilConfig.sThrowExceptionForUpperArrayOutOfBounds) { + // The array might be slightly bigger than mSize, in which case, indexing won't fail. + // Check if exception should be thrown outside of the critical path. + throw new ArrayIndexOutOfBoundsException(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 + * + * <p>For indices outside of the range <code>0...size()-1</code>, + * the behavior is undefined.</p> + */ + 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("SparseArray", "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("SparseArray", "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 = GrowingArrayUtils.insert(mKeys, mSize, i, key); + mValues = GrowingArrayUtils.insert(mValues, mSize, i, value); + mSize++; + } + } + + /** + * Returns the number of key-value mappings that this SparseArray + * currently stores. + */ + public int size() { + if (mGarbage) { + gc(); + } + + return mSize; + } + + /** + * Given an index in the range <code>0...size()-1</code>, returns + * the key from the <code>index</code>th key-value mapping that this + * SparseArray stores. + * + * <p>The keys corresponding to indices in ascending order are guaranteed to + * be in ascending order, e.g., <code>keyAt(0)</code> will return the + * smallest key and <code>keyAt(size()-1)</code> will return the largest + * key.</p> + * + * <p>For indices outside of the range <code>0...size()-1</code>, + * the behavior is undefined for apps targeting {@link android.os.Build.VERSION_CODES#P} and + * earlier, and an {@link ArrayIndexOutOfBoundsException} is thrown for apps targeting + * {@link android.os.Build.VERSION_CODES#Q} and later.</p> + */ + public int keyAt(int index) { + if (index >= mSize && UtilConfig.sThrowExceptionForUpperArrayOutOfBounds) { + // The array might be slightly bigger than mSize, in which case, indexing won't fail. + // Check if exception should be thrown outside of the critical path. + throw new ArrayIndexOutOfBoundsException(index); + } + if (mGarbage) { + gc(); + } + + return mKeys[index]; + } + + /** + * Given an index in the range <code>0...size()-1</code>, returns + * the value from the <code>index</code>th key-value mapping that this + * SparseArray stores. + * + * <p>The values corresponding to indices in ascending order are guaranteed + * to be associated with keys in ascending order, e.g., + * <code>valueAt(0)</code> will return the value associated with the + * smallest key and <code>valueAt(size()-1)</code> will return the value + * associated with the largest key.</p> + * + * <p>For indices outside of the range <code>0...size()-1</code>, + * the behavior is undefined for apps targeting {@link android.os.Build.VERSION_CODES#P} and + * earlier, and an {@link ArrayIndexOutOfBoundsException} is thrown for apps targeting + * {@link android.os.Build.VERSION_CODES#Q} and later.</p> + */ + @SuppressWarnings("unchecked") + public E valueAt(int index) { + if (index >= mSize && UtilConfig.sThrowExceptionForUpperArrayOutOfBounds) { + // The array might be slightly bigger than mSize, in which case, indexing won't fail. + // Check if exception should be thrown outside of the critical path. + throw new ArrayIndexOutOfBoundsException(index); + } + if (mGarbage) { + gc(); + } + + return (E) mValues[index]; + } + + /** + * Given an index in the range <code>0...size()-1</code>, sets a new + * value for the <code>index</code>th key-value mapping that this + * SparseArray stores. + * + * <p>For indices outside of the range <code>0...size()-1</code>, the behavior is undefined for + * apps targeting {@link android.os.Build.VERSION_CODES#P} and earlier, and an + * {@link ArrayIndexOutOfBoundsException} is thrown for apps targeting + * {@link android.os.Build.VERSION_CODES#Q} and later.</p> + */ + public void setValueAt(int index, E value) { + if (index >= mSize && UtilConfig.sThrowExceptionForUpperArrayOutOfBounds) { + // The array might be slightly bigger than mSize, in which case, indexing won't fail. + // Check if exception should be thrown outside of the critical path. + throw new ArrayIndexOutOfBoundsException(index); + } + 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 value, or a negative number if no keys map to the + * specified value. + * <p>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. + * <p>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; + } + + /** + * Returns an index for which {@link #valueAt} would return the + * specified value, or a negative number if no keys map to the + * specified value. + * <p>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. + * <p>Note also that this method uses {@code equals} unlike {@code indexOfValue}. + * @hide + */ + public int indexOfValueByValue(E value) { + if (mGarbage) { + gc(); + } + + for (int i = 0; i < mSize; i++) { + if (value == null) { + if (mValues[i] == null) { + return i; + } + } else { + if (value.equals(mValues[i])) { + return i; + } + } + } + return -1; + } + + /** + * Removes all key-value mappings from this SparseArray. + */ + 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 = GrowingArrayUtils.append(mKeys, mSize, key); + mValues = GrowingArrayUtils.append(mValues, mSize, value); + mSize++; + } + + /** + * {@inheritDoc} + * + * <p>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<mSize; i++) { + if (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(); + } +} -- GitLab