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: BaseNCodecOutputStream.java 1744727 2016-05-20 12:43:52Z sebb $
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}