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 output 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 output, final BaseNCodec basedCodec, final boolean doEncode) { 058 super(output); 059 this.baseNCodec = basedCodec; 060 this.doEncode = doEncode; 061 } 062 063 /** 064 * Writes the specified {@code byte} to this output stream. 065 * 066 * @param i 067 * source byte 068 * @throws IOException 069 * if an I/O error occurs. 070 */ 071 @Override 072 public void write(final int i) throws IOException { 073 singleByte[0] = (byte) i; 074 write(singleByte, 0, 1); 075 } 076 077 /** 078 * Writes {@code len} bytes from the specified {@code b} array starting at {@code offset} to this 079 * output stream. 080 * 081 * @param array 082 * source byte array 083 * @param offset 084 * where to start reading the bytes 085 * @param len 086 * maximum number of bytes to write 087 * 088 * @throws IOException 089 * if an I/O error occurs. 090 * @throws NullPointerException 091 * if the byte array parameter is null 092 * @throws IndexOutOfBoundsException 093 * if offset, len or buffer size are invalid 094 */ 095 @Override 096 public void write(final byte array[], final int offset, final int len) throws IOException { 097 Objects.requireNonNull(array, "array"); 098 if (offset < 0 || len < 0) { 099 throw new IndexOutOfBoundsException(); 100 } else if (offset > array.length || offset + len > array.length) { 101 throw new IndexOutOfBoundsException(); 102 } else if (len > 0) { 103 if (doEncode) { 104 baseNCodec.encode(array, offset, len, context); 105 } else { 106 baseNCodec.decode(array, offset, len, context); 107 } 108 flush(false); 109 } 110 } 111 112 /** 113 * Flushes this output stream and forces any buffered output bytes to be written out to the stream. If propagate is 114 * true, the wrapped stream will also be flushed. 115 * 116 * @param propagate 117 * boolean flag to indicate whether the wrapped OutputStream should also be flushed. 118 * @throws IOException 119 * if an I/O error occurs. 120 */ 121 private void flush(final boolean propagate) throws IOException { 122 final int avail = baseNCodec.available(context); 123 if (avail > 0) { 124 final byte[] buf = new byte[avail]; 125 final int c = baseNCodec.readResults(buf, 0, avail, context); 126 if (c > 0) { 127 out.write(buf, 0, c); 128 } 129 } 130 if (propagate) { 131 out.flush(); 132 } 133 } 134 135 /** 136 * Flushes this output stream and forces any buffered output bytes to be written out to the stream. 137 * 138 * @throws IOException 139 * if an I/O error occurs. 140 */ 141 @Override 142 public void flush() throws IOException { 143 flush(true); 144 } 145 146 /** 147 * Closes this output stream and releases any system resources associated with the stream. 148 * <p> 149 * To write the EOF marker without closing the stream, call {@link #eof()} or use an 150 * <a href="https://commons.apache.org/proper/commons-io/">Apache Commons IO</a> <a href= 151 * "https://commons.apache.org/proper/commons-io/apidocs/org/apache/commons/io/output/CloseShieldOutputStream.html" 152 * >CloseShieldOutputStream</a>. 153 * </p> 154 * 155 * @throws IOException 156 * if an I/O error occurs. 157 */ 158 @Override 159 public void close() throws IOException { 160 eof(); 161 flush(); 162 out.close(); 163 } 164 165 /** 166 * Writes EOF. 167 * 168 * @throws IOException 169 * if an I/O error occurs. 170 * @since 1.11 171 */ 172 public void eof() throws IOException { 173 // Notify encoder of EOF (-1). 174 if (doEncode) { 175 baseNCodec.encode(singleByte, 0, EOF, context); 176 } else { 177 baseNCodec.decode(singleByte, 0, EOF, context); 178 } 179 } 180 181}