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