View Javadoc
1   /*
2    * Copyright (C) 2010, 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.util;
45  
46  import java.util.AbstractMap;
47  import java.util.AbstractSet;
48  import java.util.Iterator;
49  import java.util.Map;
50  import java.util.NoSuchElementException;
51  import java.util.Set;
52  
53  import org.eclipse.jgit.lib.AnyObjectId;
54  import org.eclipse.jgit.lib.ObjectId;
55  import org.eclipse.jgit.lib.Ref;
56  import org.eclipse.jgit.lib.RefComparator;
57  
58  /**
59   * Specialized Map to present a {@code RefDatabase} namespace.
60   * <p>
61   * Although not declared as a {@link java.util.SortedMap}, iterators from this
62   * map's projections always return references in {@link RefComparator} ordering.
63   * The map's internal representation is a sorted array of {@link Ref} objects,
64   * which means lookup and replacement is O(log N), while insertion and removal
65   * can be as expensive as O(N + log N) while the list expands or contracts.
66   * Since this is not a general map implementation, all entries must be keyed by
67   * the reference name.
68   * <p>
69   * This class is really intended as a helper for {@code RefDatabase}, which
70   * needs to perform a merge-join of three sorted {@link RefList}s in order to
71   * present the unified namespace of the packed-refs file, the loose refs/
72   * directory tree, and the resolved form of any symbolic references.
73   */
74  public class RefMap extends AbstractMap<String, Ref> {
75  	/**
76  	 * Prefix denoting the reference subspace this map contains.
77  	 * <p>
78  	 * All reference names in this map must start with this prefix. If the
79  	 * prefix is not the empty string, it must end with a '/'.
80  	 */
81  	private final String prefix;
82  
83  	/** Immutable collection of the packed references at construction time. */
84  	private RefList<Ref> packed;
85  
86  	/**
87  	 * Immutable collection of the loose references at construction time.
88  	 * <p>
89  	 * If an entry appears here and in {@link #packed}, this entry must take
90  	 * precedence, as its more current. Symbolic references in this collection
91  	 * are typically unresolved, so they only tell us who their target is, but
92  	 * not the current value of the target.
93  	 */
94  	private RefList<Ref> loose;
95  
96  	/**
97  	 * Immutable collection of resolved symbolic references.
98  	 * <p>
99  	 * This collection contains only the symbolic references we were able to
100 	 * resolve at map construction time. Other loose references must be read
101 	 * from {@link #loose}. Every entry in this list must be matched by an entry
102 	 * in {@code loose}, otherwise it might be omitted by the map.
103 	 */
104 	private RefList<Ref> resolved;
105 
106 	private int size;
107 
108 	private boolean sizeIsValid;
109 
110 	private Set<Entry<String, Ref>> entrySet;
111 
112 	/** Construct an empty map with a small initial capacity. */
113 	public RefMap() {
114 		prefix = ""; //$NON-NLS-1$
115 		packed = RefList.emptyList();
116 		loose = RefList.emptyList();
117 		resolved = RefList.emptyList();
118 	}
119 
120 	/**
121 	 * Construct a map to merge 3 collections together.
122 	 *
123 	 * @param prefix
124 	 *            prefix used to slice the lists down. Only references whose
125 	 *            names start with this prefix will appear to reside in the map.
126 	 *            Must not be null, use {@code ""} (the empty string) to select
127 	 *            all list items.
128 	 * @param packed
129 	 *            items from the packed reference list, this is the last list
130 	 *            searched.
131 	 * @param loose
132 	 *            items from the loose reference list, this list overrides
133 	 *            {@code packed} if a name appears in both.
134 	 * @param resolved
135 	 *            resolved symbolic references. This list overrides the prior
136 	 *            list {@code loose}, if an item appears in both. Items in this
137 	 *            list <b>must</b> also appear in {@code loose}.
138 	 */
139 	@SuppressWarnings("unchecked")
140 	public RefMap(String prefix, RefList<? extends Ref> packed,
141 			RefList<? extends Ref> loose, RefList<? extends Ref> resolved) {
142 		this.prefix = prefix;
143 		this.packed = (RefList<Ref>) packed;
144 		this.loose = (RefList<Ref>) loose;
145 		this.resolved = (RefList<Ref>) resolved;
146 	}
147 
148 	@Override
149 	public boolean containsKey(Object name) {
150 		return get(name) != null;
151 	}
152 
153 	@Override
154 	public Ref get(Object key) {
155 		String name = toRefName((String) key);
156 		Ref ref = resolved.get(name);
157 		if (ref == null)
158 			ref = loose.get(name);
159 		if (ref == null)
160 			ref = packed.get(name);
161 		return ref;
162 	}
163 
164 	@Override
165 	public Ref put(final String keyName, Ref value) {
166 		String name = toRefName(keyName);
167 
168 		if (!name.equals(value.getName()))
169 			throw new IllegalArgumentException();
170 
171 		if (!resolved.isEmpty()) {
172 			// Collapse the resolved list into the loose list so we
173 			// can discard it and stop joining the two together.
174 			for (Ref ref : resolved)
175 				loose = loose.put(ref);
176 			resolved = RefList.emptyList();
177 		}
178 
179 		int idx = loose.find(name);
180 		if (0 <= idx) {
181 			Ref prior = loose.get(name);
182 			loose = loose.set(idx, value);
183 			return prior;
184 		} else {
185 			Ref prior = get(keyName);
186 			loose = loose.add(idx, value);
187 			sizeIsValid = false;
188 			return prior;
189 		}
190 	}
191 
192 	@Override
193 	public Ref remove(Object key) {
194 		String name = toRefName((String) key);
195 		Ref res = null;
196 		int idx;
197 		if (0 <= (idx = packed.find(name))) {
198 			res = packed.get(name);
199 			packed = packed.remove(idx);
200 			sizeIsValid = false;
201 		}
202 		if (0 <= (idx = loose.find(name))) {
203 			res = loose.get(name);
204 			loose = loose.remove(idx);
205 			sizeIsValid = false;
206 		}
207 		if (0 <= (idx = resolved.find(name))) {
208 			res = resolved.get(name);
209 			resolved = resolved.remove(idx);
210 			sizeIsValid = false;
211 		}
212 		return res;
213 	}
214 
215 	@Override
216 	public boolean isEmpty() {
217 		return entrySet().isEmpty();
218 	}
219 
220 	@Override
221 	public Set<Entry<String, Ref>> entrySet() {
222 		if (entrySet == null) {
223 			entrySet = new AbstractSet<Entry<String, Ref>>() {
224 				@Override
225 				public Iterator<Entry<String, Ref>> iterator() {
226 					return new SetIterator();
227 				}
228 
229 				@Override
230 				public int size() {
231 					if (!sizeIsValid) {
232 						size = 0;
233 						Iterator<?> i = entrySet().iterator();
234 						for (; i.hasNext(); i.next())
235 							size++;
236 						sizeIsValid = true;
237 					}
238 					return size;
239 				}
240 
241 				@Override
242 				public boolean isEmpty() {
243 					if (sizeIsValid)
244 						return 0 == size;
245 					return !iterator().hasNext();
246 				}
247 
248 				@Override
249 				public void clear() {
250 					packed = RefList.emptyList();
251 					loose = RefList.emptyList();
252 					resolved = RefList.emptyList();
253 					size = 0;
254 					sizeIsValid = true;
255 				}
256 			};
257 		}
258 		return entrySet;
259 	}
260 
261 	@Override
262 	public String toString() {
263 		StringBuilder r = new StringBuilder();
264 		boolean first = true;
265 		r.append('[');
266 		for (Ref ref : values()) {
267 			if (first)
268 				first = false;
269 			else
270 				r.append(", "); //$NON-NLS-1$
271 			r.append(ref);
272 		}
273 		r.append(']');
274 		return r.toString();
275 	}
276 
277 	private String toRefName(String name) {
278 		if (0 < prefix.length())
279 			name = prefix + name;
280 		return name;
281 	}
282 
283 	private String toMapKey(Ref ref) {
284 		String name = ref.getName();
285 		if (0 < prefix.length())
286 			name = name.substring(prefix.length());
287 		return name;
288 	}
289 
290 	private class SetIterator implements Iterator<Entry<String, Ref>> {
291 		private int packedIdx;
292 
293 		private int looseIdx;
294 
295 		private int resolvedIdx;
296 
297 		private Entry<String, Ref> next;
298 
299 		SetIterator() {
300 			if (0 < prefix.length()) {
301 				packedIdx = -(packed.find(prefix) + 1);
302 				looseIdx = -(loose.find(prefix) + 1);
303 				resolvedIdx = -(resolved.find(prefix) + 1);
304 			}
305 		}
306 
307 		public boolean hasNext() {
308 			if (next == null)
309 				next = peek();
310 			return next != null;
311 		}
312 
313 		public Entry<String, Ref> next() {
314 			if (hasNext()) {
315 				Entry<String, Ref> r = next;
316 				next = peek();
317 				return r;
318 			}
319 			throw new NoSuchElementException();
320 		}
321 
322 		public Entry<String, Ref> peek() {
323 			if (packedIdx < packed.size() && looseIdx < loose.size()) {
324 				Ref p = packed.get(packedIdx);
325 				Ref l = loose.get(looseIdx);
326 				int cmp = RefComparator.compareTo(p, l);
327 				if (cmp < 0) {
328 					packedIdx++;
329 					return toEntry(p);
330 				}
331 
332 				if (cmp == 0)
333 					packedIdx++;
334 				looseIdx++;
335 				return toEntry(resolveLoose(l));
336 			}
337 
338 			if (looseIdx < loose.size())
339 				return toEntry(resolveLoose(loose.get(looseIdx++)));
340 			if (packedIdx < packed.size())
341 				return toEntry(packed.get(packedIdx++));
342 			return null;
343 		}
344 
345 		private Ref resolveLoose(final Ref l) {
346 			if (resolvedIdx < resolved.size()) {
347 				Ref r = resolved.get(resolvedIdx);
348 				int cmp = RefComparator.compareTo(l, r);
349 				if (cmp == 0) {
350 					resolvedIdx++;
351 					return r;
352 				} else if (cmp > 0) {
353 					// WTF, we have a symbolic entry but no match
354 					// in the loose collection. That's an error.
355 					throw new IllegalStateException();
356 				}
357 			}
358 			return l;
359 		}
360 
361 		private Ent toEntry(Ref p) {
362 			if (p.getName().startsWith(prefix))
363 				return new Ent(p);
364 			packedIdx = packed.size();
365 			looseIdx = loose.size();
366 			resolvedIdx = resolved.size();
367 			return null;
368 		}
369 
370 		public void remove() {
371 			throw new UnsupportedOperationException();
372 		}
373 	}
374 
375 	private class Ent implements Entry<String, Ref> {
376 		private Ref ref;
377 
378 		Ent(Ref ref) {
379 			this.ref = ref;
380 		}
381 
382 		public String getKey() {
383 			return toMapKey(ref);
384 		}
385 
386 		public Ref getValue() {
387 			return ref;
388 		}
389 
390 		public Ref setValue(Ref value) {
391 			Ref prior = put(getKey(), value);
392 			ref = value;
393 			return prior;
394 		}
395 
396 		@Override
397 		public int hashCode() {
398 			return getKey().hashCode();
399 		}
400 
401 		@Override
402 		public boolean equals(Object obj) {
403 			if (obj instanceof Map.Entry) {
404 				final Object key = ((Map.Entry) obj).getKey();
405 				final Object val = ((Map.Entry) obj).getValue();
406 				if (key instanceof String && val instanceof Ref) {
407 					final Ref r = (Ref) val;
408 					if (r.getName().equals(ref.getName())) {
409 						final ObjectId a = r.getObjectId();
410 						final ObjectId b = ref.getObjectId();
411 						if (a != null && b != null && AnyObjectId.equals(a, b))
412 							return true;
413 					}
414 				}
415 			}
416 			return false;
417 		}
418 
419 		@Override
420 		public String toString() {
421 			return ref.toString();
422 		}
423 	}
424 }