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 }