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 }