    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.time.Duration;
    021import java.time.Instant;
    022
    023/**
    024 * A default implementation of 'ExecuteResultHandler' used for asynchronous process handling.
    025 */
    026public class DefaultExecuteResultHandler implements ExecuteResultHandler {
    027
    028    /** The interval polling the result. */
    029    private static final int SLEEP_TIME_MS = 50;
    030
    031    /** Keep track if the process is still running. */
    032    private volatile boolean hasResult;
    033
    034    /** The exit value of the finished process. */
    035    private volatile int exitValue;
    036
    037    /** Any offending exception. */
    038    private volatile ExecuteException exception;
    039
    040    /**
    041     * Constructs a new instance.
    042     */
    043    public DefaultExecuteResultHandler() {
    044        this.hasResult = false;
    045        this.exitValue = Executor.INVALID_EXITVALUE;
    046    }
    047
    048    /**
    049     * Gets the {@code exception} causing the process execution to fail.
    050     *
    051     * @return the exception.
    052     * @throws IllegalStateException if the process has not exited yet.
    053     */
    054    public ExecuteException getException() {
    055        if (!hasResult) {
    056            throw new IllegalStateException("The process has not exited yet therefore no result is available ...");
    057        }
    058        return exception;
    059    }
    060
    061    /**
    062     * Gets the {@code exitValue} of the process.
    063     *
    064     * @return the exitValue.
    065     * @throws IllegalStateException if the process has not exited yet.
    066     */
    067    public int getExitValue() {
    068        if (!hasResult) {
    069            throw new IllegalStateException("The process has not exited yet therefore no result is available ...");
    070        }
    071        return exitValue;
    072    }
    073
    074    /**
    075     * Tests whether the process exited and a result is available, i.e. exitCode or exception?
    076     *
    077     * @return true whether a result of the execution is available.
    078     */
    079    public boolean hasResult() {
    080        return hasResult;
    081    }
    082
    083    /**
    084     * @see org.apache.commons.exec.ExecuteResultHandler#onProcessComplete(int)
    085     */
    086    @Override
    087    public void onProcessComplete(final int exitValue) {
    088        this.exitValue = exitValue;
    089        this.exception = null;
    090        this.hasResult = true;
    091    }
    092
    093    /**
    094     * @see org.apache.commons.exec.ExecuteResultHandler#onProcessFailed(org.apache.commons.exec.ExecuteException)
    095     */
    096    @Override
    097    public void onProcessFailed(final ExecuteException e) {
    098        this.exitValue = e.getExitValue();
    099        this.exception = e;
    100        this.hasResult = true;
    101    }
    102
    103    /**
    104     * Causes the current thread to wait, if necessary, until the process has terminated. This method returns immediately if the process has already terminated.
    105     * If the process has not yet terminated, the calling thread will be blocked until the process exits.
    106     *
    107     * @throws InterruptedException if the current thread is {@linkplain Thread#interrupt() interrupted} by another thread while it is waiting, then the wait is
    108     *                              ended and an {@link InterruptedException} is thrown.
    109     */
    110    public void waitFor() throws InterruptedException {
    111        while (!hasResult()) {
    112            Thread.sleep(SLEEP_TIME_MS);
    113        }
    114    }
    115
    116    /**
    117     * Causes the current thread to wait, if necessary, until the process has terminated. This method returns immediately if the process has already terminated.
    118     * If the process has not yet terminated, the calling thread will be blocked until the process exits.
    119     *
    120     * @param timeout the maximum time to wait.
    121     * @throws InterruptedException if the current thread is {@linkplain Thread#interrupt() interrupted} by another thread while it is waiting, then the wait is
    122     *                              ended and an {@link InterruptedException} is thrown.
    123     * @since 1.4.0
    124     */
    125    public void waitFor(final Duration timeout) throws InterruptedException {
    126        final Instant until = Instant.now().plus(timeout);
    127        while (!hasResult() && Instant.now().isBefore(until)) {
    128            Thread.sleep(SLEEP_TIME_MS);
    129        }
    130    }
    131
    132    /**
    133     * Causes the current thread to wait, if necessary, until the process has terminated. This method returns immediately if the process has already terminated.
    134     * If the process has not yet terminated, the calling thread will be blocked until the process exits.
    135     *
    136     * @param timeoutMillis the maximum time to wait in milliseconds.
    137     * @throws InterruptedException if the current thread is {@linkplain Thread#interrupt() interrupted} by another thread while it is waiting, then the wait is
    138     *                              ended and an {@link InterruptedException} is thrown.
    139     * @deprecated Use {@link #waitFor(Duration)}.
    140     */
    141    @Deprecated
    142    public void waitFor(final long timeoutMillis) throws InterruptedException {
    143        final long untilMillis = System.currentTimeMillis() + timeoutMillis;
    144        while (!hasResult() && System.currentTimeMillis() < untilMillis) {
    145            Thread.sleep(SLEEP_TIME_MS);
    146        }
    147    }
    148
    149}