/* ** 2011 April 5 ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: ** May you do good and not evil. ** May you find forgiveness for yourself and forgive others. ** May you share freely, never taking more than you give. */ package info.ata4.bsplib.io; import info.ata4.bsplib.util.StringMacroUtils; import info.ata4.io.buffer.ByteBufferInputStream; import info.ata4.io.buffer.ByteBufferOutputStream; import info.ata4.io.lzma.LzmaDecoderProps; import info.ata4.io.lzma.LzmaEncoderProps; import info.ata4.log.LogUtils; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.logging.Level; import java.util.logging.Logger; import lzma.LzmaDecoder; import lzma.LzmaEncoder; /** * LZMA encoding and decoding helper class. * * @author Nico Bergemann <barracuda415 at yahoo.de> */ public class LzmaBuffer { private static final Logger L = LogUtils.getLogger(); public final static int LZMA_ID = StringMacroUtils.makeID("LZMA"); public final static int HEADER_SIZE = 17; private LzmaBuffer() { } public static ByteBuffer uncompress(ByteBuffer buffer) throws IOException { ByteOrder bo = buffer.order(); ByteBuffer bbc = buffer.duplicate(); bbc.order(ByteOrder.LITTLE_ENDIAN); bbc.rewind(); // ensure that this buffer is actually compressed if (bbc.remaining() < HEADER_SIZE || bbc.getInt() != LZMA_ID) { throw new IOException("Buffer is not compressed"); } // read more from the header int actualSize = bbc.getInt(); int lzmaSize = bbc.getInt(); byte[] propsData = new byte[5]; bbc.get(propsData); int lzmaSizeBuf = bbc.limit() - HEADER_SIZE; // check the size of the compressed buffer if (lzmaSizeBuf != lzmaSize) { L.log(Level.WARNING, "Difference in LZMA data length: found {0} bytes, expected {1}", new Object[]{lzmaSizeBuf, lzmaSize}); } ByteBuffer bbu = ByteBuffer.allocateDirect(actualSize); try ( ByteBufferInputStream is = new ByteBufferInputStream(bbc); ByteBufferOutputStream os = new ByteBufferOutputStream(bbu); ) { LzmaDecoder decoder = new LzmaDecoder(); // set properties LzmaDecoderProps props = new LzmaDecoderProps(); props.setIncludeSize(false); props.fromArray(propsData); props.apply(decoder); // decompress buffer if (!decoder.code(is, os, actualSize)) { throw new IOException("Error in LZMA stream"); } } // reset buffer bbu.order(bo); bbu.rewind(); return bbu; } public static ByteBuffer compress(ByteBuffer buffer) throws IOException { ByteOrder bo = buffer.order(); ByteBuffer bbu = buffer.duplicate(); bbu.rewind(); LzmaEncoderProps props = new LzmaEncoderProps(); props.setIncludeSize(false); byte[] lzma; try ( ByteBufferInputStream is = new ByteBufferInputStream(bbu); ByteArrayOutputStream os = new ByteArrayOutputStream(bbu.limit() / 8); ) { LzmaEncoder encoder = new LzmaEncoder(); props.apply(encoder); // compress buffer encoder.code(is, os); lzma = os.toByteArray(); } int size = HEADER_SIZE + lzma.length; ByteBuffer bbc = ByteBuffer.allocateDirect(size); bbc.order(ByteOrder.LITTLE_ENDIAN); // write header bbc.putInt(LZMA_ID); bbc.putInt(bbu.limit()); bbc.putInt(lzma.length); bbc.put(props.toArray()); // write lzma data bbc.put(lzma); // reset buffer bbc.order(bo); bbc.rewind(); return bbc; } public static boolean isCompressed(ByteBuffer buffer) { ByteBuffer bb = buffer.duplicate(); bb.order(ByteOrder.LITTLE_ENDIAN); bb.rewind(); // check if this buffer is compressed return bb.remaining() >= HEADER_SIZE && bb.getInt() == LZMA_ID; } }