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 }