    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one
    003 * or more contributor license agreements.  See the NOTICE file
    004 * distributed with this work for additional information
    005 * regarding copyright ownership.  The ASF licenses this file
    006 * to you under the Apache License, Version 2.0 (the
    007 * "License"); you may not use this file except in compliance
    008 * with the License.  You may obtain a copy of the License at
    009 *
    010 *   https://www.apache.org/licenses/LICENSE-2.0
    011 *
    012 * Unless required by applicable law or agreed to in writing,
    013 * software distributed under the License is distributed on an
    014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    015 * KIND, either express or implied.  See the License for the
    016 * specific language governing permissions and limitations
    017 * under the License.
    018 */
    019
    020package org.apache.commons.exec;
    021
    022import java.time.Duration;
    023import java.time.Instant;
    024
    025/**
    026 * A default implementation of 'ExecuteResultHandler' used for asynchronous process handling.
    027 */
    028public class DefaultExecuteResultHandler implements ExecuteResultHandler {
    029
    030    /** The interval polling the result. */
    031    private static final int SLEEP_TIME_MS = 50;
    032
    033    /** Keep track if the process is still running. */
    034    private volatile boolean hasResult;
    035
    036    /** The exit value of the finished process. */
    037    private volatile int exitValue;
    038
    039    /** Any offending exception. */
    040    private volatile ExecuteException exception;
    041
    042    /**
    043     * Constructs a new instance.
    044     */
    045    public DefaultExecuteResultHandler() {
    046        this.hasResult = false;
    047        this.exitValue = Executor.INVALID_EXITVALUE;
    048    }
    049
    050    /**
    051     * Gets the {@code exception} causing the process execution to fail.
    052     *
    053     * @return the exception.
    054     * @throws IllegalStateException if the process has not exited yet.
    055     */
    056    public ExecuteException getException() {
    057        if (!hasResult) {
    058            throw new IllegalStateException("The process has not exited yet therefore no result is available ...");
    059        }
    060        return exception;
    061    }
    062
    063    /**
    064     * Gets the {@code exitValue} of the process.
    065     *
    066     * @return the exitValue.
    067     * @throws IllegalStateException if the process has not exited yet.
    068     */
    069    public int getExitValue() {
    070        if (!hasResult) {
    071            throw new IllegalStateException("The process has not exited yet therefore no result is available ...");
    072        }
    073        return exitValue;
    074    }
    075
    076    /**
    077     * Tests whether the process exited and a result is available, i.e. exitCode or exception?
    078     *
    079     * @return true whether a result of the execution is available.
    080     */
    081    public boolean hasResult() {
    082        return hasResult;
    083    }
    084
    085    /**
    086     * @see org.apache.commons.exec.ExecuteResultHandler#onProcessComplete(int)
    087     */
    088    @Override
    089    public void onProcessComplete(final int exitValue) {
    090        this.exitValue = exitValue;
    091        this.exception = null;
    092        this.hasResult = true;
    093    }
    094
    095    /**
    096     * @see org.apache.commons.exec.ExecuteResultHandler#onProcessFailed(org.apache.commons.exec.ExecuteException)
    097     */
    098    @Override
    099    public void onProcessFailed(final ExecuteException e) {
    100        this.exitValue = e.getExitValue();
    101        this.exception = e;
    102        this.hasResult = true;
    103    }
    104
    105    /**
    106     * Causes the current thread to wait, if necessary, until the process has terminated. This method returns immediately if the process has already terminated.
    107     * If the process has not yet terminated, the calling thread will be blocked until the process exits.
    108     *
    109     * @throws InterruptedException if the current thread is {@linkplain Thread#interrupt() interrupted} by another thread while it is waiting, then the wait is
    110     *                              ended and an {@link InterruptedException} is thrown.
    111     */
    112    public void waitFor() throws InterruptedException {
    113        while (!hasResult()) {
    114            Thread.sleep(SLEEP_TIME_MS);
    115        }
    116    }
    117
    118    /**
    119     * Causes the current thread to wait, if necessary, until the process has terminated. This method returns immediately if the process has already terminated.
    120     * If the process has not yet terminated, the calling thread will be blocked until the process exits.
    121     *
    122     * @param timeout the maximum time to wait.
    123     * @throws InterruptedException if the current thread is {@linkplain Thread#interrupt() interrupted} by another thread while it is waiting, then the wait is
    124     *                              ended and an {@link InterruptedException} is thrown.
    125     * @since 1.4.0
    126     */
    127    public void waitFor(final Duration timeout) throws InterruptedException {
    128        final Instant until = Instant.now().plus(timeout);
    129        while (!hasResult() && Instant.now().isBefore(until)) {
    130            Thread.sleep(SLEEP_TIME_MS);
    131        }
    132    }
    133
    134    /**
    135     * Causes the current thread to wait, if necessary, until the process has terminated. This method returns immediately if the process has already terminated.
    136     * If the process has not yet terminated, the calling thread will be blocked until the process exits.
    137     *
    138     * @param timeoutMillis the maximum time to wait in milliseconds.
    139     * @throws InterruptedException if the current thread is {@linkplain Thread#interrupt() interrupted} by another thread while it is waiting, then the wait is
    140     *                              ended and an {@link InterruptedException} is thrown.
    141     * @deprecated Use {@link #waitFor(Duration)}.
    142     */
    143    @Deprecated
    144    public void waitFor(final long timeoutMillis) throws InterruptedException {
    145        final long untilMillis = System.currentTimeMillis() + timeoutMillis;
    146        while (!hasResult() && System.currentTimeMillis() < untilMillis) {
    147            Thread.sleep(SLEEP_TIME_MS);
    148        }
    149    }
    150
    151}