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    *      https://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  import org.apache.commons.codec.binary.BaseNCodecOutputStream.AbstractBuilder;
29  
30  /**
31   * Abstract superclass for Base-N output streams.
32   * <p>
33   * 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
34   * IO</a>
35   * <a href= "https://commons.apache.org/proper/commons-io/apidocs/org/apache/commons/io/output/CloseShieldOutputStream.html" >CloseShieldOutputStream</a>.
36   * </p>
37   *
38   * @param <C> A BaseNCodec subclass.
39   * @param <T> A BaseNCodecInputStream subclass.
40   * @param <B> A subclass.
41   * @see Base16OutputStream
42   * @see Base32OutputStream
43   * @see Base64OutputStream
44   * @since 1.5
45   */
46  public class BaseNCodecOutputStream<C extends BaseNCodec, T extends BaseNCodecOutputStream<C, T, B>, B extends AbstractBuilder<T, C, B>>
47          extends FilterOutputStream {
48  
49      /**
50       * Builds output stream instances in {@link BaseNCodec} format.
51       *
52       * @param <T> the output stream type to build.
53       * @param <C> A {@link BaseNCodec} subclass.
54       * @param <B> the builder subclass.
55       * @since 1.20.0
56       */
57      public abstract static class AbstractBuilder<T, C extends BaseNCodec, B extends AbstractBuilder<T, C, B>>
58          extends AbstractBaseNCodecStreamBuilder<T, C, B> {
59  
60          private OutputStream outputStream;
61  
62          /**
63           * Constructs a new instance.
64           */
65          public AbstractBuilder() {
66              // super
67          }
68  
69          /**
70           * Gets the input stream.
71           *
72           * @return the input stream.
73           */
74          protected OutputStream getOutputStream() {
75              return outputStream;
76          }
77  
78          /**
79           * Sets the input stream.
80           *
81           * @param outputStream the input stream.
82           * @return {@code this} instance.
83           */
84          public B setOutputStream(final OutputStream outputStream) {
85              this.outputStream = outputStream;
86              return asThis();
87          }
88      }
89  
90      private final boolean doEncode;
91      private final C baseNCodec;
92      private final byte[] singleByte = new byte[1];
93      private final Context context = new Context();
94  
95      /**
96       * Constructs a new instance.
97       *
98       * @param builder A builder.
99       * @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 }