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