tikhomirov@51: /* tikhomirov@420: * Copyright (c) 2011-2012 TMate Software Ltd tikhomirov@157: * tikhomirov@157: * This program is free software; you can redistribute it and/or modify tikhomirov@157: * it under the terms of the GNU General Public License as published by tikhomirov@157: * the Free Software Foundation; version 2 of the License. tikhomirov@157: * tikhomirov@157: * This program is distributed in the hope that it will be useful, tikhomirov@157: * but WITHOUT ANY WARRANTY; without even the implied warranty of tikhomirov@157: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the tikhomirov@157: * GNU General Public License for more details. tikhomirov@157: * tikhomirov@157: * For information on how to redistribute this software under tikhomirov@157: * the terms of a license other than GNU General Public License tikhomirov@157: * contact TMate Software at support@hg4j.com tikhomirov@51: */ tikhomirov@157: package org.tmatesoft.hg.internal; tikhomirov@51: tikhomirov@51: import java.io.EOFException; tikhomirov@51: import java.io.IOException; tikhomirov@51: import java.util.zip.DataFormatException; tikhomirov@51: import java.util.zip.Inflater; tikhomirov@51: import java.util.zip.ZipException; tikhomirov@51: tikhomirov@51: /** tikhomirov@51: * DataAccess counterpart for InflaterInputStream. tikhomirov@51: * XXX is it really needed to be subclass of FilterDataAccess? tikhomirov@157: * tikhomirov@157: * @author Artem Tikhomirov tikhomirov@157: * @author TMate Software Ltd. tikhomirov@51: */ tikhomirov@51: public class InflaterDataAccess extends FilterDataAccess { tikhomirov@51: tikhomirov@51: private final Inflater inflater; tikhomirov@51: private final byte[] buffer; tikhomirov@51: private final byte[] singleByte = new byte[1]; tikhomirov@51: private int decompressedPos = 0; tikhomirov@158: private int decompressedLength; tikhomirov@51: tikhomirov@420: public InflaterDataAccess(DataAccess dataAccess, long offset, int compressedLength) { tikhomirov@263: this(dataAccess, offset, compressedLength, -1, new Inflater(), new byte[512]); tikhomirov@51: } tikhomirov@51: tikhomirov@420: public InflaterDataAccess(DataAccess dataAccess, long offset, int compressedLength, int actualLength) { tikhomirov@263: this(dataAccess, offset, compressedLength, actualLength, new Inflater(), new byte[512]); tikhomirov@158: } tikhomirov@158: tikhomirov@420: public InflaterDataAccess(DataAccess dataAccess, long offset, int compressedLength, int actualLength, Inflater inflater, byte[] buf) { tikhomirov@158: super(dataAccess, offset, compressedLength); tikhomirov@263: if (inflater == null || buf == null) { tikhomirov@263: throw new IllegalArgumentException(); tikhomirov@263: } tikhomirov@51: this.inflater = inflater; tikhomirov@158: this.decompressedLength = actualLength; tikhomirov@263: buffer = buf; tikhomirov@51: } tikhomirov@51: tikhomirov@51: @Override tikhomirov@157: public InflaterDataAccess reset() throws IOException { tikhomirov@51: super.reset(); tikhomirov@51: inflater.reset(); tikhomirov@51: decompressedPos = 0; tikhomirov@157: return this; tikhomirov@51: } tikhomirov@51: tikhomirov@51: @Override tikhomirov@421: protected int available() throws IOException { tikhomirov@158: return length() - decompressedPos; tikhomirov@51: } tikhomirov@51: tikhomirov@51: @Override tikhomirov@421: public boolean isEmpty() throws IOException { tikhomirov@158: // can't use super.available() <= 0 because even when 0 < super.count < 6(?) tikhomirov@158: // decompressedPos might be already == length() tikhomirov@158: return available() <= 0; tikhomirov@51: } tikhomirov@51: tikhomirov@51: @Override tikhomirov@421: public int length() throws IOException { tikhomirov@51: if (decompressedLength != -1) { tikhomirov@51: return decompressedLength; tikhomirov@51: } tikhomirov@158: decompressedLength = 0; // guard to avoid endless loop in case length() would get invoked from below. tikhomirov@51: int c = 0; tikhomirov@421: int oldPos = decompressedPos; tikhomirov@421: byte[] dummy = new byte[buffer.length]; tikhomirov@575: try { tikhomirov@575: int toRead = -1; tikhomirov@575: do { tikhomirov@421: while (!inflater.needsInput()) { tikhomirov@421: c += inflater.inflate(dummy, 0, dummy.length); tikhomirov@158: } tikhomirov@575: if (inflater.needsInput() && (toRead = super.available()) > 0) { tikhomirov@575: // fill: tikhomirov@575: if (toRead > buffer.length) { tikhomirov@575: toRead = buffer.length; tikhomirov@575: } tikhomirov@575: super.readBytes(buffer, 0, toRead); tikhomirov@575: inflater.setInput(buffer, 0, toRead); tikhomirov@575: } tikhomirov@575: } while(toRead > 0); tikhomirov@575: } catch (DataFormatException ex) { tikhomirov@575: throw new IOException(ex.toString()); tikhomirov@51: } tikhomirov@421: decompressedLength = c + oldPos; tikhomirov@421: reset(); tikhomirov@421: seek(oldPos); tikhomirov@421: return decompressedLength; tikhomirov@51: } tikhomirov@51: tikhomirov@51: @Override tikhomirov@158: public void seek(int localOffset) throws IOException { tikhomirov@51: if (localOffset < 0 /* || localOffset >= length() */) { tikhomirov@51: throw new IllegalArgumentException(); tikhomirov@51: } tikhomirov@51: if (localOffset >= decompressedPos) { tikhomirov@420: skip(localOffset - decompressedPos); tikhomirov@51: } else { tikhomirov@51: reset(); tikhomirov@420: skip(localOffset); tikhomirov@51: } tikhomirov@51: } tikhomirov@51: tikhomirov@51: @Override tikhomirov@391: public void skip(final int bytesToSkip) throws IOException { tikhomirov@391: int bytes = bytesToSkip; tikhomirov@51: if (bytes < 0) { tikhomirov@51: bytes += decompressedPos; tikhomirov@51: if (bytes < 0) { tikhomirov@391: throw new IOException(String.format("Underflow. Rewind past start of the slice. To skip:%d, decPos:%d, decLen:%d. Left:%d", bytesToSkip, decompressedPos, decompressedLength, bytes)); tikhomirov@51: } tikhomirov@51: reset(); tikhomirov@51: // fall-through tikhomirov@51: } tikhomirov@51: while (!isEmpty() && bytes > 0) { tikhomirov@51: readByte(); tikhomirov@51: bytes--; tikhomirov@51: } tikhomirov@51: if (bytes != 0) { tikhomirov@391: throw new IOException(String.format("Underflow. Rewind past end of the slice. To skip:%d, decPos:%d, decLen:%d. Left:%d", bytesToSkip, decompressedPos, decompressedLength, bytes)); tikhomirov@51: } tikhomirov@51: } tikhomirov@51: tikhomirov@51: @Override tikhomirov@51: public byte readByte() throws IOException { tikhomirov@51: readBytes(singleByte, 0, 1); tikhomirov@51: return singleByte[0]; tikhomirov@51: } tikhomirov@51: tikhomirov@51: @Override tikhomirov@51: public void readBytes(byte[] b, int off, int len) throws IOException { tikhomirov@51: try { tikhomirov@51: int n; tikhomirov@51: while (len > 0) { tikhomirov@51: while ((n = inflater.inflate(b, off, len)) == 0) { tikhomirov@399: // XXX few last bytes (checksum?) may be ignored by inflater, thus inflate may return 0 in tikhomirov@157: // perfectly legal conditions (when all data already expanded, but there are still some bytes tikhomirov@399: // in the input stream) tikhomirov@399: int toRead = -1; tikhomirov@399: if (inflater.needsInput() && (toRead = super.available()) > 0) { tikhomirov@51: // fill: tikhomirov@51: if (toRead > buffer.length) { tikhomirov@51: toRead = buffer.length; tikhomirov@51: } tikhomirov@51: super.readBytes(buffer, 0, toRead); tikhomirov@51: inflater.setInput(buffer, 0, toRead); tikhomirov@399: } else { tikhomirov@399: // prevent hang up in this cycle if no more data is available, see Issue 25 tikhomirov@399: throw new EOFException(String.format("No more compressed data is available to satisfy request for %d bytes. [finished:%b, needDict:%b, needInp:%b, available:%d", len, inflater.finished(), inflater.needsDictionary(), inflater.needsInput(), toRead)); tikhomirov@51: } tikhomirov@51: } tikhomirov@51: off += n; tikhomirov@51: len -= n; tikhomirov@51: decompressedPos += n; tikhomirov@51: if (len == 0) { tikhomirov@51: return; // filled tikhomirov@51: } tikhomirov@51: } tikhomirov@51: } catch (DataFormatException e) { tikhomirov@51: String s = e.getMessage(); tikhomirov@51: throw new ZipException(s != null ? s : "Invalid ZLIB data format"); tikhomirov@51: } tikhomirov@51: } tikhomirov@51: }