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