org.apache.flex.swf.io.InputBitStream.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.flex.swf.io.InputBitStream.java

Source

/*
 *
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 */

package org.apache.flex.swf.io;

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.zip.InflaterInputStream;

import org.apache.commons.io.IOUtils;

import org.apache.flex.swf.Header;
import org.apache.flex.utils.DAByteArrayOutputStream;

/**
 * Implementation of {@link InputBitStream}. This implementation allows you to
 * swap the underlying {@code InputStream} source with another one.
 * <p>
 * {@code InputBitStream} doesn't buffer the source InputStream internally. It
 * has one byte buffer for reading SWF bit-values. The buffer is filled 8 bits
 * at a time. All the bit value methods read from this buffer.
 * <p>
 * If a SWF file is compressed, InputBitStream uses an
 * {@link InflaterInputStream} to decompress the source input.
 */
public class InputBitStream extends InputStream implements IInputBitStream {
    // source
    private InputStream in;

    // bit value cache
    private int bitPos = 0;

    private int bitBuf = 0;
    private long offset = 0;
    private long readBoundary = 0;

    /**
     * Create an {@code InputBitStream}.
     * 
     * @param in source {@code InputStream}
     */
    public InputBitStream(InputStream in) {
        this.in = in;
    }

    public InputBitStream(byte[] bytes) {
        this.in = new ByteArrayInputStream(bytes);
    }

    /**
     * Discard the data left in the bit value cache. Always call this method
     * after reading bit values and before reading other byte-aligned data.
     */
    @Override
    public void byteAlign() {
        bitPos = 0;
    }

    @Override
    public int read() throws IOException {
        return readByte();
    }

    @Override
    public byte[] read(int length) {
        final byte[] data = new byte[length];
        try {
            read(data);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return data;
    }

    @Override
    public boolean readBit() {
        return readBits(1) != 0;
    }

    /**
     * Read multiple bits as a padded int.
     * 
     * @param length number of bits to read
     * @return The value that was read.
     */
    protected int readBits(int length) {
        if (length == 0) {
            return 0;
        }

        int bitsLeft = length;
        int result = 0;

        if (bitPos == 0) // no value in the buffer - read a byte
        {
            bitBuf = readUI8();
            bitPos = 8;
        }

        while (true) {
            int shift = bitsLeft - bitPos;
            if (shift > 0) {
                // Consume the entire buffer
                result |= bitBuf << shift;
                bitsLeft -= bitPos;

                // Get the next byte from the input stream
                bitBuf = readUI8();
                bitPos = 8;
            } else {
                // Consume a portion of the buffer
                result |= bitBuf >> -shift;
                bitPos -= bitsLeft;
                bitBuf &= 0xff >> (8 - bitPos); // mask off the consumed bits
                return result;
            }
        }
    }

    // The following are SWF primitive type decoder methods.

    /**
     * This helps to mute the {@code IOException}. If there's no data in the
     * source input stream, calling this method will raise an runtime exception.
     * <p>
     * All the methods implementing {@code IInputBitStream} should call this
     * method when consuming the next byte from the input stream.
     * 
     * @return next byte in the input stream
     */
    protected int readByte() {
        byteAlign();
        try {
            if (offset >= readBoundary) {
                throw new RuntimeException(String.format(
                        "About to read over or reading over the boundary: %d -> %d.", offset, readBoundary));
            }

            final int n = in.read();
            offset++;
            if (-1 == n) {
                throw new RuntimeException("No more data to read.");
            } else {
                return n;
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public double readDOUBLE() {
        return Double.longBitsToDouble(readSI64());
    }

    @Override
    public long readEncodedU32() {
        long decoded = 0;
        for (int i = 0; i < 5; i++) {
            final int nextByte = readByte();
            decoded = (decoded << 7) | (nextByte & 0x7f);
            if ((nextByte & 0x80) == 0) {
                break;
            }
        }
        return decoded & 0x000000ff << 24 | decoded & 0x0000ff00 << 8 | decoded & 0x00ff0000 >>> 8
                | decoded & 0xff000000 >>> 24;
    }

    @Override
    public float readFB(int length) {
        // Convert bits to x.16 FIXED point number
        return readSB(length) / (float) 0x10000;
    }

    @Override
    public float readFIXED() {
        // Convert 32-bit int to 16.16 FIXED point number
        return readSI32() / (float) 0x10000;
    }

    @Override
    public float readFIXED8() {
        // Convert 16-bit int to 8.8 FIXED point number
        return (short) readSI16() / (float) 0x100;
    }

    @Override
    public float readFLOAT() {
        return Float.intBitsToFloat(readSI32());
    }

    @Override
    public int readSB(int length) {
        int bits = readBits(length);
        return bits << 32 - length >> 32 - length;
    }

    @Override
    public short readSI16() {
        return (short) (readByte() | readByte() << 8);
    }

    @Override
    public int readSI32() {
        return readByte() | readByte() << 8 | readByte() << 16 | readByte() << 24;
    }

    @Override
    public long readSI64() {
        return (readUI32() & 0xFFFFFFFFL) | (readUI32() << 32);
    }

    @Override
    public byte readSI8() {
        return (byte) readByte();
    }

    @Override
    public String readString() {
        final DAByteArrayOutputStream buffer = new DAByteArrayOutputStream();
        for (int nextByte = readUI8(); nextByte != 0; nextByte = readUI8()) {
            buffer.write(nextByte);
        }

        try {
            return new String(buffer.getDirectByteArray(), 0, buffer.size(), "UTF-8");
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        } finally {
            IOUtils.closeQuietly(buffer);
        }
    }

    @Override
    public int readUB(int length) {
        return readBits(length);
    }

    @Override
    public int readUI16() {
        return 0xFFFF & readSI16();
    }

    @Override
    public int readUI24() {
        return 0xFFFFFF & (readByte() | readByte() << 8 | readByte() << 16);
    }

    @Override
    public long readUI32() {
        return 0xFFFFFFFFl & readSI32();
    }

    @Override
    public short readUI8() {
        return (short) (0xFF & readSI8());
    }

    /**
     * Set if the InputStream is a compressed SWF stream.
     */
    public void setCompress(Header.Compression compression) throws IOException {
        switch (compression) {
        case NONE:
            break;
        case ZLIB:
            this.in = new BufferedInputStream(new InflaterInputStream(in));
            break;
        case LZMA:
            this.in = new LZMAInputStream(in);
            break;
        default:
            assert false;
        }
    }

    @Override
    public byte[] readToBoundary() {
        assert readBoundary > 0 : "Must set boundary before readToBoundary";

        // The conversion is safe because a tag length is SI32.
        final int len = (int) (readBoundary - offset);
        final byte[] result = this.read(len);
        return result;
    }

    @Override
    public long getOffset() {
        return offset;
    }

    @Override
    public void setReadBoundary(long offset) {
        assert offset > 0 : "Read boundary must > 0.";
        this.readBoundary = offset;
    }

    @Override
    public long getReadBoundary() {
        return this.readBoundary;
    }
}