tikhomirov@329: /* tikhomirov@329: * Copyright (c) 2011 TMate Software Ltd tikhomirov@329: * tikhomirov@329: * This program is free software; you can redistribute it and/or modify tikhomirov@329: * it under the terms of the GNU General Public License as published by tikhomirov@329: * the Free Software Foundation; version 2 of the License. tikhomirov@329: * tikhomirov@329: * This program is distributed in the hope that it will be useful, tikhomirov@329: * but WITHOUT ANY WARRANTY; without even the implied warranty of tikhomirov@329: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the tikhomirov@329: * GNU General Public License for more details. tikhomirov@329: * tikhomirov@329: * For information on how to redistribute this software under tikhomirov@329: * the terms of a license other than GNU General Public License tikhomirov@329: * contact TMate Software at support@hg4j.com tikhomirov@329: */ tikhomirov@329: package org.tmatesoft.hg.internal; tikhomirov@329: tikhomirov@329: import java.io.IOException; tikhomirov@329: import java.util.ArrayList; tikhomirov@329: tikhomirov@329: /** tikhomirov@329: * @see http://mercurial.selenic.com/wiki/BundleFormat, in Changelog group description tikhomirov@329: * tikhomirov@329: * range [start..end] in original source gets replaced with data of length (do not keep, use data.length instead) tikhomirov@329: * range [end(i)..start(i+1)] is copied from the source tikhomirov@329: * tikhomirov@329: * @author Artem Tikhomirov tikhomirov@329: * @author TMate Software Ltd. tikhomirov@329: */ tikhomirov@329: public final class Patch { tikhomirov@329: private final IntVector starts, ends; tikhomirov@329: private final ArrayList data; tikhomirov@329: tikhomirov@329: public Patch() { tikhomirov@329: starts = new IntVector(); tikhomirov@329: ends = new IntVector(); tikhomirov@329: data = new ArrayList(); tikhomirov@329: } tikhomirov@329: tikhomirov@329: public int count() { tikhomirov@329: return data.size(); tikhomirov@329: } tikhomirov@329: tikhomirov@329: // number of bytes this patch will add (or remove, if negative) from the base revision tikhomirov@329: private int patchSizeDelta() { tikhomirov@329: int rv = 0; tikhomirov@329: int prevEnd = 0; tikhomirov@329: for (int i = 0, x = data.size(); i < x; i++) { tikhomirov@329: final int start = starts.get(i); tikhomirov@329: final int len = data.get(i).length; tikhomirov@329: rv += start - prevEnd; // would copy from original tikhomirov@329: rv += len; // and add new tikhomirov@329: prevEnd = ends.get(i); tikhomirov@329: } tikhomirov@329: rv -= prevEnd; tikhomirov@329: return rv; tikhomirov@329: } tikhomirov@329: tikhomirov@329: public byte[] apply(DataAccess baseRevisionContent, int outcomeLen) throws IOException { tikhomirov@329: if (outcomeLen == -1) { tikhomirov@329: outcomeLen = baseRevisionContent.length() + patchSizeDelta(); tikhomirov@329: } tikhomirov@329: int prevEnd = 0, destIndex = 0; tikhomirov@329: byte[] rv = new byte[outcomeLen]; tikhomirov@329: for (int i = 0, x = data.size(); i < x; i++) { tikhomirov@329: final int start = starts.get(i); tikhomirov@329: baseRevisionContent.seek(prevEnd); tikhomirov@329: // copy source bytes that were not modified (up to start of the record) tikhomirov@329: baseRevisionContent.readBytes(rv, destIndex, start - prevEnd); tikhomirov@329: destIndex += start - prevEnd; tikhomirov@329: // insert new data from the patch, if any tikhomirov@329: byte[] d = data.get(i); tikhomirov@329: System.arraycopy(d, 0, rv, destIndex, d.length); tikhomirov@329: destIndex += d.length; tikhomirov@329: prevEnd = ends.get(i); tikhomirov@329: } tikhomirov@329: baseRevisionContent.seek(prevEnd); tikhomirov@329: // copy everything in the source past last record's end tikhomirov@329: baseRevisionContent.readBytes(rv, destIndex, (int) (baseRevisionContent.length() - prevEnd)); tikhomirov@329: return rv; tikhomirov@329: } tikhomirov@329: tikhomirov@329: public void clear() { tikhomirov@329: starts.clear(); tikhomirov@329: ends.clear(); tikhomirov@329: data.clear(); tikhomirov@329: } tikhomirov@329: tikhomirov@329: /** tikhomirov@329: * Initialize instance from stream. Any previous patch information (i.e. if instance if reused) is cleared first. tikhomirov@329: * Read up to the end of DataAccess and interpret data as patch records. tikhomirov@329: */ tikhomirov@329: public void read(DataAccess da) throws IOException { tikhomirov@329: clear(); tikhomirov@329: while (!da.isEmpty()) { tikhomirov@329: readOne(da); tikhomirov@329: } tikhomirov@329: } tikhomirov@329: tikhomirov@329: /** tikhomirov@329: * Caller is responsible to ensure stream got some data to read tikhomirov@329: */ tikhomirov@329: public void readOne(DataAccess da) throws IOException { tikhomirov@329: int s = da.readInt(); tikhomirov@329: int e = da.readInt(); tikhomirov@329: int len = da.readInt(); tikhomirov@329: byte[] src = new byte[len]; tikhomirov@329: da.readBytes(src, 0, len); tikhomirov@329: starts.add(s); tikhomirov@329: ends.add(e); tikhomirov@329: data.add(src); tikhomirov@329: } tikhomirov@329: tikhomirov@329: /* tikhomirov@329: private void add(Patch another, int index) { tikhomirov@329: starts.add(another.starts.get(index)); tikhomirov@329: ends.add(another.ends.get(index)); tikhomirov@329: data.add(another.data.get(index)); tikhomirov@329: } tikhomirov@329: tikhomirov@329: /** tikhomirov@329: * Modify this patch with subsequent patch tikhomirov@329: * / tikhomirov@329: public void apply(Patch another) { tikhomirov@329: Patch r = new Patch(); tikhomirov@329: int p1AppliedPos = 0; tikhomirov@329: int p1PrevEnd = 0; tikhomirov@329: for (int i = 0, j = 0, iMax = another.count(), jMax = this.count(); i < iMax; i++) { tikhomirov@329: int newerPatchEntryStart = another.starts.get(i); tikhomirov@329: int olderPatchEntryEnd; tikhomirov@329: tikhomirov@329: while (j < jMax) { tikhomirov@329: if (starts.get(j) < newerPatchEntryStart) { tikhomirov@329: if (starts.get(j)+data.get(j).length <= newerPatchEntryStart) { tikhomirov@329: r.add(this, j); tikhomirov@329: } else { tikhomirov@329: int newLen = newerPatchEntryStart - starts.get(j); tikhomirov@329: int newEnd = ends.get(j) <= newerPatchEntryStart ? ends.get(j) : newerPatchEntryStart; tikhomirov@329: r.add(starts.get(j), newEnd, data.get(j), newLen); tikhomirov@329: break; tikhomirov@329: } tikhomirov@329: } tikhomirov@329: p1AppliedPos += starts.get(j) - p1PrevEnd; tikhomirov@329: p1AppliedPos += data.get(j).length; tikhomirov@329: p1PrevEnd = ends.get(j); tikhomirov@329: j++; tikhomirov@329: } tikhomirov@329: r.add(newerPatchEntryStart, another.ends.get(i), another.data.get(i)); tikhomirov@329: p1AppliedPos += newerPatchEntryStart + p1PrevEnd - another.data.get(i).length; tikhomirov@329: // either j == jMax and another(i, i+1, ..., iMax) need to be just copied tikhomirov@329: // or new patch entry starts before end of one of original patch entries tikhomirov@329: if (olderPatchEntryEnd > (destPosition + newerPatchEntryStart)) { tikhomirov@329: destPosition += starts.get(j) - prevEnd; // count those in the original stream up to old patch start tikhomirov@329: int newLen = newerPatchEntryStart - destPosition; tikhomirov@329: } tikhomirov@329: } tikhomirov@329: } tikhomirov@329: */ tikhomirov@329: }