    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 *      https://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;
    025import java.util.Objects;
    026
    027import org.apache.commons.codec.binary.BaseNCodec.Context;
    028import org.apache.commons.codec.binary.BaseNCodecOutputStream.AbstractBuilder;
    029
    030/**
    031 * Abstract superclass for Base-N output streams.
    032 * <p>
    033 * To write the EOF marker without closing the stream, call {@link #eof()} or use an <a href="https://commons.apache.org/proper/commons-io/">Apache Commons
    034 * IO</a>
    035 * <a href= "https://commons.apache.org/proper/commons-io/apidocs/org/apache/commons/io/output/CloseShieldOutputStream.html" >CloseShieldOutputStream</a>.
    036 * </p>
    037 *
    038 * @param <C> A BaseNCodec subclass.
    039 * @param <T> A BaseNCodecInputStream subclass.
    040 * @param <B> A subclass.
    041 * @see Base16OutputStream
    042 * @see Base32OutputStream
    043 * @see Base64OutputStream
    044 * @since 1.5
    045 */
    046public class BaseNCodecOutputStream<C extends BaseNCodec, T extends BaseNCodecOutputStream<C, T, B>, B extends AbstractBuilder<T, C, B>>
    047        extends FilterOutputStream {
    048
    049    /**
    050     * Builds output stream instances in {@link BaseNCodec} format.
    051     *
    052     * @param <T> the output stream type to build.
    053     * @param <C> A {@link BaseNCodec} subclass.
    054     * @param <B> the builder subclass.
    055     * @since 1.20.0
    056     */
    057    public abstract static class AbstractBuilder<T, C extends BaseNCodec, B extends AbstractBuilder<T, C, B>>
    058        extends AbstractBaseNCodecStreamBuilder<T, C, B> {
    059
    060        private OutputStream outputStream;
    061
    062        /**
    063         * Constructs a new instance.
    064         */
    065        public AbstractBuilder() {
    066            // super
    067        }
    068
    069        /**
    070         * Gets the input stream.
    071         *
    072         * @return the input stream.
    073         */
    074        protected OutputStream getOutputStream() {
    075            return outputStream;
    076        }
    077
    078        /**
    079         * Sets the input stream.
    080         *
    081         * @param outputStream the input stream.
    082         * @return {@code this} instance.
    083         */
    084        public B setOutputStream(final OutputStream outputStream) {
    085            this.outputStream = outputStream;
    086            return asThis();
    087        }
    088    }
    089
    090    private final boolean doEncode;
    091    private final C baseNCodec;
    092    private final byte[] singleByte = new byte[1];
    093    private final Context context = new Context();
    094
    095    /**
    096     * Constructs a new instance.
    097     *
    098     * @param builder A builder.
    099     * @since 1.20.0
    100     */
    101    @SuppressWarnings("resource") // Caller closes.
    102    protected BaseNCodecOutputStream(final AbstractBuilder<T, C, B> builder) {
    103        super(builder.getOutputStream());
    104        this.baseNCodec = builder.getBaseNCodec();
    105        this.doEncode = builder.getEncode();
    106    }
    107
    108    /**
    109     * Constructs a new instance.
    110     *
    111     * TODO should this be protected?
    112     *
    113     * @param outputStream the underlying output or null.
    114     * @param basedCodec   a BaseNCodec.
    115     * @param doEncode     true to encode, false to decode, TODO should be an enum?
    116     */
    117    public BaseNCodecOutputStream(final OutputStream outputStream, final C basedCodec, final boolean doEncode) {
    118        super(outputStream);
    119        this.baseNCodec = basedCodec;
    120        this.doEncode = doEncode;
    121    }
    122
    123    /**
    124     * Closes this output stream and releases any system resources associated with the stream.
    125     * <p>
    126     * To write the EOF marker without closing the stream, call {@link #eof()} or use an <a href="https://commons.apache.org/proper/commons-io/">Apache Commons
    127     * IO</a>
    128     * <a href= "https://commons.apache.org/proper/commons-io/apidocs/org/apache/commons/io/output/CloseShieldOutputStream.html" >CloseShieldOutputStream</a>.
    129     * </p>
    130     *
    131     * @throws IOException if an I/O error occurs.
    132     */
    133    @Override
    134    public void close() throws IOException {
    135        eof();
    136        flush();
    137        out.close();
    138    }
    139
    140    /**
    141     * Writes EOF.
    142     *
    143     * @since 1.11
    144     */
    145    public void eof() {
    146        // Notify encoder of EOF (-1).
    147        if (doEncode) {
    148            baseNCodec.encode(singleByte, 0, EOF, context);
    149        } else {
    150            baseNCodec.decode(singleByte, 0, EOF, context);
    151        }
    152    }
    153
    154    /**
    155     * Flushes this output stream and forces any buffered output bytes to be written out to the stream.
    156     *
    157     * @throws IOException if an I/O error occurs.
    158     */
    159    @Override
    160    public void flush() throws IOException {
    161        flush(true);
    162    }
    163
    164    /**
    165     * Flushes this output stream and forces any buffered output bytes to be written out to the stream. If propagate is true, the wrapped stream will also be
    166     * flushed.
    167     *
    168     * @param propagate boolean flag to indicate whether the wrapped OutputStream should also be flushed.
    169     * @throws IOException if an I/O error occurs.
    170     */
    171    private void flush(final boolean propagate) throws IOException {
    172        final int avail = baseNCodec.available(context);
    173        if (avail > 0) {
    174            final byte[] buf = new byte[avail];
    175            final int c = baseNCodec.readResults(buf, 0, avail, context);
    176            if (c > 0) {
    177                out.write(buf, 0, c);
    178            }
    179        }
    180        if (propagate) {
    181            out.flush();
    182        }
    183    }
    184
    185    /**
    186     * Returns true if decoding behavior is strict. Decoding will raise an {@link IllegalArgumentException} if trailing bits are not part of a valid encoding.
    187     *
    188     * <p>
    189     * The default is false for lenient encoding. Decoding will compose trailing bits into 8-bit bytes and discard the remainder.
    190     * </p>
    191     *
    192     * @return true if using strict decoding.
    193     * @since 1.15
    194     */
    195    public boolean isStrictDecoding() {
    196        return baseNCodec.isStrictDecoding();
    197    }
    198
    199    /**
    200     * Writes {@code len} bytes from the specified {@code b} array starting at {@code offset} to this output stream.
    201     *
    202     * @param array  source byte array.
    203     * @param offset where to start reading the bytes.
    204     * @param len    maximum number of bytes to write.
    205     * @throws IOException               if an I/O error occurs.
    206     * @throws NullPointerException      if the byte array parameter is null.
    207     * @throws IndexOutOfBoundsException if offset, len or buffer size are invalid.
    208     */
    209    @Override
    210    public void write(final byte[] array, final int offset, final int len) throws IOException {
    211        Objects.requireNonNull(array, "array");
    212        if (offset < 0 || len < 0 || offset > array.length || offset + len > array.length) {
    213            throw new IndexOutOfBoundsException();
    214        }
    215        if (len > 0) {
    216            if (doEncode) {
    217                baseNCodec.encode(array, offset, len, context);
    218            } else {
    219                baseNCodec.decode(array, offset, len, context);
    220            }
    221            flush(false);
    222        }
    223    }
    224
    225    /**
    226     * Writes the specified {@code byte} to this output stream.
    227     *
    228     * @param i source byte.
    229     * @throws IOException if an I/O error occurs.
    230     */
    231    @Override
    232    public void write(final int i) throws IOException {
    233        singleByte[0] = (byte) i;
    234        write(singleByte, 0, 1);
    235    }
    236}