From 87a668d30876ca9dae9aaea874ef23838d12a215 Mon Sep 17 00:00:00 2001 From: Emmanuel Bigeon Date: Tue, 24 May 2016 14:55:45 -0400 Subject: [PATCH] Adding swt and socket frontends --- gclc-socket/.gitignore | 4 + gclc-socket/pom.xml | 105 +++++++ .../socket/SocketConsoleApplicationShell.java | 278 +++++++++++++++++ .../gclc/socket/ConsoleTestApplication.java | 74 +++++ .../bigeon/gclc/socket/TestConsoleClient.java | 95 ++++++ .../fr/bigeon/gclc/socket/TestServer.java | 59 ++++ gclc-swt/.gitignore | 4 + gclc-swt/pom.xml | 70 +++++ .../java/fr/bigeon/gclc/swt/SWTConsole.java | 292 ++++++++++++++++++ .../fr/bigeon/gclc/swt/SWTConsoleShell.java | 91 ++++++ .../test/java/fr/bigeon/gclc/swt/AppTest.java | 102 ++++++ gclc/pom.xml | 2 +- .../java/fr/bigeon/gclc/command/Command.java | 58 +++- .../fr/bigeon/gclc/command/HelpExecutor.java | 11 +- .../fr/bigeon/gclc/prompt/CLIPrompter.java | 93 +++++- 15 files changed, 1320 insertions(+), 18 deletions(-) create mode 100644 gclc-socket/.gitignore create mode 100644 gclc-socket/pom.xml create mode 100644 gclc-socket/src/main/java/fr/bigeon/gclc/socket/SocketConsoleApplicationShell.java create mode 100644 gclc-socket/src/test/java/fr/bigeon/gclc/socket/ConsoleTestApplication.java create mode 100644 gclc-socket/src/test/java/fr/bigeon/gclc/socket/TestConsoleClient.java create mode 100644 gclc-socket/src/test/java/fr/bigeon/gclc/socket/TestServer.java create mode 100644 gclc-swt/.gitignore create mode 100644 gclc-swt/pom.xml create mode 100644 gclc-swt/src/main/java/fr/bigeon/gclc/swt/SWTConsole.java create mode 100644 gclc-swt/src/main/java/fr/bigeon/gclc/swt/SWTConsoleShell.java create mode 100644 gclc-swt/src/test/java/fr/bigeon/gclc/swt/AppTest.java diff --git a/gclc-socket/.gitignore b/gclc-socket/.gitignore new file mode 100644 index 0000000..8bd3a05 --- /dev/null +++ b/gclc-socket/.gitignore @@ -0,0 +1,4 @@ +/target/ +/.settings/ +/.classpath +/.project diff --git a/gclc-socket/pom.xml b/gclc-socket/pom.xml new file mode 100644 index 0000000..23420ed --- /dev/null +++ b/gclc-socket/pom.xml @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 4.0.0 + gclc-socket + 1.0.2-SNAPSHOT + jar + http://www.bigeon.fr/emmanuel + + UTF-8 + + + + junit + junit + 4.11 + test + + + fr.bigeon + gclc + 1.1.1 + + + fr.bigeon + smu + 0.0.4 + + + + fr.bigeon + ebigeon-config + 1.6.0 + + GCLC Socket + Socket implementation of GCLC + 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 new file mode 100644 index 0000000..c20fedc --- /dev/null +++ b/gclc-socket/src/main/java/fr/bigeon/gclc/socket/SocketConsoleApplicationShell.java @@ -0,0 +1,278 @@ +/* + * Copyright E. Bigeon (2014) + * + * emmanuel@bigeon.fr + * + * This software is a computer program whose purpose is to + * Socket implementation of GCLC. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. + */ +package fr.bigeon.gclc.socket; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.io.PipedInputStream; +import java.io.PipedOutputStream; +import java.io.PrintWriter; +import java.net.ServerSocket; +import java.net.Socket; +import java.util.Arrays; + +import fr.bigeon.gclc.ConsoleApplication; +import fr.bigeon.gclc.ConsoleManager; +import fr.bigeon.smu.StringEncoder; + +/** This is a socket communicating console consoleManager + *

+ * To use this application, the following flow should be used: + * + *

+ * SocketConsoleApplicationShell shell = new SocketConsoleApplicationShell();
+ * ConsoleApplication myApplication = new MyConsoleApplication(shell.getConsoleManager(), ...);
+ * shell.setApplication(myApplication);
+ * Thread th = new Thread(shell);
+ * th.start();
+ * 
+ *

+ * This will start the application in a separate thread. The application will be + * listening on the given socket and writing back on it. If this is all your + * application, you should then {@link Thread#join()} the thread to wait for the + * end of the execution. + * + * @author Emmanuel Bigeon */ +public class SocketConsoleApplicationShell implements Runnable { + + /** The end of line character */ + protected static final String EOL = "\n"; //$NON-NLS-1$ + /** The encoder */ + private static final StringEncoder ENCODER = new StringEncoder("%", Arrays.asList(EOL)); //$NON-NLS-1$ + /** The listening port */ + private final int port; + /** The output */ + private PrintWriter output; + /** The input reader */ + private BufferedReader input; + /** The input */ + private final PipedInputStream consoleInput = new PipedInputStream(); + /** The application */ + private ConsoleApplication app; + /** The session closing command */ + private final String close; + /** The running status */ + private boolean running; + /** If the prompt should be next activity */ + private boolean doPrompt = false; + /** An object to lock on for prompt */ + private final Object promptingLock = new Object(); + + /** The console manager implementation */ + private final ConsoleManager consoleManager = new ConsoleManager() { + + private String prompt = new String(); + private StringBuffer buffer = new StringBuffer(); + + @Override + public void setPrompt(String prompt) { + this.prompt = prompt; + } + + @SuppressWarnings("synthetic-access") + @Override + public String prompt(String message) { + buffer.append(message); + String userInput = new String(); + boolean prompting = true; + while (prompting) { + output.println(ENCODER.encode(buffer.toString())); + try { + synchronized (promptingLock) { + doPrompt = true; + promptingLock.notify(); + } + userInput = input.readLine(); + doPrompt = false; + prompting = false; + } catch (final IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + buffer = new StringBuffer(); + return userInput; + } + + @Override + public String prompt() { + return prompt(prompt); + } + + @Override + public void println(String message) { + buffer.append(message + EOL); + + } + + @Override + public void println() { + buffer.append(EOL); + } + + @Override + public void print(String text) { + buffer.append(text); + } + + @Override + public String getPrompt() { + return prompt; + } + }; + /** The auto close flag. if this is true, every request closes the session + * after its call */ + private final boolean autoClose; + + /** The runnable class that will actually have the application running. + * + * @author Emmanuel Bigeon */ + private class ConsoleRunnable implements Runnable { + + /** + * + */ + public ConsoleRunnable() { + // TODO Auto-generated constructor stub + } + + /* (non-Javadoc) + * @see java.lang.Runnable#run() */ + @SuppressWarnings("synthetic-access") + @Override + public void run() { + running = true; + app.start(); + running = false; + synchronized (promptingLock) { + promptingLock.notifyAll(); + } + } + + } + + /** TODO Describe SocketConsoleApplicationShell.java Constructor + * + * @param port the port to listen to + * @param close the session closing command */ + public SocketConsoleApplicationShell(int port, String close) { + this.port = port; + this.close = close; + this.autoClose = false; + } + + /** TODO Describe SocketConsoleApplicationShell.java Constructor + * + * @param port the port to listen to + * @param autoClose if the session must be closed once the request has been + * sent */ + public SocketConsoleApplicationShell(int port, boolean autoClose) { + this.port = port; + this.close = null; + this.autoClose = autoClose; + } + + /* (non-Javadoc) + * @see java.lang.Runnable#run() */ + @Override + public void run() { + try (ServerSocket serverSocket = new ServerSocket(port)) { + final ConsoleRunnable runnable = new ConsoleRunnable(); + final Thread appTh = new Thread(runnable); + running = true; + try (PipedOutputStream outStream = new PipedOutputStream(); + BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outStream))) { + consoleInput.connect(outStream); + try (BufferedReader inBuf = new BufferedReader(new InputStreamReader(consoleInput))) { + input = inBuf; + while (running) { + try (Socket clientSocket = serverSocket.accept(); + PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true); + BufferedReader in = new BufferedReader( + new InputStreamReader(clientSocket.getInputStream()));) { + output = out; + // Initiate application + if (!appTh.isAlive()) { + appTh.start(); + } else { + output.println("Reconnected"); //$NON-NLS-1$ + } + synchronized (promptingLock) { + String ln; + if (!doPrompt) try { + promptingLock.wait(); + } catch (final InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + while (running && (ln = in.readLine()) != null) { + if (ln.equals(close)) { + break; + } + writer.write(ln + EOL); + writer.flush(); + try { + promptingLock.wait(); + } catch (final InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + if (autoClose) running = false; + } + } + } + } + } + } + } catch (final IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + /** @return the consoleManager */ + public synchronized ConsoleManager getConsoleManager() { + return consoleManager; + } + + /** @param app the application to set */ + public synchronized void setApplication(ConsoleApplication app) { + this.app = app; + } + +} diff --git a/gclc-socket/src/test/java/fr/bigeon/gclc/socket/ConsoleTestApplication.java b/gclc-socket/src/test/java/fr/bigeon/gclc/socket/ConsoleTestApplication.java new file mode 100644 index 0000000..a662c71 --- /dev/null +++ b/gclc-socket/src/test/java/fr/bigeon/gclc/socket/ConsoleTestApplication.java @@ -0,0 +1,74 @@ +/* + * Copyright E. Bigeon (2014) + * + * emmanuel@bigeon.fr + * + * This software is a computer program whose purpose is to + * Socket implementation of GCLC. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. + */ +package fr.bigeon.gclc.socket; + +import fr.bigeon.gclc.ConsoleApplication; +import fr.bigeon.gclc.ConsoleManager; +import fr.bigeon.gclc.command.Command; +import fr.bigeon.gclc.exception.InvalidCommandName; + +/** TODO Describe ConsoleTestApplication.java + * @author Emmanuel Bigeon + * + */ +public class ConsoleTestApplication extends ConsoleApplication { + + /** @param manager the manager */ + @SuppressWarnings("nls") + public ConsoleTestApplication(final ConsoleManager manager) { + super(manager, "exit", + "Welcome to the test application. Type help or test.", + "See you"); + addHelpCommand("help"); + try { + add(new Command("test") { + + @Override + public String tip() { + return "A test command"; + } + + @Override + public void execute(String... args) { + manager.println("Test command ran fine"); + } + }); + } catch (final InvalidCommandName e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + +} diff --git a/gclc-socket/src/test/java/fr/bigeon/gclc/socket/TestConsoleClient.java b/gclc-socket/src/test/java/fr/bigeon/gclc/socket/TestConsoleClient.java new file mode 100644 index 0000000..4119e7b --- /dev/null +++ b/gclc-socket/src/test/java/fr/bigeon/gclc/socket/TestConsoleClient.java @@ -0,0 +1,95 @@ +/* + * Copyright E. Bigeon (2014) + * + * emmanuel@bigeon.fr + * + * This software is a computer program whose purpose is to + * Socket implementation of GCLC. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. + */ +package fr.bigeon.gclc.socket; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.PrintWriter; +import java.net.Socket; +import java.util.ArrayList; +import java.util.Collection; + +import fr.bigeon.smu.StringEncoder; + +/** TODO Describe TestConsoleClient.java + * @author Emmanuel Bigeon + * + */ +@SuppressWarnings("nls") +public class TestConsoleClient { + @SuppressWarnings("javadoc") + private static final Collection TO_ENCODE = new ArrayList<>(); + static { + TO_ENCODE.add("\n"); + } + + @SuppressWarnings("javadoc") + private static final StringEncoder ENCODER = new StringEncoder("%", + TO_ENCODE); + + @SuppressWarnings("javadoc") + public static void main(String[] args) { + final String hostName = "127.0.0.1"; + 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()));) { + + String fromServer; + while ((fromServer = in.readLine()) != null) { + System.out.println("Server: \n" + ENCODER.decode(fromServer)); + if (fromServer.equals("Bye.")) { + break; + } + + final BufferedReader stdIn = new BufferedReader( + new InputStreamReader(System.in)); + final String fromUser = stdIn.readLine(); + if (fromUser != null) { + System.out.println("Client: " + fromUser); + out.println(fromUser); + } + } + } catch (final IOException 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 new file mode 100644 index 0000000..bff12b1 --- /dev/null +++ b/gclc-socket/src/test/java/fr/bigeon/gclc/socket/TestServer.java @@ -0,0 +1,59 @@ +/* + * Copyright E. Bigeon (2014) + * + * emmanuel@bigeon.fr + * + * This software is a computer program whose purpose is to + * Socket implementation of GCLC. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. + */ +package fr.bigeon.gclc.socket; + +/** TODO Describe TestServer.java + * @author Emmanuel Bigeon + * + */ +public class TestServer { + /** @param args no argument */ + @SuppressWarnings("nls") + public static void main(String[] args) { + final SocketConsoleApplicationShell shell = new SocketConsoleApplicationShell( + 3300, "close"); + final ConsoleTestApplication app = new ConsoleTestApplication( + shell.getConsoleManager()); + shell.setApplication(app); + final Thread serverTh = new Thread(shell, "gclcServer"); + serverTh.start(); + try { + serverTh.join(); + } catch (final InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } +} diff --git a/gclc-swt/.gitignore b/gclc-swt/.gitignore new file mode 100644 index 0000000..8bd3a05 --- /dev/null +++ b/gclc-swt/.gitignore @@ -0,0 +1,4 @@ +/target/ +/.settings/ +/.classpath +/.project diff --git a/gclc-swt/pom.xml b/gclc-swt/pom.xml new file mode 100644 index 0000000..c800141 --- /dev/null +++ b/gclc-swt/pom.xml @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 4.0.0 + gclc-swt + 1.0.0-SNAPSHOT + jar + http://www.bigeon.fr/emmanuel + + UTF-8 + + + fr.bigeon + ebigeon-config + 1.6.0 + + + + fr.bigeon + gclc + 1.1.1 + + + org.eclipse.swt.gtk.linux + x86_64 + 3.3.0-v3346 + + + fr.bigeon + collections + 1.0.1-SNAPSHOT + + + 2015 + GCLC swt + provide a swt window for console applications + diff --git a/gclc-swt/src/main/java/fr/bigeon/gclc/swt/SWTConsole.java b/gclc-swt/src/main/java/fr/bigeon/gclc/swt/SWTConsole.java new file mode 100644 index 0000000..b1d24e5 --- /dev/null +++ b/gclc-swt/src/main/java/fr/bigeon/gclc/swt/SWTConsole.java @@ -0,0 +1,292 @@ +/* + * Copyright E. Bigeon (2015) + * + * emmanuel@bigeon.fr + * + * This software is a computer program whose purpose is to + * provide a swt window for console applications. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. + */ +/** + * gclc-swt:fr.bigeon.gclc.swt.SWTConsole.java + * Created on: Apr 18, 2015 + */ +package fr.bigeon.gclc.swt; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.FocusAdapter; +import org.eclipse.swt.events.FocusEvent; +import org.eclipse.swt.events.KeyAdapter; +import org.eclipse.swt.events.KeyEvent; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Text; + +import fr.bigeon.collections.ArrayRibbon; +import fr.bigeon.collections.Ribbon; +import fr.bigeon.gclc.CommandRequestListener; +import fr.bigeon.gclc.ConsoleApplication; +import fr.bigeon.gclc.ConsoleManager; + +/** A SWT component to connect to gclc {@link ConsoleApplication} + *

+ * + * @author Emmanuel Bigeon */ +public class SWTConsole extends Composite implements ConsoleManager, CommandRequestListener { + /** The cmd prefix in the output console */ + private static final String CMD_PREFIX = "[CMD] "; //$NON-NLS-1$ + /** The console output text field */ + private final Text consoleOutput; + /** The console input text field */ + private final Text consoleInput; + /** The prompt label */ + private final Label lblPromptlabel; + /** The prompt text */ + private String prompt = ">"; //$NON-NLS-1$ + /** The command entered by the user */ + private String command = null; + /** If the prompt should be active */ + private boolean prompting = false; + /** The object for thread synchronization with the prompt */ + private final Object promptLock = new Object(); + + /** The ribbon of commands */ + private final Ribbon commands; + /** The current index in the ribbon */ + private int currentIndex = 0; + + /** Create the composite. + * + * @param parent the prent composite + * @param style the composite style */ + public SWTConsole(Composite parent, int style) { + super(parent, style); + + // inner elements + commands = new ArrayRibbon<>(10); + + setLayout(new GridLayout(2, false)); + + consoleOutput = new Text(this, SWT.BORDER | SWT.READ_ONLY | SWT.WRAP | SWT.V_SCROLL | SWT.MULTI); + consoleOutput.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 2, 1)); + consoleOutput.setRedraw(true); + consoleOutput.addFocusListener(new FocusAdapter() { + @SuppressWarnings("synthetic-access") + @Override + public void focusGained(FocusEvent e) { + consoleInput.setFocus(); + } + }); + + lblPromptlabel = new Label(this, SWT.NONE); + lblPromptlabel.setText(prompt); + + consoleInput = new Text(this, SWT.BORDER); + consoleInput.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1)); + consoleInput.addKeyListener(new KeyAdapter() { + @SuppressWarnings("synthetic-access") + @Override + public void keyPressed(KeyEvent e) { + // Enter validates the command if prompting + if (e.keyCode == '\r' && prompting) { + synchronized (promptLock) { + command = consoleInput.getText(); + prompting = false; + consoleInput.setText(new String()); + consoleOutput.append(CMD_PREFIX + command + System.lineSeparator()); + currentIndex = -1; + promptLock.notifyAll(); + } + } + + // Upper arrow retrieves previous commands + if (e.keyCode == SWT.ARROW_UP) { + if (currentIndex < commands.size() - 1) { + currentIndex++; + consoleInput.setText(commands.get(commands.size() - currentIndex - 1)); + consoleInput.setSelection(consoleInput.getText().length()); + } + } + + // Lower arrow retrieves next commands + if (e.keyCode == SWT.ARROW_DOWN) { + if (currentIndex == 0) { + currentIndex--; + consoleInput.setText(new String()); + } else if (currentIndex > 0) { + consoleInput.setText(commands.get(commands.size() - (--currentIndex) - 1)); + consoleInput.setSelection(consoleInput.getText().length()); + } + } + } + }); + + } + + @Override + protected void checkSubclass() { + // Disable the check that prevents subclassing of SWT components + } + + /* (non-Javadoc) + * @see fr.bigeon.gclc.ConsoleManager#setPrompt(java.lang.String) */ + @Override + public void setPrompt(final String prompt) { + this.prompt = prompt; + Display.getDefault().syncExec(new Runnable() { + @SuppressWarnings("synthetic-access") + @Override + public void run() { + lblPromptlabel.setText(prompt); + lblPromptlabel.pack(); + } + }); + } + + /* (non-Javadoc) + * @see fr.bigeon.gclc.ConsoleManager#getPrompt() */ + @Override + public String getPrompt() { + return prompt; + } + + /* (non-Javadoc) + * @see fr.bigeon.gclc.ConsoleManager#prompt() */ + @Override + public String prompt() { + synchronized (promptLock) { + try { + Display.getDefault().syncExec(new Runnable() { + @SuppressWarnings("synthetic-access") + @Override + public void run() { + consoleInput.setFocus(); + } + }); + prompting = true; + promptLock.wait(); + } catch (InterruptedException e) { + command = null; + } + } + return command; + } + + /* (non-Javadoc) + * @see fr.bigeon.gclc.ConsoleManager#prompt(java.lang.String) */ + @Override + public String prompt(final String message) { + synchronized (promptLock) { + try { + Display.getDefault().syncExec(new Runnable() { + @SuppressWarnings("synthetic-access") + @Override + public void run() { + lblPromptlabel.setText(message); + // relayout + SWTConsole.this.layout(); + consoleInput.setFocus(); + } + }); + prompting = true; + promptLock.wait(); + } catch (InterruptedException e) { + command = null; + } finally { + Display.getDefault().syncExec(new Runnable() { + @SuppressWarnings("synthetic-access") + @Override + public void run() { + lblPromptlabel.setText(prompt); + lblPromptlabel.pack(); + } + }); + } + } + return command; + } + + /* (non-Javadoc) + * @see fr.bigeon.gclc.ConsoleManager#println(java.lang.String) */ + @Override + public void println(final String message) { + Display.getDefault().syncExec(new Runnable() { + @SuppressWarnings("synthetic-access") + @Override + public void run() { + consoleOutput.append(message + System.lineSeparator()); + } + }); + + } + + /* (non-Javadoc) + * @see fr.bigeon.gclc.ConsoleManager#print(java.lang.String) */ + @Override + public void print(final String text) { + Display.getDefault().syncExec(new Runnable() { + @SuppressWarnings("synthetic-access") + @Override + public void run() { + consoleOutput.append(text); + } + }); + } + + /* (non-Javadoc) + * @see fr.bigeon.gclc.ConsoleManager#println() */ + @Override + public void println() { + Display.getDefault().syncExec(new Runnable() { + @SuppressWarnings("synthetic-access") + @Override + public void run() { + consoleOutput.append(System.lineSeparator()); + } + }); + } + + /* (non-Javadoc) + * @see org.eclipse.swt.widgets.Composite#setFocus() */ + @Override + public boolean setFocus() { + return consoleInput.setFocus(); + } + + /* (non-Javadoc) + * @see + * fr.bigeon.gclc.CommandRequestListener#commandRequest(java.lang.String) */ + @Override + public void commandRequest(String request) { + commands.add(request); + } + +} diff --git a/gclc-swt/src/main/java/fr/bigeon/gclc/swt/SWTConsoleShell.java b/gclc-swt/src/main/java/fr/bigeon/gclc/swt/SWTConsoleShell.java new file mode 100644 index 0000000..257545d --- /dev/null +++ b/gclc-swt/src/main/java/fr/bigeon/gclc/swt/SWTConsoleShell.java @@ -0,0 +1,91 @@ +/* + * Copyright E. Bigeon (2015) + * + * emmanuel@bigeon.fr + * + * This software is a computer program whose purpose is to + * provide a swt window for console applications. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. + */ +/** + * gclc-swt:fr.bigeon.gclc.swt.SWTConsoleShell.java + * Created on: Apr 18, 2015 + */ +package fr.bigeon.gclc.swt; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.FillLayout; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Shell; + +import fr.bigeon.gclc.CommandRequestListener; +import fr.bigeon.gclc.ConsoleManager; + +/** A shell containing a {@link SWTConsole} + *

+ * + * @author Emmanuel Bigeon */ +public class SWTConsoleShell extends Shell { + + /** The console component */ + private final SWTConsole console; + + /** Create the shell. + * + * @param display the display */ + public SWTConsoleShell(Display display) { + super(display, SWT.SHELL_TRIM); + setLayout(new FillLayout(SWT.HORIZONTAL)); + + console = new SWTConsole(this, SWT.NONE); + createContents(); + } + + /** + * Create contents of the shell. + */ + protected void createContents() { + setText("Console Application"); //$NON-NLS-1$ + setSize(450, 300); + } + + @Override + protected void checkSubclass() { + // Disable the check that prevents subclassing of SWT components + } + + /** @return the console manager */ + public ConsoleManager getManager() { + return console; + } + + /** @return the element awaiting commands */ + public CommandRequestListener getCommandListener() { + return console; + } +} diff --git a/gclc-swt/src/test/java/fr/bigeon/gclc/swt/AppTest.java b/gclc-swt/src/test/java/fr/bigeon/gclc/swt/AppTest.java new file mode 100644 index 0000000..15e96e8 --- /dev/null +++ b/gclc-swt/src/test/java/fr/bigeon/gclc/swt/AppTest.java @@ -0,0 +1,102 @@ +/* + * Copyright E. Bigeon (2015) + * + * emmanuel@bigeon.fr + * + * This software is a computer program whose purpose is to + * provide a swt window for console applications. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. + */ +package fr.bigeon.gclc.swt; + +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestSuite; + +/** + * Unit test for simple App. + */ +public class AppTest + extends TestCase +{ + /** + * Create the test case + * + * @param testName name of the test case + */ + public AppTest( String testName ) + { + super( testName ); + } + + /** + * @return the suite of tests being tested + */ + public static Test suite() + { + return new TestSuite( AppTest.class ); + } + + /** + * Rigourous Test :-) + */ + @SuppressWarnings("static-method") + public void testApp() + { +// Display display = new Display(); +// final Shell shell = new Shell(display); +// shell.setLayout(new GridLayout(1, false)); +// SWTConsole swtConsole = new SWTConsole(shell, SWT.NONE); +// swtConsole.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, +// 1, 1)); +// final ConsoleApplication appl = new ConsoleApplication(swtConsole, +// "exit", +// "Hello", "See you"); +// shell.pack(); +// shell.open(); +// Thread applThread = new Thread(new Runnable() { +// +// @Override +// public void run() { +// appl.start(); +// Display.getDefault().syncExec(new Runnable() { +// @Override +// public void run() { +// shell.dispose(); +// } +// }); +// } +// }); +// applThread.start(); +// while (!shell.isDisposed()) { +// if (!display.readAndDispatch()) display.sleep(); +// } +// display.dispose(); + assertTrue( true ); + } +} diff --git a/gclc/pom.xml b/gclc/pom.xml index 09d9d80..dbb199a 100644 --- a/gclc/pom.xml +++ b/gclc/pom.xml @@ -53,7 +53,7 @@ fr.bigeon ebigeon-config - 1.3 + 1.6.0 diff --git a/gclc/src/main/java/fr/bigeon/gclc/command/Command.java b/gclc/src/main/java/fr/bigeon/gclc/command/Command.java index 012f75d..6e24c45 100644 --- a/gclc/src/main/java/fr/bigeon/gclc/command/Command.java +++ b/gclc/src/main/java/fr/bigeon/gclc/command/Command.java @@ -42,11 +42,31 @@ import fr.bigeon.gclc.ConsoleManager; /**

* A command to execute. It is mandatory that it has a name and that name cannot - * start with minus character or contain space - * + * start with minus character or contain space. + *

+ * A command can be executed, with parameters that will be provided as an array + * of strings. + *

+ * The help mechanism can be overwritten, but is by default doing the following: + *

+ *

+ * The default behavior for the brief message is to print the tip preceeded by a + * couple of spaces. + * * @author Emmanuel BIGEON */ public abstract class Command { + /** + * + */ + private static final String EOL_LINUX = "\n"; //$NON-NLS-1$ /** The name of the command */ protected final String name; @@ -64,7 +84,19 @@ public abstract class Command { /** @param args the arguments of the command (some expect an empty array) */ public abstract void execute(String... args); - /** This prints the help associated to this command + /** This prints the help associated to this command. + *

+ * The default behavior is to print: + * + *

+     * [Command name]
+     * [brief message]
+     * 
+     * Usage:
+     * [Usage pattern]
+     * 
+     * [Usage details]
+     * 
* * @param manager the manager to print the data * @param args the arguments called with the help */ @@ -75,16 +107,30 @@ public abstract class Command { manager.println("Usage:"); //$NON-NLS-1$ manager.println(usagePattern()); manager.println(); - manager.print(usageDetail()); + String details = usageDetail(); + if (details != null && !details.isEmpty()) { + manager.print(details); + if (!(details.endsWith(EOL_LINUX) || details.endsWith(System.lineSeparator()))) { + manager.println(); + } + } } - /** @return the detailed help (should end with end of line or be empty) */ + /**

+ * This method return the detail of the help. It immediatly follows the + * {@link #usagePattern() usage pattern}. + * + * @return the detailed help (should end with end of line or be empty) */ @SuppressWarnings("static-method") protected String usageDetail() { return new String(); } - /** @return the usage pattern */ + /**

+ * This prints the usage pattern for the command. It follows the brief + * introduction on the command ({@link #brief()}) + * + * @return the usage pattern */ protected String usagePattern() { return getCommandName(); } diff --git a/gclc/src/main/java/fr/bigeon/gclc/command/HelpExecutor.java b/gclc/src/main/java/fr/bigeon/gclc/command/HelpExecutor.java index d0ca323..bc6245d 100644 --- a/gclc/src/main/java/fr/bigeon/gclc/command/HelpExecutor.java +++ b/gclc/src/main/java/fr/bigeon/gclc/command/HelpExecutor.java @@ -39,7 +39,6 @@ package fr.bigeon.gclc.command; import fr.bigeon.gclc.ConsoleManager; -import fr.bigeon.gclc.exception.InvalidCommandName; import fr.bigeon.gclc.prompt.CLIPrompterMessages; /**

@@ -55,15 +54,11 @@ public class HelpExecutor extends Command { /** @param cmdName the command name * @param consoleManager the manager for the console - * @param cmd the command to execute the help of - * @throws InvalidCommandName if the name is invalid */ - public HelpExecutor(String cmdName, ConsoleManager consoleManager, - Command cmd) throws InvalidCommandName { + * @param cmd the command to execute the help of */ + public HelpExecutor(String cmdName, ConsoleManager consoleManager, Command cmd) { super(cmdName); this.cmd = cmd; - if (consoleManager == null) - throw new NullPointerException( - "Argument cannot be null: ConsoleManager"); //$NON-NLS-1$ + if (consoleManager == null) throw new NullPointerException("Argument cannot be null: ConsoleManager"); //$NON-NLS-1$ this.consoleManager = consoleManager; } diff --git a/gclc/src/main/java/fr/bigeon/gclc/prompt/CLIPrompter.java b/gclc/src/main/java/fr/bigeon/gclc/prompt/CLIPrompter.java index a5dc533..3f03cbc 100644 --- a/gclc/src/main/java/fr/bigeon/gclc/prompt/CLIPrompter.java +++ b/gclc/src/main/java/fr/bigeon/gclc/prompt/CLIPrompter.java @@ -55,6 +55,8 @@ public class CLIPrompter { private static final String LIST_DISP_KEY = "promptlist.exit.dispkey"; //$NON-NLS-1$ @SuppressWarnings("javadoc") private static final String PROMPT = "prompt.lineprompt"; //$NON-NLS-1$ + @SuppressWarnings("javadoc") + private static final String LIST_CHOICE_SEP = "promptlist.multi.sepkey"; //$NON-NLS-1$ /** @param manager the manager * @param prompt the prompting message @@ -268,10 +270,95 @@ public class CLIPrompter { return choices.get(index); } + /** @param manager the manager + * @param The choices labels type + * @param The real choices objects + * @param choices the list of labels (in order to be displayed) + * @param choicesMap the map of label to actual objects + * @param message the prompting message + * @return the chosen objects (or an empty list) */ + public static List promptMultiChoice(ConsoleManager manager, List choices, Map choicesMap, + String message) { + List chs = promptMultiChoice(manager, choices, message); + List userChoices = new ArrayList<>(); + for (Integer integer : chs) { + userChoices.add(choicesMap.get(integer)); + } + return userChoices; + } + + /** @param manager the manager + * @param The choices labels type + * @param The real choices objects + * @param choicesMap the map of label to actual objects + * @param message the prompting message + * @return the chosen objects */ + public static List promptMultiChoice(ConsoleManager manager, Map choicesMap, String message) { + return promptMultiChoice(manager, new ArrayList<>(choicesMap.keySet()), choicesMap, message); + } + + /** @param manager the manager + * @param the type of choices + * @param choices the list of choices + * @param message the prompting message + * @return the indices of the choices */ + @SuppressWarnings("boxing") + public static List promptMultiChoice(ConsoleManager manager, List choices, String message) { + manager.println(message); + int index = listChoices(manager, choices, null); + String result = ""; //$NON-NLS-1$ + boolean keepOn = true; + List chs = new ArrayList<>(); + while (keepOn) { + keepOn = false; + result = manager.prompt(CLIPrompterMessages.getString(PROMPT)); + String[] vals = result.split(CLIPrompterMessages.getString(LIST_CHOICE_SEP)); + for (int i = 0; i < vals.length; i++) { + if (vals[i].isEmpty()) { + continue; + } + try { + int r = Integer.parseInt(vals[i]); + if (r >= 0 && r < index) { + chs.add(r); + } else { + manager.println(CLIPrompterMessages.getString("promptchoice.outofbounds", 0, index)); //$NON-NLS-1$ + listChoices(manager, choices, null); + keepOn = true; + break; + } + } catch (NumberFormatException e) { + keepOn = true; + manager.println(CLIPrompterMessages.getString("promptchoice.formaterr", 0, index)); //$NON-NLS-1$ + listChoices(manager, choices, null); + break; + } + } + } + return chs; + } + + /** @param manager the manager + * @param keys the keys to be printed + * @param choices the real choices + * @param message the message + * @param the type of elements + * @return the choice */ + public static List promptMultiChoice(ConsoleManager manager, List keys, List choices, + String message) { + List indices = promptMultiChoice(manager, keys, message); + List userChoices = new ArrayList<>(); + for (Integer integer : indices) { + userChoices.add(choices.get(integer.intValue())); + } + return userChoices; + } + /** @param manager the manager * @param choices the choices * @param cancel the cancel option if it exists - * @return the number of choices plus one */ + * @return the number of choices plus one (or the number of choices if there + * is a cancel) */ private static int listChoices(ConsoleManager manager, List choices, String cancel) { int index = 0; @@ -279,9 +366,9 @@ public class CLIPrompter { manager.println((index++) + ") " + u); //$NON-NLS-1$ } if (cancel != null) { - manager.println((index++) + ") " + cancel); //$NON-NLS-1$ + manager.println((index) + ") " + cancel); //$NON-NLS-1$ } - return --index; + return index; } /** This methods prompt the user for a list of elements