1 /* 2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.net.module.util; 18 19 import android.annotation.NonNull; 20 21 import com.android.internal.annotations.VisibleForTesting; 22 23 import java.util.Arrays; 24 import java.util.StringJoiner; 25 import java.util.function.IntConsumer; 26 import java.util.function.IntPredicate; 27 28 /** 29 * A growing array of primitive ints. 30 * 31 * <p>This is similar to ArrayList<Integer>, but avoids the cost of boxing (each Integer costs 32 * 16 bytes) and creation / garbage collection of individual Integer objects. 33 * 34 * <p>This class does not use any heuristic for growing capacity, so every call to 35 * {@link #add(int)} may reallocate the backing array. Callers should use 36 * {@link #ensureHasCapacity(int)} to minimize this behavior when they plan to add several values. 37 */ 38 public class GrowingIntArray { 39 private int[] mValues; 40 private int mLength; 41 42 /** 43 * Create an empty GrowingIntArray with the given capacity. 44 */ GrowingIntArray(int initialCapacity)45 public GrowingIntArray(int initialCapacity) { 46 mValues = new int[initialCapacity]; 47 mLength = 0; 48 } 49 50 /** 51 * Create a GrowingIntArray with an initial array of values. 52 * 53 * <p>The array will be used as-is and may be modified, so callers must stop using it after 54 * calling this constructor. 55 */ GrowingIntArray(int[] initialValues)56 protected GrowingIntArray(int[] initialValues) { 57 mValues = initialValues; 58 mLength = initialValues.length; 59 } 60 61 /** 62 * Add a value to the array. 63 */ add(int value)64 public void add(int value) { 65 ensureHasCapacity(1); 66 mValues[mLength] = value; 67 mLength++; 68 } 69 70 /** 71 * Get the current number of values in the array. 72 */ length()73 public int length() { 74 return mLength; 75 } 76 77 /** 78 * Get the value at a given index. 79 * 80 * @throws ArrayIndexOutOfBoundsException if the index is out of bounds. 81 */ get(int index)82 public int get(int index) { 83 if (index < 0 || index >= mLength) { 84 throw new ArrayIndexOutOfBoundsException(index); 85 } 86 return mValues[index]; 87 } 88 89 /** 90 * Iterate over all values in the array. 91 */ forEach(@onNull IntConsumer consumer)92 public void forEach(@NonNull IntConsumer consumer) { 93 for (int i = 0; i < mLength; i++) { 94 consumer.accept(mValues[i]); 95 } 96 } 97 98 /** 99 * Remove all values matching a predicate. 100 * 101 * @return true if at least one value was removed. 102 */ removeValues(@onNull IntPredicate predicate)103 public boolean removeValues(@NonNull IntPredicate predicate) { 104 int newQueueLength = 0; 105 for (int i = 0; i < mLength; i++) { 106 final int cb = mValues[i]; 107 if (!predicate.test(cb)) { 108 mValues[newQueueLength] = cb; 109 newQueueLength++; 110 } 111 } 112 if (mLength != newQueueLength) { 113 mLength = newQueueLength; 114 return true; 115 } 116 return false; 117 } 118 119 /** 120 * Indicates whether the array contains the given value. 121 */ contains(int value)122 public boolean contains(int value) { 123 for (int i = 0; i < mLength; i++) { 124 if (mValues[i] == value) { 125 return true; 126 } 127 } 128 return false; 129 } 130 131 /** 132 * Remove all values from the array. 133 */ clear()134 public void clear() { 135 mLength = 0; 136 } 137 138 /** 139 * Ensure at least the given number of values can be added to the array without reallocating. 140 * 141 * @param capacity The minimum number of additional values the array must be able to hold. 142 */ ensureHasCapacity(int capacity)143 public void ensureHasCapacity(int capacity) { 144 if (mValues.length >= mLength + capacity) { 145 return; 146 } 147 mValues = Arrays.copyOf(mValues, mLength + capacity); 148 } 149 150 @VisibleForTesting getBackingArrayLength()151 int getBackingArrayLength() { 152 return mValues.length; 153 } 154 155 /** 156 * Shrink the array backing this class to the minimum required length. 157 */ shrinkToLength()158 public void shrinkToLength() { 159 if (mValues.length != mLength) { 160 mValues = Arrays.copyOf(mValues, mLength); 161 } 162 } 163 164 /** 165 * Get values as array by shrinking the internal array to length and returning it. 166 * 167 * <p>This avoids reallocations if the array is already the correct length, but callers should 168 * stop using this instance of {@link GrowingIntArray} if they use the array returned by this 169 * method. 170 */ getMinimizedBackingArray()171 public int[] getMinimizedBackingArray() { 172 shrinkToLength(); 173 return mValues; 174 } 175 176 /** 177 * Get the String representation of an item in the array, for use by {@link #toString()}. 178 */ valueToString(int item)179 protected String valueToString(int item) { 180 return String.valueOf(item); 181 } 182 183 @NonNull 184 @Override toString()185 public String toString() { 186 final StringJoiner joiner = new StringJoiner(",", "[", "]"); 187 forEach(item -> joiner.add(valueToString(item))); 188 return joiner.toString(); 189 } 190 } 191