# HG changeset patch # User Artem Tikhomirov # Date 1299672997 -3600 # Node ID b413b16d10a50cc027f4c38e4df5a9fedd618a79 # Parent d5268ca7715b8d96204fc62abc632e8f55761547 Integer offsets and file length explictly, rather than casts throughout code. Inflater may benefit from total length hint, but shall calculate it by its own if needed diff -r d5268ca7715b -r b413b16d10a5 cmdline/org/tmatesoft/hg/console/Main.java --- a/cmdline/org/tmatesoft/hg/console/Main.java Wed Mar 09 05:22:17 2011 +0100 +++ b/cmdline/org/tmatesoft/hg/console/Main.java Wed Mar 09 13:16:37 2011 +0100 @@ -76,7 +76,9 @@ System.out.println(f1.isCopy()); System.out.println(f2.isCopy()); ByteArrayChannel bac = new ByteArrayChannel(); - f1.content(0, bac); + f1.content(1, bac); // 0: 1151, 1: 1139 + System.out.println(bac.toArray().length); + f2.content(0, bac = new ByteArrayChannel()); // 0: 14269 System.out.println(bac.toArray().length); } diff -r d5268ca7715b -r b413b16d10a5 src/org/tmatesoft/hg/internal/ByteArrayDataAccess.java --- a/src/org/tmatesoft/hg/internal/ByteArrayDataAccess.java Wed Mar 09 05:22:17 2011 +0100 +++ b/src/org/tmatesoft/hg/internal/ByteArrayDataAccess.java Wed Mar 09 13:16:37 2011 +0100 @@ -64,11 +64,11 @@ return this; } @Override - public long length() { + public int length() { return length; } @Override - public void seek(long offset) { + public void seek(int offset) { pos = (int) offset; } @Override diff -r d5268ca7715b -r b413b16d10a5 src/org/tmatesoft/hg/internal/DataAccess.java --- a/src/org/tmatesoft/hg/internal/DataAccess.java Wed Mar 09 05:22:17 2011 +0100 +++ b/src/org/tmatesoft/hg/internal/DataAccess.java Wed Mar 09 13:16:37 2011 +0100 @@ -32,7 +32,7 @@ public boolean isEmpty() { return true; } - public long length() { + public int length() { return 0; } /** @@ -45,7 +45,7 @@ return this; } // absolute positioning - public void seek(long offset) throws IOException { + public void seek(int offset) throws IOException { throw new UnsupportedOperationException(); } // relative positioning @@ -95,7 +95,7 @@ // FIXME exception handling is not right, just for the sake of quick test public byte[] byteArray() throws IOException { reset(); - byte[] rv = new byte[(int) length()]; + byte[] rv = new byte[length()]; readBytes(rv, 0, rv.length); return rv; } diff -r d5268ca7715b -r b413b16d10a5 src/org/tmatesoft/hg/internal/DataAccessProvider.java --- a/src/org/tmatesoft/hg/internal/DataAccessProvider.java Wed Mar 09 05:22:17 2011 +0100 +++ b/src/org/tmatesoft/hg/internal/DataAccessProvider.java Wed Mar 09 13:16:37 2011 +0100 @@ -23,6 +23,8 @@ import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel; +import org.tmatesoft.hg.core.HgBadStateException; + /** * * @author Artem Tikhomirov @@ -48,16 +50,20 @@ } try { FileChannel fc = new FileInputStream(f).getChannel(); - if (fc.size() > mapioMagicBoundary) { + int flen = (int) fc.size(); + if (fc.size() - flen != 0) { + throw new HgBadStateException("Files greater than 2Gb are not yet supported"); + } + if (flen > mapioMagicBoundary) { // TESTS: bufLen of 1024 was used to test MemMapFileAccess - return new MemoryMapFileAccess(fc, fc.size(), mapioMagicBoundary); + return new MemoryMapFileAccess(fc, flen, mapioMagicBoundary); } else { // XXX once implementation is more or less stable, // may want to try ByteBuffer.allocateDirect() to see // if there's any performance gain. boolean useDirectBuffer = false; - // TESTS: bufferSize of 100 was used to check buffer underflow states when readBytes reads chunks bigger than bufSize - return new FileAccess(fc, fc.size(), bufferSize, useDirectBuffer); + // TESTS: bufferSize of 100 was used to check buffer underflow states when readBytes reads chunks bigger than bufSize + return new FileAccess(fc, flen, bufferSize, useDirectBuffer); } } catch (IOException ex) { // unlikely to happen, we've made sure file exists. @@ -69,12 +75,12 @@ // DOESN'T WORK YET private static class MemoryMapFileAccess extends DataAccess { private FileChannel fileChannel; - private final long size; + private final int size; private long position = 0; // always points to buffer's absolute position in the file private final int memBufferSize; private MappedByteBuffer buffer; - public MemoryMapFileAccess(FileChannel fc, long channelSize, int /*long?*/ bufferSize) { + public MemoryMapFileAccess(FileChannel fc, int channelSize, int /*long?*/ bufferSize) { fileChannel = fc; size = channelSize; memBufferSize = bufferSize; @@ -86,7 +92,7 @@ } @Override - public long length() { + public int length() { return size; } @@ -97,7 +103,7 @@ } @Override - public void seek(long offset) { + public void seek(int offset) { assert offset >= 0; // offset may not necessarily be further than current position in the file (e.g. rewind) if (buffer != null && /*offset is within buffer*/ offset >= position && (offset - position) < buffer.limit()) { @@ -181,14 +187,14 @@ // (almost) regular file access - FileChannel and buffers. private static class FileAccess extends DataAccess { private FileChannel fileChannel; - private final long size; + private final int size; private ByteBuffer buffer; - private long bufferStartInFile = 0; // offset of this.buffer in the file. + private int bufferStartInFile = 0; // offset of this.buffer in the file. - public FileAccess(FileChannel fc, long channelSize, int bufferSizeHint, boolean useDirect) { + public FileAccess(FileChannel fc, int channelSize, int bufferSizeHint, boolean useDirect) { fileChannel = fc; size = channelSize; - final int capacity = size < bufferSizeHint ? (int) size : bufferSizeHint; + final int capacity = size < bufferSizeHint ? size : bufferSizeHint; buffer = useDirect ? ByteBuffer.allocateDirect(capacity) : ByteBuffer.allocate(capacity); buffer.flip(); // or .limit(0) to indicate it's empty } @@ -199,7 +205,7 @@ } @Override - public long length() { + public int length() { return size; } @@ -210,7 +216,7 @@ } @Override - public void seek(long offset) throws IOException { + public void seek(int offset) throws IOException { if (offset > size) { throw new IllegalArgumentException(); } diff -r d5268ca7715b -r b413b16d10a5 src/org/tmatesoft/hg/internal/FilterDataAccess.java --- a/src/org/tmatesoft/hg/internal/FilterDataAccess.java Wed Mar 09 05:22:17 2011 +0100 +++ b/src/org/tmatesoft/hg/internal/FilterDataAccess.java Wed Mar 09 13:16:37 2011 +0100 @@ -28,11 +28,11 @@ */ public class FilterDataAccess extends DataAccess { private final DataAccess dataAccess; - private final long offset; + private final int offset; private final int length; private int count; - public FilterDataAccess(DataAccess dataAccess, long offset, int length) { + public FilterDataAccess(DataAccess dataAccess, int offset, int length) { this.dataAccess = dataAccess; this.offset = offset; this.length = length; @@ -55,12 +55,12 @@ } @Override - public long length() { + public int length() { return length; } @Override - public void seek(long localOffset) throws IOException { + public void seek(int localOffset) throws IOException { if (localOffset < 0 || localOffset > length) { throw new IllegalArgumentException(); } diff -r d5268ca7715b -r b413b16d10a5 src/org/tmatesoft/hg/internal/InflaterDataAccess.java --- a/src/org/tmatesoft/hg/internal/InflaterDataAccess.java Wed Mar 09 05:22:17 2011 +0100 +++ b/src/org/tmatesoft/hg/internal/InflaterDataAccess.java Wed Mar 09 13:16:37 2011 +0100 @@ -22,6 +22,8 @@ import java.util.zip.Inflater; import java.util.zip.ZipException; +import org.tmatesoft.hg.core.HgBadStateException; + /** * DataAccess counterpart for InflaterInputStream. @@ -36,15 +38,20 @@ private final byte[] buffer; private final byte[] singleByte = new byte[1]; private int decompressedPos = 0; - private int decompressedLength = -1; + private int decompressedLength; - public InflaterDataAccess(DataAccess dataAccess, long offset, int length) { - this(dataAccess, offset, length, new Inflater(), 512); + public InflaterDataAccess(DataAccess dataAccess, int offset, int compressedLength) { + this(dataAccess, offset, compressedLength, -1, new Inflater(), 512); } - public InflaterDataAccess(DataAccess dataAccess, long offset, int length, Inflater inflater, int bufSize) { - super(dataAccess, offset, length); + public InflaterDataAccess(DataAccess dataAccess, int offset, int compressedLength, int actualLength) { + this(dataAccess, offset, compressedLength, actualLength, new Inflater(), 512); + } + + public InflaterDataAccess(DataAccess dataAccess, int offset, int compressedLength, int actualLength, Inflater inflater, int bufSize) { + super(dataAccess, offset, compressedLength); this.inflater = inflater; + this.decompressedLength = actualLength; buffer = new byte[bufSize]; } @@ -58,39 +65,53 @@ @Override protected int available() { - throw new IllegalStateException("Can't tell how much uncompressed data left"); + return length() - decompressedPos; } @Override public boolean isEmpty() { - return super.available() <= 0 && inflater.finished(); // and/or inflater.getRemaining() <= 0 ? + // can't use super.available() <= 0 because even when 0 < super.count < 6(?) + // decompressedPos might be already == length() + return available() <= 0; } @Override - public long length() { + public int length() { if (decompressedLength != -1) { return decompressedLength; } + decompressedLength = 0; // guard to avoid endless loop in case length() would get invoked from below. int c = 0; try { int oldPos = decompressedPos; - while (!isEmpty()) { - readByte(); - c++; + byte[] dummy = new byte[buffer.length]; + int toRead; + while ((toRead = super.available()) > 0) { + if (toRead > buffer.length) { + toRead = buffer.length; + } + super.readBytes(buffer, 0, toRead); + inflater.setInput(buffer, 0, toRead); + try { + while (!inflater.needsInput()) { + c += inflater.inflate(dummy, 0, dummy.length); + } + } catch (DataFormatException ex) { + throw new HgBadStateException(ex); + } } decompressedLength = c + oldPos; reset(); seek(oldPos); return decompressedLength; } catch (IOException ex) { - ex.printStackTrace(); // FIXME log error decompressedLength = -1; // better luck next time? - return 0; + throw new HgBadStateException(ex); // XXX perhaps, checked exception } } @Override - public void seek(long localOffset) throws IOException { + public void seek(int localOffset) throws IOException { if (localOffset < 0 /* || localOffset >= length() */) { throw new IllegalArgumentException(); } diff -r d5268ca7715b -r b413b16d10a5 src/org/tmatesoft/hg/internal/RevlogStream.java --- a/src/org/tmatesoft/hg/internal/RevlogStream.java Wed Mar 09 05:22:17 2011 +0100 +++ b/src/org/tmatesoft/hg/internal/RevlogStream.java Wed Mar 09 13:16:37 2011 +0100 @@ -26,6 +26,7 @@ import java.util.LinkedList; import java.util.List; +import org.tmatesoft.hg.core.HgBadStateException; import org.tmatesoft.hg.core.Nodeid; import org.tmatesoft.hg.repo.HgRepository; @@ -77,7 +78,7 @@ revision = indexSize - 1; } try { - int recordOffset = inline ? (int) index.get(revision).offset : revision * REVLOGV1_RECORD_SIZE; + int recordOffset = inline ? index.get(revision).getIntOffset() : revision * REVLOGV1_RECORD_SIZE; daIndex.seek(recordOffset + 12); // 6+2+4 int actualLen = daIndex.readInt(); return actualLen; @@ -99,7 +100,7 @@ } DataAccess daIndex = getIndexStream(); try { - int recordOffset = inline ? (int) index.get(revision).offset : revision * REVLOGV1_RECORD_SIZE; + int recordOffset = inline ? index.get(revision).getIntOffset() : revision * REVLOGV1_RECORD_SIZE; daIndex.seek(recordOffset + 32); byte[] rv = new byte[20]; daIndex.readBytes(rv, 0, 20); @@ -122,7 +123,7 @@ } DataAccess daIndex = getIndexStream(); try { - int recordOffset = inline ? (int) index.get(revision).offset : revision * REVLOGV1_RECORD_SIZE; + int recordOffset = inline ? index.get(revision).getIntOffset() : revision * REVLOGV1_RECORD_SIZE; daIndex.seek(recordOffset + 20); int linkRev = daIndex.readInt(); return linkRev; @@ -208,11 +209,11 @@ i = start; } - daIndex.seek(inline ? index.get(i).offset : i * REVLOGV1_RECORD_SIZE); + daIndex.seek(inline ? index.get(i).getIntOffset() : i * REVLOGV1_RECORD_SIZE); for (; i <= end; i++ ) { if (inline && needData) { // inspector reading data (though FilterDataAccess) may have affected index position - daIndex.seek(index.get(i).offset); + daIndex.seek(index.get(i).getIntOffset()); } long l = daIndex.readLong(); // 0 @SuppressWarnings("unused") @@ -231,7 +232,7 @@ DataAccess userDataAccess = null; if (needData) { final byte firstByte; - long streamOffset = index.get(i).offset; + int streamOffset = index.get(i).getIntOffset(); DataAccess streamDataAccess; if (inline) { streamDataAccess = daIndex; @@ -240,9 +241,10 @@ streamDataAccess = daData; daData.seek(streamOffset); } + final boolean patchToPrevious = baseRevision != i; // XXX not sure if this is the right way to detect a patch firstByte = streamDataAccess.readByte(); if (firstByte == 0x78 /* 'x' */) { - userDataAccess = new InflaterDataAccess(streamDataAccess, streamOffset, compressedLen); + userDataAccess = new InflaterDataAccess(streamDataAccess, streamOffset, compressedLen, patchToPrevious ? -1 : actualLen); } else if (firstByte == 0x75 /* 'u' */) { userDataAccess = new FilterDataAccess(streamDataAccess, streamOffset+1, compressedLen-1); } else { @@ -251,7 +253,7 @@ userDataAccess = new FilterDataAccess(streamDataAccess, streamOffset, compressedLen); } // XXX - if (baseRevision != i) { // XXX not sure if this is the right way to detect a patch + if (patchToPrevious) { // this is a patch LinkedList patches = new LinkedList(); while (!userDataAccess.isEmpty()) { @@ -281,7 +283,7 @@ } } } catch (IOException ex) { - throw new IllegalStateException(ex); // FIXME need better handling + throw new HgBadStateException(ex); // FIXME need better handling } finally { daIndex.done(); if (daData != null) { @@ -345,14 +347,23 @@ // perhaps, package-local or protected, if anyone else from low-level needs them // XXX think over if we should keep offset in case of separate data file - we read the field anyway. Perhaps, distinct entry classes for Inline and non-inline indexes? private static class IndexEntry { - public final long offset; // for separate .i and .d - copy of index record entry, for inline index - actual offset of the record in the .i file (record entry + revision * record size)) + private final int/*long*/ offset; // for separate .i and .d - copy of index record entry, for inline index - actual offset of the record in the .i file (record entry + revision * record size)) //public final int length; // data past fixed record (need to decide whether including header size or not), and whether length is of compressed data or not public final int baseRevision; public IndexEntry(long o, int baseRev) { - offset = o; + offset = (int) o; + // Index file stores offsets as long, but since DataAccess doesn't support long length() and others yet, + // no reason to operate with long offsets + if (o != offset) { + throw new HgBadStateException("Data too big, offset didn't fit to sizeof(int)"); + } baseRevision = baseRev; } + + public int getIntOffset() { + return offset; + } } // mpatch.c : apply() @@ -360,7 +371,7 @@ public/*for HgBundle; until moved to better place*/static byte[] apply(DataAccess baseRevisionContent, int outcomeLen, List patch) throws IOException { int last = 0, destIndex = 0; if (outcomeLen == -1) { - outcomeLen = (int) baseRevisionContent.length(); + outcomeLen = baseRevisionContent.length(); for (PatchRecord pr : patch) { outcomeLen += pr.start - last + pr.len; last = pr.end; diff -r d5268ca7715b -r b413b16d10a5 src/org/tmatesoft/hg/repo/HgDataFile.java --- a/src/org/tmatesoft/hg/repo/HgDataFile.java Wed Mar 09 05:22:17 2011 +0100 +++ b/src/org/tmatesoft/hg/repo/HgDataFile.java Wed Mar 09 13:16:37 2011 +0100 @@ -320,7 +320,7 @@ @Override protected void prepare(int revisionNumber, DataAccess da) throws HgException, IOException { - long daLength = da.length(); + final int daLength = da.length(); if (daLength < 4 || da.readByte() != 1 || da.readByte() != 10) { metadata.recordNone(revisionNumber); da.reset();