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      * The underlying stream is closed even if final conversion or flushing fails. If closing also fails, its exception is suppressed on the original exception.
127      * </p>
128      * <p>
129      * 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
130      * IO</a>
131      * <a href= "https://commons.apache.org/proper/commons-io/apidocs/org/apache/commons/io/output/CloseShieldOutputStream.html" >CloseShieldOutputStream</a>.
132      * </p>
133      *
134      * @throws IOException Thrown if an I/O error occurs.
135      */
136     @Override
137     public void close() throws IOException {
138         try (OutputStream outputStream = out) { // NOPMD
139             eof();
140             flush();
141         }
142     }
143 
144     /**
145      * Notifies the decoder or encoder of EOF (-1).
146      *
147      * @throws IOException Thrown when a problem is detected processing data.
148      * @since 1.11
149      */
150     public void eof() throws IOException {
151         BaseNCodec.code(doEncode, baseNCodec, singleByte, 0, EOF, context);
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 Thrown 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 Thrown 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      * Tests whether decoding behavior is strict.
187      *
188      * <p>
189      * Strict decoding rejects invalid trailing bits and, for Base32 and Base64, noncanonical input. Decoding errors are reported as {@link IOException}.
190      * To complete validation, call {@link #eof()} or {@link #close()}. Decoded bytes can be emitted before a later validation error.
191      * </p>
192      *
193      * @return true if using strict decoding.
194      * @since 1.15
195      */
196     public boolean isStrictDecoding() {
197         return baseNCodec.isStrictDecoding();
198     }
199 
200     /**
201      * Writes {@code len} bytes from the specified {@code b} array starting at {@code offset} to this output stream.
202      *
203      * @param array  source byte array.
204      * @param offset where to start reading the bytes.
205      * @param len    maximum number of bytes to write.
206      * @throws IOException               Thrown if an I/O error occurs.
207      * @throws NullPointerException      Thrown if the byte array parameter is null.
208      * @throws IndexOutOfBoundsException Thrown if the offset, length, or buffer size is invalid.
209      */
210     @Override
211     public void write(final byte[] array, final int offset, final int len) throws IOException {
212         Objects.requireNonNull(array, "array");
213         if (offset < 0 || len < 0 || offset > array.length || offset + len > array.length) {
214             throw new IndexOutOfBoundsException();
215         }
216         if (len > 0) {
217             BaseNCodec.code(doEncode, baseNCodec, array, offset, len, context);
218             flush(false);
219         }
220     }
221 
222     /**
223      * Writes the specified {@code byte} to this output stream.
224      *
225      * @param i source byte.
226      * @throws IOException Thrown if an I/O error occurs.
227      */
228     @Override
229     public void write(final int i) throws IOException {
230         singleByte[0] = (byte) i;
231         write(singleByte, 0, 1);
232     }
233 }