view src/org/tmatesoft/hg/internal/FileAnnotation.java @ 555:e623aa2ca526

Annotate: RevisionDescriptor provides extra knowledge about inspected/annotated revision
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Fri, 22 Feb 2013 19:03:25 +0100
parents a5fd757d1b5d
children e55f17a7a195
line wrap: on
line source
/*
 * Copyright (c) 2013 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.internal;

import java.util.LinkedList;

import org.tmatesoft.hg.core.HgIterateDirection;
import org.tmatesoft.hg.internal.AnnotateFacility.*;
import org.tmatesoft.hg.repo.HgDataFile;

/**
 * Produce output like 'hg annotate' does
 * 
 * @author Artem Tikhomirov
 * @author TMate Software Ltd.
 */
public class FileAnnotation implements AnnotateFacility.BlockInspector, RevisionDescriptor.Recipient {

	@Experimental(reason="The line-by-line inspector likely to become part of core/command API")
	@Callback
	public interface LineInspector {
		/**
		 * Not necessarily invoked sequentially by line numbers
		 */
		void line(int lineNumber, int changesetRevIndex, LineDescriptor ld);
	}

	public interface LineDescriptor {
		int totalLines();
	}

	/**
	 * Annotate file revision, line by line.
	 */
	public static void annotate(HgDataFile df, int changelogRevisionIndex, LineInspector insp) {
		if (!df.exists()) {
			return;
		}
		FileAnnotation fa = new FileAnnotation(insp);
		AnnotateFacility af = new AnnotateFacility();
		af.annotate(df, changelogRevisionIndex, fa, HgIterateDirection.NewToOld);
	}

	// blocks deleted in the target, as reported at the previous step
	private LinkedList<DeleteBlock> deleted = new LinkedList<DeleteBlock>();
	// blocks deleted in the origin, to become deletions in target at the next step
	private LinkedList<DeleteBlock> newDeleted = new LinkedList<DeleteBlock>();
	// keeps <startSeq1, startSeq2, len> of equal blocks, origin to target, from previous step
	// XXX smth like IntSliceVector to access triples (or slices of any size, in fact)
	// with easy indexing, e.g. #get(sliceIndex, indexWithinSlice)
	// and vect.get(7,2) instead of vect.get(7*SIZEOF_SLICE+2)
	private IntVector identical = new IntVector(20 * 3, 2 * 3);
	// equal blocks of the current iteration, to be recalculated before next step
	// to track line number (current target to ultimate target) mapping 
	private IntVector newIdentical = new IntVector(20 * 3, 2 * 3);

	private boolean[] knownLines;
	private final LineInspector delegate;

	public FileAnnotation(LineInspector lineInspector) {
		delegate = lineInspector;
	}

	public void start(RevisionDescriptor rd) {
		if (knownLines == null) {
			knownLines = new boolean[rd.target().elementCount()];
		}
	}

	//		private static void ppp(IntVector v) {
	//			for (int i = 0; i < v.size(); i+= 3) {
	//				int len = v.get(i+2);
	//				System.out.printf("[%d..%d) == [%d..%d);  ", v.get(i), v.get(i) + len, v.get(i+1), v.get(i+1) + len);
	//			}
	//			System.out.println();
	//		}

	public void done(RevisionDescriptor rd) {
		if (identical.size() > 0) {
			// update line numbers of the intermediate target to point to ultimate target's line numbers
			IntVector v = new IntVector(identical.size(), 2 * 3);
			for (int i = 0; i < newIdentical.size(); i += 3) {
				int originLine = newIdentical.get(i);
				int targetLine = newIdentical.get(i + 1);
				int length = newIdentical.get(i + 2);
				int startTargetLine = -1, startOriginLine = -1, c = 0;
				for (int j = 0; j < length; j++) {
					int lnInFinal = mapLineIndex(targetLine + j);
					if (lnInFinal == -1 || (startTargetLine != -1 && lnInFinal != startTargetLine + c)) {
						// the line is not among "same" in ultimate origin
						// or belongs to another/next "same" chunk 
						if (startOriginLine == -1) {
							continue;
						}
						v.add(startOriginLine);
						v.add(startTargetLine);
						v.add(c);
						c = 0;
						startOriginLine = startTargetLine = -1;
						// fall-through to check if it's not complete miss but a next chunk
					}
					if (lnInFinal != -1) {
						if (startOriginLine == -1) {
							startOriginLine = originLine + j;
							startTargetLine = lnInFinal;
							c = 1;
						} else {
							assert lnInFinal == startTargetLine + c;
							c++;
						}
					}
				}
				if (startOriginLine != -1) {
					assert c > 0;
					v.add(startOriginLine);
					v.add(startTargetLine);
					v.add(c);
				}
			}
			newIdentical.clear();
			identical = v;
		} else {
			IntVector li = newIdentical;
			newIdentical = identical;
			identical = li;
		}
		LinkedList<DeleteBlock> ld = newDeleted;
		deleted.clear();
		newDeleted = deleted;
		deleted = ld;
	}

	public void same(EqualBlock block) {
		newIdentical.add(block.originStart());
		newIdentical.add(block.targetStart());
		newIdentical.add(block.length());
	}

	public void added(AddBlock block) {
		for (int i = 0, ln = block.firstAddedLine(), x = block.totalAddedLines(); i < x; i++, ln++) {
			int lnInFinal = mapLineIndex(ln);
			if (lnInFinal != -1 && !knownLines[lnInFinal]) {
				delegate.line(lnInFinal, block.targetChangesetIndex(), new LineDescriptorImpl());
				knownLines[lnInFinal] = true;
			}
		}
	}

	public void changed(ChangeBlock block) {
		deleted(block);
		added(block);
	}

	public void deleted(DeleteBlock block) {
		newDeleted.add(block);
	}

	// line - index in the target
	private boolean isDeleted(int line) {
		for (DeleteBlock b : deleted) {
			if (b.firstRemovedLine() > line) {
				break;
			}
			// line >= b.firstRemovedLine
			if (b.firstRemovedLine() + b.totalRemovedLines() > line) {
				return true;
			}
		}
		return false;
	}

	// map target lines to the lines of the revision being annotated (the one that came first)
	private int mapLineIndex(int ln) {
		if (isDeleted(ln)) {
			return -1;
		}
		if (identical.isEmpty()) {
			return ln;
		}
		for (int i = 0; i < identical.size(); i += 3) {
			final int originStart = identical.get(i);
			if (originStart > ln) {
				//					assert false;
				return -1;
			}
			// ln >= b.originStart
			final int length = identical.get(i + 2);
			if (originStart + length > ln) {
				int targetStart = identical.get(i + 1);
				return targetStart + (ln - originStart);
			}
		}
		//			assert false;
		return -1;
	}

	private final class LineDescriptorImpl implements LineDescriptor {
		LineDescriptorImpl() {
		}

		public int totalLines() {
			return FileAnnotation.this.knownLines.length;
		}
	}
}