View Javadoc
1   /*
2    * Copyright (C) 2009, Google Inc.
3    * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
4    * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
5    * Copyright (C) 2013, Robin Rosenberg
6    * and other copyright owners as documented in the project's IP log.
7    *
8    * This program and the accompanying materials are made available
9    * under the terms of the Eclipse Distribution License v1.0 which
10   * accompanies this distribution, is reproduced below, and is
11   * available at http://www.eclipse.org/org/documents/edl-v10.php
12   *
13   * All rights reserved.
14   *
15   * Redistribution and use in source and binary forms, with or
16   * without modification, are permitted provided that the following
17   * conditions are met:
18   *
19   * - Redistributions of source code must retain the above copyright
20   *   notice, this list of conditions and the following disclaimer.
21   *
22   * - Redistributions in binary form must reproduce the above
23   *   copyright notice, this list of conditions and the following
24   *   disclaimer in the documentation and/or other materials provided
25   *   with the distribution.
26   *
27   * - Neither the name of the Eclipse Foundation, Inc. nor the
28   *   names of its contributors may be used to endorse or promote
29   *   products derived from this software without specific prior
30   *   written permission.
31   *
32   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
33   * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
34   * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
35   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
36   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
37   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
38   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
39   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
40   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
41   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
42   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
43   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
44   * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
45   */
46  
47  package org.eclipse.jgit.treewalk.filter;
48  
49  import org.eclipse.jgit.util.RawParseUtils;
50  
51  /**
52   * Specialized set for byte arrays, interpreted as strings for use in
53   * {@link PathFilterGroup.Group}. Most methods assume the hash is already know
54   * and therefore requires the caller to supply it beforehand. The implementation
55   * is a loose derivative of ObjectIdSubclassMap.
56   * <p>
57   * The class is only intended for use by PathFilterGroup.
58   * <p>
59   * The arrays stored may not be changed after adding.
60   */
61  class ByteArraySet {
62  
63  	private int size;
64  
65  	private int grow;
66  
67  	private int mask;
68  
69  	private byte[][] table;
70  
71  	/**
72  	 * Create an empty set.
73  	 *
74  	 * @param capacity
75  	 */
76  	ByteArraySet(int capacity) {
77  		initTable(1 << Integer.highestOneBit((capacity * 2) - 1));
78  	}
79  
80  	private byte[] get(byte[] toFind, int length, int hash) {
81  		final int msk = mask;
82  		int i = hash & msk;
83  		final byte[][] tbl = table;
84  		byte[] obj;
85  
86  		while ((obj = tbl[i]) != null) {
87  			if (equals(obj, toFind, length))
88  				return obj;
89  			i = (i + 1) & msk;
90  		}
91  		return null;
92  	}
93  
94  	private static boolean equals(byte[] storedObj, byte[] toFind, int length) {
95  		if (storedObj.length != length || toFind.length < length)
96  			return false;
97  		for (int i = 0; i < length; ++i) {
98  			if (storedObj[i] != toFind[i])
99  				return false;
100 		}
101 		return true;
102 	}
103 
104 	/**
105 	 * Returns true if this set contains the specified array.
106 	 *
107 	 * @param toFind
108 	 *            array to find.
109 	 * @param length
110 	 *            The number of bytes in toFind that are used
111 	 * @param hash
112 	 *            pre-computed hash of toFind
113 	 * @return true if the mapping exists for this byte array; false otherwise.
114 	 */
115 	boolean contains(byte[] toFind, int length, int hash) {
116 		return get(toFind, length, hash) != null;
117 	}
118 
119 	/**
120 	 * Store a byte array for future lookup.
121 	 * <p>
122 	 * Stores {@code newValue}, but only if it does not already exist in the
123 	 * set. Callers can tell if the value is new by checking the return value
124 	 * with reference equality:
125 	 *
126 	 * <pre>
127 	 * byte[] obj = ...;
128 	 * boolean wasNew = map.addIfAbsent(array, length, hash) == array;
129 	 * </pre>
130 	 *
131 	 * @param newValue
132 	 *            the array to store by reference if the length is the same as
133 	 *            the length parameter
134 	 * @param length
135 	 *            The number of bytes in newValue that are used
136 	 * @param hash
137 	 *            pre-computed hash of toFind
138 	 * @return {@code newValue} if stored, or the prior value already stored and
139 	 *         that would have been returned had the caller used
140 	 *         {@code get(newValue)} first.
141 	 */
142 	byte[] addIfAbsent(final byte[] newValue, int length, int hash) {
143 		final int msk = mask;
144 		int i = hash & msk;
145 		final byte[][] tbl = table;
146 		byte[] obj;
147 
148 		while ((obj = tbl[i]) != null) {
149 			if (equals(obj, newValue, length))
150 				return obj;
151 			i = (i + 1) & msk;
152 		}
153 
154 		byte[] valueToInsert = copyIfNotSameSize(newValue, length);
155 		if (++size == grow) {
156 			grow();
157 			insert(valueToInsert, hash);
158 		} else
159 			tbl[i] = valueToInsert;
160 		return valueToInsert;
161 	}
162 
163 	private static byte[] copyIfNotSameSize(byte[] newValue, int length) {
164 		if (newValue.length == length)
165 			return newValue;
166 		byte[] ret = new byte[length];
167 		System.arraycopy(newValue, 0, ret, 0, length);
168 		return ret;
169 	}
170 
171 	/**
172 	 * @return number of arrays in the set
173 	 */
174 	int size() {
175 		return size;
176 	}
177 
178 	/** @return true if {@link #size()} is 0. */
179 	boolean isEmpty() {
180 		return size == 0;
181 	}
182 
183 	private void insert(byte[] newValue, int hash) {
184 		final int msk = mask;
185 		int j = hash & msk;
186 		final byte[][] tbl = table;
187 		while (tbl[j] != null)
188 			j = (j + 1) & msk;
189 		tbl[j] = newValue;
190 	}
191 
192 	private Hasher hasher = new Hasher(null, 0);
193 
194 	private void grow() {
195 		final byte[][] oldTable = table;
196 		final int oldSize = table.length;
197 
198 		initTable(oldSize << 1);
199 		for (int i = 0; i < oldSize; i++) {
200 			final byte[] obj = oldTable[i];
201 			if (obj != null) {
202 				hasher.init(obj, obj.length);
203 				insert(obj, hasher.hash());
204 			}
205 		}
206 	}
207 
208 	private void initTable(int sz) {
209 		if (sz < 2)
210 			sz = 2;
211 		grow = sz >> 1;
212 		mask = sz - 1;
213 		table = new byte[sz][];
214 	}
215 
216 	/** {@inheritDoc} */
217 	@Override
218 	public String toString() {
219 		StringBuilder sb = new StringBuilder();
220 		sb.append('[');
221 		for (byte[] b : table) {
222 			if (b == null)
223 				continue;
224 			if (sb.length() > 1)
225 				sb.append(" , "); //$NON-NLS-1$
226 			sb.append('"');
227 			sb.append(RawParseUtils.decode(b));
228 			sb.append('"');
229 			sb.append('(');
230 			sb.append(chainlength(b));
231 			sb.append(')');
232 		}
233 		sb.append(']');
234 		return sb.toString();
235 	}
236 
237 	private int chainlength(byte[] b) {
238 		Hasher h = new Hasher(b, b.length);
239 		int hash = h.hash();
240 		final int msk = mask;
241 		int i = hash & msk;
242 		final byte[][] tbl = table;
243 		byte[] obj;
244 
245 		int n = 0;
246 		while ((obj = tbl[i]) != null) {
247 			if (equals(obj, b, b.length))
248 				return n;
249 			i = (i + 1) & msk;
250 			++n;
251 		}
252 		return -1;
253 	}
254 
255 	/**
256 	 * An incremental hash function.
257 	 */
258 	static class Hasher {
259 		private int hash;
260 
261 		private int pos;
262 
263 		private byte[] data;
264 
265 		private int length;
266 
267 		Hasher(byte[] data, int length) {
268 			init(data, length);
269 		}
270 
271 		void init(byte[] d, int l) {
272 			this.data = d;
273 			this.length = l;
274 			pos = 0;
275 			hash = 0;
276 		}
277 
278 		int hash() {
279 			while (pos < length)
280 				hash = hash * 31 + data[pos++];
281 			return hash;
282 		}
283 
284 		int nextHash() {
285 			for (;;) {
286 				hash = hash * 31 + data[pos];
287 				++pos;
288 				if (pos == length || data[pos] == '/')
289 					return hash;
290 			}
291 		}
292 
293 		int getHash() {
294 			return hash;
295 		}
296 
297 		boolean hasNext() {
298 			return pos < length;
299 		}
300 
301 		public int length() {
302 			return pos;
303 		}
304 
305 		@Override
306 		public String toString() {
307 			StringBuilder sb = new StringBuilder();
308 			for (int i = 0; i < pos; ++i)
309 				sb.append((char) data[i]);
310 			sb.append(" | "); //$NON-NLS-1$
311 			for (int i = pos; i < length; ++i)
312 				sb.append((char) data[i]);
313 			return sb.toString();
314 		}
315 	}
316 
317 	byte[][] toArray() {
318 		byte[][] ret = new byte[size][];
319 		int i = 0;
320 		for (byte[] entry : table) {
321 			if (entry != null)
322 				ret[i++] = entry;
323 		}
324 		return ret;
325 	}
326 
327 }