changeset 347:8da7ade36c57

Add specific IAE subclass to handle wrong (e.g. outdated after rollback) revisions
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Tue, 22 Nov 2011 05:25:57 +0100
parents 6d2c6b2469fc
children a0864b2892cd
files src/org/tmatesoft/hg/core/HgInvalidRevisionException.java src/org/tmatesoft/hg/internal/RevlogStream.java src/org/tmatesoft/hg/repo/HgDataFile.java src/org/tmatesoft/hg/repo/HgInternals.java src/org/tmatesoft/hg/repo/Revlog.java
diffstat 5 files changed, 154 insertions(+), 27 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/org/tmatesoft/hg/core/HgInvalidRevisionException.java	Tue Nov 22 05:25:57 2011 +0100
@@ -0,0 +1,115 @@
+/*
+ * Copyright (c) 2011 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.core;
+
+import org.tmatesoft.hg.internal.Experimental;
+import org.tmatesoft.hg.repo.HgRepository;
+
+/**
+ * Use of revision or local revision index that is not valid for a given revlog.
+ *  
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+@SuppressWarnings("serial")
+@Experimental(reason="1) Whether to use checked or runtime exception is not yet decided. 2) Perhaps, its use not bound to wrong arguments")
+public class HgInvalidRevisionException extends IllegalArgumentException {
+	private Nodeid rev;
+	private Integer revIdx;
+	// next two make sense only when revIdx is present
+	private int rangeLeftBoundary = -1, rangeRightBoundary = -1;
+
+	/**
+	 * 
+	 * this exception is not expected to be initialized with another exception, although those who need to, 
+	 * may still use {@link #initCause(Throwable)}
+	 * @param message optional description of the issue
+	 * @param revision invalid revision, may be  <code>null</code> if revisionIndex is used
+	 * @param revisionIndex invalid revision index, may be <code>null</code> if not known and revision is supplied 
+	 */
+	public HgInvalidRevisionException(String message, Nodeid revision, Integer revisionIndex) {
+		super(message);
+		assert revision != null || revisionIndex != null; 
+		rev = revision;
+		revIdx = revisionIndex;
+	}
+
+	public HgInvalidRevisionException(Nodeid revision) {
+		this(null, revision, null);
+	}
+	
+	public HgInvalidRevisionException(int revisionIndex) {
+		this(null, null, revisionIndex);
+	}
+
+	public Nodeid getRevision() {
+		return rev;
+	}
+	
+	public Integer getRevisionIndex() {
+		return revIdx;
+	}
+
+	public HgInvalidRevisionException setRevision(Nodeid revision) {
+		assert revision != null;
+		rev = revision;
+		return this;
+	}
+	
+	// int, not Integer is on purpose, not to clear exception completely
+	public HgInvalidRevisionException setRevisionIndex(int revisionIndex) {
+		revIdx = revisionIndex;
+		return this;
+	}
+
+	public HgInvalidRevisionException setRevisionIndex(int revisionIndex, int rangeLeft, int rangeRight) {
+		revIdx = revisionIndex;
+		rangeLeftBoundary = rangeLeft;
+		rangeRightBoundary = rangeRight;
+		return this;
+	}
+
+	@Override
+	public String getMessage() {
+		String msg = super.getMessage();
+		if (msg != null) {
+			return msg;
+		}
+		StringBuilder sb = new StringBuilder();
+		sb.append('[');
+		if (rev != null) {
+			sb.append(rev.shortNotation());
+			sb.append(' ');
+		}
+		if (revIdx != null) {
+			String sr;
+			switch (revIdx) {
+			case HgRepository.BAD_REVISION : sr = "UNKNOWN"; break;
+			case HgRepository.TIP : sr = "TIP"; break;
+			case HgRepository.WORKING_COPY: sr = "WORKING-COPY"; break;
+			default : sr = revIdx.toString();
+			}
+			if (rangeLeftBoundary != -1 || rangeRightBoundary != -1) {
+				sb.append(String.format("%s is not from [%d..%d]", sr, rangeLeftBoundary, rangeRightBoundary));
+			} else {
+				sb.append(sr);
+			}
+		}
+		sb.append(']');
+		return sb.toString();
+	}
+}
--- a/src/org/tmatesoft/hg/internal/RevlogStream.java	Tue Nov 22 04:02:37 2011 +0100
+++ b/src/org/tmatesoft/hg/internal/RevlogStream.java	Tue Nov 22 05:25:57 2011 +0100
@@ -24,6 +24,7 @@
 import java.util.zip.Inflater;
 
 import org.tmatesoft.hg.core.HgBadStateException;
+import org.tmatesoft.hg.core.HgInvalidRevisionException;
 import org.tmatesoft.hg.core.Nodeid;
 import org.tmatesoft.hg.repo.HgInternals;
 import org.tmatesoft.hg.repo.HgRepository;
@@ -105,13 +106,13 @@
 	/**
 	 * @throws HgBadStateException if internal read operation failed
 	 */
-	public byte[] nodeid(int revision) {
+	public byte[] nodeid(int revision) throws HgInvalidRevisionException {
 		final int indexSize = revisionCount();
 		if (revision == TIP) {
 			revision = indexSize - 1;
 		}
 		if (revision < 0 || revision >= indexSize) {
-			throw new IllegalArgumentException(Integer.toString(revision));
+			throw new HgInvalidRevisionException(revision).setRevisionIndex(revision, 0, indexSize);
 		}
 		DataAccess daIndex = getIndexStream();
 		try {
@@ -191,7 +192,7 @@
 
 	// should be possible to use TIP, ALL, or -1, -2, -n notation of Hg
 	// ? boolean needsNodeid
-	public void iterate(int start, int end, boolean needData, Inspector inspector) {
+	public void iterate(int start, int end, boolean needData, Inspector inspector) throws HgInvalidRevisionException {
 		initOutline();
 		final int indexSize = revisionCount();
 		if (indexSize == 0) {
@@ -224,13 +225,16 @@
 	 * @param needData whether inspector needs access to header only
 	 * @param inspector callback to process entries
 	 */
-	public void iterate(int[] sortedRevisions, boolean needData, Inspector inspector) {
+	public void iterate(int[] sortedRevisions, boolean needData, Inspector inspector) throws HgInvalidRevisionException {
 		final int indexSize = revisionCount();
 		if (indexSize == 0 || sortedRevisions.length == 0) {
 			return;
 		}
-		if (sortedRevisions[0] > indexSize || sortedRevisions[sortedRevisions.length - 1] > indexSize) {
-			throw new IllegalArgumentException(String.format("Can't iterate [%d, %d] in range [0..%d]", sortedRevisions[0], sortedRevisions[sortedRevisions.length - 1], indexSize));
+		if (sortedRevisions[0] > indexSize) {
+			throw new HgInvalidRevisionException(String.format("Can't iterate [%d, %d] in range [0..%d]", sortedRevisions[0], sortedRevisions[sortedRevisions.length - 1], indexSize), null, sortedRevisions[0]);
+		}
+		if (sortedRevisions[sortedRevisions.length - 1] > indexSize) {
+			throw new HgInvalidRevisionException(String.format("Can't iterate [%d, %d] in range [0..%d]", sortedRevisions[0], sortedRevisions[sortedRevisions.length - 1], indexSize), null, sortedRevisions[sortedRevisions.length - 1]);
 		}
 
 		ReaderN1 r = new ReaderN1(needData, inspector);
--- a/src/org/tmatesoft/hg/repo/HgDataFile.java	Tue Nov 22 04:02:37 2011 +0100
+++ b/src/org/tmatesoft/hg/repo/HgDataFile.java	Tue Nov 22 05:25:57 2011 +0100
@@ -33,6 +33,7 @@
 
 import org.tmatesoft.hg.core.HgDataStreamException;
 import org.tmatesoft.hg.core.HgException;
+import org.tmatesoft.hg.core.HgInvalidRevisionException;
 import org.tmatesoft.hg.core.HgLogCommand;
 import org.tmatesoft.hg.core.Nodeid;
 import org.tmatesoft.hg.internal.DataAccess;
@@ -177,7 +178,7 @@
 //	}
 	
 	/*XXX not sure distinct method contentWithFilters() is the best way to do, perhaps, callers shall add filters themselves?*/
-	public void contentWithFilters(int revision, ByteChannel sink) throws HgDataStreamException, CancelledException {
+	public void contentWithFilters(int revision, ByteChannel sink) throws HgDataStreamException, CancelledException, HgInvalidRevisionException {
 		if (revision == WORKING_COPY) {
 			workingCopy(sink); // pass un-mangled sink
 		} else {
@@ -187,7 +188,7 @@
 
 	// for data files need to check heading of the file content for possible metadata
 	// @see http://mercurial.selenic.com/wiki/FileFormats#data.2BAC8-
-	public void content(int revision, ByteChannel sink) throws HgDataStreamException, CancelledException {
+	public void content(int revision, ByteChannel sink) throws HgDataStreamException, CancelledException, HgInvalidRevisionException {
 		if (revision == TIP) {
 			revision = getLastRevision();
 		}
@@ -198,7 +199,7 @@
 			return;
 		}
 		if (wrongLocalRevision(revision) || revision == BAD_REVISION) {
-			throw new IllegalArgumentException(String.valueOf(revision));
+			throw new HgInvalidRevisionException(revision);
 		}
 		if (sink == null) {
 			throw new IllegalArgumentException();
@@ -350,7 +351,7 @@
 		history(0, getLastRevision(), inspector);
 	}
 
-	public void history(int start, int end, HgChangelog.Inspector inspector) {
+	public void history(int start, int end, HgChangelog.Inspector inspector) throws HgInvalidRevisionException {
 		if (!exists()) {
 			throw new IllegalStateException("Can't get history of invalid repository file node"); 
 		}
--- a/src/org/tmatesoft/hg/repo/HgInternals.java	Tue Nov 22 04:02:37 2011 +0100
+++ b/src/org/tmatesoft/hg/repo/HgInternals.java	Tue Nov 22 05:25:57 2011 +0100
@@ -25,6 +25,7 @@
 import java.net.InetAddress;
 import java.net.UnknownHostException;
 
+import org.tmatesoft.hg.core.HgInvalidRevisionException;
 import org.tmatesoft.hg.core.SessionContext;
 import org.tmatesoft.hg.internal.Experimental;
 import org.tmatesoft.hg.internal.RelativePathRewrite;
@@ -139,13 +140,15 @@
 		return rev < 0 && rev != TIP && rev != WORKING_COPY && rev != BAD_REVISION; 
 	}
 
-	// throws IllegalArgumentException if [start..end] range is not a subrange of [0..lastRevision]
-	public static void checkRevlogRange(int start, int end, int lastRevision) {
+	// throws HgInvalidRevisionException or IllegalArgumentException if [start..end] range is not a subrange of [0..lastRevision]
+	public static void checkRevlogRange(int start, int end, int lastRevision) throws HgInvalidRevisionException {
 		if (start < 0 || start > lastRevision) {
-			throw new IllegalArgumentException(String.format("Bad left range boundary %d in [0..%d]", start, lastRevision));
+			final String m = String.format("Bad left range boundary %d in [0..%d]", start, lastRevision);
+			throw new HgInvalidRevisionException(m, null, start).setRevisionIndex(start, 0, lastRevision);
 		}
 		if (end < 0 || end > lastRevision) {
-			throw new IllegalArgumentException(String.format("Bad right range boundary %d in [0..%d]", end, lastRevision));
+			final String m = String.format("Bad right range boundary %d in [0..%d]", end, lastRevision);
+			throw new HgInvalidRevisionException(m, null, end).setRevisionIndex(end, 0, lastRevision);
 		}
 		if (end < start) {
 			throw new IllegalArgumentException(String.format("Bad range [%d..%d]", start, end));
--- a/src/org/tmatesoft/hg/repo/Revlog.java	Tue Nov 22 04:02:37 2011 +0100
+++ b/src/org/tmatesoft/hg/repo/Revlog.java	Tue Nov 22 05:25:57 2011 +0100
@@ -30,6 +30,7 @@
 
 import org.tmatesoft.hg.core.HgBadStateException;
 import org.tmatesoft.hg.core.HgException;
+import org.tmatesoft.hg.core.HgInvalidRevisionException;
 import org.tmatesoft.hg.core.Nodeid;
 import org.tmatesoft.hg.internal.ArrayHelper;
 import org.tmatesoft.hg.internal.DataAccess;
@@ -86,7 +87,7 @@
 		return content.revisionCount() - 1;
 	}
 	
-	public final Nodeid getRevision(int revision) {
+	public final Nodeid getRevision(int revision) throws HgInvalidRevisionException {
 		// XXX cache nodeids? Rather, if context.getCache(this).getRevisionMap(create == false) != null, use it
 		return Nodeid.fromBinary(content.nodeid(revision), 0);
 	}
@@ -94,14 +95,14 @@
 	/**
 	 * FIXME need to be careful about (1) ordering of the revisions in the return list; (2) modifications (sorting) of the argument array 
 	 */
-	public final List<Nodeid> getRevisions(int... revisions) {
+	public final List<Nodeid> getRevisions(int... revisions) throws HgInvalidRevisionException {
 		ArrayList<Nodeid> rv = new ArrayList<Nodeid>(revisions.length);
 		Arrays.sort(revisions);
 		getRevisionsInternal(rv, revisions);
 		return rv;
 	}
 	
-	/*package-local*/ void getRevisionsInternal(final List<Nodeid> retVal, int[] sortedRevs) {
+	/*package-local*/ void getRevisionsInternal(final List<Nodeid> retVal, int[] sortedRevs) throws HgInvalidRevisionException {
 		// once I have getRevisionMap and may find out whether it is avalable from cache,
 		// may use it, perhaps only for small number of revisions
 		content.iterate(sortedRevs, false, new RevlogStream.Inspector() {
@@ -114,17 +115,19 @@
 
 	/**
 	 * Get local revision number (index) of the specified revision.
+	 * If unsure, use {@link #isKnown(Nodeid)} to find out whether nodeid belongs to this revlog.
 	 * 
 	 * For occasional queries, this method works with decent performance, despite its O(n/2) approach.
 	 * Alternatively, if you need to perform multiple queries (e.g. at least 15-20), {@link RevisionMap} may come handy.
-	 *   
+	 * 
 	 * @param nid revision to look up 
-	 * @return revision index, or {@link HgRepository#BAD_REVISION} if specified revision not found. 
+	 * @return revision local index in this revlog
+	 * @throws HgInvalidRevisionException if supplied nodeid doesn't identify any revision from this revlog  
 	 */
-	public final int getLocalRevision(Nodeid nid) {
+	public final int getLocalRevision(Nodeid nid) throws HgInvalidRevisionException {
 		int revision = content.findLocalRevisionNumber(nid);
 		if (revision == BAD_REVISION) {
-			throw new IllegalArgumentException(String.format("%s doesn't represent a revision of %s", nid.toString(), this /*XXX HgDataFile.getPath might be more suitable here*/));
+			throw new HgInvalidRevisionException(String.format("Bad revision of %s", this /*XXX HgDataFile.getPath might be more suitable here*/), nid, null);
 		}
 		return revision;
 	}
@@ -146,14 +149,14 @@
 	 * Access to revision data as is (decompressed, but otherwise unprocessed, i.e. not parsed for e.g. changeset or manifest entries) 
 	 * @param nodeid
 	 */
-	protected void rawContent(Nodeid nodeid, ByteChannel sink) throws HgException, IOException, CancelledException {
+	protected void rawContent(Nodeid nodeid, ByteChannel sink) throws HgException, IOException, CancelledException, HgInvalidRevisionException {
 		rawContent(getLocalRevision(nodeid), sink);
 	}
 	
 	/**
 	 * @param revision - repo-local index of this file change (not a changelog revision number!)
 	 */
-	protected void rawContent(int revision, ByteChannel sink) throws HgException, IOException, CancelledException {
+	protected void rawContent(int revision, ByteChannel sink) throws HgException, IOException, CancelledException, HgInvalidRevisionException {
 		if (sink == null) {
 			throw new IllegalArgumentException();
 		}
@@ -170,11 +173,12 @@
 	 * @param parentRevisions - int[2] to get local revision numbers of parents (e.g. {6, -1})
 	 * @param parent1 - byte[20] or null, if parent's nodeid is not needed
 	 * @param parent2 - byte[20] or null, if second parent's nodeid is not needed
-	 * @return
+	 * @throws HgInvalidRevisionException
+	 * @throws IllegalArgumentException
 	 */
-	public void parents(int revision, int[] parentRevisions, byte[] parent1, byte[] parent2) {
+	public void parents(int revision, int[] parentRevisions, byte[] parent1, byte[] parent2) throws HgInvalidRevisionException {
 		if (revision != TIP && !(revision >= 0 && revision < content.revisionCount())) {
-			throw new IllegalArgumentException(String.valueOf(revision));
+			throw new HgInvalidRevisionException(revision);
 		}
 		if (parentRevisions == null || parentRevisions.length < 2) {
 			throw new IllegalArgumentException(String.valueOf(parentRevisions));
@@ -221,7 +225,7 @@
 	}
 	
 	@Experimental
-	public void walk(int start, int end, final Revlog.Inspector inspector) {
+	public void walk(int start, int end, final Revlog.Inspector inspector) throws HgInvalidRevisionException {
 		int lastRev = getLastRevision();
 		if (start == TIP) {
 			start = lastRev;