    001    /*
    002     * Licensed to the Apache Software Foundation (ASF) under one or more
    003     * contributor license agreements.  See the NOTICE file distributed with
    004     * this work for additional information regarding copyright ownership.
    005     * The ASF licenses this file to You under the Apache License, Version 2.0
    006     * (the "License"); you may not use this file except in compliance with
    007     * the License.  You may obtain a copy of the License at
    008     *
    009     *      http://www.apache.org/licenses/LICENSE-2.0
    010     *
    011     * Unless required by applicable law or agreed to in writing, software
    012     * distributed under the License is distributed on an "AS IS" BASIS,
    013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014     * See the License for the specific language governing permissions and
    015     * limitations under the License.
    016     */
    017
    018    package org.apache.commons.codec.binary;
    019
    020    import static org.apache.commons.codec.binary.BaseNCodec.EOF;
    021
    022    import java.io.FilterOutputStream;
    023    import java.io.IOException;
    024    import java.io.OutputStream;
    025
    026    import org.apache.commons.codec.binary.BaseNCodec.Context;
    027
    028    /**
    029     * Abstract superclass for Base-N output streams.
    030     *
    031     * @since 1.5
    032     * @version $Id: BaseNCodecOutputStream.java 1429868 2013-01-07 16:08:05Z ggregory $
    033     */
    034    public class BaseNCodecOutputStream extends FilterOutputStream {
    035
    036        private final boolean doEncode;
    037
    038        private final BaseNCodec baseNCodec;
    039
    040        private final byte[] singleByte = new byte[1];
    041
    042        private final Context context = new Context();
    043
    044        // TODO should this be protected?
    045        public BaseNCodecOutputStream(final OutputStream out, final BaseNCodec basedCodec, final boolean doEncode) {
    046            super(out);
    047            this.baseNCodec = basedCodec;
    048            this.doEncode = doEncode;
    049        }
    050
    051        /**
    052         * Writes the specified <code>byte</code> to this output stream.
    053         *
    054         * @param i
    055         *            source byte
    056         * @throws IOException
    057         *             if an I/O error occurs.
    058         */
    059        @Override
    060        public void write(final int i) throws IOException {
    061            singleByte[0] = (byte) i;
    062            write(singleByte, 0, 1);
    063        }
    064
    065        /**
    066         * Writes <code>len</code> bytes from the specified <code>b</code> array starting at <code>offset</code> to this
    067         * output stream.
    068         *
    069         * @param b
    070         *            source byte array
    071         * @param offset
    072         *            where to start reading the bytes
    073         * @param len
    074         *            maximum number of bytes to write
    075         *
    076         * @throws IOException
    077         *             if an I/O error occurs.
    078         * @throws NullPointerException
    079         *             if the byte array parameter is null
    080         * @throws IndexOutOfBoundsException
    081         *             if offset, len or buffer size are invalid
    082         */
    083        @Override
    084        public void write(final byte b[], final int offset, final int len) throws IOException {
    085            if (b == null) {
    086                throw new NullPointerException();
    087            } else if (offset < 0 || len < 0) {
    088                throw new IndexOutOfBoundsException();
    089            } else if (offset > b.length || offset + len > b.length) {
    090                throw new IndexOutOfBoundsException();
    091            } else if (len > 0) {
    092                if (doEncode) {
    093                    baseNCodec.encode(b, offset, len, context);
    094                } else {
    095                    baseNCodec.decode(b, offset, len, context);
    096                }
    097                flush(false);
    098            }
    099        }
    100
    101        /**
    102         * Flushes this output stream and forces any buffered output bytes to be written out to the stream. If propogate is
    103         * true, the wrapped stream will also be flushed.
    104         *
    105         * @param propogate
    106         *            boolean flag to indicate whether the wrapped OutputStream should also be flushed.
    107         * @throws IOException
    108         *             if an I/O error occurs.
    109         */
    110        private void flush(final boolean propogate) throws IOException {
    111            final int avail = baseNCodec.available(context);
    112            if (avail > 0) {
    113                final byte[] buf = new byte[avail];
    114                final int c = baseNCodec.readResults(buf, 0, avail, context);
    115                if (c > 0) {
    116                    out.write(buf, 0, c);
    117                }
    118            }
    119            if (propogate) {
    120                out.flush();
    121            }
    122        }
    123
    124        /**
    125         * Flushes this output stream and forces any buffered output bytes to be written out to the stream.
    126         *
    127         * @throws IOException
    128         *             if an I/O error occurs.
    129         */
    130        @Override
    131        public void flush() throws IOException {
    132            flush(true);
    133        }
    134
    135        /**
    136         * Closes this output stream and releases any system resources associated with the stream.
    137         *
    138         * @throws IOException
    139         *             if an I/O error occurs.
    140         */
    141        @Override
    142        public void close() throws IOException {
    143            // Notify encoder of EOF (-1).
    144            if (doEncode) {
    145                baseNCodec.encode(singleByte, 0, EOF, context);
    146            } else {
    147                baseNCodec.decode(singleByte, 0, EOF, context);
    148            }
    149            flush();
    150            out.close();
    151        }
    152
    153    }