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 * 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}