    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 *      https://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}