1/* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 * in compliance with the License. You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software distributed under the License 10 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 * or implied. See the License for the specific language governing permissions and limitations under 12 * the License. 13 */ 14package android.support.v4.util; 15 16/** 17 * CircularArray is a generic circular array data structure that provides O(1) random read, O(1) 18 * prepend and O(1) append. The CircularArray automatically grows its capacity when number of added 19 * items is over its capacity. 20 */ 21public final class CircularArray<E> { 22 private E[] mElements; 23 private int mHead; 24 private int mTail; 25 private int mCapacityBitmask; 26 27 private void doubleCapacity() { 28 int n = mElements.length; 29 int r = n - mHead; 30 int newCapacity = n << 1; 31 if (newCapacity < 0) { 32 throw new RuntimeException("Max array capacity exceeded"); 33 } 34 Object[] a = new Object[newCapacity]; 35 System.arraycopy(mElements, mHead, a, 0, r); 36 System.arraycopy(mElements, 0, a, r, mHead); 37 mElements = (E[]) a; 38 mHead = 0; 39 mTail = n; 40 mCapacityBitmask = newCapacity - 1; 41 } 42 43 /** 44 * Creates a circular array with default capacity. 45 */ 46 public CircularArray() { 47 this(8); 48 } 49 50 /** 51 * Creates a circular array with capacity for at least {@code minCapacity} 52 * elements. 53 * 54 * @param minCapacity the minimum capacity, between 1 and 2^30 inclusive 55 */ 56 public CircularArray(int minCapacity) { 57 if (minCapacity < 1) { 58 throw new IllegalArgumentException("capacity must be >= 1"); 59 } 60 if (minCapacity > (2 << 29)) { 61 throw new IllegalArgumentException("capacity must be <= 2^30"); 62 } 63 64 // If minCapacity isn't a power of 2, round up to the next highest 65 // power of 2. 66 final int arrayCapacity; 67 if (Integer.bitCount(minCapacity) != 1) { 68 arrayCapacity = Integer.highestOneBit(minCapacity - 1) << 1; 69 } else { 70 arrayCapacity = minCapacity; 71 } 72 73 mCapacityBitmask = arrayCapacity - 1; 74 mElements = (E[]) new Object[arrayCapacity]; 75 } 76 77 /** 78 * Add an element in front of the CircularArray. 79 * @param e Element to add. 80 */ 81 public void addFirst(E e) { 82 mHead = (mHead - 1) & mCapacityBitmask; 83 mElements[mHead] = e; 84 if (mHead == mTail) { 85 doubleCapacity(); 86 } 87 } 88 89 /** 90 * Add an element at end of the CircularArray. 91 * @param e Element to add. 92 */ 93 public void addLast(E e) { 94 mElements[mTail] = e; 95 mTail = (mTail + 1) & mCapacityBitmask; 96 if (mTail == mHead) { 97 doubleCapacity(); 98 } 99 } 100 101 /** 102 * Remove first element from front of the CircularArray and return it. 103 * @return The element removed. 104 * @throws ArrayIndexOutOfBoundsException if CircularArray is empty. 105 */ 106 public E popFirst() { 107 if (mHead == mTail) { 108 throw new ArrayIndexOutOfBoundsException(); 109 } 110 E result = mElements[mHead]; 111 mElements[mHead] = null; 112 mHead = (mHead + 1) & mCapacityBitmask; 113 return result; 114 } 115 116 /** 117 * Remove last element from end of the CircularArray and return it. 118 * @return The element removed. 119 * @throws ArrayIndexOutOfBoundsException if CircularArray is empty. 120 */ 121 public E popLast() { 122 if (mHead == mTail) { 123 throw new ArrayIndexOutOfBoundsException(); 124 } 125 int t = (mTail - 1) & mCapacityBitmask; 126 E result = mElements[t]; 127 mElements[t] = null; 128 mTail = t; 129 return result; 130 } 131 132 /** 133 * Remove all elements from the CircularArray. 134 */ 135 public void clear() { 136 removeFromStart(size()); 137 } 138 139 /** 140 * Remove multiple elements from front of the CircularArray, ignore when numOfElements 141 * is less than or equals to 0. 142 * @param numOfElements Number of elements to remove. 143 * @throws ArrayIndexOutOfBoundsException if numOfElements is larger than 144 * {@link #size()} 145 */ 146 public void removeFromStart(int numOfElements) { 147 if (numOfElements <= 0) { 148 return; 149 } 150 if (numOfElements > size()) { 151 throw new ArrayIndexOutOfBoundsException(); 152 } 153 int end = mElements.length; 154 if (numOfElements < end - mHead) { 155 end = mHead + numOfElements; 156 } 157 for (int i = mHead; i < end; i++) { 158 mElements[i] = null; 159 } 160 int removed = (end - mHead); 161 numOfElements -= removed; 162 mHead = (mHead + removed) & mCapacityBitmask; 163 if (numOfElements > 0) { 164 // mHead wrapped to 0 165 for (int i = 0; i < numOfElements; i++) { 166 mElements[i] = null; 167 } 168 mHead = numOfElements; 169 } 170 } 171 172 /** 173 * Remove multiple elements from end of the CircularArray, ignore when numOfElements 174 * is less than or equals to 0. 175 * @param numOfElements Number of elements to remove. 176 * @throws ArrayIndexOutOfBoundsException if numOfElements is larger than 177 * {@link #size()} 178 */ 179 public void removeFromEnd(int numOfElements) { 180 if (numOfElements <= 0) { 181 return; 182 } 183 if (numOfElements > size()) { 184 throw new ArrayIndexOutOfBoundsException(); 185 } 186 int start = 0; 187 if (numOfElements < mTail) { 188 start = mTail - numOfElements; 189 } 190 for (int i = start; i < mTail; i++) { 191 mElements[i] = null; 192 } 193 int removed = (mTail - start); 194 numOfElements -= removed; 195 mTail = mTail - removed; 196 if (numOfElements > 0) { 197 // mTail wrapped to mElements.length 198 mTail = mElements.length; 199 int newTail = mTail - numOfElements; 200 for (int i = newTail; i < mTail; i++) { 201 mElements[i] = null; 202 } 203 mTail = newTail; 204 } 205 } 206 207 /** 208 * Get first element of the CircularArray. 209 * @return The first element. 210 * @throws {@link ArrayIndexOutOfBoundsException} if CircularArray is empty. 211 */ 212 public E getFirst() { 213 if (mHead == mTail) { 214 throw new ArrayIndexOutOfBoundsException(); 215 } 216 return mElements[mHead]; 217 } 218 219 /** 220 * Get last element of the CircularArray. 221 * @return The last element. 222 * @throws {@link ArrayIndexOutOfBoundsException} if CircularArray is empty. 223 */ 224 public E getLast() { 225 if (mHead == mTail) { 226 throw new ArrayIndexOutOfBoundsException(); 227 } 228 return mElements[(mTail - 1) & mCapacityBitmask]; 229 } 230 231 /** 232 * Get nth (0 <= n <= size()-1) element of the CircularArray. 233 * @param n The zero based element index in the CircularArray. 234 * @return The nth element. 235 * @throws {@link ArrayIndexOutOfBoundsException} if n < 0 or n >= size(). 236 */ 237 public E get(int n) { 238 if (n < 0 || n >= size()) { 239 throw new ArrayIndexOutOfBoundsException(); 240 } 241 return mElements[(mHead + n) & mCapacityBitmask]; 242 } 243 244 /** 245 * Get number of elements in the CircularArray. 246 * @return Number of elements in the CircularArray. 247 */ 248 public int size() { 249 return (mTail - mHead) & mCapacityBitmask; 250 } 251 252 /** 253 * Return true if size() is 0. 254 * @return true if size() is 0. 255 */ 256 public boolean isEmpty() { 257 return mHead == mTail; 258 } 259 260} 261