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 }