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