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