tikhomirov@51: /* tikhomirov@157: * Copyright (c) 2011 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@157: 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@51: private int decompressedLength = -1; tikhomirov@51: tikhomirov@51: public InflaterDataAccess(DataAccess dataAccess, long offset, int length) { tikhomirov@51: this(dataAccess, offset, length, new Inflater(), 512); tikhomirov@51: } tikhomirov@51: tikhomirov@51: public InflaterDataAccess(DataAccess dataAccess, long offset, int length, Inflater inflater, int bufSize) { tikhomirov@51: super(dataAccess, offset, length); tikhomirov@51: this.inflater = inflater; tikhomirov@51: buffer = new byte[bufSize]; 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@51: protected int available() { tikhomirov@51: throw new IllegalStateException("Can't tell how much uncompressed data left"); tikhomirov@51: } tikhomirov@51: tikhomirov@51: @Override tikhomirov@51: public boolean isEmpty() { tikhomirov@51: return super.available() <= 0 && inflater.finished(); // and/or inflater.getRemaining() <= 0 ? tikhomirov@51: } tikhomirov@51: tikhomirov@51: @Override tikhomirov@51: public long length() { tikhomirov@51: if (decompressedLength != -1) { tikhomirov@51: return decompressedLength; tikhomirov@51: } tikhomirov@51: int c = 0; tikhomirov@51: try { tikhomirov@51: int oldPos = decompressedPos; tikhomirov@51: while (!isEmpty()) { tikhomirov@51: readByte(); tikhomirov@51: c++; tikhomirov@51: } tikhomirov@51: decompressedLength = c + oldPos; tikhomirov@51: reset(); tikhomirov@51: seek(oldPos); tikhomirov@51: return decompressedLength; tikhomirov@51: } catch (IOException ex) { tikhomirov@51: ex.printStackTrace(); // FIXME log error tikhomirov@51: decompressedLength = -1; // better luck next time? tikhomirov@51: return 0; tikhomirov@51: } tikhomirov@51: } tikhomirov@51: tikhomirov@51: @Override tikhomirov@51: public void seek(long localOffset) throws IOException { tikhomirov@51: if (localOffset < 0 /* || localOffset >= length() */) { tikhomirov@51: throw new IllegalArgumentException(); tikhomirov@51: } tikhomirov@51: if (localOffset >= decompressedPos) { tikhomirov@51: skip((int) (localOffset - decompressedPos)); tikhomirov@51: } else { tikhomirov@51: reset(); tikhomirov@51: skip((int) localOffset); tikhomirov@51: } tikhomirov@51: } tikhomirov@51: tikhomirov@51: @Override tikhomirov@51: public void skip(int bytes) throws IOException { tikhomirov@51: if (bytes < 0) { tikhomirov@51: bytes += decompressedPos; tikhomirov@51: if (bytes < 0) { tikhomirov@51: throw new IOException("Underflow. Rewind past start of the slice."); 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@51: throw new IOException("Underflow. Rewind past end of the slice"); 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@157: // FIXME 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@157: // in the input stream tikhomirov@51: if (inflater.finished() || inflater.needsDictionary()) { tikhomirov@51: throw new EOFException(); tikhomirov@51: } tikhomirov@51: if (inflater.needsInput()) { tikhomirov@51: // fill: tikhomirov@51: int toRead = super.available(); 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@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: }