View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.codec.binary;
19  
20  import static org.apache.commons.codec.binary.BaseNCodec.EOF;
21  
22  import java.io.FilterOutputStream;
23  import java.io.IOException;
24  import java.io.OutputStream;
25  import java.util.Objects;
26  
27  import org.apache.commons.codec.binary.BaseNCodec.Context;
28  
29  /**
30   * Abstract superclass for Base-N output streams.
31   * <p>
32   * To write the EOF marker without closing the stream, call {@link #eof()} or use an <a
33   * href="https://commons.apache.org/proper/commons-io/">Apache Commons IO</a> <a href=
34   * "https://commons.apache.org/proper/commons-io/apidocs/org/apache/commons/io/output/CloseShieldOutputStream.html"
35   * >CloseShieldOutputStream</a>.
36   * </p>
37   *
38   * @since 1.5
39   */
40  public class BaseNCodecOutputStream extends FilterOutputStream {
41  
42      private final boolean doEncode;
43  
44      private final BaseNCodec baseNCodec;
45  
46      private final byte[] singleByte = new byte[1];
47  
48      private final Context context = new Context();
49  
50      /**
51       * TODO should this be protected?
52       *
53       * @param outputStream the underlying output or null.
54       * @param basedCodec a BaseNCodec.
55       * @param doEncode true to encode, false to decode, TODO should be an enum?
56       */
57      public BaseNCodecOutputStream(final OutputStream outputStream, final BaseNCodec basedCodec, final boolean doEncode) {
58          super(outputStream);
59          this.baseNCodec = basedCodec;
60          this.doEncode = doEncode;
61      }
62  
63      /**
64       * Closes this output stream and releases any system resources associated with the stream.
65       * <p>
66       * To write the EOF marker without closing the stream, call {@link #eof()} or use an
67       * <a href="https://commons.apache.org/proper/commons-io/">Apache Commons IO</a> <a href=
68       * "https://commons.apache.org/proper/commons-io/apidocs/org/apache/commons/io/output/CloseShieldOutputStream.html"
69       * >CloseShieldOutputStream</a>.
70       * </p>
71       *
72       * @throws IOException
73       *             if an I/O error occurs.
74       */
75      @Override
76      public void close() throws IOException {
77          eof();
78          flush();
79          out.close();
80      }
81  
82      /**
83       * Writes EOF.
84       *
85       * @since 1.11
86       */
87      public void eof() {
88          // Notify encoder of EOF (-1).
89          if (doEncode) {
90              baseNCodec.encode(singleByte, 0, EOF, context);
91          } else {
92              baseNCodec.decode(singleByte, 0, EOF, context);
93          }
94      }
95  
96      /**
97       * Flushes this output stream and forces any buffered output bytes to be written out to the stream.
98       *
99       * @throws IOException
100      *             if an I/O error occurs.
101      */
102     @Override
103     public void flush() throws IOException {
104         flush(true);
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      * Returns true if decoding behavior is strict. Decoding will raise an
132      * {@link IllegalArgumentException} if trailing bits are not part of a valid encoding.
133      *
134      * <p>
135      * The default is false for lenient encoding. Decoding will compose trailing bits
136      * into 8-bit bytes and discard the remainder.
137      * </p>
138      *
139      * @return true if using strict decoding
140      * @since 1.15
141      */
142     public boolean isStrictDecoding() {
143         return baseNCodec.isStrictDecoding();
144     }
145 
146     /**
147      * Writes {@code len} bytes from the specified {@code b} array starting at {@code offset} to this
148      * output stream.
149      *
150      * @param array
151      *            source byte array
152      * @param offset
153      *            where to start reading the bytes
154      * @param len
155      *            maximum number of bytes to write
156      *
157      * @throws IOException
158      *             if an I/O error occurs.
159      * @throws NullPointerException
160      *             if the byte array parameter is null
161      * @throws IndexOutOfBoundsException
162      *             if offset, len or buffer size are invalid
163      */
164     @Override
165     public void write(final byte[] array, final int offset, final int len) throws IOException {
166         Objects.requireNonNull(array, "array");
167         if (offset < 0 || len < 0) {
168             throw new IndexOutOfBoundsException();
169         }
170         if (offset > array.length || offset + len > array.length) {
171             throw new IndexOutOfBoundsException();
172         }
173         if (len > 0) {
174             if (doEncode) {
175                 baseNCodec.encode(array, offset, len, context);
176             } else {
177                 baseNCodec.decode(array, offset, len, context);
178             }
179             flush(false);
180         }
181     }
182 
183     /**
184      * Writes the specified {@code byte} to this output stream.
185      *
186      * @param i
187      *            source byte
188      * @throws IOException
189      *             if an I/O error occurs.
190      */
191     @Override
192     public void write(final int i) throws IOException {
193         singleByte[0] = (byte) i;
194         write(singleByte, 0, 1);
195     }
196 
197 }