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 }