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}