diff src/org/tmatesoft/hg/repo/HgRevisionMap.java @ 433:be697c3e951e

Revlog.RevisionMap helper class got promoted as TLC, renamed to HgRevisionMap
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Fri, 30 Mar 2012 16:43:09 +0200
parents
children 6526d8adbc0f
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/org/tmatesoft/hg/repo/HgRevisionMap.java	Fri Mar 30 16:43:09 2012 +0200
@@ -0,0 +1,116 @@
+/*
+ * Copyright (c) 2011-2012 TMate Software Ltd
+ *  
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * For information on how to redistribute this software under
+ * the terms of a license other than GNU General Public License
+ * contact TMate Software at support@hg4j.com
+ */
+package org.tmatesoft.hg.repo;
+
+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;
+
+/**
+ * Effective int to Nodeid and vice versa translation. It's advised to use this class instead of 
+ * multiple {@link Revlog#getRevisionIndex(Nodeid)} calls. Rule of thumb is 20+ calls (given 
+ * initialization costs). It's also important to take into account memory consumption, for huge
+ * repositories use of this class may pay off only when accessing greatest fraction of all revisions.  
+ * 
+ * <p>Next code snippet shows instantiation and sample use: 
+ * <pre>
+ *   RevisionMap<HgChangelog> clogMap = new RevisionMap<HgChangelog>(clog).init();
+ *   RevisionMap<HgDataFile> fileMap = new RevisionMap<HgDataFile>(fileNode).init();
+ *   
+ *   int fileRevIndex = 0;
+ *   Nodeid fileRev = fileMap.revision(fileRevIndex);
+ *   int csetRevIndex = fileNode.getChangesetRevisionIndex(fileRevIndex);
+ *   Nodeid csetRev = clogMap.revision(localCset);
+ *   changesetToNodeidMap.put(csetRev, fileRev);
+ * </pre>
+ * 
+ * <p>
+ * {@link Revlog#getRevisionIndex(Nodeid)} with straightforward lookup approach performs O(n/2)
+ * <p>
+ * {@link HgRevisionMap#revisionIndex(Nodeid)} is log(n), plus initialization is O(n) (just once).
+ * 
+ * @see HgParentChildMap
+ * 
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public final class HgRevisionMap<T extends Revlog> implements RevisionInspector {
+	/*
+	 * in fact, initialization is much slower as it instantiates Nodeids, while #getRevisionIndex
+	 * compares directly against byte buffer. Measuring cpython with 70k+ gives 3 times difference (47 vs 171)
+	 * for complete changelog iteration. 
+	 */
+	
+	/*
+	 * 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;
+
+	public HgRevisionMap(T owner) {
+		revlog = owner;
+	}
+	
+	public HgRepository getRepo() {
+		return revlog.getRepo();
+	}
+	
+	public void next(int revisionIndex, Nodeid revision, int linkedRevision) {
+		sequential[revisionIndex] = sorted[revisionIndex] = revision;
+	}
+
+	/**
+	 * @return <code>this</code> for convenience.
+	 */
+	public HgRevisionMap<T> init(/*XXX Pool<Nodeid> to reuse nodeids, if possible. */) throws HgInvalidControlFileException{
+		// 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();
+		return this;
+	}
+	
+	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;
+	}
+}
\ No newline at end of file