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