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.html 889935 2013-12-11 05:05:13Z 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    }