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 }