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