View Javadoc
1   /*
2    * Copyright (C) 2010, 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.Arrays;
47  import java.util.Collections;
48  import java.util.Iterator;
49  import java.util.List;
50  import java.util.NoSuchElementException;
51  
52  import org.eclipse.jgit.lib.Ref;
53  import org.eclipse.jgit.lib.RefComparator;
54  
55  /**
56   * Specialized variant of an ArrayList to support a {@code RefDatabase}.
57   * <p>
58   * This list is a hybrid of a Map&lt;String,Ref&gt; and of a List&lt;Ref&gt;. It
59   * tracks reference instances by name by keeping them sorted and performing
60   * binary search to locate an entry. Lookup time is O(log N), but addition and
61   * removal is O(N + log N) due to the list expansion or contraction costs.
62   * <p>
63   * This list type is copy-on-write. Mutation methods return a new copy of the
64   * list, leaving {@code this} unmodified. As a result we cannot easily implement
65   * the {@link java.util.List} interface contract.
66   *
67   * @param <T>
68   *            the type of reference being stored in the collection.
69   */
70  public class RefList<T extends Ref> implements Iterable<Ref> {
71  	private static final RefList<Ref> EMPTY = new RefList<Ref>(new Ref[0], 0);
72  
73  	/**
74  	 * @return an empty unmodifiable reference list.
75  	 * @param <T>
76  	 *            the type of reference being stored in the collection.
77  	 */
78  	@SuppressWarnings("unchecked")
79  	public static <T extends Ref> RefList<T> emptyList() {
80  		return (RefList<T>) EMPTY;
81  	}
82  
83  	final Ref[] list;
84  
85  	final int cnt;
86  
87  	RefList(Ref[] list, int cnt) {
88  		this.list = list;
89  		this.cnt = cnt;
90  	}
91  
92  	/**
93  	 * Initialize this list to use the same backing array as another list.
94  	 *
95  	 * @param src
96  	 *            the source list.
97  	 */
98  	protected RefList(RefList<T> src) {
99  		this.list = src.list;
100 		this.cnt = src.cnt;
101 	}
102 
103 	public Iterator<Ref> iterator() {
104 		return new Iterator<Ref>() {
105 			private int idx;
106 
107 			public boolean hasNext() {
108 				return idx < cnt;
109 			}
110 
111 			public Ref next() {
112 				if (idx < cnt)
113 					return list[idx++];
114 				throw new NoSuchElementException();
115 			}
116 
117 			public void remove() {
118 				throw new UnsupportedOperationException();
119 			}
120 		};
121 	}
122 
123 	/** @return this cast as an immutable, standard {@link java.util.List}. */
124 	public final List<Ref> asList() {
125 		final List<Ref> r = Arrays.asList(list).subList(0, cnt);
126 		return Collections.unmodifiableList(r);
127 	}
128 
129 	/** @return number of items in this list. */
130 	public final int size() {
131 		return cnt;
132 	}
133 
134 	/** @return true if the size of this list is 0. */
135 	public final boolean isEmpty() {
136 		return cnt == 0;
137 	}
138 
139 	/**
140 	 * Locate an entry by name.
141 	 *
142 	 * @param name
143 	 *            the name of the reference to find.
144 	 * @return the index the reference is at. If the entry is not present
145 	 *         returns a negative value. The insertion position for the given
146 	 *         name can be computed from {@code -(index + 1)}.
147 	 */
148 	public final int find(String name) {
149 		int high = cnt;
150 		if (high == 0)
151 			return -1;
152 		int low = 0;
153 		do {
154 			final int mid = (low + high) >>> 1;
155 			final int cmp = RefComparator.compareTo(list[mid], name);
156 			if (cmp < 0)
157 				low = mid + 1;
158 			else if (cmp == 0)
159 				return mid;
160 			else
161 				high = mid;
162 		} while (low < high);
163 		return -(low + 1);
164 	}
165 
166 	/**
167 	 * Determine if a reference is present.
168 	 *
169 	 * @param name
170 	 *            name of the reference to find.
171 	 * @return true if the reference is present; false if it is not.
172 	 */
173 	public final boolean contains(String name) {
174 		return 0 <= find(name);
175 	}
176 
177 	/**
178 	 * Get a reference object by name.
179 	 *
180 	 * @param name
181 	 *            the name of the reference.
182 	 * @return the reference object; null if it does not exist in this list.
183 	 */
184 	public final T get(String name) {
185 		int idx = find(name);
186 		return 0 <= idx ? get(idx) : null;
187 	}
188 
189 	/**
190 	 * Get the reference at a particular index.
191 	 *
192 	 * @param idx
193 	 *            the index to obtain. Must be {@code 0 <= idx < size()}.
194 	 * @return the reference value, never null.
195 	 */
196 	@SuppressWarnings("unchecked")
197 	public final T get(int idx) {
198 		return (T) list[idx];
199 	}
200 
201 	/**
202 	 * Obtain a builder initialized with the first {@code n} elements.
203 	 * <p>
204 	 * Copies the first {@code n} elements from this list into a new builder,
205 	 * which can be used by the caller to add additional elements.
206 	 *
207 	 * @param n
208 	 *            the number of elements to copy.
209 	 * @return a new builder with the first {@code n} elements already added.
210 	 */
211 	public final Builder<T> copy(int n) {
212 		Builder<T> r = new Builder<T>(Math.max(16, n));
213 		r.addAll(list, 0, n);
214 		return r;
215 	}
216 
217 	/**
218 	 * Obtain a new copy of the list after changing one element.
219 	 * <p>
220 	 * This list instance is not affected by the replacement. Because this
221 	 * method copies the entire list, it runs in O(N) time.
222 	 *
223 	 * @param idx
224 	 *            index of the element to change.
225 	 * @param ref
226 	 *            the new value, must not be null.
227 	 * @return copy of this list, after replacing {@code idx} with {@code ref} .
228 	 */
229 	public final RefList<T> set(int idx, T ref) {
230 		Ref[] newList = new Ref[cnt];
231 		System.arraycopy(list, 0, newList, 0, cnt);
232 		newList[idx] = ref;
233 		return new RefList<T>(newList, cnt);
234 	}
235 
236 	/**
237 	 * Add an item at a specific index.
238 	 * <p>
239 	 * This list instance is not affected by the addition. Because this method
240 	 * copies the entire list, it runs in O(N) time.
241 	 *
242 	 * @param idx
243 	 *            position to add the item at. If negative the method assumes it
244 	 *            was a direct return value from {@link #find(String)} and will
245 	 *            adjust it to the correct position.
246 	 * @param ref
247 	 *            the new reference to insert.
248 	 * @return copy of this list, after making space for and adding {@code ref}.
249 	 */
250 	public final RefList<T> add(int idx, T ref) {
251 		if (idx < 0)
252 			idx = -(idx + 1);
253 
254 		Ref[] newList = new Ref[cnt + 1];
255 		if (0 < idx)
256 			System.arraycopy(list, 0, newList, 0, idx);
257 		newList[idx] = ref;
258 		if (idx < cnt)
259 			System.arraycopy(list, idx, newList, idx + 1, cnt - idx);
260 		return new RefList<T>(newList, cnt + 1);
261 	}
262 
263 	/**
264 	 * Remove an item at a specific index.
265 	 * <p>
266 	 * This list instance is not affected by the addition. Because this method
267 	 * copies the entire list, it runs in O(N) time.
268 	 *
269 	 * @param idx
270 	 *            position to remove the item from.
271 	 * @return copy of this list, after making removing the item at {@code idx}.
272 	 */
273 	public final RefList<T> remove(int idx) {
274 		if (cnt == 1)
275 			return emptyList();
276 		Ref[] newList = new Ref[cnt - 1];
277 		if (0 < idx)
278 			System.arraycopy(list, 0, newList, 0, idx);
279 		if (idx + 1 < cnt)
280 			System.arraycopy(list, idx + 1, newList, idx, cnt - (idx + 1));
281 		return new RefList<T>(newList, cnt - 1);
282 	}
283 
284 	/**
285 	 * Store a reference, adding or replacing as necessary.
286 	 * <p>
287 	 * This list instance is not affected by the store. The correct position is
288 	 * determined, and the item is added if missing, or replaced if existing.
289 	 * Because this method copies the entire list, it runs in O(N + log N) time.
290 	 *
291 	 * @param ref
292 	 *            the reference to store.
293 	 * @return copy of this list, after performing the addition or replacement.
294 	 */
295 	public final RefList<T> put(T ref) {
296 		int idx = find(ref.getName());
297 		if (0 <= idx)
298 			return set(idx, ref);
299 		return add(idx, ref);
300 	}
301 
302 	@Override
303 	public String toString() {
304 		StringBuilder r = new StringBuilder();
305 		r.append('[');
306 		if (cnt > 0) {
307 			r.append(list[0]);
308 			for (int i = 1; i < cnt; i++) {
309 				r.append(", "); //$NON-NLS-1$
310 				r.append(list[i]);
311 			}
312 		}
313 		r.append(']');
314 		return r.toString();
315 	}
316 
317 	/**
318 	 * Builder to facilitate fast construction of an immutable RefList.
319 	 *
320 	 * @param <T>
321 	 *            type of reference being stored.
322 	 */
323 	public static class Builder<T extends Ref> {
324 		private Ref[] list;
325 
326 		private int size;
327 
328 		/** Create an empty list ready for items to be added. */
329 		public Builder() {
330 			this(16);
331 		}
332 
333 		/**
334 		 * Create an empty list with at least the specified capacity.
335 		 *
336 		 * @param capacity
337 		 *            the new capacity.
338 		 */
339 		public Builder(int capacity) {
340 			list = new Ref[capacity];
341 		}
342 
343 		/** @return number of items in this builder's internal collection. */
344 		public int size() {
345 			return size;
346 		}
347 
348 		/**
349 		 * Get the reference at a particular index.
350 		 *
351 		 * @param idx
352 		 *            the index to obtain. Must be {@code 0 <= idx < size()}.
353 		 * @return the reference value, never null.
354 		 */
355 		@SuppressWarnings("unchecked")
356 		public T get(int idx) {
357 			return (T) list[idx];
358 		}
359 
360 		/**
361 		 * Remove an item at a specific index.
362 		 *
363 		 * @param idx
364 		 *            position to remove the item from.
365 		 */
366 		public void remove(int idx) {
367 			System.arraycopy(list, idx + 1, list, idx, size - (idx + 1));
368 			size--;
369 		}
370 
371 		/**
372 		 * Add the reference to the end of the array.
373 		 * <p>
374 		 * References must be added in sort order, or the array must be sorted
375 		 * after additions are complete using {@link #sort()}.
376 		 *
377 		 * @param ref
378 		 */
379 		public void add(T ref) {
380 			if (list.length == size) {
381 				Ref[] n = new Ref[size * 2];
382 				System.arraycopy(list, 0, n, 0, size);
383 				list = n;
384 			}
385 			list[size++] = ref;
386 		}
387 
388 		/**
389 		 * Add all items from a source array.
390 		 * <p>
391 		 * References must be added in sort order, or the array must be sorted
392 		 * after additions are complete using {@link #sort()}.
393 		 *
394 		 * @param src
395 		 *            the source array.
396 		 * @param off
397 		 *            position within {@code src} to start copying from.
398 		 * @param cnt
399 		 *            number of items to copy from {@code src}.
400 		 */
401 		public void addAll(Ref[] src, int off, int cnt) {
402 			if (list.length < size + cnt) {
403 				Ref[] n = new Ref[Math.max(size * 2, size + cnt)];
404 				System.arraycopy(list, 0, n, 0, size);
405 				list = n;
406 			}
407 			System.arraycopy(src, off, list, size, cnt);
408 			size += cnt;
409 		}
410 
411 		/**
412 		 * Replace a single existing element.
413 		 *
414 		 * @param idx
415 		 *            index, must have already been added previously.
416 		 * @param ref
417 		 *            the new reference.
418 		 */
419 		public void set(int idx, T ref) {
420 			list[idx] = ref;
421 		}
422 
423 		/** Sort the list's backing array in-place. */
424 		public void sort() {
425 			Arrays.sort(list, 0, size, RefComparator.INSTANCE);
426 		}
427 
428 		/** @return an unmodifiable list using this collection's backing array. */
429 		public RefList<T> toRefList() {
430 			return new RefList<T>(list, size);
431 		}
432 
433 		@Override
434 		public String toString() {
435 			return toRefList().toString();
436 		}
437 	}
438 }