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 }