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.vfs2.util;
18
19 import java.io.BufferedOutputStream;
20 import java.io.IOException;
21 import java.io.OutputStream;
22 import java.util.concurrent.atomic.AtomicBoolean;
23
24 import org.apache.commons.vfs2.FileSystemException;
25
26 /**
27 * An OutputStream that provides buffering and end-of-stream monitoring.
28 */
29 public class MonitorOutputStream extends BufferedOutputStream {
30
31 private final AtomicBoolean finished = new AtomicBoolean(false);
32
33 /**
34 * Constructs a MonitorOutputStream from the passed OutputStream
35 *
36 * @param out The output stream to wrap.
37 */
38 public MonitorOutputStream(final OutputStream out) {
39 super(out);
40 }
41
42 /**
43 * Constructs a MonitorOutputStream from the passed OutputStream and with the specified buffer size
44 *
45 * @param out The output stream to wrap.
46 * @param bufferSize The buffer size to use.
47 * @since 2.4
48 */
49 public MonitorOutputStream(final OutputStream out, final int bufferSize) {
50 super(out, bufferSize);
51 }
52
53 /**
54 * Closes this output stream.
55 * <p>
56 * This makes sure the buffers are flushed, close the output stream and it will call {@link #onClose()} and re-throw
57 * last exception from any of the three.
58 * </p>
59 * <p>
60 * This does nothing if the stream is closed already.
61 * </p>
62 *
63 * @throws IOException if an IO error occurs.
64 */
65 @Override
66 public void close() throws IOException {
67 // do not use super.close()
68 // on Java 8 it might throw self suppression, see JDK-8042377
69 // in older Java it silently ignores flush() errors
70 if (finished.getAndSet(true)) {
71 return;
72 }
73
74 IOException exc = null;
75
76 // flush the buffer and out stream
77 try {
78 super.flush();
79 } catch (final IOException ioe) {
80 exc = ioe;
81 }
82
83 // close the out stream without using super.close()
84 try {
85 super.out.close();
86 } catch (final IOException ioe) {
87 exc = ioe;
88 }
89
90 // Notify of end of output
91 try {
92 onClose();
93 } catch (final IOException ioe) {
94 exc = ioe;
95 }
96
97 if (exc != null) {
98 throw exc;
99 }
100 }
101
102 /**
103 * @param b The character to write.
104 * @throws IOException if an error occurs.
105 * @since 2.0
106 */
107 @Override
108 public synchronized void write(final int b) throws IOException {
109 assertOpen();
110 super.write(b);
111 }
112
113 /**
114 * @param b The byte array.
115 * @param off The offset into the array.
116 * @param len The number of bytes to write.
117 * @throws IOException if an error occurs.
118 * @since 2.0
119 */
120 @Override
121 public synchronized void write(final byte[] b, final int off, final int len) throws IOException {
122 assertOpen();
123 super.write(b, off, len);
124 }
125
126 /**
127 * @throws IOException if an error occurs.
128 * @since 2.0
129 */
130 @Override
131 public synchronized void flush() throws IOException {
132 assertOpen();
133 super.flush();
134 }
135
136 /**
137 * @param b The byte array.
138 * @throws IOException if an error occurs.
139 * @since 2.0
140 */
141 @Override
142 public void write(final byte[] b) throws IOException {
143 assertOpen();
144 super.write(b);
145 }
146
147 /**
148 * Check if file is still open.
149 * <p>
150 * This is a workaround for an oddity with Java's BufferedOutputStream where you can write to even if the stream has
151 * been closed.
152 * </p>
153 *
154 * @throws FileSystemException if already closed.
155 * @since 2.0
156 */
157 protected void assertOpen() throws FileSystemException {
158 if (finished.get()) {
159 throw new FileSystemException("vfs.provider/closed.error");
160 }
161 }
162
163 /**
164 * Called after this stream is closed.
165 * <p>
166 * This implementation does nothing.
167 * </p>
168 *
169 * @throws IOException if an error occurs.
170 */
171 // IOException is needed because subclasses may need to throw it
172 protected void onClose() throws IOException {
173 }
174 }