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 * TODO should this be protected?
52 *
53 * @param outputStream the underlying output or null.
54 * @param basedCodec a BaseNCodec.
55 * @param doEncode true to encode, false to decode, TODO should be an enum?
56 */
57 public BaseNCodecOutputStream(final OutputStream outputStream, final BaseNCodec basedCodec, final boolean doEncode) {
58 super(outputStream);
59 this.baseNCodec = basedCodec;
60 this.doEncode = doEncode;
61 }
62
63 /**
64 * Closes this output stream and releases any system resources associated with the stream.
65 * <p>
66 * To write the EOF marker without closing the stream, call {@link #eof()} or use an
67 * <a href="https://commons.apache.org/proper/commons-io/">Apache Commons IO</a> <a href=
68 * "https://commons.apache.org/proper/commons-io/apidocs/org/apache/commons/io/output/CloseShieldOutputStream.html"
69 * >CloseShieldOutputStream</a>.
70 * </p>
71 *
72 * @throws IOException
73 * if an I/O error occurs.
74 */
75 @Override
76 public void close() throws IOException {
77 eof();
78 flush();
79 out.close();
80 }
81
82 /**
83 * Writes EOF.
84 *
85 * @since 1.11
86 */
87 public void eof() {
88 // Notify encoder of EOF (-1).
89 if (doEncode) {
90 baseNCodec.encode(singleByte, 0, EOF, context);
91 } else {
92 baseNCodec.decode(singleByte, 0, EOF, context);
93 }
94 }
95
96 /**
97 * Flushes this output stream and forces any buffered output bytes to be written out to the stream.
98 *
99 * @throws IOException
100 * if an I/O error occurs.
101 */
102 @Override
103 public void flush() throws IOException {
104 flush(true);
105 }
106
107 /**
108 * Flushes this output stream and forces any buffered output bytes to be written out to the stream. If propagate is
109 * true, the wrapped stream will also be flushed.
110 *
111 * @param propagate
112 * boolean flag to indicate whether the wrapped OutputStream should also be flushed.
113 * @throws IOException
114 * if an I/O error occurs.
115 */
116 private void flush(final boolean propagate) throws IOException {
117 final int avail = baseNCodec.available(context);
118 if (avail > 0) {
119 final byte[] buf = new byte[avail];
120 final int c = baseNCodec.readResults(buf, 0, avail, context);
121 if (c > 0) {
122 out.write(buf, 0, c);
123 }
124 }
125 if (propagate) {
126 out.flush();
127 }
128 }
129
130 /**
131 * Returns true if decoding behavior is strict. Decoding will raise an
132 * {@link IllegalArgumentException} if trailing bits are not part of a valid encoding.
133 *
134 * <p>
135 * The default is false for lenient encoding. Decoding will compose trailing bits
136 * into 8-bit bytes and discard the remainder.
137 * </p>
138 *
139 * @return true if using strict decoding
140 * @since 1.15
141 */
142 public boolean isStrictDecoding() {
143 return baseNCodec.isStrictDecoding();
144 }
145
146 /**
147 * Writes {@code len} bytes from the specified {@code b} array starting at {@code offset} to this
148 * output stream.
149 *
150 * @param array
151 * source byte array
152 * @param offset
153 * where to start reading the bytes
154 * @param len
155 * maximum number of bytes to write
156 *
157 * @throws IOException
158 * if an I/O error occurs.
159 * @throws NullPointerException
160 * if the byte array parameter is null
161 * @throws IndexOutOfBoundsException
162 * if offset, len or buffer size are invalid
163 */
164 @Override
165 public void write(final byte[] array, final int offset, final int len) throws IOException {
166 Objects.requireNonNull(array, "array");
167 if (offset < 0 || len < 0) {
168 throw new IndexOutOfBoundsException();
169 }
170 if (offset > array.length || offset + len > array.length) {
171 throw new IndexOutOfBoundsException();
172 }
173 if (len > 0) {
174 if (doEncode) {
175 baseNCodec.encode(array, offset, len, context);
176 } else {
177 baseNCodec.decode(array, offset, len, context);
178 }
179 flush(false);
180 }
181 }
182
183 /**
184 * Writes the specified {@code byte} to this output stream.
185 *
186 * @param i
187 * source byte
188 * @throws IOException
189 * if an I/O error occurs.
190 */
191 @Override
192 public void write(final int i) throws IOException {
193 singleByte[0] = (byte) i;
194 write(singleByte, 0, 1);
195 }
196
197 }