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 int h = toFind.w1; 139 V obj = directory[h & mask][h >>> SEGMENT_SHIFT]; 140 for (; obj != null; obj = (V) obj.next) 141 if (equals(obj, toFind)) 142 return obj; 143 return null; 144 } 145 146 /** 147 * {@inheritDoc} 148 * <p> 149 * Returns true if this map contains the specified object. 150 */ 151 @Override 152 public boolean contains(AnyObjectId toFind) { 153 return get(toFind) != null; 154 } 155 156 /** 157 * Store an object for future lookup. 158 * <p> 159 * An existing mapping for <b>must not</b> be in this map. Callers must 160 * first call {@link #get(AnyObjectId)} to verify there is no current 161 * mapping prior to adding a new mapping, or use {@link #addIfAbsent(Entry)}. 162 * 163 * @param newValue 164 * the object to store. 165 */ 166 public <Q extends V> void add(Q newValue) { 167 if (++size == grow) 168 grow(); 169 170 int h = newValue.w1; 171 V[] table = directory[h & mask]; 172 h >>>= SEGMENT_SHIFT; 173 174 newValue.next = table[h]; 175 table[h] = newValue; 176 } 177 178 /** 179 * Store an object for future lookup. 180 * <p> 181 * Stores {@code newValue}, but only if there is not already an object for 182 * the same object name. Callers can tell if the value is new by checking 183 * the return value with reference equality: 184 * 185 * <pre> 186 * V obj = ...; 187 * boolean wasNew = map.addIfAbsent(obj) == obj; 188 * </pre> 189 * 190 * @param newValue 191 * the object to store. 192 * @return {@code newValue} if stored, or the prior value already stored and 193 * that would have been returned had the caller used 194 * {@code get(newValue)} first. 195 */ 196 @SuppressWarnings("unchecked") 197 public <Q extends V> V addIfAbsent(Q newValue) { 198 int h = newValue.w1; 199 V[] table = directory[h & mask]; 200 h >>>= SEGMENT_SHIFT; 201 202 for (V obj = table[h]; obj != null; obj = (V) obj.next) 203 if (equals(obj, newValue)) 204 return obj; 205 206 newValue.next = table[h]; 207 table[h] = newValue; 208 209 if (++size == grow) 210 grow(); 211 return newValue; 212 } 213 214 /** 215 * Get number of objects in this map. 216 * 217 * @return number of objects in this map. 218 */ 219 public int size() { 220 return size; 221 } 222 223 /** 224 * Whether this map is empty 225 * 226 * @return true if {@link #size()} is 0. 227 */ 228 public boolean isEmpty() { 229 return size == 0; 230 } 231 232 /** {@inheritDoc} */ 233 @Override 234 public Iterator<V> iterator() { 235 return new Iterator<V>() { 236 private int found; 237 private int dirIdx; 238 private int tblIdx; 239 private V next; 240 241 @Override 242 public boolean hasNext() { 243 return found < size; 244 } 245 246 @Override 247 public V next() { 248 if (next != null) 249 return found(next); 250 251 for (;;) { 252 V[] table = directory[dirIdx]; 253 if (tblIdx == table.length) { 254 if (++dirIdx >= (1 << bits)) 255 throw new NoSuchElementException(); 256 table = directory[dirIdx]; 257 tblIdx = 0; 258 } 259 260 while (tblIdx < table.length) { 261 V v = table[tblIdx++]; 262 if (v != null) 263 return found(v); 264 } 265 } 266 } 267 268 @SuppressWarnings("unchecked") 269 private V found(V v) { 270 found++; 271 next = (V) v.next; 272 return v; 273 } 274 275 @Override 276 public void remove() { 277 throw new UnsupportedOperationException(); 278 } 279 }; 280 } 281 282 @SuppressWarnings("unchecked") 283 private void grow() { 284 final int oldDirLen = 1 << bits; 285 final int s = 1 << bits; 286 287 bits++; 288 mask = (1 << bits) - 1; 289 grow = computeGrowAt(bits); 290 291 // Quadruple the directory if it needs to expand. Expanding the 292 // directory is expensive because it generates garbage, so try 293 // to avoid doing it often. 294 // 295 final int newDirLen = 1 << bits; 296 if (directory.length < newDirLen) { 297 V[][] newDir = (V[][]) new Entry[newDirLen << 1][]; 298 System.arraycopy(directory, 0, newDir, 0, oldDirLen); 299 directory = newDir; 300 } 301 302 // For every bucket of every old segment, split the chain between 303 // the old segment and the new segment's corresponding bucket. To 304 // select between them use the lowest bit that was just added into 305 // the mask above. This causes the table to double in capacity. 306 // 307 for (int dirIdx = 0; dirIdx < oldDirLen; dirIdx++) { 308 final V[] oldTable = directory[dirIdx]; 309 final V[] newTable = newSegment(); 310 311 for (int i = 0; i < oldTable.length; i++) { 312 V chain0 = null; 313 V chain1 = null; 314 V next; 315 316 for (V obj = oldTable[i]; obj != null; obj = next) { 317 next = (V) obj.next; 318 319 if ((obj.w1 & s) == 0) { 320 obj.next = chain0; 321 chain0 = obj; 322 } else { 323 obj.next = chain1; 324 chain1 = obj; 325 } 326 } 327 328 oldTable[i] = chain0; 329 newTable[i] = chain1; 330 } 331 332 directory[oldDirLen + dirIdx] = newTable; 333 } 334 } 335 336 @SuppressWarnings("unchecked") 337 private final V[] newSegment() { 338 return (V[]) new Entry[1 << SEGMENT_BITS]; 339 } 340 341 private static final int computeGrowAt(int bits) { 342 return 1 << (bits + SEGMENT_BITS); 343 } 344 345 private static final boolean equals(AnyObjectId firstObjectId, 346 AnyObjectId secondObjectId) { 347 return firstObjectId.w2 == secondObjectId.w2 348 && firstObjectId.w3 == secondObjectId.w3 349 && firstObjectId.w4 == secondObjectId.w4 350 && firstObjectId.w5 == secondObjectId.w5 351 && firstObjectId.w1 == secondObjectId.w1; 352 } 353 354 /** Type of entry stored in the {@link ObjectIdOwnerMap}. */ 355 public static abstract class Entry extends ObjectId { 356 transient Entry next; 357 358 /** 359 * Initialize this entry with a specific ObjectId. 360 * 361 * @param id 362 * the id the entry represents. 363 */ 364 public Entry(AnyObjectId id) { 365 super(id); 366 } 367 } 368 }