1 /* 2 * Copyright (C) 2011, 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.lib; 45 46 import java.util.Arrays; 47 import java.util.Iterator; 48 import java.util.NoSuchElementException; 49 50 /** 51 * Fast, efficient map for {@link org.eclipse.jgit.lib.ObjectId} subclasses in 52 * only one map. 53 * <p> 54 * To use this map type, applications must have their entry value type extend 55 * from {@link org.eclipse.jgit.lib.ObjectIdOwnerMap.Entry}, which itself 56 * extends from ObjectId. 57 * <p> 58 * Object instances may only be stored in <b>ONE</b> ObjectIdOwnerMap. This 59 * restriction exists because the map stores internal map state within each 60 * object instance. If an instance is be placed in another ObjectIdOwnerMap it 61 * could corrupt one or both map's internal state. 62 * <p> 63 * If an object instance must be in more than one map, applications may use 64 * ObjectIdOwnerMap for one of the maps, and 65 * {@link org.eclipse.jgit.lib.ObjectIdSubclassMap} for the other map(s). It is 66 * encouraged to use ObjectIdOwnerMap for the map that is accessed most often, 67 * as this implementation runs faster than the more general ObjectIdSubclassMap 68 * implementation. 69 * 70 * @param <V> 71 * type of subclass of ObjectId that will be stored in the map. 72 */ 73 public class ObjectIdOwnerMap<V extends ObjectIdOwnerMap.Entry> 74 implements Iterable<V>, ObjectIdSet { 75 /** Size of the initial directory, will grow as necessary. */ 76 private static final int INITIAL_DIRECTORY = 1024; 77 78 /** Number of bits in a segment's index. Segments are 2^11 in size. */ 79 private static final int SEGMENT_BITS = 11; 80 81 private static final int SEGMENT_SHIFT = 32 - SEGMENT_BITS; 82 83 /** 84 * Top level directory of the segments. 85 * <p> 86 * The low {@link #bits} of the SHA-1 are used to select the segment from 87 * this directory. Each segment is constant sized at 2^SEGMENT_BITS. 88 */ 89 V[][] directory; 90 91 /** Total number of objects in this map. */ 92 int size; 93 94 /** The map doubles in capacity when {@link #size} reaches this target. */ 95 private int grow; 96 97 /** Number of low bits used to form the index into {@link #directory}. */ 98 int bits; 99 100 /** Low bit mask to index into {@link #directory}, {@code 2^bits-1}. */ 101 private int mask; 102 103 /** 104 * Create an empty map. 105 */ 106 @SuppressWarnings("unchecked") 107 public ObjectIdOwnerMap() { 108 bits = 0; 109 mask = 0; 110 grow = computeGrowAt(bits); 111 112 directory = (V[][]) new Entry[INITIAL_DIRECTORY][]; 113 directory[0] = newSegment(); 114 } 115 116 /** 117 * Remove all entries from this map. 118 */ 119 public void clear() { 120 size = 0; 121 122 for (V[] tbl : directory) { 123 if (tbl == null) 124 break; 125 Arrays.fill(tbl, null); 126 } 127 } 128 129 /** 130 * Lookup an existing mapping. 131 * 132 * @param toFind 133 * the object identifier to find. 134 * @return the instance mapped to toFind, or null if no mapping exists. 135 */ 136 @SuppressWarnings("unchecked") 137 public V get(AnyObjectId toFind) { 138 if (toFind == null) { 139 return null; 140 } 141 int h = toFind.w1; 142 V obj = directory[h & mask][h >>> SEGMENT_SHIFT]; 143 for (; obj != null; obj = (V) obj.next) 144 if (equals(obj, toFind)) 145 return obj; 146 return null; 147 } 148 149 /** 150 * {@inheritDoc} 151 * <p> 152 * Returns true if this map contains the specified object. 153 */ 154 @Override 155 public boolean contains(AnyObjectId toFind) { 156 return get(toFind) != null; 157 } 158 159 /** 160 * Store an object for future lookup. 161 * <p> 162 * An existing mapping for <b>must not</b> be in this map. Callers must 163 * first call {@link #get(AnyObjectId)} to verify there is no current 164 * mapping prior to adding a new mapping, or use {@link #addIfAbsent(Entry)}. 165 * 166 * @param newValue 167 * the object to store. 168 */ 169 public <Q extends V> void add(Q newValue) { 170 if (++size == grow) 171 grow(); 172 173 int h = newValue.w1; 174 V[] table = directory[h & mask]; 175 h >>>= SEGMENT_SHIFT; 176 177 newValue.next = table[h]; 178 table[h] = newValue; 179 } 180 181 /** 182 * Store an object for future lookup. 183 * <p> 184 * Stores {@code newValue}, but only if there is not already an object for 185 * the same object name. Callers can tell if the value is new by checking 186 * the return value with reference equality: 187 * 188 * <pre> 189 * V obj = ...; 190 * boolean wasNew = map.addIfAbsent(obj) == obj; 191 * </pre> 192 * 193 * @param newValue 194 * the object to store. 195 * @return {@code newValue} if stored, or the prior value already stored and 196 * that would have been returned had the caller used 197 * {@code get(newValue)} first. 198 */ 199 @SuppressWarnings("unchecked") 200 public <Q extends V> V addIfAbsent(Q newValue) { 201 int h = newValue.w1; 202 V[] table = directory[h & mask]; 203 h >>>= SEGMENT_SHIFT; 204 205 for (V obj = table[h]; obj != null; obj = (V) obj.next) 206 if (equals(obj, newValue)) 207 return obj; 208 209 newValue.next = table[h]; 210 table[h] = newValue; 211 212 if (++size == grow) 213 grow(); 214 return newValue; 215 } 216 217 /** 218 * Get number of objects in this map. 219 * 220 * @return number of objects in this map. 221 */ 222 public int size() { 223 return size; 224 } 225 226 /** 227 * Whether this map is empty 228 * 229 * @return true if {@link #size()} is 0. 230 */ 231 public boolean isEmpty() { 232 return size == 0; 233 } 234 235 /** {@inheritDoc} */ 236 @Override 237 public Iterator<V> iterator() { 238 return new Iterator<V>() { 239 private int found; 240 private int dirIdx; 241 private int tblIdx; 242 private V next; 243 244 @Override 245 public boolean hasNext() { 246 return found < size; 247 } 248 249 @Override 250 public V next() { 251 if (next != null) 252 return found(next); 253 254 for (;;) { 255 V[] table = directory[dirIdx]; 256 if (tblIdx == table.length) { 257 if (++dirIdx >= (1 << bits)) 258 throw new NoSuchElementException(); 259 table = directory[dirIdx]; 260 tblIdx = 0; 261 } 262 263 while (tblIdx < table.length) { 264 V v = table[tblIdx++]; 265 if (v != null) 266 return found(v); 267 } 268 } 269 } 270 271 @SuppressWarnings("unchecked") 272 private V found(V v) { 273 found++; 274 next = (V) v.next; 275 return v; 276 } 277 278 @Override 279 public void remove() { 280 throw new UnsupportedOperationException(); 281 } 282 }; 283 } 284 285 @SuppressWarnings("unchecked") 286 private void grow() { 287 final int oldDirLen = 1 << bits; 288 final int s = 1 << bits; 289 290 bits++; 291 mask = (1 << bits) - 1; 292 grow = computeGrowAt(bits); 293 294 // Quadruple the directory if it needs to expand. Expanding the 295 // directory is expensive because it generates garbage, so try 296 // to avoid doing it often. 297 // 298 final int newDirLen = 1 << bits; 299 if (directory.length < newDirLen) { 300 V[][] newDir = (V[][]) new Entry[newDirLen << 1][]; 301 System.arraycopy(directory, 0, newDir, 0, oldDirLen); 302 directory = newDir; 303 } 304 305 // For every bucket of every old segment, split the chain between 306 // the old segment and the new segment's corresponding bucket. To 307 // select between them use the lowest bit that was just added into 308 // the mask above. This causes the table to double in capacity. 309 // 310 for (int dirIdx = 0; dirIdx < oldDirLen; dirIdx++) { 311 final V[] oldTable = directory[dirIdx]; 312 final V[] newTable = newSegment(); 313 314 for (int i = 0; i < oldTable.length; i++) { 315 V chain0 = null; 316 V chain1 = null; 317 V next; 318 319 for (V obj = oldTable[i]; obj != null; obj = next) { 320 next = (V) obj.next; 321 322 if ((obj.w1 & s) == 0) { 323 obj.next = chain0; 324 chain0 = obj; 325 } else { 326 obj.next = chain1; 327 chain1 = obj; 328 } 329 } 330 331 oldTable[i] = chain0; 332 newTable[i] = chain1; 333 } 334 335 directory[oldDirLen + dirIdx] = newTable; 336 } 337 } 338 339 @SuppressWarnings("unchecked") 340 private final V[] newSegment() { 341 return (V[]) new Entry[1 << SEGMENT_BITS]; 342 } 343 344 private static final int computeGrowAt(int bits) { 345 return 1 << (bits + SEGMENT_BITS); 346 } 347 348 private static final boolean equals(AnyObjectId firstObjectId, 349 AnyObjectId secondObjectId) { 350 return firstObjectId.w2 == secondObjectId.w2 351 && firstObjectId.w3 == secondObjectId.w3 352 && firstObjectId.w4 == secondObjectId.w4 353 && firstObjectId.w5 == secondObjectId.w5 354 && firstObjectId.w1 == secondObjectId.w1; 355 } 356 357 /** Type of entry stored in the {@link ObjectIdOwnerMap}. */ 358 public static abstract class Entry extends ObjectId { 359 transient Entry next; 360 361 /** 362 * Initialize this entry with a specific ObjectId. 363 * 364 * @param id 365 * the id the entry represents. 366 */ 367 public Entry(AnyObjectId id) { 368 super(id); 369 } 370 } 371 }