changeset 657:6334b0267103

ParentChildMap can supply RevisionMap. Refactor ArrayHelper to keep most of sorted/reverse index magic inside
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Thu, 04 Jul 2013 20:27:45 +0200
parents a937e63b6e02
children d10399f80f4e
files src/org/tmatesoft/hg/internal/ArrayHelper.java src/org/tmatesoft/hg/repo/HgBranches.java src/org/tmatesoft/hg/repo/HgParentChildMap.java src/org/tmatesoft/hg/repo/HgRevisionMap.java test/org/tmatesoft/hg/test/TestAuxUtilities.java test/org/tmatesoft/hg/test/TestRevisionMaps.java
diffstat 6 files changed, 238 insertions(+), 114 deletions(-) [+]
line wrap: on
line diff
--- a/src/org/tmatesoft/hg/internal/ArrayHelper.java	Thu Jul 04 18:40:03 2013 +0200
+++ b/src/org/tmatesoft/hg/internal/ArrayHelper.java	Thu Jul 04 20:27:45 2013 +0200
@@ -16,40 +16,107 @@
  */
 package org.tmatesoft.hg.internal;
 
+import java.util.Arrays;
+
 /**
  * Internal alternative to Arrays.sort to build reversed index along with sorting
+ * and to perform lookup (binary search) without sorted array, using reversed index.
  * 
  * @author Artem Tikhomirov
  * @author TMate Software Ltd.
  */
-public class ArrayHelper {
-	private int[] reverse;
+public final class ArrayHelper<T extends Comparable<T>> {
+	private int[] reverse; // aka sorted2natural
+	private final T[] data;
+	private T[] sorted;
+	
+	public ArrayHelper(T[] _data) {
+		assert _data != null;
+		data = _data;
+	}
 
-	@SuppressWarnings("unchecked")
-	public void sort(Comparable<?>[] a) {
-//		Object[] aux = (Object[]) a.clone();
-		reverse = new int[a.length];
-		sort1((Comparable<Object>[])a, 0, a.length);
+	/**
+	 * Sort data this helper wraps, possibly using supplied array (optional)
+	 * to keep sorted elements
+	 * @param sortDest array to keep sorted values at, or <code>null</code>
+	 * @param sortDestIsEmpty <code>false</code> when sortDest already contains copy of data to be sorted
+	 * @param keepSorted <code>true</code> to save sorted array for future use (e.g. in
+	 */
+	public void sort(T[] sortDest, boolean sortDestIsEmpty, boolean keepSorted) {
+		if (sortDest != null) {
+			assert sortDest.length >= data.length;
+			if (sortDestIsEmpty) {
+				System.arraycopy(data, 0, sortDest, 0, data.length);
+			}
+			sorted = sortDest;
+		} else {
+			sorted = data.clone();
+		}
+		reverse = new int[data.length];
 		for (int i = 0; i < reverse.length; i++) {
-			// element that was not moved don't have an index in reverse.
-			// perhaps, can do it inside sort alg?
-			// Alternatively, may start with filling reverse[] array with initial indexes and
-			// avoid != 0 comparisons in #swap altogether?
-			if (reverse[i] == 0) {
-				reverse[i] = i+1;
-			}
+			// initial reverse indexes, so that elements that do
+			// not move during sort got correct indexes
+			reverse[i] = i;
 		}
+		sort1(0, data.length);
+		if (!keepSorted) {
+			sorted = null;
+		}
+	}
+
+	/**
+	 * @return all reverse indexes
+	 */
+	public int[] getReverseIndexes() {
+		return reverse;
+	}
+	
+	public int getReverseIndex(int sortedIndex) {
+		return reverse[sortedIndex];
+	}
+	
+	public T get(int index) {
+		return data[index];
+	}
+	
+	public T[] getData() {
+		return data;
+	}
+
+	/**
+	 * Look up sorted index of the value, using sort information 
+	 * @return same value as {@link Arrays#binarySearch(Object[], Object)} does
+	 */
+	public int binarySearchSorted(T value) {
+		if (sorted != null) {
+			return Arrays.binarySearch(sorted, 0, data.length, value);
+		}
+		return binarySearchWithReverse(0, data.length, value);
+	}
+
+	/**
+	 * Look up index of the value in the original array.
+	 * @return index in original data, or <code>defaultValue</code> if value not found
+	 */
+	public int binarySearch(T value, int defaultValue) {
+		int x = binarySearchSorted(value);
+		if (x < 0) {
+			return defaultValue;
+		}
+		return reverse[x];
 	}
 
 	/**
 	 * Slightly modified version of Arrays.sort1(int[], int, int) quicksort alg (just to deal with Object[])
 	 */
-    private void sort1(Comparable<Object> x[], int off, int len) {
+    private void sort1(int off, int len) {
+		@SuppressWarnings("unchecked")
+		Comparable<Object>[] x = (Comparable<Object>[]) sorted;
     	// Insertion sort on smallest arrays
     	if (len < 7) {
     	    for (int i=off; i<len+off; i++)
     			for (int j=i; j>off && x[j-1].compareTo(x[j]) > 0; j--)
-    			    swap(x, j, j-1);
+    			    swap(j, j-1);
     	    return;
     	}
 
@@ -60,11 +127,11 @@
     	    int n = off + len - 1;
     	    if (len > 40) {        // Big arrays, pseudomedian of 9
     			int s = len/8;
-	    		l = med3(x, l,     l+s, l+2*s);
-	    		m = med3(x, m-s,   m,   m+s);
-	    		n = med3(x, n-2*s, n-s, n);
+	    		l = med3(l,     l+s, l+2*s);
+	    		m = med3(m-s,   m,   m+s);
+	    		n = med3(n-2*s, n-s, n);
     	    }
-    	    m = med3(x, l, m, n); // Mid-size, med of 3
+    	    m = med3(l, m, n); // Mid-size, med of 3
     	}
     	Comparable<Object> v = x[m];
 
@@ -73,67 +140,84 @@
     	while(true) {
     	    while (b <= c && x[b].compareTo(v) <= 0) {
     			if (x[b] == v)
-    			    swap(x, a++, b);
+    			    swap(a++, b);
     			b++;
     	    }
     	    while (c >= b && x[c].compareTo(v) >= 0) {
     			if (x[c] == v)
-    			    swap(x, c, d--);
+    			    swap(c, d--);
     			c--;
     	    }
     	    if (b > c)
     			break;
-    	    swap(x, b++, c--);
+    	    swap(b++, c--);
     	}
 
     	// Swap partition elements back to middle
     	int s, n = off + len;
-    	s = Math.min(a-off, b-a  );  vecswap(x, off, b-s, s);
-    	s = Math.min(d-c,   n-d-1);  vecswap(x, b,   n-s, s);
+    	s = Math.min(a-off, b-a  );  vecswap(off, b-s, s);
+    	s = Math.min(d-c,   n-d-1);  vecswap(b,   n-s, s);
 
     	// Recursively sort non-partition-elements
     	if ((s = b-a) > 1)
-    	    sort1(x, off, s);
+    	    sort1(off, s);
     	if ((s = d-c) > 1)
-    	    sort1(x, n-s, s);
+    	    sort1(n-s, s);
     }
 
     /**
      * Swaps x[a .. (a+n-1)] with x[b .. (b+n-1)].
      */
-    private void vecswap(Object[] x, int a, int b, int n) {
+    private void vecswap(int a, int b, int n) {
 		for (int i=0; i<n; i++, a++, b++) {
-		    swap(x, a, b);
+		    swap(a, b);
 		}
     }
 
     /**
      * Returns the index of the median of the three indexed integers.
      */
-    private static int med3(Comparable<Object>[] x, int a, int b, int c) {
-	return (x[a].compareTo(x[b]) < 0 ?
-		(x[b].compareTo(x[c]) < 0 ? b : x[a].compareTo(x[c]) < 0 ? c : a) :
-		(x[b].compareTo(x[c]) > 0 ? b : x[a].compareTo(x[c]) > 0 ? c : a));
+    private int med3(int a, int b, int c) {
+		@SuppressWarnings("unchecked")
+		Comparable<Object>[] x = (Comparable<Object>[]) sorted;
+		return (x[a].compareTo(x[b]) < 0 ?
+			(x[b].compareTo(x[c]) < 0 ? b : x[a].compareTo(x[c]) < 0 ? c : a) :
+			(x[b].compareTo(x[c]) > 0 ? b : x[a].compareTo(x[c]) > 0 ? c : a));
     }
 
-
-	/**
-	 * @return the reverse
-	 */
-	public int[] getReverse() {
-		return reverse;
-	}
-
-	/**
+    /**
 	 * Swaps x[a] with x[b].
 	 */
-	private void swap(Object[] x, int a, int b) {
+	private void swap(int a, int b) {
+		Object[] x = sorted;
 		Object t = x[a];
 		x[a] = x[b];
 		x[b] = t;
-		int z1 = reverse[a] != 0 ? reverse[a] : a+1;
-		int z2 = reverse[b] != 0 ? reverse[b] : b+1;
+		int z1 = reverse[a];
+		int z2 = reverse[b];
 		reverse[b] = z1;
 		reverse[a] = z2;
 	}
+
+	// copied from Arrays.binarySearch0, update to be instance method and to use reverse indexes
+	private int binarySearchWithReverse(int fromIndex, int toIndex, T key) {
+		int low = fromIndex;
+		int high = toIndex - 1;
+
+		while (low <= high) {
+			int mid = (low + high) >>> 1;
+			// data[reverse[x]] gives sorted value at index x
+			T midVal = data[reverse[mid]];
+			int cmp = midVal.compareTo(key);
+
+			if (cmp < 0)
+				low = mid + 1;
+			else if (cmp > 0)
+				high = mid - 1;
+			else
+				return mid; // key found
+		}
+		return -(low + 1);  // key not found.
+	}
+
 }
--- a/src/org/tmatesoft/hg/repo/HgBranches.java	Thu Jul 04 18:40:03 2013 +0200
+++ b/src/org/tmatesoft/hg/repo/HgBranches.java	Thu Jul 04 20:27:45 2013 +0200
@@ -46,7 +46,8 @@
 import org.tmatesoft.hg.util.ProgressSupport;
 
 /**
- *
+ * Access information about branches in the repository
+ * 
  * @author Artem Tikhomirov
  * @author TMate Software Ltd.
  */
@@ -125,14 +126,17 @@
 	void collect(final ProgressSupport ps) throws HgRuntimeException {
 		branches.clear();
 		final HgRepository repo = internalRepo.getRepo();
-		ps.start(1 + repo.getChangelog().getRevisionCount() * 2);
+		final HgChangelog clog = repo.getChangelog();
+		final HgRevisionMap<HgChangelog> rmap;
+		ps.start(1 + clog.getRevisionCount() * 2);
 		//
 		int lastCached = readCache();
-		isCacheActual = lastCached == repo.getChangelog().getLastRevision();
+		isCacheActual = lastCached == clog.getLastRevision();
 		if (!isCacheActual) {
-			final HgParentChildMap<HgChangelog> pw = new HgParentChildMap<HgChangelog>(repo.getChangelog());
+			// XXX need a way to share HgParentChildMap<HgChangelog>
+			final HgParentChildMap<HgChangelog> pw = new HgParentChildMap<HgChangelog>(clog);
 			pw.init();
-			ps.worked(repo.getChangelog().getRevisionCount());
+			ps.worked(clog.getRevisionCount());
 			//
 			// first revision branch found at
 			final HashMap<String, Nodeid> branchStart = new HashMap<String, Nodeid>();
@@ -167,7 +171,7 @@
 			};
 			// XXX alternatively may iterate with pw.all().subList(lastCached)
 			// but need an effective way to find out branch of particular changeset
-			repo.getChangelog().range(lastCached == -1 ? 0 : lastCached+1, HgRepository.TIP, insp);
+			clog.range(lastCached == -1 ? 0 : lastCached+1, HgRepository.TIP, insp);
 			//
 			// build BranchInfo, based on found and cached 
 			for (String bn : branchStart.keySet()) {
@@ -192,10 +196,10 @@
 				}
 				branches.put(bn, bi);
 			}
-		} // !cacheActual
-		final HgChangelog clog = repo.getChangelog();
-		/// FIXME use HgParentChildMap if available (need to decide how to get HgRevisionMap and HgParentChildMap to a common denominator)
-		final HgRevisionMap<HgChangelog> rmap = new HgRevisionMap<HgChangelog>(clog).init();
+			rmap = pw.getRevisionMap();
+		} else { // !cacheActual
+			rmap = new HgRevisionMap<HgChangelog>(clog).init(); 
+		}
 		for (BranchInfo bi : branches.values()) {
 			bi.validate(clog, rmap);
 		}
--- a/src/org/tmatesoft/hg/repo/HgParentChildMap.java	Thu Jul 04 18:40:03 2013 +0200
+++ b/src/org/tmatesoft/hg/repo/HgParentChildMap.java	Thu Jul 04 20:27:45 2013 +0200
@@ -28,6 +28,7 @@
 import java.util.List;
 
 import org.tmatesoft.hg.core.Nodeid;
+import org.tmatesoft.hg.internal.ArrayHelper;
 import org.tmatesoft.hg.internal.IntMap;
 import org.tmatesoft.hg.repo.Revlog.ParentInspector;
 
@@ -62,14 +63,15 @@
 
 	// IMPORTANT: Nodeid instances shall be shared between all arrays
 
+	private final T revlog;
 	private Nodeid[] sequential; // natural repository order, childrenOf rely on ordering
-	private Nodeid[] sorted; // for binary search
-	private int[] sorted2natural; // indexes in sorted to indexes in sequential
+	private Nodeid[] sorted; // for binary search, just an origin of the actual value in use, the one inside seqWrapper
 	private Nodeid[] firstParent; // parents by natural order (i.e. firstParent[A] is parent of revision with index A)
 	private Nodeid[] secondParent;
-	private final T revlog;
 	private IntMap<Nodeid> heads;
 	private BitSet headsBitSet; // 1 indicates revision got children, != null only during init;
+	private HgRevisionMap<T> revisionIndexMap;
+	private ArrayHelper<Nodeid> seqWrapper; 
 
 
 	public HgParentChildMap(T owner) {
@@ -111,18 +113,13 @@
 		secondParent = new Nodeid[revisionCount];
 		//
 		sequential = new Nodeid[revisionCount];
-		sorted = new Nodeid[revisionCount];
+		sorted = new Nodeid[revisionCount]; 
 		headsBitSet = new BitSet(revisionCount);
 		revlog.indexWalk(0, TIP, this);
-		Arrays.sort(sorted);
-		// FIXME use ArrayHelper instead
-		sorted2natural = new int[revisionCount];
-		for (int i = 0; i < revisionCount; i++) {
-			Nodeid n = sequential[i];
-			int x = Arrays.binarySearch(sorted, n);
-			assertSortedIndex(x);
-			sorted2natural[x] = i;
-		}
+		seqWrapper = new ArrayHelper<Nodeid>(sequential);
+		// HgRevisionMap doesn't keep sorted, try alternative here.
+		// reference this.sorted (not only from ArrayHelper) helps to track ownership in hprof/mem dumps
+		seqWrapper.sort(sorted, false, true);
 		// no reason to keep BitSet, number of heads is usually small
 		IntMap<Nodeid> _heads = new IntMap<Nodeid>(headsBitSet.size() - headsBitSet.cardinality());
 		int index = 0;
@@ -151,16 +148,16 @@
 	 * @return <code>true</code> if revision matches any revision in this revlog
 	 */
 	public boolean knownNode(Nodeid nid) {
-		return Arrays.binarySearch(sorted, nid) >= 0;
+		return seqWrapper.binarySearchSorted(nid) >= 0;
 	}
 
 	/**
 	 * null if none. only known nodes (as per #knownNode) are accepted as arguments
 	 */
 	public Nodeid firstParent(Nodeid nid) {
-		int x = Arrays.binarySearch(sorted, nid);
+		int x = seqWrapper.binarySearchSorted(nid);
 		assertSortedIndex(x);
-		int i = sorted2natural[x];
+		int i = seqWrapper.getReverseIndex(x);
 		return firstParent[i];
 	}
 
@@ -171,9 +168,9 @@
 	}
 	
 	public Nodeid secondParent(Nodeid nid) {
-		int x = Arrays.binarySearch(sorted, nid);
+		int x = seqWrapper.binarySearchSorted(nid);
 		assertSortedIndex(x);
-		int i = sorted2natural[x];
+		int i = seqWrapper.getReverseIndex(x);
 		return secondParent[i];
 	}
 
@@ -183,9 +180,9 @@
 	}
 
 	public boolean appendParentsOf(Nodeid nid, Collection<Nodeid> c) {
-		int x = Arrays.binarySearch(sorted, nid);
+		int x = seqWrapper.binarySearchSorted(nid);
 		assertSortedIndex(x);
-		int i = sorted2natural[x];
+		int i = seqWrapper.getReverseIndex(x);
 		Nodeid p1 = firstParent[i];
 		boolean modified = false;
 		if (p1 != null) {
@@ -214,9 +211,9 @@
 		// first, find earliest index of roots in question, as there's  no sense 
 		// to check children among nodes prior to branch's root node
 		for (Nodeid r : roots) {
-			int x = Arrays.binarySearch(sorted, r);
+			int x = seqWrapper.binarySearchSorted(r);
 			assertSortedIndex(x);
-			int i = sorted2natural[x];
+			int i = seqWrapper.getReverseIndex(x);
 			if (i < earliestRevision) {
 				earliestRevision = i;
 			}
@@ -231,22 +228,19 @@
 		return result;
 	}
 	
-	public long AAA = 0;
-	
 	/**
 	 * @return revisions that have supplied revision as their immediate parent
 	 */
 	public List<Nodeid> directChildren(Nodeid nid) {
-		int x = Arrays.binarySearch(sorted, nid);
+		int x = seqWrapper.binarySearchSorted(nid);
 		assertSortedIndex(x);
-		nid = sorted[x]; // canonical instance
-		int start = sorted2natural[x];
+		int start = seqWrapper.getReverseIndex(x);
+		nid = sequential[start]; // canonical instance
 		if (!hasChildren(start)) {
 			return Collections.emptyList();
 		}
 		ArrayList<Nodeid> result = new ArrayList<Nodeid>(5);
 		for (int i = start + 1; i < sequential.length; i++) {
-			AAA++;
 			if (nid == firstParent[i] || nid == secondParent[i]) {
 				result.add(sequential[i]);
 			}
@@ -259,9 +253,9 @@
 	 * @return <code>true</code> if there's any node in this revlog that has specified node as one of its parents. 
 	 */
 	public boolean hasChildren(Nodeid nid) {
-		int x = Arrays.binarySearch(sorted, nid);
+		int x = seqWrapper.binarySearchSorted(nid);
 		assertSortedIndex(x);
-		int i = sorted2natural[x];
+		int i = seqWrapper.getReverseIndex(x);
 		return hasChildren(i);
 	}
 
@@ -280,19 +274,19 @@
 	 * @return <code>true</code> if <code>wannaBeChild</code> is among children of <code>root</code>
 	 */
 	public boolean isChild(Nodeid root, Nodeid wannaBeChild) {
-		int x = Arrays.binarySearch(sorted, root);
+		int x = seqWrapper.binarySearchSorted(root);
 		assertSortedIndex(x);
-		root = sorted[x]; // canonical instance
-		final int start = sorted2natural[x];
+		final int start = seqWrapper.getReverseIndex(x);
+		root = sequential[start]; // canonical instance
 		if (!hasChildren(start)) {
 			return false; // root got no children at all
 		}
-		int y = Arrays.binarySearch(sorted, wannaBeChild);
+		int y = seqWrapper.binarySearchSorted(wannaBeChild);
 		if (y < 0) {
 			return false; // not found
 		}
-		wannaBeChild = sorted[y]; // canonicalize
-		final int end = sorted2natural[y];
+		final int end = seqWrapper.getReverseIndex(y);
+		wannaBeChild = sequential[end]; // canonicalize
 		if (end <= start) {
 			return false; // potential child was in repository earlier than root
 		}
@@ -312,6 +306,17 @@
 	public Collection<Nodeid> heads() {
 		return heads.values();
 	}
+	
+	/**
+	 * @return map of revision to indexes
+	 */
+	public HgRevisionMap<T> getRevisionMap() {
+		if (revisionIndexMap == null) {
+			revisionIndexMap = new HgRevisionMap<T>(revlog);
+			revisionIndexMap.init(seqWrapper);
+		}
+		return revisionIndexMap;
+	}
 
 	private boolean hasChildren(int sequentialIndex) {
 		return !heads.containsKey(sequentialIndex);
--- a/src/org/tmatesoft/hg/repo/HgRevisionMap.java	Thu Jul 04 18:40:03 2013 +0200
+++ b/src/org/tmatesoft/hg/repo/HgRevisionMap.java	Thu Jul 04 20:27:45 2013 +0200
@@ -19,8 +19,6 @@
 import static org.tmatesoft.hg.repo.HgRepository.BAD_REVISION;
 import static org.tmatesoft.hg.repo.HgRepository.TIP;
 
-import java.util.Arrays;
-
 import org.tmatesoft.hg.core.Nodeid;
 import org.tmatesoft.hg.internal.ArrayHelper;
 import org.tmatesoft.hg.repo.Revlog.RevisionInspector;
@@ -60,15 +58,14 @@
 	 * for complete changelog iteration. 
 	 */
 	
+	private final T revlog;
 	/*
 	 * XXX 3 * (x * 4) bytes. Can I do better?
 	 * It seems, yes. Don't need to keep sorted, always can emulate it with indirect access to sequential through sorted2natural.
 	 * i.e. instead sorted[mid].compareTo(toFind), do sequential[sorted2natural[mid]].compareTo(toFind) 
 	 */
-	private Nodeid[] sequential; // natural repository order, childrenOf rely on ordering
-	private Nodeid[] sorted; // for binary search
-	private int[] sorted2natural;
-	private final T revlog;
+	private Nodeid[] sequential; // natural repository order
+	private ArrayHelper<Nodeid> seqWrapper;
 
 	public HgRevisionMap(T owner) {
 		revlog = owner;
@@ -79,7 +76,7 @@
 	}
 	
 	public void next(int revisionIndex, Nodeid revision, int linkedRevision) {
-		sequential[revisionIndex] = sorted[revisionIndex] = revision;
+		sequential[revisionIndex] = revision;
 	}
 
 	/**
@@ -89,28 +86,29 @@
 		// XXX HgRepository.register((RepoChangeListener) this); // listen to changes in repo, re-init if needed?
 		final int revisionCount = revlog.getRevisionCount();
 		sequential = new Nodeid[revisionCount];
-		sorted = new Nodeid[revisionCount];
 		revlog.indexWalk(0, TIP, this);
 		// next is alternative to Arrays.sort(sorted), and build sorted2natural looking up each element of sequential in sorted.
 		// the way sorted2natural was build is O(n*log n).  
-		final ArrayHelper ah = new ArrayHelper();
-		ah.sort(sorted);
-		// note, values in ArrayHelper#getReversed are 1-based indexes, not 0-based 
-		sorted2natural = ah.getReverse();
+		seqWrapper = new ArrayHelper<Nodeid>(sequential);
+		seqWrapper.sort(null, true, false);
 		return this;
 	}
+
+	/* friendly initializer to use from HgParentChildMap
+	/*package*/ void init(ArrayHelper<Nodeid> _seqWrapper) {
+		assert _seqWrapper.getData().length == revlog.getRevisionCount();
+		sequential = _seqWrapper.getData();
+		seqWrapper = _seqWrapper;
+	}
 	
 	public Nodeid revision(int revisionIndex) {
 		return sequential[revisionIndex];
 	}
+
 	public int revisionIndex(Nodeid revision) {
 		if (revision == null || revision.isNull()) {
 			return BAD_REVISION;
 		}
-		int x = Arrays.binarySearch(sorted, revision);
-		if (x < 0) {
-			return BAD_REVISION;
-		}
-		return sorted2natural[x]-1;
+		return seqWrapper.binarySearch(revision, BAD_REVISION);
 	}
 }
\ No newline at end of file
--- a/test/org/tmatesoft/hg/test/TestAuxUtilities.java	Thu Jul 04 18:40:03 2013 +0200
+++ b/test/org/tmatesoft/hg/test/TestAuxUtilities.java	Thu Jul 04 20:27:45 2013 +0200
@@ -23,6 +23,7 @@
 
 import java.io.IOException;
 import java.nio.ByteBuffer;
+import java.util.Arrays;
 
 import org.junit.Assert;
 import org.junit.Rule;
@@ -62,25 +63,31 @@
 	@Test
 	public void testArrayHelper() {
 		String[] initial = {"d", "w", "k", "b", "c", "i", "a", "r", "e", "h" };
-		ArrayHelper ah = new ArrayHelper();
+		ArrayHelper<String> ah = new ArrayHelper<String>(initial);
 		String[] result = initial.clone();
-		ah.sort(result);
-		String[] restored = restore(result, ah.getReverse());
+		ah.sort(result, false, false);
+		String[] restored = restore(result, ah.getReverseIndexes());
 		assertArrayEquals(initial, restored);
 		//
 		// few elements are on the right place from the very start and do not shift during sort.
 		// make sure for them we've got correct reversed indexes as well
 		initial = new String[] {"d", "h", "c", "b", "k", "i", "a", "r", "e", "w" };
-		ah.sort(result = initial.clone());
-		restored = restore(result, ah.getReverse());
+		ah = new ArrayHelper<String>(initial);
+		ah.sort(result = new String[initial.length], true, true);
+		restored = restore(result, ah.getReverseIndexes());
 		assertArrayEquals(initial, restored);
+		for (int i = 0; i < initial.length; i++) {
+			String s = initial[i];
+			errorCollector.assertEquals(i, ah.binarySearch(s, -1));
+			errorCollector.assertEquals(Arrays.binarySearch(result, s), ah.binarySearchSorted(s));
+		}
 	}
 
 	private static String[] restore(String[] sorted, int[] sortReverse) {
 		String[] rebuilt = new String[sorted.length];
 		for (int i = 0; i < sorted.length; i++) {
 			int indexInOriginal = sortReverse[i];
-			rebuilt[indexInOriginal-1] = sorted[i];
+			rebuilt[indexInOriginal] = sorted[i];
 		}
 		return rebuilt;
 	}
--- a/test/org/tmatesoft/hg/test/TestRevisionMaps.java	Thu Jul 04 18:40:03 2013 +0200
+++ b/test/org/tmatesoft/hg/test/TestRevisionMaps.java	Thu Jul 04 20:27:45 2013 +0200
@@ -28,6 +28,7 @@
 import org.tmatesoft.hg.repo.HgChangelog;
 import org.tmatesoft.hg.repo.HgParentChildMap;
 import org.tmatesoft.hg.repo.HgRepository;
+import org.tmatesoft.hg.repo.HgRevisionMap;
 
 /**
  * 
@@ -101,4 +102,29 @@
 		errorCollector.assertEquals(Collections.emptyList(), parentHelper.directChildren(allRevs[7]));
 	}
 
+	@Test
+	public void testRevisionMap() throws HgException {
+		// XXX this test may benefit from external huge repository
+		final HgRepository repo = Configuration.get().find("test-annotate");
+		Nodeid[] allRevs = RepoUtils.allRevisions(repo);
+		final HgChangelog clog = repo.getChangelog();
+		final HgRevisionMap<HgChangelog> rmap = new HgRevisionMap<HgChangelog>(clog).init();
+		doTestRevisionMap(allRevs, rmap);
+	}
+
+	@Test
+	public void testRevisionMapFromParentChildMap() throws HgException {
+		final HgRepository repo = Configuration.get().find("test-annotate");
+		Nodeid[] allRevs = RepoUtils.allRevisions(repo);
+		HgParentChildMap<HgChangelog> parentHelper = new HgParentChildMap<HgChangelog>(repo.getChangelog());
+		parentHelper.init();
+		doTestRevisionMap(allRevs, parentHelper.getRevisionMap());
+	}
+
+	private void doTestRevisionMap(Nodeid[] allRevs, HgRevisionMap<HgChangelog> rmap) {
+		for (int i = 0; i < allRevs.length; i++) {
+			errorCollector.assertEquals(i, rmap.revisionIndex(allRevs[i]));
+			errorCollector.assertEquals(allRevs[i], rmap.revision(i));
+		}
+	}
 }