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 }