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 and others
6    *
7    * This program and the accompanying materials are made available under the
8    * terms of the Eclipse Distribution License v. 1.0 which is available at
9    * https://www.eclipse.org/org/documents/edl-v10.php.
10   *
11   * SPDX-License-Identifier: BSD-3-Clause
12   */
13  
14  package org.eclipse.jgit.treewalk.filter;
15  
16  import org.eclipse.jgit.util.RawParseUtils;
17  
18  /**
19   * Specialized set for byte arrays, interpreted as strings for use in
20   * {@link PathFilterGroup.Group}. Most methods assume the hash is already know
21   * and therefore requires the caller to supply it beforehand. The implementation
22   * is a loose derivative of ObjectIdSubclassMap.
23   * <p>
24   * The class is only intended for use by PathFilterGroup.
25   * <p>
26   * The arrays stored may not be changed after adding.
27   */
28  class ByteArraySet {
29  
30  	private int size;
31  
32  	private int grow;
33  
34  	private int mask;
35  
36  	private byte[][] table;
37  
38  	/**
39  	 * Create an empty set.
40  	 *
41  	 * @param capacity
42  	 */
43  	ByteArraySet(int capacity) {
44  		initTable(1 << Integer.highestOneBit((capacity * 2) - 1));
45  	}
46  
47  	private byte[] get(byte[] toFind, int length, int hash) {
48  		final int msk = mask;
49  		int i = hash & msk;
50  		final byte[][] tbl = table;
51  		byte[] obj;
52  
53  		while ((obj = tbl[i]) != null) {
54  			if (equals(obj, toFind, length))
55  				return obj;
56  			i = (i + 1) & msk;
57  		}
58  		return null;
59  	}
60  
61  	private static boolean equals(byte[] storedObj, byte[] toFind, int length) {
62  		if (storedObj.length != length || toFind.length < length)
63  			return false;
64  		for (int i = 0; i < length; ++i) {
65  			if (storedObj[i] != toFind[i])
66  				return false;
67  		}
68  		return true;
69  	}
70  
71  	/**
72  	 * Returns true if this set contains the specified array.
73  	 *
74  	 * @param toFind
75  	 *            array to find.
76  	 * @param length
77  	 *            The number of bytes in toFind that are used
78  	 * @param hash
79  	 *            pre-computed hash of toFind
80  	 * @return true if the mapping exists for this byte array; false otherwise.
81  	 */
82  	boolean contains(byte[] toFind, int length, int hash) {
83  		return get(toFind, length, hash) != null;
84  	}
85  
86  	/**
87  	 * Store a byte array for future lookup.
88  	 * <p>
89  	 * Stores {@code newValue}, but only if it does not already exist in the
90  	 * set. Callers can tell if the value is new by checking the return value
91  	 * with reference equality:
92  	 *
93  	 * <pre>
94  	 * byte[] obj = ...;
95  	 * boolean wasNew = map.addIfAbsent(array, length, hash) == array;
96  	 * </pre>
97  	 *
98  	 * @param newValue
99  	 *            the array to store by reference if the length is the same as
100 	 *            the length parameter
101 	 * @param length
102 	 *            The number of bytes in newValue that are used
103 	 * @param hash
104 	 *            pre-computed hash of toFind
105 	 * @return {@code newValue} if stored, or the prior value already stored and
106 	 *         that would have been returned had the caller used
107 	 *         {@code get(newValue)} first.
108 	 */
109 	byte[] addIfAbsent(final byte[] newValue, int length, int hash) {
110 		final int msk = mask;
111 		int i = hash & msk;
112 		final byte[][] tbl = table;
113 		byte[] obj;
114 
115 		while ((obj = tbl[i]) != null) {
116 			if (equals(obj, newValue, length))
117 				return obj;
118 			i = (i + 1) & msk;
119 		}
120 
121 		byte[] valueToInsert = copyIfNotSameSize(newValue, length);
122 		if (++size == grow) {
123 			grow();
124 			insert(valueToInsert, hash);
125 		} else
126 			tbl[i] = valueToInsert;
127 		return valueToInsert;
128 	}
129 
130 	private static byte[] copyIfNotSameSize(byte[] newValue, int length) {
131 		if (newValue.length == length)
132 			return newValue;
133 		byte[] ret = new byte[length];
134 		System.arraycopy(newValue, 0, ret, 0, length);
135 		return ret;
136 	}
137 
138 	/**
139 	 * @return number of arrays in the set
140 	 */
141 	int size() {
142 		return size;
143 	}
144 
145 	/** @return true if {@link #size()} is 0. */
146 	boolean isEmpty() {
147 		return size == 0;
148 	}
149 
150 	private void insert(byte[] newValue, int hash) {
151 		final int msk = mask;
152 		int j = hash & msk;
153 		final byte[][] tbl = table;
154 		while (tbl[j] != null)
155 			j = (j + 1) & msk;
156 		tbl[j] = newValue;
157 	}
158 
159 	private Hasher hasher = new Hasher(null, 0);
160 
161 	private void grow() {
162 		final byte[][] oldTable = table;
163 		final int oldSize = table.length;
164 
165 		initTable(oldSize << 1);
166 		for (int i = 0; i < oldSize; i++) {
167 			final byte[] obj = oldTable[i];
168 			if (obj != null) {
169 				hasher.init(obj, obj.length);
170 				insert(obj, hasher.hash());
171 			}
172 		}
173 	}
174 
175 	private void initTable(int sz) {
176 		if (sz < 2)
177 			sz = 2;
178 		grow = sz >> 1;
179 		mask = sz - 1;
180 		table = new byte[sz][];
181 	}
182 
183 	/** {@inheritDoc} */
184 	@Override
185 	public String toString() {
186 		StringBuilder sb = new StringBuilder();
187 		sb.append('[');
188 		for (byte[] b : table) {
189 			if (b == null)
190 				continue;
191 			if (sb.length() > 1)
192 				sb.append(" , "); //$NON-NLS-1$
193 			sb.append('"');
194 			sb.append(RawParseUtils.decode(b));
195 			sb.append('"');
196 			sb.append('(');
197 			sb.append(chainlength(b));
198 			sb.append(')');
199 		}
200 		sb.append(']');
201 		return sb.toString();
202 	}
203 
204 	private int chainlength(byte[] b) {
205 		Hasher h = new Hasher(b, b.length);
206 		int hash = h.hash();
207 		final int msk = mask;
208 		int i = hash & msk;
209 		final byte[][] tbl = table;
210 		byte[] obj;
211 
212 		int n = 0;
213 		while ((obj = tbl[i]) != null) {
214 			if (equals(obj, b, b.length))
215 				return n;
216 			i = (i + 1) & msk;
217 			++n;
218 		}
219 		return -1;
220 	}
221 
222 	/**
223 	 * An incremental hash function.
224 	 */
225 	static class Hasher {
226 		private int hash;
227 
228 		private int pos;
229 
230 		private byte[] data;
231 
232 		private int length;
233 
234 		Hasher(byte[] data, int length) {
235 			init(data, length);
236 		}
237 
238 		void init(byte[] d, int l) {
239 			this.data = d;
240 			this.length = l;
241 			pos = 0;
242 			hash = 0;
243 		}
244 
245 		int hash() {
246 			while (pos < length)
247 				hash = hash * 31 + data[pos++];
248 			return hash;
249 		}
250 
251 		int nextHash() {
252 			for (;;) {
253 				hash = hash * 31 + data[pos];
254 				++pos;
255 				if (pos == length || data[pos] == '/')
256 					return hash;
257 			}
258 		}
259 
260 		int getHash() {
261 			return hash;
262 		}
263 
264 		boolean hasNext() {
265 			return pos < length;
266 		}
267 
268 		public int length() {
269 			return pos;
270 		}
271 
272 		@Override
273 		public String toString() {
274 			StringBuilder sb = new StringBuilder();
275 			for (int i = 0; i < pos; ++i)
276 				sb.append((char) data[i]);
277 			sb.append(" | "); //$NON-NLS-1$
278 			for (int i = pos; i < length; ++i)
279 				sb.append((char) data[i]);
280 			return sb.toString();
281 		}
282 	}
283 
284 	byte[][] toArray() {
285 		byte[][] ret = new byte[size][];
286 		int i = 0;
287 		for (byte[] entry : table) {
288 			if (entry != null)
289 				ret[i++] = entry;
290 		}
291 		return ret;
292 	}
293 
294 }