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.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}