    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
    018package org.apache.commons.codec.binary;
    019
    020import static org.apache.commons.codec.binary.BaseNCodec.EOF;
    021
    022import java.io.FilterOutputStream;
    023import java.io.IOException;
    024import java.io.OutputStream;
    025
    026import org.apache.commons.codec.binary.BaseNCodec.Context;
    027
    028/**
    029 * Abstract superclass for Base-N output streams.
    030 * <p>
    031 * To write the EOF marker without closing the stream, call {@link #eof()} or use an <a
    032 * href="https://commons.apache.org/proper/commons-io/">Apache Commons IO</a> <a href=
    033 * "https://commons.apache.org/proper/commons-io/apidocs/org/apache/commons/io/output/CloseShieldOutputStream.html"
    034 * >CloseShieldOutputStream</a>.
    035 * </p>
    036 *
    037 * @since 1.5
    038 * @version $Id$
    039 */
    040public class BaseNCodecOutputStream extends FilterOutputStream {
    041
    042    private final boolean doEncode;
    043
    044    private final BaseNCodec baseNCodec;
    045
    046    private final byte[] singleByte = new byte[1];
    047
    048    private final Context context = new Context();
    049
    050    // TODO should this be protected?
    051    public BaseNCodecOutputStream(final OutputStream out, final BaseNCodec basedCodec, final boolean doEncode) {
    052        super(out);
    053        this.baseNCodec = basedCodec;
    054        this.doEncode = doEncode;
    055    }
    056
    057    /**
    058     * Writes the specified <code>byte</code> to this output stream.
    059     *
    060     * @param i
    061     *            source byte
    062     * @throws IOException
    063     *             if an I/O error occurs.
    064     */
    065    @Override
    066    public void write(final int i) throws IOException {
    067        singleByte[0] = (byte) i;
    068        write(singleByte, 0, 1);
    069    }
    070
    071    /**
    072     * Writes <code>len</code> bytes from the specified <code>b</code> array starting at <code>offset</code> to this
    073     * output stream.
    074     *
    075     * @param b
    076     *            source byte array
    077     * @param offset
    078     *            where to start reading the bytes
    079     * @param len
    080     *            maximum number of bytes to write
    081     *
    082     * @throws IOException
    083     *             if an I/O error occurs.
    084     * @throws NullPointerException
    085     *             if the byte array parameter is null
    086     * @throws IndexOutOfBoundsException
    087     *             if offset, len or buffer size are invalid
    088     */
    089    @Override
    090    public void write(final byte b[], final int offset, final int len) throws IOException {
    091        if (b == null) {
    092            throw new NullPointerException();
    093        } else if (offset < 0 || len < 0) {
    094            throw new IndexOutOfBoundsException();
    095        } else if (offset > b.length || offset + len > b.length) {
    096            throw new IndexOutOfBoundsException();
    097        } else if (len > 0) {
    098            if (doEncode) {
    099                baseNCodec.encode(b, offset, len, context);
    100            } else {
    101                baseNCodec.decode(b, offset, len, context);
    102            }
    103            flush(false);
    104        }
    105    }
    106
    107    /**
    108     * Flushes this output stream and forces any buffered output bytes to be written out to the stream. If propagate is
    109     * true, the wrapped stream will also be flushed.
    110     *
    111     * @param propagate
    112     *            boolean flag to indicate whether the wrapped OutputStream should also be flushed.
    113     * @throws IOException
    114     *             if an I/O error occurs.
    115     */
    116    private void flush(final boolean propagate) throws IOException {
    117        final int avail = baseNCodec.available(context);
    118        if (avail > 0) {
    119            final byte[] buf = new byte[avail];
    120            final int c = baseNCodec.readResults(buf, 0, avail, context);
    121            if (c > 0) {
    122                out.write(buf, 0, c);
    123            }
    124        }
    125        if (propagate) {
    126            out.flush();
    127        }
    128    }
    129
    130    /**
    131     * Flushes this output stream and forces any buffered output bytes to be written out to the stream.
    132     *
    133     * @throws IOException
    134     *             if an I/O error occurs.
    135     */
    136    @Override
    137    public void flush() throws IOException {
    138        flush(true);
    139    }
    140
    141    /**
    142     * Closes this output stream and releases any system resources associated with the stream.
    143     * <p>
    144     * To write the EOF marker without closing the stream, call {@link #eof()} or use an
    145     * <a href="https://commons.apache.org/proper/commons-io/">Apache Commons IO</a> <a href=
    146     * "https://commons.apache.org/proper/commons-io/apidocs/org/apache/commons/io/output/CloseShieldOutputStream.html"
    147     * >CloseShieldOutputStream</a>.
    148     * </p>
    149     *
    150     * @throws IOException
    151     *             if an I/O error occurs.
    152     */
    153    @Override
    154    public void close() throws IOException {
    155        eof();
    156        flush();
    157        out.close();
    158    }
    159
    160    /**
    161     * Writes EOF.
    162     *
    163     * @throws IOException
    164     *             if an I/O error occurs.
    165     * @since 1.11
    166     */
    167    public void eof() throws IOException {
    168        // Notify encoder of EOF (-1).
    169        if (doEncode) {
    170            baseNCodec.encode(singleByte, 0, EOF, context);
    171        } else {
    172            baseNCodec.decode(singleByte, 0, EOF, context);
    173        }
    174    }
    175
    176}