001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * https://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018package org.apache.commons.codec.binary; 019 020import static org.apache.commons.codec.binary.BaseNCodec.EOF; 021 022import java.io.FilterOutputStream; 023import java.io.IOException; 024import java.io.OutputStream; 025import java.util.Objects; 026 027import org.apache.commons.codec.binary.BaseNCodec.Context; 028import org.apache.commons.codec.binary.BaseNCodecOutputStream.AbstractBuilder; 029 030/** 031 * Abstract superclass for Base-N output streams. 032 * <p> 033 * 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 034 * IO</a> 035 * <a href= "https://commons.apache.org/proper/commons-io/apidocs/org/apache/commons/io/output/CloseShieldOutputStream.html" >CloseShieldOutputStream</a>. 036 * </p> 037 * 038 * @param <C> A BaseNCodec subclass. 039 * @param <T> A BaseNCodecInputStream subclass. 040 * @param <B> A subclass. 041 * @see Base16OutputStream 042 * @see Base32OutputStream 043 * @see Base64OutputStream 044 * @since 1.5 045 */ 046public class BaseNCodecOutputStream<C extends BaseNCodec, T extends BaseNCodecOutputStream<C, T, B>, B extends AbstractBuilder<T, C, B>> 047 extends FilterOutputStream { 048 049 /** 050 * Builds output stream instances in {@link BaseNCodec} format. 051 * 052 * @param <T> the output stream type to build. 053 * @param <C> A {@link BaseNCodec} subclass. 054 * @param <B> the builder subclass. 055 * @since 1.20.0 056 */ 057 public abstract static class AbstractBuilder<T, C extends BaseNCodec, B extends AbstractBuilder<T, C, B>> 058 extends AbstractBaseNCodecStreamBuilder<T, C, B> { 059 060 private OutputStream outputStream; 061 062 /** 063 * Constructs a new instance. 064 */ 065 public AbstractBuilder() { 066 // super 067 } 068 069 /** 070 * Gets the input stream. 071 * 072 * @return the input stream. 073 */ 074 protected OutputStream getOutputStream() { 075 return outputStream; 076 } 077 078 /** 079 * Sets the input stream. 080 * 081 * @param outputStream the input stream. 082 * @return {@code this} instance. 083 */ 084 public B setOutputStream(final OutputStream outputStream) { 085 this.outputStream = outputStream; 086 return asThis(); 087 } 088 } 089 090 private final boolean doEncode; 091 private final C baseNCodec; 092 private final byte[] singleByte = new byte[1]; 093 private final Context context = new Context(); 094 095 /** 096 * Constructs a new instance. 097 * 098 * @param builder A builder. 099 * @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}