From 1c7e4d35452285974a7a4dd499b977979b2a256d Mon Sep 17 00:00:00 2001 From: DmitriyMX Date: Thu, 15 Jun 2017 17:49:37 +0300 Subject: [PATCH] =?UTF-8?q?Zond:=20=D0=B7=D0=B0=D0=BF=D1=83=D1=81=D0=BA=20?= =?UTF-8?q?=D0=B2=D0=BD=D0=B5=D1=88=D0=BD=D0=B8=D1=85=20=D0=BF=D1=80=D0=BE?= =?UTF-8?q?=D0=B3=D1=80=D0=B0=D0=BC=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- zond/build.gradle | 11 ++ zond/src/main/java/asys/zond/Main.java | 31 +++- .../java/asys/zond/ZondCommandHandler.java | 39 +++++ .../java/asys/zond/shell/CommandLooper.java | 4 + .../java/asys/zond/shell/ShellStdOut.java | 8 + .../org/apache/commons/exec/StreamPumper.java | 152 ++++++++++++++++++ 6 files changed, 242 insertions(+), 3 deletions(-) create mode 100644 zond/src/main/java/org/apache/commons/exec/StreamPumper.java diff --git a/zond/build.gradle b/zond/build.gradle index 4f37fa8..eba758b 100644 --- a/zond/build.gradle +++ b/zond/build.gradle @@ -7,11 +7,20 @@ mainClassName = "asys.zond.Main" configurations { included + includedEx compile.extendsFrom included + compile.extendsFrom includedEx +} + +def zp(FileTree ft) { + return ft.matching { + exclude 'org/apache/commons/exec/StreamPumper.class' + } } jar { dependsOn configurations.included + dependsOn configurations.includedEx manifest { attributes 'Implementation-Title': 'ASys Zond', 'Implementation-Version': version, @@ -19,8 +28,10 @@ jar { } baseName = project.group + '.' + project.name from { configurations.included.collect { it.isDirectory() ? it : zipTree(it) } } + from { configurations.includedEx.collect { it.isDirectory() ? it : zp(zipTree(it)) } } } dependencies { included group: 'jline', name: 'jline', version: '2.14.3' + includedEx group: 'org.apache.commons', name: 'commons-exec', version: '1.3' } diff --git a/zond/src/main/java/asys/zond/Main.java b/zond/src/main/java/asys/zond/Main.java index f5cc9af..d9247b8 100644 --- a/zond/src/main/java/asys/zond/Main.java +++ b/zond/src/main/java/asys/zond/Main.java @@ -6,21 +6,46 @@ package asys.zond; import asys.zond.shell.Shell; +import org.apache.commons.exec.CommandLine; +import org.apache.commons.exec.DefaultExecutor; +import org.apache.commons.exec.Executor; +import org.apache.commons.exec.PumpStreamHandler; import java.io.IOException; +import java.io.PrintStream; +import java.util.Arrays; +import java.util.stream.Collectors; public class Main { + private Executor executor; + private CommandLine commandLine; + public static void main(String[] args) throws IOException { - new Main().start(); + new Main().start(args); } - private void start() { + private void start(String[] args) { + ZondCommandHandler commandHandler = new ZondCommandHandler(); + startShell(commandHandler); + initExecCommand(args, Shell.getInstance().getOutput()); + commandHandler.setExecutor(executor, commandLine); + } + + private void startShell(ZondCommandHandler commandHandler) { Shell shell = Shell.getInstance(); try { - shell.setCommandHandler(new ZondCommandHandler()); + shell.setCommandHandler(commandHandler); shell.start(System.in); } catch (Exception e) { e.printStackTrace(); } } + + private void initExecCommand(String[] args, PrintStream stdout) { + String cmdLine = Arrays.stream(args).collect(Collectors.joining(" ")); + commandLine = CommandLine.parse(cmdLine); + executor = new DefaultExecutor(); + PumpStreamHandler pumpStreamHandler = new PumpStreamHandler(stdout); + executor.setStreamHandler(pumpStreamHandler); + } } \ No newline at end of file diff --git a/zond/src/main/java/asys/zond/ZondCommandHandler.java b/zond/src/main/java/asys/zond/ZondCommandHandler.java index 11b47df..5d905dd 100644 --- a/zond/src/main/java/asys/zond/ZondCommandHandler.java +++ b/zond/src/main/java/asys/zond/ZondCommandHandler.java @@ -6,8 +6,19 @@ package asys.zond; import asys.zond.shell.CommandHandler; import asys.zond.shell.Shell; +import org.apache.commons.exec.CommandLine; +import org.apache.commons.exec.ExecuteException; +import org.apache.commons.exec.ExecuteWatchdog; +import org.apache.commons.exec.Executor; + +import java.io.IOException; public class ZondCommandHandler implements CommandHandler { + private Executor executor; + private CommandLine commandLine; + private ExecuteWatchdog watchdog; + private Thread threadExec; + @Override public void handle(String commandLine) { if (commandLine.startsWith(":")) { @@ -19,7 +30,35 @@ public class ZondCommandHandler implements CommandHandler { private void internalCommand(String line) { if (line.equalsIgnoreCase("exit") || line.equalsIgnoreCase("quit")) { + if (watchdog != null) { + watchdog.destroyProcess(); + threadExec.interrupt(); + } Shell.getInstance().shutdown(); + } else if (line.equalsIgnoreCase("start")) { + if (watchdog == null) { + watchdog = new ExecuteWatchdog(ExecuteWatchdog.INFINITE_TIMEOUT); + executor.setWatchdog(watchdog); + Runnable task = () -> { + int code = 0; + try { + code = executor.execute(commandLine); + } catch (ExecuteException ignore) { + } catch (IOException e) { + Shell.getInstance().getOutput().println("Exception message: " + e.getMessage()); + code = -99; + } + watchdog = null; + Shell.getInstance().getOutput().println("Process finished. Code: " + code); + }; + threadExec = new Thread(task, "Zond Exec"); + threadExec.start(); + } } } + + void setExecutor(Executor executor, CommandLine commandLine) { + this.executor = executor; + this.commandLine = commandLine; + } } diff --git a/zond/src/main/java/asys/zond/shell/CommandLooper.java b/zond/src/main/java/asys/zond/shell/CommandLooper.java index 0460312..681577e 100644 --- a/zond/src/main/java/asys/zond/shell/CommandLooper.java +++ b/zond/src/main/java/asys/zond/shell/CommandLooper.java @@ -11,10 +11,12 @@ import java.io.IOException; public class CommandLooper implements Runnable { private ConsoleReader consoleReader; private CommandHandler commandHandler; + private ShellStdOut output; CommandLooper(ConsoleReader consoleReader, CommandHandler commandHandler) { this.consoleReader = consoleReader; this.commandHandler = commandHandler; + this.output = (ShellStdOut) Shell.getInstance().getOutput(); } @Override @@ -22,7 +24,9 @@ public class CommandLooper implements Runnable { while (!Thread.currentThread().isInterrupted()) { String line; try { + output.needDrawLine = true; line = consoleReader.readLine(); + output.needDrawLine = false; } catch (IOException e) { break; } diff --git a/zond/src/main/java/asys/zond/shell/ShellStdOut.java b/zond/src/main/java/asys/zond/shell/ShellStdOut.java index 6eac1c3..7205e2c 100644 --- a/zond/src/main/java/asys/zond/shell/ShellStdOut.java +++ b/zond/src/main/java/asys/zond/shell/ShellStdOut.java @@ -13,6 +13,7 @@ public class ShellStdOut extends PrintStream { private ConsoleReader consoleReader; private PrintWriter writer; private ShellOutputHook shellOutputHook; + boolean needDrawLine = false; ShellStdOut() { super(System.out, true); @@ -38,6 +39,12 @@ public class ShellStdOut extends PrintStream { writer.print(line); cleanTrashLine(line); writer.println(); + if (needDrawLine) { + try { + consoleReader.drawLine(); + } catch (Throwable ignore) { + } + } writer.flush(); } @@ -60,6 +67,7 @@ public class ShellStdOut extends PrintStream { @Override public void write(byte[] buf, int off, int len) { if ((char)buf[len-1] == '\n') len--; + if ((char)buf[len-1] == '\r') len--; _print(new String(buf, off, len)); } diff --git a/zond/src/main/java/org/apache/commons/exec/StreamPumper.java b/zond/src/main/java/org/apache/commons/exec/StreamPumper.java new file mode 100644 index 0000000..b50bc13 --- /dev/null +++ b/zond/src/main/java/org/apache/commons/exec/StreamPumper.java @@ -0,0 +1,152 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.commons.exec; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import org.apache.commons.exec.util.DebugUtils; + +/** + * Copies all data from an input stream to an output stream. + * + * @version $Id: StreamPumper.java 1557263 2014-01-10 21:18:09Z ggregory $ + */ +public class StreamPumper implements Runnable { + + /** the default size of the internal buffer for copying the streams */ + private static final int DEFAULT_SIZE = 1024; + + /** the input stream to pump from */ + private final InputStream is; + + /** the output stream to pmp into */ + private final OutputStream os; + + /** the size of the internal buffer for copying the streams */ + private final int size; + + /** was the end of the stream reached */ + private boolean finished; + + /** close the output stream when exhausted */ + private final boolean closeWhenExhausted; + + /** + * Create a new stream pumper. + * + * @param is input stream to read data from + * @param os output stream to write data to. + * @param closeWhenExhausted if true, the output stream will be closed when the input is exhausted. + */ + public StreamPumper(final InputStream is, final OutputStream os, + final boolean closeWhenExhausted) { + this.is = is; + this.os = os; + this.size = DEFAULT_SIZE; + this.closeWhenExhausted = closeWhenExhausted; + } + + /** + * Create a new stream pumper. + * + * @param is input stream to read data from + * @param os output stream to write data to. + * @param closeWhenExhausted if true, the output stream will be closed when the input is exhausted. + * @param size the size of the internal buffer for copying the streams + */ + public StreamPumper(final InputStream is, final OutputStream os, + final boolean closeWhenExhausted, final int size) { + this.is = is; + this.os = os; + this.size = size > 0 ? size : DEFAULT_SIZE; + this.closeWhenExhausted = closeWhenExhausted; + } + + /** + * Create a new stream pumper. + * + * @param is input stream to read data from + * @param os output stream to write data to. + */ + public StreamPumper(final InputStream is, final OutputStream os) { + this(is, os, false); + } + + /** + * Copies data from the input stream to the output stream. Terminates as + * soon as the input stream is closed or an error occurs. + */ + public void run() { + synchronized (this) { + // Just in case this object is reused in the future + finished = false; + } + + final byte[] buf = new byte[this.size]; + + int length; + try { + //hack: пропатчили алгоритм + while (!Thread.currentThread().isInterrupted() && (length = is.read(buf)) > 0) { + os.write(buf, 0, length); + os.flush(); + } + } catch (final Exception e) { + // nothing to do - happens quite often with watchdog + } finally { + if (closeWhenExhausted) { + try { + os.close(); + } catch (final IOException e) { + final String msg = "Got exception while closing exhausted output stream"; + DebugUtils.handleException(msg ,e); + } + } + synchronized (this) { + finished = true; + notifyAll(); + } + } + } + + /** + * Tells whether the end of the stream has been reached. + * + * @return true is the stream has been exhausted. + */ + public synchronized boolean isFinished() { + return finished; + } + + /** + * This method blocks until the stream pumper finishes. + * + * @exception InterruptedException + * if any thread interrupted the current thread before or while the current thread was waiting for a + * notification. + * @see #isFinished() + */ + public synchronized void waitFor() throws InterruptedException { + while (!isFinished()) { + wait(); + } + } +}