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 */
017
018package org.apache.commons.exec;
019
020import java.io.IOException;
021import java.io.InputStream;
022import java.io.OutputStream;
023
024import org.apache.commons.exec.util.DebugUtils;
025
026/**
027 * Copies all data from an input stream to an output stream.
028 */
029public class StreamPumper implements Runnable {
030
031    /** The default size of the internal buffer for copying the streams. */
032    private static final int DEFAULT_SIZE = 1024;
033
034    /** The input stream to pump from. */
035    private final InputStream is;
036
037    /** The output stream to pmp into. */
038    private final OutputStream os;
039
040    /** The size of the internal buffer for copying the streams. */
041    private final int size;
042
043    /** Was the end of the stream reached. */
044    private boolean finished;
045
046    /** Close the output stream when exhausted. */
047    private final boolean closeWhenExhausted;
048
049    /**
050     * Constructs a new stream pumper.
051     *
052     * @param is input stream to read data from.
053     * @param os output stream to write data to.
054     */
055    public StreamPumper(final InputStream is, final OutputStream os) {
056        this(is, os, false);
057    }
058
059    /**
060     * Constructs a new stream pumper.
061     *
062     * @param is                 input stream to read data from.
063     * @param os                 output stream to write data to.
064     * @param closeWhenExhausted if true, the output stream will be closed when the input is exhausted.
065     */
066    public StreamPumper(final InputStream is, final OutputStream os, final boolean closeWhenExhausted) {
067        this.is = is;
068        this.os = os;
069        this.size = DEFAULT_SIZE;
070        this.closeWhenExhausted = closeWhenExhausted;
071    }
072
073    /**
074     * Constructs a new stream pumper.
075     *
076     * @param is                 input stream to read data from.
077     * @param os                 output stream to write data to.
078     * @param closeWhenExhausted if true, the output stream will be closed when the input is exhausted.
079     * @param size               the size of the internal buffer for copying the streams.
080     */
081    public StreamPumper(final InputStream is, final OutputStream os, final boolean closeWhenExhausted, final int size) {
082        this.is = is;
083        this.os = os;
084        this.size = size > 0 ? size : DEFAULT_SIZE;
085        this.closeWhenExhausted = closeWhenExhausted;
086    }
087
088    /**
089     * Tests whether the end of the stream has been reached.
090     *
091     * @return true is the stream has been exhausted.
092     */
093    public synchronized boolean isFinished() {
094        return finished;
095    }
096
097    /**
098     * Copies data from the input stream to the output stream. Terminates as soon as the input stream is closed or an error occurs.
099     */
100    @Override
101    public void run() {
102        synchronized (this) {
103            // Just in case this object is reused in the future
104            finished = false;
105        }
106
107        final byte[] buf = new byte[this.size];
108
109        int length;
110        try {
111            while ((length = is.read(buf)) > 0) {
112                os.write(buf, 0, length);
113            }
114        } catch (final Exception ignored) {
115            // nothing to do - happens quite often with watchdog
116        } finally {
117            if (closeWhenExhausted) {
118                try {
119                    os.close();
120                } catch (final IOException e) {
121                    final String msg = "Got exception while closing exhausted output stream";
122                    DebugUtils.handleException(msg, e);
123                }
124            }
125            synchronized (this) {
126                finished = true;
127                notifyAll();
128            }
129        }
130    }
131
132    /**
133     * This method blocks until the stream pumper finishes.
134     *
135     * @throws InterruptedException if any thread interrupted the current thread before or while the current thread was waiting for a notification.
136     * @see #isFinished()
137     */
138    public synchronized void waitFor() throws InterruptedException {
139        while (!isFinished()) {
140            wait();
141        }
142    }
143}