001/*
002 * ModeShape (http://www.modeshape.org)
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *       http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.modeshape.common.util;
017
018import java.io.IOException;
019import java.io.InputStream;
020import org.modeshape.common.CommonI18n;
021import org.modeshape.common.annotation.NotThreadSafe;
022import org.modeshape.common.logging.Logger;
023
024/**
025 * An {@link InputStream} implementation that wraps another stream and makes sure that {@link java.io.InputStream#close()}
026 * is always called on the wrapped stream when there is no more content to read or when an unexpected exception occurs.
027 */
028@NotThreadSafe
029public class SelfClosingInputStream extends InputStream {
030
031    private static final Logger LOGGER = Logger.getLogger(SelfClosingInputStream.class);
032
033    private final InputStream stream;
034
035    /**
036     * Create a self-closing {@link InputStream} that wraps another input stream.
037     *
038     * @param stream the wrapped {@link java.io.InputStream}; may not be null.
039     */
040    public SelfClosingInputStream( InputStream stream ) {
041        assert stream != null;
042        this.stream = stream;
043    }
044
045    @Override
046    public int available() throws IOException {
047        try {
048            return stream.available();
049        } catch (IOException e) {
050            closeStream();
051            throw e;
052        } catch (RuntimeException e) {
053            closeStream();
054            throw e;
055        }
056    }
057
058    @Override
059    public void close() throws IOException {
060        stream.close();
061    }
062
063    @Override
064    public int hashCode() {
065        return stream.hashCode();
066    }
067
068    @Override
069    public void mark( int readlimit ) {
070        try {
071            stream.mark(readlimit);
072        } catch (RuntimeException e) {
073            closeStream();
074            throw e;
075        }
076    }
077
078    @Override
079    public boolean markSupported() {
080        try {
081            return stream.markSupported();
082        } catch (RuntimeException e) {
083            closeStream();
084            throw e;
085        }
086    }
087
088    @Override
089    public int read( byte[] b,
090                     int off,
091                     int len ) throws IOException {
092        try {
093            int result = stream.read(b, off, len);
094            if (result == -1) {
095                // the end of the stream has been reached ...
096                closeStream();
097            }
098            return result;
099        } catch (IOException e) {
100            closeStream();
101            throw e;
102        } catch (RuntimeException e) {
103            closeStream();
104            throw e;
105        }
106    }
107
108    @Override
109    public int read( byte[] b ) throws IOException {
110        try {
111            int result = stream.read(b);
112            if (result == -1) {
113                // the end of the stream has been reached ...
114                closeStream();
115            }
116            return result;
117        } catch (IOException e) {
118            closeStream();
119            throw e;
120        } catch (RuntimeException e) {
121            closeStream();
122            throw e;
123        }
124    }
125
126    @Override
127    public int read() throws IOException {
128        try {
129            int result = stream.read();
130            if (result == -1) {
131                // the end of the stream has been reached ...
132                closeStream();
133            }
134            return result;
135        } catch (IOException e) {
136            closeStream();
137            throw e;
138        } catch (RuntimeException e) {
139            closeStream();
140            throw e;
141        }
142    }
143
144    @Override
145    public void reset() throws IOException {
146        try {
147            stream.reset();
148        } catch (IOException e) {
149            closeStream();
150            throw e;
151        } catch (RuntimeException e) {
152            closeStream();
153            throw e;
154        }
155    }
156
157    @Override
158    public long skip( long n ) throws IOException {
159        try {
160            return stream.skip(n);
161        } catch (IOException e) {
162            closeStream();
163            throw e;
164        } catch (RuntimeException e) {
165            closeStream();
166            throw e;
167        }
168    }
169
170    @Override
171    public String toString() {
172        return stream.toString();
173    }
174
175    /**
176     * Returns the stream that this instance wraps.
177     *
178     * @return an {@link InputStream} instance, never null.
179     */
180    public InputStream wrappedStream() {
181        return stream;
182    }
183
184    private void closeStream() {
185        try {
186            stream.close();
187        } catch (IOException e) {
188            LOGGER.error(e, CommonI18n.errorClosingWrappedStream);
189        }
190    }
191}