diff --git a/gclc-socket/src/main/java/fr/bigeon/gclc/socket/ConsoleRunnable.java b/gclc-socket/src/main/java/fr/bigeon/gclc/socket/ConsoleRunnable.java index 2a6d2ea..9ff565e 100644 --- a/gclc-socket/src/main/java/fr/bigeon/gclc/socket/ConsoleRunnable.java +++ b/gclc-socket/src/main/java/fr/bigeon/gclc/socket/ConsoleRunnable.java @@ -44,7 +44,7 @@ import fr.bigeon.gclc.ConsoleApplication; * * @author Emmanuel Bigeon */ public class ConsoleRunnable implements Runnable { - + /** The actual application */ private final ConsoleApplication app; /** The synchronization object */ @@ -74,4 +74,9 @@ public class ConsoleRunnable implements Runnable { app.exit(); } + /** @return if the application is running */ + public boolean isRunning() { + return app.isRunning(); + } + } diff --git a/gclc-socket/src/main/java/fr/bigeon/gclc/socket/SocketConsoleApplicationShell.java b/gclc-socket/src/main/java/fr/bigeon/gclc/socket/SocketConsoleApplicationShell.java index 5bd2577..f7ef0e3 100644 --- a/gclc-socket/src/main/java/fr/bigeon/gclc/socket/SocketConsoleApplicationShell.java +++ b/gclc-socket/src/main/java/fr/bigeon/gclc/socket/SocketConsoleApplicationShell.java @@ -110,7 +110,7 @@ public class SocketConsoleApplicationShell implements Runnable { /** The application shutdown string */ private final String applicationShutdown; /** The charset for the communication. */ - private Charset charset; + private final Charset charset; /** Create a socket application shell which will listen on the given port * and close session upon the provided string reception by client @@ -156,8 +156,7 @@ public class SocketConsoleApplicationShell implements Runnable { // Create the streams try (PipedOutputStream outStream = new PipedOutputStream(); BufferedWriter writer = new BufferedWriter( - new OutputStreamWriter(outStream, - charset)); + new OutputStreamWriter(outStream, charset)); InputStreamReader isr = new InputStreamReader(consoleInput, charset); BufferedReader inBuf = new BufferedReader(isr)) { @@ -187,24 +186,24 @@ public class SocketConsoleApplicationShell implements Runnable { Thread appThOld = null; Thread appThNext = new Thread(runnable); while (running) { + LOGGER.info("Opening client"); //$NON-NLS-1$ try (Socket clientSocket = serverSocket.accept(); - PrintWriter out = new PrintWriter( - new OutputStreamWriter(clientSocket.getOutputStream(), - charset), - true); + PrintWriter out = new PrintWriter(new OutputStreamWriter( + clientSocket.getOutputStream(), charset), true); InputStreamReader isr = new InputStreamReader( - clientSocket.getInputStream(), - charset); + clientSocket.getInputStream(), charset); BufferedReader in = new BufferedReader(isr);) { // this is not threaded to avoid several clients at the same // time consoleManager.setOutput(out); // Initiate application - if (appThOld == null || !appThOld.isAlive()) { + + if (appThOld == null || !appThOld.isAlive() || + !runnable.isRunning()) { appThNext.start(); // Prepare next start appThOld = appThNext; - appThNext = new Thread(runnable); + appThNext = new Thread(runnable, "gclc-ctrl"); //$NON-NLS-1$ } else { out.println("Reconnected"); //$NON-NLS-1$ } @@ -215,7 +214,9 @@ public class SocketConsoleApplicationShell implements Runnable { "Socket closed with exception (probably due to server interruption)", //$NON-NLS-1$ e); } + LOGGER.info("Closing client"); //$NON-NLS-1$ } + LOGGER.info("Out client"); //$NON-NLS-1$ } /** active communication between server and client diff --git a/gclc-socket/src/test/java/fr/bigeon/gclc/socket/SocketConsoleApplicationTest.java b/gclc-socket/src/test/java/fr/bigeon/gclc/socket/SocketConsoleApplicationTest.java index bb13883..4f66691 100644 --- a/gclc-socket/src/test/java/fr/bigeon/gclc/socket/SocketConsoleApplicationTest.java +++ b/gclc-socket/src/test/java/fr/bigeon/gclc/socket/SocketConsoleApplicationTest.java @@ -38,6 +38,9 @@ */ package fr.bigeon.gclc.socket; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; @@ -60,7 +63,8 @@ public class SocketConsoleApplicationTest { @Test public void integrationTest() { - Thread server = TestServer.startServer(false); + Thread server; + server = TestServer.startServer("bye"); try { Thread.sleep(100); } catch (InterruptedException e1) { @@ -70,27 +74,89 @@ public class SocketConsoleApplicationTest { final int portNumber = 3300; try (Socket kkSocket = new Socket(hostName, portNumber); - PrintWriter out = new PrintWriter(kkSocket.getOutputStream(), - true); - BufferedReader in = new BufferedReader( - new InputStreamReader(kkSocket.getInputStream()));) { + PrintWriter out = new PrintWriter(kkSocket.getOutputStream(), + true); + BufferedReader in = new BufferedReader( + new InputStreamReader(kkSocket.getInputStream()));) { String fromServer; int i = 0; - String[] cmds = {"help", "toto", "test", "close"}; + String[] cmds = {"help", "toto", "test", "bye"}; while ((fromServer = in.readLine()) != null) { - System.out.println("Server: \n" + ENCODER.decode(fromServer)); +// System.out.println("Server: \n" + ENCODER.decode(fromServer)); if (fromServer.equals("Bye.")) { break; } final String fromUser = cmds[i]; if (fromUser != null) { - System.out.println("Client: " + fromUser); +// System.out.println("Client: " + fromUser); out.println(fromUser); } i++; } + assertEquals(4, i); + } catch (final IOException e) { + e.printStackTrace(); + } + + try (Socket kkSocket = new Socket(hostName, portNumber); + PrintWriter out = new PrintWriter(kkSocket.getOutputStream(), + true); + BufferedReader in = new BufferedReader( + new InputStreamReader(kkSocket.getInputStream()));) { + + String fromServer; + int i = 0; + String[] cmds = {"help", "toto", "test", + ConsoleTestApplication.EXIT}; + while ((fromServer = in.readLine()) != null) { +// System.out.println("Server: \n" + ENCODER.decode(fromServer)); + if (fromServer.equals("Bye.")) { + break; + } + + final String fromUser = cmds[i]; + if (fromUser != null) { +// System.out.println("Client: " + fromUser); + out.println(fromUser); + } + i++; + } + assertEquals(4, i); + } catch (final IOException e) { + e.printStackTrace(); + } + try { + Thread.sleep(100); + } catch (InterruptedException e1) { + e1.printStackTrace(); + } + + try (Socket kkSocket = new Socket(hostName, portNumber); + PrintWriter out = new PrintWriter(kkSocket.getOutputStream(), + true); + BufferedReader in = new BufferedReader( + new InputStreamReader(kkSocket.getInputStream()));) { + + String fromServer; + int i = 0; + String[] cmds = {"help", "toto", "test", + ConsoleTestApplication.EXIT}; + while ((fromServer = in.readLine()) != null) { +// System.out.println("Server: \n" + ENCODER.decode(fromServer)); + if (fromServer.equals("Bye.")) { + break; + } + + final String fromUser = cmds[i]; + if (fromUser != null) { +// System.out.println("Client: " + fromUser); + out.println(fromUser); + } + i++; + } + assertEquals(4, i); } catch (final IOException e) { e.printStackTrace(); } @@ -108,31 +174,39 @@ public class SocketConsoleApplicationTest { } try (Socket kkSocket = new Socket(hostName, portNumber); - PrintWriter out = new PrintWriter(kkSocket.getOutputStream(), - true); - BufferedReader in = new BufferedReader( - new InputStreamReader(kkSocket.getInputStream()));) { + PrintWriter out = new PrintWriter(kkSocket.getOutputStream(), + true); + BufferedReader in = new BufferedReader( + new InputStreamReader(kkSocket.getInputStream()));) { String fromServer; int i = 0; String[] cmds = {"help", "test", "close"}; while ((fromServer = in.readLine()) != null) { -// System.out.println("Server: \n" + ENCODER.decode(fromServer)); + assertTrue(i < 2); + System.out.println("Server: \n" + ENCODER.decode(fromServer)); if (fromServer.equals("Bye.")) { break; } final String fromUser = cmds[i]; if (fromUser != null) { -// System.out.println("Client: " + fromUser); + System.out.println("Client: " + fromUser); out.println(fromUser); } i++; } + assertEquals(2, i); } catch (final IOException e) { e.printStackTrace(); } + Thread srv = TestServer.getServer(); TestServer.closeServer(); - + try { + srv.join(); + } catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } } } diff --git a/gclc-socket/src/test/java/fr/bigeon/gclc/socket/TestServer.java b/gclc-socket/src/test/java/fr/bigeon/gclc/socket/TestServer.java index 465767c..4a4b144 100644 --- a/gclc-socket/src/test/java/fr/bigeon/gclc/socket/TestServer.java +++ b/gclc-socket/src/test/java/fr/bigeon/gclc/socket/TestServer.java @@ -85,6 +85,18 @@ public class TestServer { return getServer(); } + public static Thread startServer(String closeConnection) { + if (SHELL == null) { + SHELL = new SocketConsoleApplicationShell(3300, closeConnection, + ConsoleTestApplication.EXIT, Charset.forName("UTF-8")); + final ConsoleTestApplication app = new ConsoleTestApplication( + SHELL.getConsoleManager()); + SHELL.setApplication(app); + server = null; + } + return getServer(); + } + public static void closeServer() { SHELL.stop(); SHELL = null; diff --git a/gclc-swt/src/main/java/fr/bigeon/gclc/swt/ConsoleDelayIO.java b/gclc-swt/src/main/java/fr/bigeon/gclc/swt/ConsoleDelayIO.java index abb7c08..51630e4 100644 --- a/gclc-swt/src/main/java/fr/bigeon/gclc/swt/ConsoleDelayIO.java +++ b/gclc-swt/src/main/java/fr/bigeon/gclc/swt/ConsoleDelayIO.java @@ -40,13 +40,13 @@ package fr.bigeon.gclc.swt; import fr.bigeon.gclc.manager.ConsoleManager; -/** +/** This class represents an object used to send commands to a console + * application. *

- * TODO + * The sending of command is done in two phases. Define the input through get + * and set, and then validate the input. * - * @author Emmanuel Bigeon - * - */ + * @author Emmanuel Bigeon */ public interface ConsoleDelayIO extends ConsoleManager { /** Actually send the input as the prompt next input. */ void validateInput(); diff --git a/gclc/src/main/java/fr/bigeon/gclc/ConsoleApplication.java b/gclc/src/main/java/fr/bigeon/gclc/ConsoleApplication.java index 78473b7..c720d3d 100644 --- a/gclc/src/main/java/fr/bigeon/gclc/ConsoleApplication.java +++ b/gclc/src/main/java/fr/bigeon/gclc/ConsoleApplication.java @@ -37,6 +37,7 @@ package fr.bigeon.gclc; import java.io.IOException; +import java.io.InterruptedIOException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -106,18 +107,13 @@ public class ConsoleApplication implements ICommandProvider { return root.add(cmd); } - /** Add a command request listener. - *

- * A listener can listen several times to the same application. - * - * @param listener the listener to add. */ + /** @param listener the command listener */ public final void addListener(CommandRequestListener listener) { listeners.add(listener); } /* (non-Javadoc) - * @see - * fr.bigeon.gclc.command.ICommandProvider#executeSub(java.lang.String, + * @see fr.bigeon.gclc.command.ICommandProvider#executeSub(java.lang.String, * java.lang.String[]) */ @Override public final void executeSub(String command, @@ -125,10 +121,11 @@ public class ConsoleApplication implements ICommandProvider { root.executeSub(command, args); } - /** Exit this running application before next command prompt */ + /** Signify to the application that no command should be inputed anymore */ public final void exit() { LOGGER.fine("Request exiting application..."); //$NON-NLS-1$ running = false; + manager.interruptPrompt(); } /* (non-Javadoc) @@ -143,8 +140,8 @@ public class ConsoleApplication implements ICommandProvider { return manager; } - /** @param cmd the command to interpret - * @throws IOException if the manager was closed */ + /** @param cmd the command + * @throws IOException if the command could not be parsed */ public final void interpretCommand(String cmd) throws IOException { List args; try { @@ -170,7 +167,7 @@ public class ConsoleApplication implements ICommandProvider { } } - /** @param listener the listener to remove (once) */ + /** @param listener the command listener to remove */ public final void removeListener(CommandRequestListener listener) { for (int i = 0; i < listeners.size(); i++) { if (listeners.get(i) == listener) { @@ -180,14 +177,23 @@ public class ConsoleApplication implements ICommandProvider { } } - /** Launches the prompting application */ + /** Start the application */ public final void start() { try { running = true; if (header != null) { manager.println(header); } - do { + } catch (IOException e) { + // The manager was closed + running = false; + LOGGER.log(Level.WARNING, + "The console manager was closed. Closing the application as no one can reach it.", //$NON-NLS-1$ + e); + return; + } + do { + try { final String cmd = manager.prompt(); if (cmd == null || cmd.isEmpty()) { continue; @@ -196,22 +202,34 @@ public class ConsoleApplication implements ICommandProvider { listener.commandRequest(cmd); } interpretCommand(cmd); - } while (running); - if (footer != null) { - manager.println(footer); + } catch (InterruptedIOException e) { + LOGGER.log(Level.INFO, + "Prompt interrupted. It is likely the application is closing.", //$NON-NLS-1$ + e); + } catch (IOException e) { + // The manager was closed + running = false; + LOGGER.log(Level.WARNING, + "The console manager was closed. Closing the application as no one can reach it.", //$NON-NLS-1$ + e); + } + } while (running); + if (footer != null) { + try { + manager.println(footer); + } catch (IOException e) { + // The manager was closed + running = false; + LOGGER.log(Level.WARNING, + "The console manager was closed.", //$NON-NLS-1$ + e); } - running = false; - LOGGER.fine("Exiting application."); //$NON-NLS-1$ - } catch (IOException e) { - // The manager was closed - running = false; - LOGGER.log(Level.WARNING, - "The console manager was closed. Closing the application as no one can reach it.", //$NON-NLS-1$ - e); } + running = false; + LOGGER.fine("Exiting application."); //$NON-NLS-1$ } - /** @return if the application is running */ + /** @return the running status */ public boolean isRunning() { return running; } diff --git a/gclc/src/main/java/fr/bigeon/gclc/command/ParametrizedCommand.java b/gclc/src/main/java/fr/bigeon/gclc/command/ParametrizedCommand.java index 5186f06..4312dd1 100644 --- a/gclc/src/main/java/fr/bigeon/gclc/command/ParametrizedCommand.java +++ b/gclc/src/main/java/fr/bigeon/gclc/command/ParametrizedCommand.java @@ -154,8 +154,9 @@ public abstract class ParametrizedCommand extends Command { } } - /** @param parameters the command parameters */ - protected abstract void doExecute(CommandParameters parameters); + /** @param parameters the command parameters + * @throws CommandRunException if the command failed */ + protected abstract void doExecute(CommandParameters parameters) throws CommandRunException; /* (non-Javadoc) * @see fr.bigeon.gclc.command.Command#execute(java.lang.String[]) */ diff --git a/gclc/src/main/java/fr/bigeon/gclc/manager/ConsoleManager.java b/gclc/src/main/java/fr/bigeon/gclc/manager/ConsoleManager.java index b3d9c14..154d1dc 100644 --- a/gclc/src/main/java/fr/bigeon/gclc/manager/ConsoleManager.java +++ b/gclc/src/main/java/fr/bigeon/gclc/manager/ConsoleManager.java @@ -39,6 +39,7 @@ package fr.bigeon.gclc.manager; import java.io.IOException; +import java.io.InterruptedIOException; /**

* A console manager is in charge of the basic prompts and prints on a console. @@ -69,15 +70,15 @@ public interface ConsoleManager { void println(String message) throws IOException; /** @return the user inputed string - * @throws IOException if the manager is closed or could not read the - * prompt */ - String prompt() throws IOException; + * @throws IOException if the manager is closed or could not read the prompt + * @throws InterruptedIOException if the prompt was interrupted */ + String prompt() throws IOException, InterruptedIOException; /** @param message the message to prompt the user * @return the user inputed string - * @throws IOException if the manager is closed or could not read the - * prompt */ - String prompt(String message) throws IOException; + * @throws IOException if the manager is closed or could not read the prompt + * @throws InterruptedIOException if the prompt was interrupted */ + String prompt(String message) throws IOException, InterruptedIOException; /**

* Set a prompting prefix. @@ -92,4 +93,7 @@ public interface ConsoleManager { /** @return if the manager is closed. */ boolean isClosed(); + + /** Indicate to the manager that is should interrompt the prompting */ + void interruptPrompt(); } diff --git a/gclc/src/main/java/fr/bigeon/gclc/manager/ReadRunnable.java b/gclc/src/main/java/fr/bigeon/gclc/manager/ReadRunnable.java new file mode 100644 index 0000000..8515de3 --- /dev/null +++ b/gclc/src/main/java/fr/bigeon/gclc/manager/ReadRunnable.java @@ -0,0 +1,54 @@ +/** + * gclc:fr.bigeon.gclc.manager.ReadRunnable.java + * Created on: Nov 21, 2016 + */ +package fr.bigeon.gclc.manager; + +import java.io.BufferedReader; +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + *

+ * TODO + * + * @author Emmanuel Bigeon + * + */ +public class ReadRunnable implements Runnable { + + /** The logger */ + private static final Logger LOGGER = Logger + .getLogger(ReadRunnable.class.getName()); + /** The result */ + private String result = ""; + /** The input buffer */ + private final BufferedReader in; + + /** @param in the input buffer */ + protected ReadRunnable(BufferedReader in) { + super(); + this.in = in; + } + + /* (non-Javadoc) + * @see java.lang.Runnable#run() */ + @Override + public void run() { + try { + result = in.readLine(); + while (result != null && result.length() > 0 && + result.charAt(0) == 0) { + result = result.substring(1); + } + } catch (final IOException e) { + LOGGER.log(Level.SEVERE, "Unable to read prompt", e); //$NON-NLS-1$ + } + } + + /** @return the result */ + public String getResult() { + return result; + } +} diff --git a/gclc/src/main/java/fr/bigeon/gclc/manager/SystemConsoleManager.java b/gclc/src/main/java/fr/bigeon/gclc/manager/SystemConsoleManager.java index 0151454..b785a1f 100644 --- a/gclc/src/main/java/fr/bigeon/gclc/manager/SystemConsoleManager.java +++ b/gclc/src/main/java/fr/bigeon/gclc/manager/SystemConsoleManager.java @@ -44,7 +44,6 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.io.PrintStream; import java.nio.charset.Charset; -import java.util.logging.Level; import java.util.logging.Logger; /** A console using the input stream and print stream. @@ -75,6 +74,8 @@ public class SystemConsoleManager implements ConsoleManager { // NOSONAR /** If the manager is closed */ private boolean closed = false; + private Thread reading; + /** This default constructor relies on the system defined standart output * and input stream. */ public SystemConsoleManager() { @@ -140,18 +141,24 @@ public class SystemConsoleManager implements ConsoleManager { // NOSONAR @Override public String prompt(String message) throws IOException { checkOpen(); - String result = EMPTY; out.print(message); - try { +// ReadRunnable rr = new ReadRunnable(in); +// reading = new Thread(rr, "prompt"); //$NON-NLS-1$ +// reading.start(); +// try { +// reading.join(); +// } catch (final InterruptedException e) { +// LOGGER.log(Level.SEVERE, "Prompt reading interrupted", e); //$NON-NLS-1$ +// throw new InterruptedIOException("Prompt interruption"); //$NON-NLS-1$ +// } + String result = EMPTY; result = in.readLine(); while (result != null && result.length() > 0 && result.charAt(0) == 0) { result = result.substring(1); } - } catch (final IOException e) { - LOGGER.log(Level.SEVERE, "Unable to read prompt", e); //$NON-NLS-1$ - } return result; +// return rr.getResult(); } /** @param prompt the prompt to set */ @@ -174,4 +181,13 @@ public class SystemConsoleManager implements ConsoleManager { // NOSONAR return closed; } + /* (non-Javadoc) + * @see fr.bigeon.gclc.manager.ConsoleManager#interruptPrompt() */ + @Override + public void interruptPrompt() { + if (reading != null) { + reading.interrupt(); + } + } + } diff --git a/gclc/src/test/java/fr/bigeon/gclc/test/utils/TestConsoleManager.java b/gclc/src/test/java/fr/bigeon/gclc/test/utils/TestConsoleManager.java index 29f23bb..e050b83 100644 --- a/gclc/src/test/java/fr/bigeon/gclc/test/utils/TestConsoleManager.java +++ b/gclc/src/test/java/fr/bigeon/gclc/test/utils/TestConsoleManager.java @@ -74,7 +74,7 @@ public class TestConsoleManager implements ConsoleManager, AutoCloseable { PipedOutputStream out = new PipedOutputStream(commandOutput); commandBuffOutput = new BufferedReader( new InputStreamReader(commandOutput, Charset.defaultCharset())); - outPrint = new PrintStream(out); + outPrint = new PrintStream(out, true, Charset.defaultCharset().name()); innerManager = new SystemConsoleManager(outPrint, in, Charset.forName("UTF-8")); } @@ -141,4 +141,11 @@ public class TestConsoleManager implements ConsoleManager, AutoCloseable { public String readNextLine() throws IOException { return commandBuffOutput.readLine(); } + + /* (non-Javadoc) + * @see fr.bigeon.gclc.manager.ConsoleManager#interruptPrompt() */ + @Override + public void interruptPrompt() { + innerManager.interruptPrompt(); + } }