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<String,Ref> and of a List<Ref>. 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<>() {
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 }