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