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