tikhomirov@51: /* tikhomirov@51: * Copyright (c) 2011 Artem Tikhomirov tikhomirov@51: */ tikhomirov@51: package com.tmate.hgkit.fs; 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@51: * @author artem 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@51: public void reset() throws IOException { tikhomirov@51: super.reset(); tikhomirov@51: inflater.reset(); tikhomirov@51: decompressedPos = 0; 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: System.out.println("Seek: " + localOffset); 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@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: }