tikhomirov@546: /* tikhomirov@546: * Copyright (c) 2013 TMate Software Ltd tikhomirov@546: * tikhomirov@546: * This program is free software; you can redistribute it and/or modify tikhomirov@546: * it under the terms of the GNU General Public License as published by tikhomirov@546: * the Free Software Foundation; version 2 of the License. tikhomirov@546: * tikhomirov@546: * This program is distributed in the hope that it will be useful, tikhomirov@546: * but WITHOUT ANY WARRANTY; without even the implied warranty of tikhomirov@546: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the tikhomirov@546: * GNU General Public License for more details. tikhomirov@546: * tikhomirov@546: * For information on how to redistribute this software under tikhomirov@546: * the terms of a license other than GNU General Public License tikhomirov@546: * contact TMate Software at support@hg4j.com tikhomirov@546: */ tikhomirov@548: package org.tmatesoft.hg.internal; tikhomirov@546: tikhomirov@546: import java.util.LinkedList; tikhomirov@546: tikhomirov@555: import org.tmatesoft.hg.core.HgIterateDirection; tikhomirov@555: import org.tmatesoft.hg.internal.AnnotateFacility.*; tikhomirov@555: import org.tmatesoft.hg.repo.HgDataFile; tikhomirov@546: tikhomirov@546: /** tikhomirov@555: * Produce output like 'hg annotate' does tikhomirov@546: * tikhomirov@546: * @author Artem Tikhomirov tikhomirov@546: * @author TMate Software Ltd. tikhomirov@546: */ tikhomirov@555: public class FileAnnotation implements AnnotateFacility.BlockInspector, RevisionDescriptor.Recipient { tikhomirov@546: tikhomirov@555: @Experimental(reason="The line-by-line inspector likely to become part of core/command API") tikhomirov@555: @Callback tikhomirov@555: public interface LineInspector { tikhomirov@555: /** tikhomirov@555: * Not necessarily invoked sequentially by line numbers tikhomirov@555: */ tikhomirov@555: void line(int lineNumber, int changesetRevIndex, LineDescriptor ld); tikhomirov@555: } tikhomirov@555: tikhomirov@555: public interface LineDescriptor { tikhomirov@555: int totalLines(); tikhomirov@555: } tikhomirov@555: tikhomirov@555: /** tikhomirov@555: * Annotate file revision, line by line. tikhomirov@555: */ tikhomirov@555: public static void annotate(HgDataFile df, int changelogRevisionIndex, LineInspector insp) { tikhomirov@555: if (!df.exists()) { tikhomirov@555: return; tikhomirov@555: } tikhomirov@555: FileAnnotation fa = new FileAnnotation(insp); tikhomirov@555: AnnotateFacility af = new AnnotateFacility(); tikhomirov@555: af.annotate(df, changelogRevisionIndex, fa, HgIterateDirection.NewToOld); tikhomirov@555: } tikhomirov@555: tikhomirov@555: // blocks deleted in the target, as reported at the previous step tikhomirov@555: private LinkedList deleted = new LinkedList(); tikhomirov@555: // blocks deleted in the origin, to become deletions in target at the next step tikhomirov@555: private LinkedList newDeleted = new LinkedList(); tikhomirov@555: // keeps of equal blocks, origin to target, from previous step tikhomirov@555: // XXX smth like IntSliceVector to access triples (or slices of any size, in fact) tikhomirov@555: // with easy indexing, e.g. #get(sliceIndex, indexWithinSlice) tikhomirov@555: // and vect.get(7,2) instead of vect.get(7*SIZEOF_SLICE+2) tikhomirov@555: private IntVector identical = new IntVector(20 * 3, 2 * 3); tikhomirov@555: // equal blocks of the current iteration, to be recalculated before next step tikhomirov@555: // to track line number (current target to ultimate target) mapping tikhomirov@555: private IntVector newIdentical = new IntVector(20 * 3, 2 * 3); tikhomirov@555: tikhomirov@555: private boolean[] knownLines; tikhomirov@555: private final LineInspector delegate; tikhomirov@555: tikhomirov@555: public FileAnnotation(LineInspector lineInspector) { tikhomirov@555: delegate = lineInspector; tikhomirov@555: } tikhomirov@555: tikhomirov@555: public void start(RevisionDescriptor rd) { tikhomirov@555: if (knownLines == null) { tikhomirov@555: knownLines = new boolean[rd.target().elementCount()]; tikhomirov@555: } tikhomirov@555: } tikhomirov@555: tikhomirov@555: // private static void ppp(IntVector v) { tikhomirov@555: // for (int i = 0; i < v.size(); i+= 3) { tikhomirov@555: // int len = v.get(i+2); tikhomirov@555: // System.out.printf("[%d..%d) == [%d..%d); ", v.get(i), v.get(i) + len, v.get(i+1), v.get(i+1) + len); tikhomirov@555: // } tikhomirov@555: // System.out.println(); tikhomirov@555: // } tikhomirov@555: tikhomirov@555: public void done(RevisionDescriptor rd) { tikhomirov@555: if (identical.size() > 0) { tikhomirov@555: // update line numbers of the intermediate target to point to ultimate target's line numbers tikhomirov@555: IntVector v = new IntVector(identical.size(), 2 * 3); tikhomirov@555: for (int i = 0; i < newIdentical.size(); i += 3) { tikhomirov@555: int originLine = newIdentical.get(i); tikhomirov@555: int targetLine = newIdentical.get(i + 1); tikhomirov@555: int length = newIdentical.get(i + 2); tikhomirov@555: int startTargetLine = -1, startOriginLine = -1, c = 0; tikhomirov@555: for (int j = 0; j < length; j++) { tikhomirov@555: int lnInFinal = mapLineIndex(targetLine + j); tikhomirov@555: if (lnInFinal == -1 || (startTargetLine != -1 && lnInFinal != startTargetLine + c)) { tikhomirov@555: // the line is not among "same" in ultimate origin tikhomirov@555: // or belongs to another/next "same" chunk tikhomirov@555: if (startOriginLine == -1) { tikhomirov@555: continue; tikhomirov@546: } tikhomirov@546: v.add(startOriginLine); tikhomirov@546: v.add(startTargetLine); tikhomirov@546: v.add(c); tikhomirov@555: c = 0; tikhomirov@555: startOriginLine = startTargetLine = -1; tikhomirov@555: // fall-through to check if it's not complete miss but a next chunk tikhomirov@555: } tikhomirov@555: if (lnInFinal != -1) { tikhomirov@555: if (startOriginLine == -1) { tikhomirov@555: startOriginLine = originLine + j; tikhomirov@555: startTargetLine = lnInFinal; tikhomirov@555: c = 1; tikhomirov@555: } else { tikhomirov@555: assert lnInFinal == startTargetLine + c; tikhomirov@555: c++; tikhomirov@555: } tikhomirov@546: } tikhomirov@546: } tikhomirov@555: if (startOriginLine != -1) { tikhomirov@555: assert c > 0; tikhomirov@555: v.add(startOriginLine); tikhomirov@555: v.add(startTargetLine); tikhomirov@555: v.add(c); tikhomirov@546: } tikhomirov@546: } tikhomirov@555: newIdentical.clear(); tikhomirov@555: identical = v; tikhomirov@555: } else { tikhomirov@555: IntVector li = newIdentical; tikhomirov@555: newIdentical = identical; tikhomirov@555: identical = li; tikhomirov@546: } tikhomirov@555: LinkedList ld = newDeleted; tikhomirov@555: deleted.clear(); tikhomirov@555: newDeleted = deleted; tikhomirov@555: deleted = ld; tikhomirov@555: } tikhomirov@555: tikhomirov@555: public void same(EqualBlock block) { tikhomirov@555: newIdentical.add(block.originStart()); tikhomirov@555: newIdentical.add(block.targetStart()); tikhomirov@555: newIdentical.add(block.length()); tikhomirov@555: } tikhomirov@555: tikhomirov@555: public void added(AddBlock block) { tikhomirov@555: for (int i = 0, ln = block.firstAddedLine(), x = block.totalAddedLines(); i < x; i++, ln++) { tikhomirov@555: int lnInFinal = mapLineIndex(ln); tikhomirov@555: if (lnInFinal != -1 && !knownLines[lnInFinal]) { tikhomirov@555: delegate.line(lnInFinal, block.targetChangesetIndex(), new LineDescriptorImpl()); tikhomirov@555: knownLines[lnInFinal] = true; tikhomirov@555: } tikhomirov@555: } tikhomirov@555: } tikhomirov@555: tikhomirov@555: public void changed(ChangeBlock block) { tikhomirov@555: deleted(block); tikhomirov@555: added(block); tikhomirov@555: } tikhomirov@555: tikhomirov@555: public void deleted(DeleteBlock block) { tikhomirov@555: newDeleted.add(block); tikhomirov@555: } tikhomirov@555: tikhomirov@555: // line - index in the target tikhomirov@555: private boolean isDeleted(int line) { tikhomirov@555: for (DeleteBlock b : deleted) { tikhomirov@555: if (b.firstRemovedLine() > line) { tikhomirov@555: break; tikhomirov@555: } tikhomirov@555: // line >= b.firstRemovedLine tikhomirov@555: if (b.firstRemovedLine() + b.totalRemovedLines() > line) { tikhomirov@555: return true; tikhomirov@555: } tikhomirov@555: } tikhomirov@555: return false; tikhomirov@555: } tikhomirov@555: tikhomirov@555: // map target lines to the lines of the revision being annotated (the one that came first) tikhomirov@555: private int mapLineIndex(int ln) { tikhomirov@555: if (isDeleted(ln)) { tikhomirov@555: return -1; tikhomirov@555: } tikhomirov@555: if (identical.isEmpty()) { tikhomirov@555: return ln; tikhomirov@555: } tikhomirov@555: for (int i = 0; i < identical.size(); i += 3) { tikhomirov@555: final int originStart = identical.get(i); tikhomirov@555: if (originStart > ln) { tikhomirov@555: // assert false; tikhomirov@555: return -1; tikhomirov@555: } tikhomirov@555: // ln >= b.originStart tikhomirov@555: final int length = identical.get(i + 2); tikhomirov@555: if (originStart + length > ln) { tikhomirov@555: int targetStart = identical.get(i + 1); tikhomirov@555: return targetStart + (ln - originStart); tikhomirov@555: } tikhomirov@555: } tikhomirov@555: // assert false; tikhomirov@555: return -1; tikhomirov@555: } tikhomirov@555: tikhomirov@555: private final class LineDescriptorImpl implements LineDescriptor { tikhomirov@555: LineDescriptorImpl() { tikhomirov@546: } tikhomirov@546: tikhomirov@555: public int totalLines() { tikhomirov@555: return FileAnnotation.this.knownLines.length; tikhomirov@546: } tikhomirov@555: } tikhomirov@555: }