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 * @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 }