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}