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