View Javadoc
1   /*
2    * Copyright (C) 2011, Google Inc.
3    * and other copyright owners as documented in the project's IP log.
4    *
5    * This program and the accompanying materials are made available
6    * under the terms of the Eclipse Distribution License v1.0 which
7    * accompanies this distribution, is reproduced below, and is
8    * available at http://www.eclipse.org/org/documents/edl-v10.php
9    *
10   * All rights reserved.
11   *
12   * Redistribution and use in source and binary forms, with or
13   * without modification, are permitted provided that the following
14   * conditions are met:
15   *
16   * - Redistributions of source code must retain the above copyright
17   *   notice, this list of conditions and the following disclaimer.
18   *
19   * - Redistributions in binary form must reproduce the above
20   *   copyright notice, this list of conditions and the following
21   *   disclaimer in the documentation and/or other materials provided
22   *   with the distribution.
23   *
24   * - Neither the name of the Eclipse Foundation, Inc. nor the
25   *   names of its contributors may be used to endorse or promote
26   *   products derived from this software without specific prior
27   *   written permission.
28   *
29   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
30   * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
31   * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
32   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
33   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
34   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
35   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
36   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
37   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
38   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
39   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
40   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
41   * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
42   */
43  
44  package org.eclipse.jgit.util;
45  
46  import java.util.AbstractList;
47  import java.util.Arrays;
48  import java.util.Iterator;
49  import java.util.NoSuchElementException;
50  
51  /**
52   * Random access list that allocates entries in blocks.
53   * <p>
54   * Unlike {@link java.util.ArrayList}, this type does not need to reallocate the
55   * internal array in order to expand the capacity of the list. Access to any
56   * element is constant time, but requires two array lookups instead of one.
57   * <p>
58   * To handle common usages, {@link #add(Object)} and {@link #iterator()} use
59   * internal code paths to amortize out the second array lookup, making addition
60   * and simple iteration closer to one array operation per element processed.
61   * <p>
62   * Similar to {@code ArrayList}, adding or removing from any position except the
63   * end of the list requires O(N) time to copy all elements between the
64   * modification point and the end of the list. Applications are strongly
65   * encouraged to not use this access pattern with this list implementation.
66   *
67   * @param <T>
68   *            type of list element.
69   */
70  public class BlockList<T> extends AbstractList<T> {
71  	private static final int BLOCK_BITS = 10;
72  
73  	static final int BLOCK_SIZE = 1 << BLOCK_BITS;
74  
75  	private static final int BLOCK_MASK = BLOCK_SIZE - 1;
76  
77  	private T[][] directory;
78  
79  	private int size;
80  
81  	private int tailDirIdx;
82  
83  	private int tailBlkIdx;
84  
85  	private T[] tailBlock;
86  
87  	/** Initialize an empty list. */
88  	public BlockList() {
89  		directory = BlockList.<T> newDirectory(256);
90  		directory[0] = BlockList.<T> newBlock();
91  		tailBlock = directory[0];
92  	}
93  
94  	/**
95  	 * Initialize an empty list with an expected capacity.
96  	 *
97  	 * @param capacity
98  	 *            number of elements expected to be in the list.
99  	 */
100 	public BlockList(int capacity) {
101 		int dirSize = toDirectoryIndex(capacity);
102 		if ((capacity & BLOCK_MASK) != 0 || dirSize == 0)
103 			dirSize++;
104 		directory = BlockList.<T> newDirectory(dirSize);
105 		directory[0] = BlockList.<T> newBlock();
106 		tailBlock = directory[0];
107 	}
108 
109 	@Override
110 	public int size() {
111 		return size;
112 	}
113 
114 	@Override
115 	public void clear() {
116 		for (T[] block : directory) {
117 			if (block != null)
118 				Arrays.fill(block, null);
119 		}
120 		size = 0;
121 		tailDirIdx = 0;
122 		tailBlkIdx = 0;
123 		tailBlock = directory[0];
124 	}
125 
126 	@Override
127 	public T get(int index) {
128 		if (index < 0 || size <= index)
129 			throw new IndexOutOfBoundsException(String.valueOf(index));
130 		return directory[toDirectoryIndex(index)][toBlockIndex(index)];
131 	}
132 
133 	@Override
134 	public T set(int index, T element) {
135 		if (index < 0 || size <= index)
136 			throw new IndexOutOfBoundsException(String.valueOf(index));
137 		T[] blockRef = directory[toDirectoryIndex(index)];
138 		int blockIdx = toBlockIndex(index);
139 		T old = blockRef[blockIdx];
140 		blockRef[blockIdx] = element;
141 		return old;
142 	}
143 
144 	/**
145 	 * Quickly append all elements of another BlockList.
146 	 *
147 	 * @param src
148 	 *            the list to copy elements from.
149 	 */
150 	public void addAll(BlockList<T> src) {
151 		if (src.size == 0)
152 			return;
153 
154 		int srcDirIdx = 0;
155 		for (; srcDirIdx < src.tailDirIdx; srcDirIdx++)
156 			addAll(src.directory[srcDirIdx], 0, BLOCK_SIZE);
157 		if (src.tailBlkIdx != 0)
158 			addAll(src.tailBlock, 0, src.tailBlkIdx);
159 	}
160 
161 	/**
162 	 * Quickly append all elements from an array.
163 	 *
164 	 * @param src
165 	 *            the source array.
166 	 * @param srcIdx
167 	 *            first index to copy.
168 	 * @param srcCnt
169 	 *            number of elements to copy.
170 	 */
171 	public void addAll(T[] src, int srcIdx, int srcCnt) {
172 		while (0 < srcCnt) {
173 			int i = tailBlkIdx;
174 			int n = Math.min(srcCnt, BLOCK_SIZE - i);
175 			if (n == 0) {
176 				// Our tail is full, expand by one.
177 				add(src[srcIdx++]);
178 				srcCnt--;
179 				continue;
180 			}
181 
182 			System.arraycopy(src, srcIdx, tailBlock, i, n);
183 			tailBlkIdx += n;
184 			size += n;
185 			srcIdx += n;
186 			srcCnt -= n;
187 		}
188 	}
189 
190 	@Override
191 	public boolean add(T element) {
192 		int i = tailBlkIdx;
193 		if (i < BLOCK_SIZE) {
194 			// Fast-path: Append to current tail block.
195 			tailBlock[i] = element;
196 			tailBlkIdx = i + 1;
197 			size++;
198 			return true;
199 		}
200 
201 		// Slow path: Move to the next block, expanding if necessary.
202 		if (++tailDirIdx == directory.length) {
203 			T[][] newDir = BlockList.<T> newDirectory(directory.length << 1);
204 			System.arraycopy(directory, 0, newDir, 0, directory.length);
205 			directory = newDir;
206 		}
207 
208 		T[] blockRef = directory[tailDirIdx];
209 		if (blockRef == null) {
210 			blockRef = BlockList.<T> newBlock();
211 			directory[tailDirIdx] = blockRef;
212 		}
213 		blockRef[0] = element;
214 		tailBlock = blockRef;
215 		tailBlkIdx = 1;
216 		size++;
217 		return true;
218 	}
219 
220 	@Override
221 	public void add(int index, T element) {
222 		if (index == size) {
223 			// Fast-path: append onto the end of the list.
224 			add(element);
225 
226 		} else if (index < 0 || size < index) {
227 			throw new IndexOutOfBoundsException(String.valueOf(index));
228 
229 		} else {
230 			// Slow-path: the list needs to expand and insert.
231 			// Do this the naive way, callers shouldn't abuse
232 			// this class by entering this code path.
233 			//
234 			add(null); // expand the list by one
235 			for (int oldIdx = size - 2; index <= oldIdx; oldIdx--)
236 				set(oldIdx + 1, get(oldIdx));
237 			set(index, element);
238 		}
239 	}
240 
241 	@Override
242 	public T remove(int index) {
243 		if (index == size - 1) {
244 			// Fast-path: remove the last element.
245 			T[] blockRef = directory[toDirectoryIndex(index)];
246 			int blockIdx = toBlockIndex(index);
247 			T old = blockRef[blockIdx];
248 			blockRef[blockIdx] = null;
249 			size--;
250 			if (0 < tailBlkIdx)
251 				tailBlkIdx--;
252 			else
253 				resetTailBlock();
254 			return old;
255 
256 		} else if (index < 0 || size <= index) {
257 			throw new IndexOutOfBoundsException(String.valueOf(index));
258 
259 		} else {
260 			// Slow-path: the list needs to contract and remove.
261 			// Do this the naive way, callers shouldn't abuse
262 			// this class by entering this code path.
263 			//
264 			T old = get(index);
265 			for (; index < size - 1; index++)
266 				set(index, get(index + 1));
267 			set(size - 1, null);
268 			size--;
269 			resetTailBlock();
270 			return old;
271 		}
272 	}
273 
274 	private void resetTailBlock() {
275 		tailDirIdx = toDirectoryIndex(size);
276 		tailBlkIdx = toBlockIndex(size);
277 		tailBlock = directory[tailDirIdx];
278 	}
279 
280 	@Override
281 	public Iterator<T> iterator() {
282 		return new MyIterator();
283 	}
284 
285 	private static final int toDirectoryIndex(int index) {
286 		return index >>> BLOCK_BITS;
287 	}
288 
289 	private static final int toBlockIndex(int index) {
290 		return index & BLOCK_MASK;
291 	}
292 
293 	@SuppressWarnings("unchecked")
294 	private static <T> T[][] newDirectory(int size) {
295 		return (T[][]) new Object[size][];
296 	}
297 
298 	@SuppressWarnings("unchecked")
299 	private static <T> T[] newBlock() {
300 		return (T[]) new Object[BLOCK_SIZE];
301 	}
302 
303 	private class MyIterator implements Iterator<T> {
304 		private int index;
305 
306 		private int dirIdx;
307 
308 		private int blkIdx;
309 
310 		private T[] block = directory[0];
311 
312 		public boolean hasNext() {
313 			return index < size;
314 		}
315 
316 		public T next() {
317 			if (size <= index)
318 				throw new NoSuchElementException();
319 
320 			T res = block[blkIdx];
321 			if (++blkIdx == BLOCK_SIZE) {
322 				if (++dirIdx < directory.length)
323 					block = directory[dirIdx];
324 				else
325 					block = null;
326 				blkIdx = 0;
327 			}
328 			index++;
329 			return res;
330 		}
331 
332 		public void remove() {
333 			if (index == 0)
334 				throw new IllegalStateException();
335 
336 			BlockList.this.remove(--index);
337 
338 			dirIdx = toDirectoryIndex(index);
339 			blkIdx = toBlockIndex(index);
340 			block = directory[dirIdx];
341 		}
342 	}
343 }