/* * * 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); } }