Java tutorial
/*- * Copyright (C) 2006-2008 Erik Larsson * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free * Software Foundation, either version 3 of the License, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more * details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package io.takari.jdkget.osx.dmg.udif; import java.io.BufferedInputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.zip.DataFormatException; import java.util.zip.Inflater; import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream; import io.takari.jdkget.osx.io.RandomAccessInputStream; import io.takari.jdkget.osx.io.ReadableRandomAccessStream; import io.takari.jdkget.osx.io.RuntimeIOException; import io.takari.jdkget.osx.io.SynchronizedRandomAccessStream; public abstract class UDIFBlockInputStream extends InputStream { protected ReadableRandomAccessStream raf; protected UDIFBlock block; protected final int addInOffset; private long globalBytesRead; // 16 KiB buffer... is it reasonable? protected final byte[] buffer = new byte[16384]; protected int bufferPos = 0; // Initializing this to zero will make read call fillBuffer at first call protected int bufferDataLength = 0; private final byte[] skipBuffer = new byte[4096]; /** * Subclasses use this variable to report how many bytes were read into the * buffer. */ protected int fillSize; /** * Creates a new UDIFBlockInputStream. * * @param raf the RandomAccessFile representing the UDIF file * @param block the block that we should read (usually obtained via * {@link PlistPartition#getBlocks()}) * @param addInOffset the number to add to the block's inOffset to find the * data. */ protected UDIFBlockInputStream(ReadableRandomAccessStream raf, UDIFBlock block, int addInOffset) { this.raf = raf; this.block = block; this.addInOffset = addInOffset; //fillBuffer(); //bufferPos = buffer.length; } /** * This method WILL throw a RuntimeException if <code>block</code> has a * type that there is no handler for. */ public static UDIFBlockInputStream getStream(ReadableRandomAccessStream raf, UDIFBlock block) throws IOException, RuntimeIOException { switch (block.getBlockType()) { case UDIFBlock.BT_ZLIB: return new ZlibBlockInputStream(raf, block, 0); case UDIFBlock.BT_BZIP2: return new Bzip2BlockInputStream(raf, block, 0); case UDIFBlock.BT_COPY: return new CopyBlockInputStream(raf, block, 0); case UDIFBlock.BT_ZERO: case UDIFBlock.BT_ZERO2: return new ZeroBlockInputStream(raf, block, 0); case UDIFBlock.BT_END: case UDIFBlock.BT_UNKNOWN: throw new RuntimeException("Block type is a marker and " + "contains no data."); case UDIFBlock.BT_ADC: default: throw new RuntimeException("No handler for block type " + block.getBlockTypeAsString()); } } /** * In case the available amount of bytes is larger than Integer.MAX_INT, * Integer.MAX_INT is returned. */ @Override public int available() throws IOException { long available = block.getOutSize() - globalBytesRead; if (available > Integer.MAX_VALUE) return Integer.MAX_VALUE; else return (int) available; } /** * This method does NOT close the underlying RandomAccessFile. It can be * reused afterwards. */ @Override public void close() throws IOException { } /** Not supported. */ @Override public void mark(int readlimit) { } /** Returns false, because it isn't supported. */ @Override public boolean markSupported() { return false; } /** @see java.io.InputStream */ @Override public int read() throws IOException { byte[] b = new byte[1]; return read(b, 0, 1); } /** @see java.io.InputStream */ @Override public int read(byte[] b) throws IOException { return read(b, 0, b.length); } /** @see java.io.InputStream */ @Override public int read(byte[] b, int off, int len) throws IOException { // System.out.println("UDIFBlockInputStream.read(b, " + off + ", " + len + ") {"); final int bytesToRead = len; int bytesRead = 0; int outPos = off; while (bytesRead < bytesToRead) { int bytesRemainingInBuffer = bufferDataLength - bufferPos; if (bytesRemainingInBuffer == 0) { // System.out.println(" first call to fillBuffer"); fillBuffer(); // System.out.println(" bufferDataLength=" + bufferDataLength + ",bufferPos=" + bufferPos); bytesRemainingInBuffer = bufferDataLength - bufferPos; if (bytesRemainingInBuffer == 0) { // We apparently have no more data. if (bytesRead == 0) { //System.out.println("return: -1 }"); return -1; } else break; } } // System.out.println(" bytesRemainingInBuffer=" + // bytesRemainingInBuffer + ",bufferPos=" + bufferPos + // ",bufferDataLength=" + bufferDataLength); int bytesToReadFromBuffer = Math.min(bytesToRead - bytesRead, bytesRemainingInBuffer); // System.out.println(" bytesToReadFromBuffer=" + // bytesToReadFromBuffer); // System.out.println(" System.arraycopy(buffer, " + bufferPos + // ", b, " + outPos + ", " + bytesToReadFromBuffer + ");"); System.arraycopy(buffer, bufferPos, b, outPos, bytesToReadFromBuffer); outPos += bytesToReadFromBuffer; bufferPos += bytesToReadFromBuffer; bytesRead += bytesToReadFromBuffer; } globalBytesRead += bytesRead; // System.out.println("return: " + bytesRead + " }"); return bytesRead; } /** Does nothing. Not supported. */ @Override public void reset() throws IOException { } /** * Skips as many bytes as possible. If end of file is reached, the number * of bytes skipped is returned. */ @Override public long skip(long n) throws IOException { long bytesSkipped = 0; while (bytesSkipped < n) { int curSkip = (int) Math.min(n - bytesSkipped, skipBuffer.length); if (curSkip < 0) { throw new RuntimeException("Internal error: curSkip is " + "negative (" + curSkip + ")."); } int res = read(skipBuffer, 0, curSkip); if (res > 0) bytesSkipped += res; else break; } return bytesSkipped; } protected abstract void fillBuffer() throws IOException; public static class ZlibBlockInputStream extends UDIFBlockInputStream { private final Inflater inflater; private final byte[] inBuffer; private long inPos; public ZlibBlockInputStream(ReadableRandomAccessStream raf, UDIFBlock block, int addInOffset) throws IOException { super(raf, block, addInOffset); inflater = new Inflater(); inBuffer = new byte[4096]; inPos = 0; feedInflater(); } private void feedInflater() throws IOException { //System.err.println("ZlibBlockInputStream.feedInflater() {"); long seekPos = addInOffset + inPos + block.getTrueInOffset(); //System.out.println(" seeking to " + seekPos + " (file length: " + // raf.length() + ")"); raf.seek(seekPos); long bytesLeftToRead = block.getInSize() - inPos; int bytesToFeed = (int) Math.min(inBuffer.length, bytesLeftToRead); //System.out.println(" bytesToFeed=" + bytesToFeed); int curBytesRead = raf.read(inBuffer, 0, bytesToFeed); inPos += curBytesRead; inflater.setInput(inBuffer, 0, curBytesRead); //System.out.println(" curBytesRead=" + curBytesRead); //System.out.println("}"); } @Override protected void fillBuffer() throws RuntimeIOException, IOException { //System.err.println("ZlibBlockInputStream.fillBuffer() {"); //if(inflater == null) // System.err.println("INFLATER IS NULL"); //if(inBuffer == null) // System.err.println("INBUFFER IS NULL"); if (inflater.finished()) { //System.out.println("inflater claims to be finished..."); bufferPos = 0; bufferDataLength = 0; } try { int bytesInflated = 0; while (bytesInflated < buffer.length && !inflater.finished()) { if (inflater.needsInput()) feedInflater(); int res = inflater.inflate(buffer, bytesInflated, buffer.length - bytesInflated); if (res >= 0) bytesInflated += res; else throw new RuntimeException("Negative return value when inflating"); } // The fillBuffer method is responsible for updating bufferPos // and bufferDataLength bufferPos = 0; bufferDataLength = bytesInflated; } catch (DataFormatException e) { RuntimeException re = new RuntimeException("Invalid zlib data!"); re.initCause(e); throw re; } //System.out.println("}"); } } public static class CopyBlockInputStream extends UDIFBlockInputStream { private long inPos = 0; public CopyBlockInputStream(ReadableRandomAccessStream raf, UDIFBlock block, int addInOffset) throws RuntimeIOException { super(raf, block, addInOffset); } @Override protected void fillBuffer() throws IOException { raf.seek(addInOffset + inPos + block.getTrueInOffset()); final int bytesToRead = (int) Math.min(block.getInSize() - inPos, buffer.length); int totalBytesRead = 0; while (totalBytesRead < bytesToRead) { int bytesRead = raf.read(buffer, totalBytesRead, bytesToRead - totalBytesRead); if (bytesRead < 0) break; else { totalBytesRead += bytesRead; inPos += bytesRead; } } // The fillBuffer method is responsible for updating bufferPos and // bufferDataLength bufferPos = 0; bufferDataLength = totalBytesRead; } /** Extremely more efficient skip method! */ @Override public long skip(long n) throws IOException { final long bytesToSkip = Math.min(block.getInSize() - inPos, n); if (bytesToSkip < 0) { throw new RuntimeException("Internal error: bytesToSkip is " + "negative (" + bytesToSkip + ")."); } inPos += bytesToSkip; // make read() refill buffer at next call.. bufferPos = 0; bufferDataLength = 0; return bytesToSkip; } } public static class ZeroBlockInputStream extends UDIFBlockInputStream { private long outPos = 0; public ZeroBlockInputStream(ReadableRandomAccessStream raf, UDIFBlock block, int addInOffset) throws RuntimeIOException { super(raf, block, addInOffset); } @Override protected void fillBuffer() throws IOException { final int bytesToWrite = (int) Math.min(block.getOutSize() - outPos, buffer.length); io.takari.jdkget.osx.util.Util.zero(buffer, 0, bytesToWrite); outPos += bytesToWrite; // The fillBuffer method is responsible for updating bufferPos and // bufferDataLength bufferPos = 0; bufferDataLength = bytesToWrite; } /** Extremely more efficient skip method! */ @Override public long skip(long n) throws IOException { final long bytesToSkip = Math.min(block.getOutSize() - outPos, n); if (bytesToSkip < 0) { throw new RuntimeException("Internal error: bytesToSkip is " + "negative (" + bytesToSkip + ")."); } outPos += bytesToSkip; // make read() refill buffer at next call.. bufferPos = 0; bufferDataLength = 0; return bytesToSkip; } } public static class Bzip2BlockInputStream extends UDIFBlockInputStream { private final byte[] BZIP2_SIGNATURE = { 0x42, 0x5A }; // 'BZ' private InputStream bzip2DataStream; private BZip2CompressorInputStream decompressingStream; private long outPos = 0; public Bzip2BlockInputStream(ReadableRandomAccessStream raf, UDIFBlock block, int addInOffset) throws IOException, RuntimeIOException { super(raf, block, addInOffset); if (false) { byte[] inBuffer = new byte[4096]; String basename = System.nanoTime() + ""; File outFile = new File(basename + "_bz2.bin"); int i = 1; while (outFile.exists()) outFile = new File(basename + "_" + i++ + "_bz2.bin"); System.err.println("Creating a new Bzip2BlockInputStream. " + "Dumping bzip2 block data to file \"" + outFile + "\""); FileOutputStream outStream = new FileOutputStream(outFile); raf.seek(block.getTrueInOffset()); long bytesWritten = 0; long bytesToWrite = block.getInSize(); while (bytesWritten < bytesToWrite) { int curBytesRead = raf.read(inBuffer, 0, (int) Math.min(bytesToWrite - bytesWritten, inBuffer.length)); if (curBytesRead <= 0) throw new RuntimeException("Unable to read bzip2 " + "block fully."); outStream.write(inBuffer, 0, curBytesRead); bytesWritten += curBytesRead; } outStream.close(); } bzip2DataStream = new RandomAccessInputStream(new SynchronizedRandomAccessStream(raf), block.getTrueInOffset(), block.getInSize()); byte[] signature = new byte[2]; if (bzip2DataStream.read(signature) != signature.length) throw new RuntimeException("Read error!"); if (!io.takari.jdkget.osx.util.Util.arraysEqual(signature, BZIP2_SIGNATURE)) throw new RuntimeException("Invalid bzip2 block!"); /* Buffering needed because of implementation issues in * CBZip2InputStream. */ decompressingStream = new BZip2CompressorInputStream(new BufferedInputStream(bzip2DataStream)); } @Override protected void fillBuffer() throws IOException { final int bytesToRead = (int) Math.min(block.getOutSize() - outPos, buffer.length); int totalBytesRead = 0; while (totalBytesRead < bytesToRead) { int bytesRead = decompressingStream.read(buffer, totalBytesRead, bytesToRead - totalBytesRead); if (bytesRead < 0) break; else { totalBytesRead += bytesRead; outPos += bytesRead; } } // The fillBuffer method is responsible for updating bufferPos and // bufferDataLength bufferPos = 0; bufferDataLength = totalBytesRead; } @Override public void close() throws IOException { decompressingStream.close(); bzip2DataStream.close(); } } }