You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

RefMap.java 11KB

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