mirror of https://github.com/octoleo/plantuml.git
287 lines
6.9 KiB
Java
287 lines
6.9 KiB
Java
/* ========================================================================
|
|
* PlantUML : a free UML diagram generator
|
|
* ========================================================================
|
|
*
|
|
* (C) Copyright 2009-2017, Arnaud Roques
|
|
*
|
|
* Project Info: http://plantuml.com
|
|
*
|
|
* If you like this project or if you find it useful, you can support us at:
|
|
*
|
|
* http://plantuml.com/patreon (only 1$ per month!)
|
|
* http://plantuml.com/paypal
|
|
*
|
|
* This file is part of PlantUML.
|
|
*
|
|
* PlantUML is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* PlantUML distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
|
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
|
|
* License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
|
|
* USA.
|
|
*
|
|
*
|
|
* Original Author: Arnaud Roques
|
|
*
|
|
*
|
|
*/
|
|
package net.sourceforge.plantuml.cucadiagram.dot;
|
|
|
|
import java.io.File;
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.io.OutputStream;
|
|
import java.util.concurrent.locks.Lock;
|
|
import java.util.concurrent.locks.ReentrantLock;
|
|
|
|
import net.sourceforge.plantuml.OptionFlags;
|
|
import net.sourceforge.plantuml.api.MyRunnable;
|
|
import net.sourceforge.plantuml.api.TimeoutExecutor;
|
|
|
|
public class ProcessRunner {
|
|
|
|
private final String[] cmd;
|
|
|
|
private String error;
|
|
private String out;
|
|
|
|
private volatile ProcessState state = ProcessState.INIT();
|
|
private final Lock changeState = new ReentrantLock();
|
|
|
|
public ProcessRunner(String[] cmd) {
|
|
this.cmd = cmd;
|
|
}
|
|
|
|
public ProcessState run(byte in[], OutputStream redirection) {
|
|
return run(in, redirection, null);
|
|
}
|
|
|
|
public ProcessState run(byte in[], OutputStream redirection, File dir) {
|
|
if (this.state.differs(ProcessState.INIT())) {
|
|
throw new IllegalStateException();
|
|
}
|
|
this.state = ProcessState.RUNNING();
|
|
final MainThread mainThread = new MainThread(cmd, dir, redirection, in);
|
|
try {
|
|
// http://steveliles.github.io/invoking_processes_from_java.html
|
|
final long timeoutMs = OptionFlags.getInstance().getTimeoutMs();
|
|
final boolean done = new TimeoutExecutor(timeoutMs).executeNow(mainThread);
|
|
} finally {
|
|
changeState.lock();
|
|
try {
|
|
if (state.equals(ProcessState.RUNNING())) {
|
|
state = ProcessState.TIMEOUT();
|
|
// mainThread.cancel();
|
|
}
|
|
} finally {
|
|
changeState.unlock();
|
|
}
|
|
}
|
|
if (state.equals(ProcessState.TERMINATED_OK())) {
|
|
assert mainThread != null;
|
|
this.error = mainThread.getError();
|
|
this.out = mainThread.getOut();
|
|
}
|
|
return state;
|
|
}
|
|
|
|
class MainThread implements MyRunnable {
|
|
|
|
private final String[] cmd;
|
|
private final File dir;
|
|
private final OutputStream redirection;
|
|
private final byte[] in;
|
|
private volatile Process process;
|
|
private volatile ThreadStream errorStream;
|
|
private volatile ThreadStream outStream;
|
|
|
|
public MainThread(String[] cmd, File dir, OutputStream redirection, byte[] in) {
|
|
this.cmd = cmd;
|
|
this.dir = dir;
|
|
this.redirection = redirection;
|
|
this.in = in;
|
|
}
|
|
|
|
public String getOut() {
|
|
return outStream.getString();
|
|
}
|
|
|
|
public String getError() {
|
|
return errorStream.getString();
|
|
}
|
|
|
|
public void runJob() throws InterruptedException {
|
|
try {
|
|
startThreads();
|
|
if (state.equals(ProcessState.RUNNING())) {
|
|
final int result = joinInternal();
|
|
}
|
|
} finally {
|
|
changeState.lock();
|
|
try {
|
|
if (state.equals(ProcessState.RUNNING())) {
|
|
state = ProcessState.TERMINATED_OK();
|
|
}
|
|
} finally {
|
|
changeState.unlock();
|
|
}
|
|
if (process != null) {
|
|
process.destroy();
|
|
close(process.getErrorStream());
|
|
close(process.getOutputStream());
|
|
close(process.getInputStream());
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
public void cancelJob() {
|
|
// The changeState lock is ok
|
|
// assert changeState.tryLock();
|
|
// assert state == ProcessState.TIMEOUT;
|
|
if (process != null) {
|
|
errorStream.cancel();
|
|
outStream.cancel();
|
|
process.destroy();
|
|
// interrupt();
|
|
close(process.getErrorStream());
|
|
close(process.getOutputStream());
|
|
close(process.getInputStream());
|
|
}
|
|
}
|
|
|
|
private void startThreads() {
|
|
try {
|
|
process = Runtime.getRuntime().exec(cmd, null, dir);
|
|
} catch (IOException e) {
|
|
e.printStackTrace();
|
|
changeState.lock();
|
|
try {
|
|
state = ProcessState.IO_EXCEPTION1(e);
|
|
} finally {
|
|
changeState.unlock();
|
|
}
|
|
e.printStackTrace();
|
|
return;
|
|
}
|
|
errorStream = new ThreadStream(process.getErrorStream(), null);
|
|
outStream = new ThreadStream(process.getInputStream(), redirection);
|
|
errorStream.start();
|
|
outStream.start();
|
|
if (in != null) {
|
|
final OutputStream os = process.getOutputStream();
|
|
try {
|
|
try {
|
|
os.write(in);
|
|
} finally {
|
|
os.close();
|
|
}
|
|
} catch (IOException e) {
|
|
changeState.lock();
|
|
try {
|
|
state = ProcessState.IO_EXCEPTION2(e);
|
|
} finally {
|
|
changeState.unlock();
|
|
}
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
}
|
|
|
|
public int joinInternal() throws InterruptedException {
|
|
errorStream.join();
|
|
outStream.join();
|
|
final int result = process.waitFor();
|
|
return result;
|
|
}
|
|
|
|
}
|
|
|
|
class ThreadStream extends Thread {
|
|
|
|
private volatile InputStream streamToRead;
|
|
private volatile OutputStream redirection;
|
|
private volatile StringBuffer sb = new StringBuffer();
|
|
|
|
ThreadStream(InputStream streamToRead, OutputStream redirection) {
|
|
this.streamToRead = streamToRead;
|
|
this.redirection = redirection;
|
|
}
|
|
|
|
public String getString() {
|
|
if (sb == null) {
|
|
return "";
|
|
}
|
|
return sb.toString();
|
|
}
|
|
|
|
public void cancel() {
|
|
assert state.equals(ProcessState.TIMEOUT()) || state.equals(ProcessState.RUNNING()) : state;
|
|
this.interrupt();
|
|
sb = null;
|
|
streamToRead = null;
|
|
redirection = null;
|
|
// Because of this, some NPE may occurs in run() method, but we do not care
|
|
}
|
|
|
|
@Override
|
|
public void run() {
|
|
int read = 0;
|
|
try {
|
|
while ((read = streamToRead.read()) != -1) {
|
|
if (state.equals(ProcessState.TIMEOUT())) {
|
|
return;
|
|
}
|
|
if (redirection == null) {
|
|
sb.append((char) read);
|
|
} else {
|
|
redirection.write(read);
|
|
}
|
|
}
|
|
} catch (Throwable e) {
|
|
System.err.println("ProcessRunnerA " + e);
|
|
e.printStackTrace();
|
|
sb.append('\n');
|
|
sb.append(e.toString());
|
|
}
|
|
}
|
|
}
|
|
|
|
public final String getError() {
|
|
return error;
|
|
}
|
|
|
|
public final String getOut() {
|
|
return out;
|
|
}
|
|
|
|
private void close(InputStream is) {
|
|
try {
|
|
if (is != null) {
|
|
is.close();
|
|
}
|
|
} catch (IOException e) {
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
|
|
private void close(OutputStream os) {
|
|
try {
|
|
if (os != null) {
|
|
os.close();
|
|
}
|
|
} catch (IOException e) {
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
|
|
}
|