blob: b0e43d871f169d054f5f38bde86afb234c2f9a1a [file] [log] [blame]
/*
*
* 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.IOException;
import java.io.OutputStream;
import java.util.zip.DeflaterOutputStream;
import org.apache.flex.utils.DAByteArrayOutputStream;
/**
* The output stream that can write SWF primitive data types. It contains an
* in-memory buffer. The buffer is optionally compressed.
*/
public class OutputBitStream implements IOutputBitStream
{
// optional filter for compression
private final OutputStream filteredOutput;
// final byte stream
private final DAByteArrayOutputStream flatOutputBuffer;
// Bit buffer pointer. Must start as a full byte with value of 8
private int bitPos = 8;
// Bit buffer.
private byte currentByte = 0x00;
// True if the output SWF stream is compressed.
private final boolean useCompression;
/**
* Create an uncompressed {@code OutputBitStream}.
*/
public OutputBitStream()
{
this(false);
}
/**
* Create an {@code OutputBitStream}.
*
* @param useCompression true if the output stream is compressed.
*/
public OutputBitStream(boolean useCompression)
{
this.useCompression = useCompression;
flatOutputBuffer = new DAByteArrayOutputStream();
if (useCompression)
{
filteredOutput = new DeflaterOutputStream(flatOutputBuffer);
}
else
{
// skip compression filter
filteredOutput = this.flatOutputBuffer;
}
}
@Override
public int getBitPos()
{
return bitPos;
}
@Override
public void byteAlign()
{
if (bitPos != 8)
{
writeByte(currentByte);
currentByte = 0;
bitPos = 8;
}
}
/**
* Close internal output buffer.
*/
@Override
public void close() throws IOException
{
filteredOutput.close();
}
/**
* Flush piped output stream. Calling this method automatically flushes bit
* buffer.
*/
@Override
public void flush()
{
byteAlign();
try
{
if (useCompression)
{
((DeflaterOutputStream)filteredOutput).finish();
}
filteredOutput.flush();
}
catch (IOException e)
{
throw new RuntimeException(e);
}
}
@Override
public byte[] getBytes()
{
flush();
return flatOutputBuffer.getDirectByteArray();
}
@Override
public void reset()
{
flatOutputBuffer.reset();
}
@Override
public int size()
{
return flatOutputBuffer.size();
}
/**
* Get the bytes in the final output stream. This method create a copy of
* the buffer.
*
* @return a copy of buffered bytes in the output stream.
*/
public byte[] toByteArray()
{
return flatOutputBuffer.toByteArray();
}
@Override
public void write(byte[] data)
{
try
{
filteredOutput.write(data);
}
catch (IOException e)
{
throw new RuntimeException(e);
}
}
@Override
public void write(byte[] data, int off, int len)
{
try
{
filteredOutput.write(data, off, len);
}
catch (IOException e)
{
throw new RuntimeException(e);
}
}
@Override
public void writeBit(boolean data)
{
writeUB(data ? 1 : 0, 1);
}
private void writeBits(int data, int size)
{
while (size > 0)
{
if (size > bitPos)
{
//more bits left to write than shift out what will fit
currentByte |= data << (32 - size) >>> (32 - bitPos);
// shift all the way left, then right to right
// justify the data to be or'ed in
writeByte(currentByte);
size -= bitPos;
currentByte = 0;
bitPos = 8;
}
else
{
currentByte |= data << (32 - size) >>> (32 - bitPos);
bitPos -= size;
size = 0;
if (bitPos == 0)
{
// current byte is filled
writeByte(currentByte);
currentByte = 0;
bitPos = 8;
}
}
}
}
/**
* Write the lower 8 bits of a 32-bit integer as a byte onto the output
* stream. This function mute the IOException.
*
* @param value byte value
*/
private void writeByte(int value)
{
try
{
filteredOutput.write(value);
}
catch (IOException e)
{
throw new RuntimeException(e);
}
}
private void writeByte(long value)
{
writeByte((int)value);
}
@Override
public void writeDOUBLE(double value)
{
writeSI64(Double.doubleToLongBits(value));
}
@Override
public void writeEncodedU32(long value)
{
value &= 0xffffffffL;
do
{
byte fragment = (byte)(value & 0x7f);
value >>= 7;
if (value > 0)
{
fragment |= 0x80;
}
writeByte(fragment);
}
while (value > 0);
}
@Override
public void writeFB(double data, int size)
{
final int bits = (int)(data * 0x10000);
writeSB(bits, size);
}
@Override
public void writeFIXED(double value)
{
final int bytes = (int)(value * 0x010000) & 0xffffffff;
writeUI32(bytes);
}
@Override
public void writeFIXED8(double value)
{
final int bytes = (int)(value * 0x0100) & 0xffff;
writeUI16(bytes);
}
@Override
public void writeFLOAT(float value)
{
writeSI32(Float.floatToIntBits(value));
}
@Override
public void writeSB(int data, int size)
{
assert (data >= -(1 << (size - 1)) && data <= (1 << (size - 1)) - 1);
writeBits(data, size);
}
@Override
public void writeSI16(int value)
{
writeByte(value);
writeByte(value >> 8);
}
@Override
public void writeSI32(int value)
{
writeByte(value);
writeByte(value >> 8);
writeByte(value >> 16);
writeByte(value >> 24);
}
@Override
public void writeSI64(long value)
{
writeSI32((int)value);
writeSI32((int)(value >> 32));
}
@Override
public void writeSI8(int value)
{
writeByte(value);
}
@Override
public void writeString(String value)
{
try
{
filteredOutput.write(value.getBytes("UTF-8"));
filteredOutput.write(0);
}
catch (Exception e)
{
throw new RuntimeException(e);
}
}
@Override
public void writeUB(int data, int size)
{
assert (data >= 0 && data <= (1 << size) - 1);
writeBits(data, size);
}
@Override
public void writeUI16(int value)
{
writeSI16(value);
}
@Override
public void writeUI24(long value)
{
writeByte(value);
writeByte(value >> 8);
writeByte(value >> 16);
}
@Override
public void writeUI32(long value)
{
writeByte(value);
writeByte(value >> 8);
writeByte(value >> 16);
writeByte(value >> 24);
}
@Override
public void writeUI8(int value)
{
writeByte(value);
}
}