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       * Constructs a new instance.
52       *
53       * TODO should this be protected?
54       *
55       * @param outputStream the underlying output or null.
56       * @param basedCodec a BaseNCodec.
57       * @param doEncode true to encode, false to decode, TODO should be an enum?
58       */
59      public BaseNCodecOutputStream(final OutputStream outputStream, final BaseNCodec basedCodec, final boolean doEncode) {
60          super(outputStream);
61          this.baseNCodec = basedCodec;
62          this.doEncode = doEncode;
63      }
64  
65      /**
66       * Closes this output stream and releases any system resources associated with the stream.
67       * <p>
68       * To write the EOF marker without closing the stream, call {@link #eof()} or use an
69       * <a href="https://commons.apache.org/proper/commons-io/">Apache Commons IO</a> <a href=
70       * "https://commons.apache.org/proper/commons-io/apidocs/org/apache/commons/io/output/CloseShieldOutputStream.html"
71       * >CloseShieldOutputStream</a>.
72       * </p>
73       *
74       * @throws IOException
75       *             if an I/O error occurs.
76       */
77      @Override
78      public void close() throws IOException {
79          eof();
80          flush();
81          out.close();
82      }
83  
84      /**
85       * Writes EOF.
86       *
87       * @since 1.11
88       */
89      public void eof() {
90          // Notify encoder of EOF (-1).
91          if (doEncode) {
92              baseNCodec.encode(singleByte, 0, EOF, context);
93          } else {
94              baseNCodec.decode(singleByte, 0, EOF, context);
95          }
96      }
97  
98      /**
99       * Flushes this output stream and forces any buffered output bytes to be written out to the stream.
100      *
101      * @throws IOException
102      *             if an I/O error occurs.
103      */
104     @Override
105     public void flush() throws IOException {
106         flush(true);
107     }
108 
109     /**
110      * Flushes this output stream and forces any buffered output bytes to be written out to the stream. If propagate is
111      * true, the wrapped stream will also be flushed.
112      *
113      * @param propagate
114      *            boolean flag to indicate whether the wrapped OutputStream should also be flushed.
115      * @throws IOException
116      *             if an I/O error occurs.
117      */
118     private void flush(final boolean propagate) throws IOException {
119         final int avail = baseNCodec.available(context);
120         if (avail > 0) {
121             final byte[] buf = new byte[avail];
122             final int c = baseNCodec.readResults(buf, 0, avail, context);
123             if (c > 0) {
124                 out.write(buf, 0, c);
125             }
126         }
127         if (propagate) {
128             out.flush();
129         }
130     }
131 
132     /**
133      * Returns true if decoding behavior is strict. Decoding will raise an
134      * {@link IllegalArgumentException} if trailing bits are not part of a valid encoding.
135      *
136      * <p>
137      * The default is false for lenient encoding. Decoding will compose trailing bits
138      * into 8-bit bytes and discard the remainder.
139      * </p>
140      *
141      * @return true if using strict decoding
142      * @since 1.15
143      */
144     public boolean isStrictDecoding() {
145         return baseNCodec.isStrictDecoding();
146     }
147 
148     /**
149      * Writes {@code len} bytes from the specified {@code b} array starting at {@code offset} to this
150      * output stream.
151      *
152      * @param array
153      *            source byte array
154      * @param offset
155      *            where to start reading the bytes
156      * @param len
157      *            maximum number of bytes to write
158      *
159      * @throws IOException
160      *             if an I/O error occurs.
161      * @throws NullPointerException
162      *             if the byte array parameter is null
163      * @throws IndexOutOfBoundsException
164      *             if offset, len or buffer size are invalid
165      */
166     @Override
167     public void write(final byte[] array, final int offset, final int len) throws IOException {
168         Objects.requireNonNull(array, "array");
169         if (offset < 0 || len < 0) {
170             throw new IndexOutOfBoundsException();
171         }
172         if (offset > array.length || offset + len > array.length) {
173             throw new IndexOutOfBoundsException();
174         }
175         if (len > 0) {
176             if (doEncode) {
177                 baseNCodec.encode(array, offset, len, context);
178             } else {
179                 baseNCodec.decode(array, offset, len, context);
180             }
181             flush(false);
182         }
183     }
184 
185     /**
186      * Writes the specified {@code byte} to this output stream.
187      *
188      * @param i
189      *            source byte
190      * @throws IOException
191      *             if an I/O error occurs.
192      */
193     @Override
194     public void write(final int i) throws IOException {
195         singleByte[0] = (byte) i;
196         write(singleByte, 0, 1);
197     }
198 
199 }