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 * http://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; 028 029/** 030 * Abstract superclass for Base-N output streams. 031 * <p> 032 * To write the EOF marker without closing the stream, call {@link #eof()} or use an <a 033 * href="https://commons.apache.org/proper/commons-io/">Apache Commons IO</a> <a href= 034 * "https://commons.apache.org/proper/commons-io/apidocs/org/apache/commons/io/output/CloseShieldOutputStream.html" 035 * >CloseShieldOutputStream</a>. 036 * </p> 037 * 038 * @since 1.5 039 */ 040public class BaseNCodecOutputStream extends FilterOutputStream { 041 042 private final boolean doEncode; 043 044 private final BaseNCodec baseNCodec; 045 046 private final byte[] singleByte = new byte[1]; 047 048 private final Context context = new Context(); 049 050 /** 051 * TODO should this be protected? 052 * 053 * @param outputStream the underlying output or null. 054 * @param basedCodec a BaseNCodec. 055 * @param doEncode true to encode, false to decode, TODO should be an enum? 056 */ 057 public BaseNCodecOutputStream(final OutputStream outputStream, final BaseNCodec basedCodec, final boolean doEncode) { 058 super(outputStream); 059 this.baseNCodec = basedCodec; 060 this.doEncode = doEncode; 061 } 062 063 /** 064 * Closes this output stream and releases any system resources associated with the stream. 065 * <p> 066 * To write the EOF marker without closing the stream, call {@link #eof()} or use an 067 * <a href="https://commons.apache.org/proper/commons-io/">Apache Commons IO</a> <a href= 068 * "https://commons.apache.org/proper/commons-io/apidocs/org/apache/commons/io/output/CloseShieldOutputStream.html" 069 * >CloseShieldOutputStream</a>. 070 * </p> 071 * 072 * @throws IOException 073 * if an I/O error occurs. 074 */ 075 @Override 076 public void close() throws IOException { 077 eof(); 078 flush(); 079 out.close(); 080 } 081 082 /** 083 * Writes EOF. 084 * 085 * @since 1.11 086 */ 087 public void eof() { 088 // Notify encoder of EOF (-1). 089 if (doEncode) { 090 baseNCodec.encode(singleByte, 0, EOF, context); 091 } else { 092 baseNCodec.decode(singleByte, 0, EOF, context); 093 } 094 } 095 096 /** 097 * Flushes this output stream and forces any buffered output bytes to be written out to the stream. 098 * 099 * @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}