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 */ 017package org.apache.commons.vfs2.util; 018 019import java.io.BufferedOutputStream; 020import java.io.IOException; 021import java.io.OutputStream; 022import java.util.concurrent.atomic.AtomicBoolean; 023 024import org.apache.commons.vfs2.FileSystemException; 025 026/** 027 * An OutputStream that provides buffering and end-of-stream monitoring. 028 */ 029public class MonitorOutputStream extends BufferedOutputStream { 030 private final AtomicBoolean finished = new AtomicBoolean(false); 031 032 public MonitorOutputStream(final OutputStream out) { 033 super(out); 034 } 035 036 /** 037 * Closes this output stream. 038 * <p> 039 * This makes sure the buffers are flushed, close the output stream and it will call {@link #onClose()} and re-throw 040 * last exception from any of the three. 041 * <p> 042 * This does nothing if the stream is closed already. 043 * 044 * @throws IOException if an error occurs. 045 */ 046 @Override 047 public void close() throws IOException { 048 // do not use super.close() 049 // on Java 8 it might throw self suppression, see JDK-8042377 050 // in older Java it silently ignores flush() errors 051 if (finished.getAndSet(true)) { 052 return; 053 } 054 055 IOException exc = null; 056 057 // flush the buffer and out stream 058 try { 059 super.flush(); 060 } catch (final IOException ioe) { 061 exc = ioe; 062 } 063 064 // close the out stream without using super.close() 065 try { 066 super.out.close(); 067 } catch (final IOException ioe) { 068 exc = ioe; 069 } 070 071 // Notify of end of output 072 try { 073 onClose(); 074 } catch (final IOException ioe) { 075 exc = ioe; 076 } 077 078 if (exc != null) { 079 throw exc; 080 } 081 } 082 083 /** 084 * @param b The character to write. 085 * @throws IOException if an error occurs. 086 * @since 2.0 087 */ 088 @Override 089 public synchronized void write(final int b) throws IOException { 090 assertOpen(); 091 super.write(b); 092 } 093 094 /** 095 * @param b The byte array. 096 * @param off The offset into the array. 097 * @param len The number of bytes to write. 098 * @throws IOException if an error occurs. 099 * @since 2.0 100 */ 101 @Override 102 public synchronized void write(final byte[] b, final int off, final int len) throws IOException { 103 assertOpen(); 104 super.write(b, off, len); 105 } 106 107 /** 108 * @throws IOException if an error occurs. 109 * @since 2.0 110 */ 111 @Override 112 public synchronized void flush() throws IOException { 113 assertOpen(); 114 super.flush(); 115 } 116 117 /** 118 * @param b The byte array. 119 * @throws IOException if an error occurs. 120 * @since 2.0 121 */ 122 @Override 123 public void write(final byte[] b) throws IOException { 124 assertOpen(); 125 super.write(b); 126 } 127 128 /** 129 * Check if file is still open. 130 * <p> 131 * This is a workaround for an oddity with Java's BufferedOutputStream where you can write to even if the stream has 132 * been closed. 133 * 134 * @throws FileSystemException if already closed. 135 * @since 2.0 136 */ 137 protected void assertOpen() throws FileSystemException { 138 if (finished.get()) { 139 throw new FileSystemException("vfs.provider/closed.error"); 140 } 141 } 142 143 /** 144 * Called after this stream is closed. 145 * <p> 146 * This implementation does nothing. 147 * 148 * @throws IOException if an error occurs. 149 */ 150 // IOException is needed because subclasses may need to throw it 151 protected void onClose() throws IOException { 152 } 153}