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