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 package org.apache.commons.io.output; 18 19 import java.io.FilterOutputStream; 20 import java.io.IOException; 21 import java.io.OutputStream; 22 23 import org.apache.commons.io.IOUtils; 24 import org.apache.commons.io.build.AbstractStreamBuilder; 25 26 /** 27 * A Proxy stream which acts as expected, that is it passes the method 28 * calls on to the proxied stream and doesn't change which methods are 29 * being called. It is an alternative base class to FilterOutputStream 30 * to increase reusability. 31 * <p> 32 * See the protected methods for ways in which a subclass can easily decorate 33 * a stream with custom pre-, post- or error processing functionality. 34 * </p> 35 */ 36 public class ProxyOutputStream extends FilterOutputStream { 37 38 /** 39 * Builds instances of {@link ProxyOutputStream}. 40 * <p> 41 * This class does not provide a convenience static {@code builder()} method so that subclasses can. 42 * </p> 43 * 44 * @since 2.19.0 45 */ 46 public static class Builder extends AbstractStreamBuilder<ProxyOutputStream, Builder> { 47 48 /** 49 * Constructs a new builder of {@link ProxyOutputStream}. 50 */ 51 public Builder() { 52 // empty 53 } 54 55 /** 56 * Builds a new {@link ProxyOutputStream}. 57 * <p> 58 * This builder uses the following aspects: 59 * </p> 60 * <ul> 61 * <li>{@link #getOutputStream()} is the target aspect.</li> 62 * </ul> 63 * 64 * @return a new instance. 65 * @throws IllegalStateException if the {@code origin} is {@code null}. 66 * @throws UnsupportedOperationException if the origin cannot be converted to an {@link OutputStream}. 67 * @throws IOException if an I/O error occurs converting to an {@link OutputStream} using {@link #getOutputStream()}. 68 * @see #getOutputStream() 69 * @see #getUnchecked() 70 */ 71 @SuppressWarnings("resource") // caller closes 72 @Override 73 public ProxyOutputStream get() throws IOException { 74 return new ProxyOutputStream(getOutputStream()); 75 } 76 77 } 78 79 /** 80 * Constructs a new ProxyOutputStream. 81 * 82 * @param delegate the OutputStream to delegate to 83 */ 84 public ProxyOutputStream(final OutputStream delegate) { 85 // the delegate is stored in a protected superclass variable named 'out' 86 super(delegate); 87 } 88 89 /** 90 * Invoked by the write methods after the proxied call has returned 91 * successfully. The number of bytes written (1 for the 92 * {@link #write(int)} method, buffer length for {@link #write(byte[])}, 93 * etc.) is given as an argument. 94 * <p> 95 * Subclasses can override this method to add common post-processing 96 * functionality without having to override all the write methods. 97 * The default implementation does nothing. 98 * 99 * @param n number of bytes written 100 * @throws IOException if the post-processing fails 101 * @since 2.0 102 */ 103 @SuppressWarnings("unused") // Possibly thrown from subclasses. 104 protected void afterWrite(final int n) throws IOException { 105 // noop 106 } 107 108 /** 109 * Invoked by the write methods before the call is proxied. The number 110 * of bytes to be written (1 for the {@link #write(int)} method, buffer 111 * length for {@link #write(byte[])}, etc.) is given as an argument. 112 * <p> 113 * Subclasses can override this method to add common pre-processing 114 * functionality without having to override all the write methods. 115 * The default implementation does nothing. 116 * 117 * @param n number of bytes to be written 118 * @throws IOException if the pre-processing fails 119 * @since 2.0 120 */ 121 @SuppressWarnings("unused") // Possibly thrown from subclasses. 122 protected void beforeWrite(final int n) throws IOException { 123 // noop 124 } 125 126 /** 127 * Invokes the delegate's {@code close()} method. 128 * @throws IOException if an I/O error occurs. 129 */ 130 @Override 131 public void close() throws IOException { 132 IOUtils.close(out, this::handleIOException); 133 } 134 135 /** 136 * Invokes the delegate's {@code flush()} method. 137 * @throws IOException if an I/O error occurs. 138 */ 139 @Override 140 public void flush() throws IOException { 141 try { 142 out.flush(); 143 } catch (final IOException e) { 144 handleIOException(e); 145 } 146 } 147 148 /** 149 * Handle any IOExceptions thrown. 150 * <p> 151 * This method provides a point to implement custom exception 152 * handling. The default behavior is to re-throw the exception. 153 * @param e The IOException thrown 154 * @throws IOException if an I/O error occurs. 155 * @since 2.0 156 */ 157 protected void handleIOException(final IOException e) throws IOException { 158 throw e; 159 } 160 161 /** 162 * Sets the underlying output stream. 163 * 164 * @param out the underlying output stream. 165 * @return this instance. 166 * @since 2.19.0 167 */ 168 public ProxyOutputStream setReference(final OutputStream out) { 169 this.out = out; 170 return this; 171 } 172 173 /** 174 * Unwraps this instance by returning the underlying {@link OutputStream}. 175 * <p> 176 * Use with caution; useful to query the underlying {@link OutputStream}. 177 * </p> 178 * 179 * @return the underlying {@link OutputStream}. 180 */ 181 OutputStream unwrap() { 182 return out; 183 } 184 185 /** 186 * Invokes the delegate's {@code write(byte[])} method. 187 * @param bts the bytes to write 188 * @throws IOException if an I/O error occurs. 189 */ 190 @Override 191 public void write(final byte[] bts) throws IOException { 192 try { 193 final int len = IOUtils.length(bts); 194 beforeWrite(len); 195 out.write(bts); 196 afterWrite(len); 197 } catch (final IOException e) { 198 handleIOException(e); 199 } 200 } 201 202 /** 203 * Invokes the delegate's {@code write(byte[])} method. 204 * @param bts the bytes to write 205 * @param st The start offset 206 * @param end The number of bytes to write 207 * @throws IOException if an I/O error occurs. 208 */ 209 @Override 210 public void write(final byte[] bts, final int st, final int end) throws IOException { 211 try { 212 beforeWrite(end); 213 out.write(bts, st, end); 214 afterWrite(end); 215 } catch (final IOException e) { 216 handleIOException(e); 217 } 218 } 219 220 /** 221 * Invokes the delegate's {@code write(int)} method. 222 * @param idx the byte to write 223 * @throws IOException if an I/O error occurs. 224 */ 225 @Override 226 public void write(final int idx) throws IOException { 227 try { 228 beforeWrite(1); 229 out.write(idx); 230 afterWrite(1); 231 } catch (final IOException e) { 232 handleIOException(e); 233 } 234 } 235 236 }