From 1ffe321898e3282aa53ad537a3920c39434e5911 Mon Sep 17 00:00:00 2001 From: Emmanuel Bigeon Date: Thu, 2 Jun 2016 12:50:36 -0400 Subject: [PATCH] gclc clean up. socket and swt configured for current stable gclc gclc items moved around. gclc ready for release. gclc internationalization gclc extract command interface socket server issue (close socket) "fixed" swt minor improvments (user con't input commands while the application is actually running one, the history keeps track of mispelled commands to). --- .../bigeon/gclc/socket/ConsoleRunnable.java | 77 +++ .../socket/SocketConsoleApplicationShell.java | 310 +++++----- .../socket/ThreadedServerConsoleManager.java | 158 +++++ .../gclc/socket/ConsoleRunnableTest.java | 231 +++++++ .../gclc/socket/ConsoleTestApplication.java | 14 +- .../socket/SocketConsoleApplicationTest.java | 138 +++++ .../fr/bigeon/gclc/socket/TestServer.java | 62 +- gclc-swt/pom.xml | 2 +- .../java/fr/bigeon/gclc/swt/SWTConsole.java | 311 +++++----- .../fr/bigeon/gclc/swt/SWTConsoleShell.java | 27 +- .../test/java/fr/bigeon/gclc/swt/AppTest.java | 72 ++- .../bigeon/gclc/CommandRequestListener.java | 2 +- .../fr/bigeon/gclc/ConsoleApplication.java | 292 +++++---- .../java/fr/bigeon/gclc/command/Command.java | 59 +- .../gclc/command/CommandParameters.java | 116 ++-- .../bigeon/gclc/command/CommandProvider.java | 42 +- .../fr/bigeon/gclc/command/HelpExecutor.java | 35 +- .../java/fr/bigeon/gclc/command/ICommand.java | 77 +++ .../bigeon/gclc/command/ICommandProvider.java | 28 +- .../gclc/command/ParametrizedCommand.java | 28 +- .../fr/bigeon/gclc/command/SubedCommand.java | 161 ++--- .../gclc/command/UnrecognizedCommand.java | 28 +- .../fr/bigeon/gclc/command/package-info.java | 13 +- .../CommandParsingException.java} | 37 +- .../gclc/exception/CommandRunException.java | 12 +- .../gclc/exception/InvalidCommandName.java | 2 +- .../java/fr/bigeon/gclc/i18n/Messages.java | 81 +++ .../gclc/{ => manager}/ConsoleManager.java | 35 +- .../SystemConsoleManager.java | 144 +++-- .../fr/bigeon/gclc/prompt/CLIPrompter.java | 564 +++++++++--------- .../gclc/prompt/CLIPrompterMessages.java | 39 +- .../fr/bigeon/gclc/prompt/package-info.java | 10 +- .../java/fr/bigeon/gclc/tools/PrintUtils.java | 31 +- .../fr/bigeon/gclc/l10n/messages.properties | 1 + 34 files changed, 2139 insertions(+), 1100 deletions(-) create mode 100644 gclc-socket/src/main/java/fr/bigeon/gclc/socket/ConsoleRunnable.java create mode 100644 gclc-socket/src/main/java/fr/bigeon/gclc/socket/ThreadedServerConsoleManager.java create mode 100644 gclc-socket/src/test/java/fr/bigeon/gclc/socket/ConsoleRunnableTest.java create mode 100644 gclc-socket/src/test/java/fr/bigeon/gclc/socket/SocketConsoleApplicationTest.java create mode 100644 gclc/src/main/java/fr/bigeon/gclc/command/ICommand.java rename gclc/src/main/java/fr/bigeon/gclc/{system/package-info.java => exception/CommandParsingException.java} (68%) create mode 100644 gclc/src/main/java/fr/bigeon/gclc/i18n/Messages.java rename gclc/src/main/java/fr/bigeon/gclc/{ => manager}/ConsoleManager.java (93%) rename gclc/src/main/java/fr/bigeon/gclc/{system => manager}/SystemConsoleManager.java (64%) create mode 100644 gclc/src/main/resources/fr/bigeon/gclc/l10n/messages.properties 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 new file mode 100644 index 0000000..2a6d2ea --- /dev/null +++ b/gclc-socket/src/main/java/fr/bigeon/gclc/socket/ConsoleRunnable.java @@ -0,0 +1,77 @@ +/* + * 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. + */ +/** + * gclc-socket:fr.bigeon.gclc.socket.ConsoleRunnable.java + * Created on: Jun 1, 2016 + */ +package fr.bigeon.gclc.socket; + +import fr.bigeon.gclc.ConsoleApplication; + +/** A runnable class that will actually have the application running. + * + * @author Emmanuel Bigeon */ +public class ConsoleRunnable implements Runnable { + + /** The actual application */ + private final ConsoleApplication app; + /** The synchronization object */ + private final Object promptingLock; + + /** @param app the application + * @param promptingLock the synchronization object */ + public ConsoleRunnable(ConsoleApplication app, Object promptingLock) { + super(); + this.app = app; + this.promptingLock = promptingLock; + } + + /* (non-Javadoc) + * @see java.lang.Runnable#run() */ + @Override + public void run() { + app.start(); + synchronized (promptingLock) { + // release all waiting elements before ending + promptingLock.notifyAll(); + } + } + + /** Stop the application (it will finish its current operation) */ + public void stop() { + app.exit(); + } + +} 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 c20fedc..3d3b250 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 @@ -44,7 +44,10 @@ import java.io.PipedOutputStream; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; +import java.net.SocketException; import java.util.Arrays; +import java.util.logging.Level; +import java.util.logging.Logger; import fr.bigeon.gclc.ConsoleApplication; import fr.bigeon.gclc.ConsoleManager; @@ -70,16 +73,20 @@ import fr.bigeon.smu.StringEncoder; * @author Emmanuel Bigeon */ public class SocketConsoleApplicationShell implements Runnable { + /** + * + */ + private static final String INTERRUPTION_WHILE_WORKING = "Interruption while application was working"; //$NON-NLS-1$ /** 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$ + private static final StringEncoder ENCODER = new StringEncoder("%", //$NON-NLS-1$ + Arrays.asList(EOL)); + /** The class logger */ + private static final Logger LOGGER = Logger + .getLogger(SocketConsoleApplicationShell.class.getName()); /** 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 */ @@ -88,180 +95,174 @@ public class SocketConsoleApplicationShell implements Runnable { 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; - } - }; + private final ThreadedServerConsoleManager consoleManager = new ThreadedServerConsoleManager( + ENCODER, promptingLock); /** The auto close flag. if this is true, every request closes the session * after its call */ private final boolean autoClose; + /** The server socket */ + private ServerSocket serverSocket; + /** The application shutdown string */ + private final String applicationShutdown; - /** 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 + /** Create a socket application shell which will listen on the given port + * and close session upon the provided string reception by client * * @param port the port to listen to - * @param close the session closing command */ - public SocketConsoleApplicationShell(int port, String close) { + * @param close the session closing command + * @param applicationShutdown the appication shut down command */ + public SocketConsoleApplicationShell(int port, String close, + String applicationShutdown) { this.port = port; this.close = close; + this.applicationShutdown = applicationShutdown; this.autoClose = false; } - /** TODO Describe SocketConsoleApplicationShell.java Constructor + /** Create a socket application shell which will listen on the given port + * and auto close session after one instruction * * @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) { + * sent + * @param applicationShutdown the application shutdown command */ + public SocketConsoleApplicationShell(int port, boolean autoClose, + String applicationShutdown) { this.port = port; - this.close = null; this.autoClose = autoClose; + this.applicationShutdown = applicationShutdown; + this.close = autoClose ? null : "close"; //$NON-NLS-1$ } /* (non-Javadoc) * @see java.lang.Runnable#run() */ @Override public void run() { - try (ServerSocket serverSocket = new ServerSocket(port)) { - final ConsoleRunnable runnable = new ConsoleRunnable(); + try (ServerSocket actualServerSocket = new ServerSocket(port)) { + this.serverSocket = actualServerSocket; + final ConsoleRunnable runnable = new ConsoleRunnable(app, + promptingLock); final Thread appTh = new Thread(runnable); running = true; try (PipedOutputStream outStream = new PipedOutputStream(); - BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outStream))) { + 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; - } - } - } - } + try (InputStreamReader isr = new InputStreamReader( + consoleInput); + BufferedReader inBuf = new BufferedReader(isr);) { + consoleManager.setInput(inBuf); + runSokectServer(appTh, writer); + // Close the application + // Pass command to application + writer.write(applicationShutdown + EOL); + writer.flush(); } } } catch (final IOException e) { - // TODO Auto-generated catch block - e.printStackTrace(); + LOGGER.log(Level.SEVERE, + "Communication error between client and server", e); //$NON-NLS-1$ + } + } + + /** @param appTh the application thread + * @param writer the writer to the application + * @throws IOException if the communication with the client failed */ + private void runSokectServer(Thread appTh, + BufferedWriter writer) throws IOException { + while (running) { + try (Socket clientSocket = serverSocket.accept(); + PrintWriter out = new PrintWriter( + clientSocket.getOutputStream(), true); + BufferedReader in = new BufferedReader(new InputStreamReader( + clientSocket.getInputStream()));) { + // this is not threaded to avoid several clients at the same + // time + consoleManager.setOutput(out); + // Initiate application + if (!appTh.isAlive()) { + appTh.start(); + } else { + out.println("Reconnected"); //$NON-NLS-1$ + } + communicate(writer, in); + } catch (SocketException e) { + LOGGER.log(Level.INFO, "Socket closed", e); //$NON-NLS-1$ + } + } + } + + /** active communication between server and client + * + * @param writer the writer to the application + * @param in the input from the client + * @throws IOException if the communication failed */ + private void communicate(BufferedWriter writer, + BufferedReader in) throws IOException { + synchronized (promptingLock) { + if (!consoleManager.isPrompting()) { + try { + // wait for application to finish its operation + promptingLock.wait(); + } catch (final InterruptedException e) { + LOGGER.log(Level.SEVERE, INTERRUPTION_WHILE_WORKING, e); + } + } + if (autoClose) { + communicateOnce(in, writer); + } else { + communicateLoop(in, writer); + } + } + } + + /** @param in the input from the client + * @param writer the output to the client + * @throws IOException if the communication failed */ + private void communicateOnce(BufferedReader in, + BufferedWriter writer) throws IOException { + String ln; + if ((ln = in.readLine()) != null) { + if (ln.equals(close)) { + return; + } + // Pass command to application + writer.write(ln + EOL); + writer.flush(); + try { + // Wait for application process to + // finish + promptingLock.wait(); + } catch (final InterruptedException e) { + LOGGER.log(Level.SEVERE, INTERRUPTION_WHILE_WORKING, e); + } + } + } + + /** @param in the input from the client + * @param writer the output to the client + * @throws IOException if the communication failed */ + private void communicateLoop(BufferedReader in, + BufferedWriter writer) throws IOException { + String ln; + while (running && (ln = in.readLine()) != null) { + if (ln.equals(close)) { + break; + } + // Pass command to application + writer.write(ln + EOL); + writer.flush(); + try { + // Wait for application process to + // finish + promptingLock.wait(); + } catch (final InterruptedException e) { + LOGGER.log(Level.SEVERE, INTERRUPTION_WHILE_WORKING, e); + } } } @@ -275,4 +276,21 @@ public class SocketConsoleApplicationShell implements Runnable { this.app = app; } + /** This method will request the server to stop. + *

+ * In most cases, this will terminate communication on every client. On some + * cases, the */ + public void stop() { + running = false; + try { + serverSocket.close(); + } catch (IOException e) { + LOGGER.log(Level.SEVERE, "Exception in closing socket server", e); //$NON-NLS-1$ + } + synchronized (promptingLock) { + promptingLock.notifyAll(); + } + app.exit(); + } + } diff --git a/gclc-socket/src/main/java/fr/bigeon/gclc/socket/ThreadedServerConsoleManager.java b/gclc-socket/src/main/java/fr/bigeon/gclc/socket/ThreadedServerConsoleManager.java new file mode 100644 index 0000000..3b4ef78 --- /dev/null +++ b/gclc-socket/src/main/java/fr/bigeon/gclc/socket/ThreadedServerConsoleManager.java @@ -0,0 +1,158 @@ +/* + * 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. + */ +/** + * gclc-socket:fr.bigeon.gclc.socket.ThreadedServerConsoleManager.java + * Created on: Jun 1, 2016 + */ +package fr.bigeon.gclc.socket; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.logging.Level; +import java.util.logging.Logger; + +import fr.bigeon.gclc.ConsoleManager; +import fr.bigeon.smu.StringEncoder; + +/** The console manager for socket communication + * + * @author Emmanuel Bigeon */ +public class ThreadedServerConsoleManager implements ConsoleManager { + + /** The eol character */ + private static final String EOL = "\n"; //$NON-NLS-1$ + /** The class logger */ + private static final Logger LOGGER = Logger + .getLogger(ThreadedServerConsoleManager.class.getName()); + /** The prompting sequence */ + private String prompt = new String(); + /** The buffer of data to send to the user */ + private StringBuilder buffer = new StringBuilder(); + /** The synchronized object */ + private final Object promptingLock; + /** The output to write data comming from the application */ + private PrintWriter output; + /** The encoder to encode data coming from the application */ + private final StringEncoder encoder; + /** The input to wait data from the user */ + private BufferedReader input; + /** the prompting status */ + private boolean doPrompt; + + /** Create the console manager. + * + * @param encoder the encoder for output + * @param promptingLock the synchronization object */ + public ThreadedServerConsoleManager(StringEncoder encoder, + Object promptingLock) { + super(); + this.encoder = encoder; + this.promptingLock = promptingLock; + } + + /** @param input the input to set */ + public void setInput(BufferedReader input) { + this.input = input; + } + + /** @param output the output to set */ + public void setOutput(PrintWriter output) { + this.output = output; + } + + @Override + public void setPrompt(String prompt) { + this.prompt = prompt; + } + + @Override + public String prompt(String message) { + buffer.append(message); + String userInput = new String(); + boolean prompting = true; + while (prompting) { + // Send buffer content + output.println(encoder.encode(buffer.toString())); + try { + synchronized (promptingLock) { + doPrompt = true; + promptingLock.notify(); + } + userInput = input.readLine(); + doPrompt = false; + prompting = false; + } catch (final IOException e) { + LOGGER.log(Level.SEVERE, "input reading error", e); //$NON-NLS-1$ + } + } + // Renew buffer + buffer = new StringBuilder(); + return userInput; + } + + /** @return the prompting status */ + public synchronized boolean isPrompting() { + return doPrompt; + } + + @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; + } + + +} diff --git a/gclc-socket/src/test/java/fr/bigeon/gclc/socket/ConsoleRunnableTest.java b/gclc-socket/src/test/java/fr/bigeon/gclc/socket/ConsoleRunnableTest.java new file mode 100644 index 0000000..8508811 --- /dev/null +++ b/gclc-socket/src/test/java/fr/bigeon/gclc/socket/ConsoleRunnableTest.java @@ -0,0 +1,231 @@ +/* + * 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. + */ +/** + * gclc-socket:fr.bigeon.gclc.socket.ConsoleRunnableTest.java + * Created on: Jun 1, 2016 + */ +package fr.bigeon.gclc.socket; + +import org.junit.Test; + +import fr.bigeon.gclc.ConsoleApplication; +import fr.bigeon.gclc.ConsoleManager; +import fr.bigeon.gclc.system.SystemConsoleManager; + +/** Test class for {@link ConsoleRunnable} + * + * @author Emmanuel Bigeon */ +@SuppressWarnings({"static-method", "unused"}) +public class ConsoleRunnableTest { + + /** + * Test method for {@link fr.bigeon.gclc.socket.ConsoleRunnable#ConsoleRunnable(fr.bigeon.gclc.ConsoleApplication, java.lang.Object)}. + */ + @Test + public void testConsoleRunnable() { + Object lock = new Object(); + ConsoleApplication app = new ConsoleTestApplication( + new SystemConsoleManager()); + ConsoleRunnable runnable = new ConsoleRunnable(app, lock); + + } + + /** + * Test method for {@link fr.bigeon.gclc.socket.ConsoleRunnable#run()}. + */ + @Test + public void testRunFlow() { + Object lock = new Object(); + ConsoleApplication app = new ConsoleTestApplication( + new ConsoleManager() { + + @Override + public void setPrompt(String prompt) { + // do nothing + } + + @Override + public String getPrompt() { + // Not used in test + return ""; //$NON-NLS-1$ + } + + @Override + public String prompt() { + try { + Thread.sleep(1000); + } catch (InterruptedException e) { // NOSONAR + // do nothing + } + return "mock"; //$NON-NLS-1$ + } + + @Override + public String prompt(String message) { + return prompt(); + } + + @Override + public void println(String message) { + // do nothing + } + + @Override + public void println() { + // do nothing + } + + @Override + public void print(String text) { + // do nothing + } + }); + ConsoleRunnable runnable = new ConsoleRunnable(app, lock); + + Thread th = new Thread(runnable); + th.start(); + + runnable.stop(); + } + + /** + * Test method for {@link fr.bigeon.gclc.socket.ConsoleRunnable#stop()}. + */ + @Test + public void testStop() { + Object lock = new Object(); + ConsoleApplication app = new ConsoleTestApplication( + new ConsoleManager() { + + @Override + public void setPrompt(String prompt) { + throw new RuntimeException("Not implemented yet"); //$NON-NLS-1$ + } + + @Override + public String getPrompt() { + throw new RuntimeException("Not implemented yet"); //$NON-NLS-1$ + } + + @Override + public String prompt() { + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + return "mock"; //$NON-NLS-1$ + } + + @Override + public String prompt(String message) { + throw new RuntimeException("Not implemented yet"); //$NON-NLS-1$ + } + + @Override + public void println(String message) { + // + } + + @Override + public void println() { + // + } + + @Override + public void print(String text) { + // + } + }); + ConsoleRunnable runnable = new ConsoleRunnable(app, lock); + runnable.stop(); + Thread th = new Thread(runnable); + th.start(); + runnable.stop(); + runnable.stop(); + } + + /** Test method for {@link fr.bigeon.gclc.socket.ConsoleRunnable#stop()}. */ + @Test + public void testRun() { + Object lock = new Object(); + ConsoleApplication app = new ConsoleTestApplication( + new ConsoleManager() { + + @Override + public void setPrompt(String prompt) { + throw new RuntimeException("Not implemented yet"); //$NON-NLS-1$ + } + + @Override + public String getPrompt() { + throw new RuntimeException("Not implemented yet"); //$NON-NLS-1$ + } + + @Override + public String prompt() { + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + return "exit"; //$NON-NLS-1$ + } + + @Override + public String prompt(String message) { + throw new RuntimeException("Not implemented yet"); //$NON-NLS-1$ + } + + @Override + public void println(String message) { + // + } + + @Override + public void println() { + // + } + + @Override + public void print(String text) { + // + } + }); + ConsoleRunnable runnable = new ConsoleRunnable(app, lock); + runnable.run(); + } + +} 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 index a662c71..32ca902 100644 --- a/gclc-socket/src/test/java/fr/bigeon/gclc/socket/ConsoleTestApplication.java +++ b/gclc-socket/src/test/java/fr/bigeon/gclc/socket/ConsoleTestApplication.java @@ -39,16 +39,18 @@ import fr.bigeon.gclc.ConsoleManager; import fr.bigeon.gclc.command.Command; import fr.bigeon.gclc.exception.InvalidCommandName; -/** TODO Describe ConsoleTestApplication.java - * @author Emmanuel Bigeon - * - */ +/** A test-purpose application + * + * @author Emmanuel Bigeon */ public class ConsoleTestApplication extends ConsoleApplication { + /** Exit command */ + public static final String EXIT = "exit"; //$NON-NLS-1$ + /** @param manager the manager */ @SuppressWarnings("nls") public ConsoleTestApplication(final ConsoleManager manager) { - super(manager, "exit", + super(manager, EXIT, "Welcome to the test application. Type help or test.", "See you"); addHelpCommand("help"); @@ -66,9 +68,7 @@ public class ConsoleTestApplication extends ConsoleApplication { } }); } catch (final InvalidCommandName e) { - // TODO Auto-generated catch block e.printStackTrace(); } } - } 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 new file mode 100644 index 0000000..91d5907 --- /dev/null +++ b/gclc-socket/src/test/java/fr/bigeon/gclc/socket/SocketConsoleApplicationTest.java @@ -0,0 +1,138 @@ +/* + * 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. + */ +/** + * gclc-socket:fr.bigeon.gclc.socket.SocketConsoleApplicationTest.java + * Created on: Jun 1, 2016 + */ +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.Arrays; + +import org.junit.Test; + +import fr.bigeon.smu.StringEncoder; + +/** Test class for {@link SocketConsoleApplicationShell} + * + * @author Emmanuel Bigeon */ +@SuppressWarnings({"static-method", "unused", "javadoc", "nls"}) +public class SocketConsoleApplicationTest { + + private static final StringEncoder ENCODER = new StringEncoder("%", + Arrays.asList("\n")); //$NON-NLS-1$ + + @Test + public void integrationTest() { + Thread server = TestServer.startServer(false); + try { + Thread.sleep(100); + } catch (InterruptedException e1) { + e1.printStackTrace(); + } + 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; + int i = 0; + String[] cmds = {"help", "test", "close"}; + 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++; + } + } catch (final IOException e) { + e.printStackTrace(); + } + TestServer.closeServer(); + try { + Thread.sleep(100); + } catch (InterruptedException e2) { + e2.printStackTrace(); + } + server = TestServer.startServer(true); + 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", "test", "close"}; + 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++; + } + } catch (final IOException e) { + e.printStackTrace(); + } + TestServer.closeServer(); + + } +} 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 bff12b1..0df3e3f 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 @@ -34,26 +34,58 @@ */ package fr.bigeon.gclc.socket; -/** TODO Describe TestServer.java - * @author Emmanuel Bigeon - * - */ +/** A test server + * + * @author Emmanuel Bigeon */ +@SuppressWarnings({"javadoc", "nls"}) public class TestServer { + + private static SocketConsoleApplicationShell SHELL; + private static Thread server; + /** @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(); + public static void main(String... args) { try { - serverTh.join(); + startServer(false).join(); } catch (final InterruptedException e) { - // TODO Auto-generated catch block e.printStackTrace(); } } + + public static Thread getServer() { + if (server == null) { + server = new Thread(getShell(), "gclcServer"); + server.start(); + } + return server; + } + + /** @return */ + private static SocketConsoleApplicationShell getShell() { + if (SHELL == null) { + SHELL = new SocketConsoleApplicationShell(3300, "close", + ConsoleTestApplication.EXIT); + final ConsoleTestApplication app = new ConsoleTestApplication( + SHELL.getConsoleManager()); + SHELL.setApplication(app); + } + return SHELL; + } + + public static Thread startServer(boolean autoClose) { + if (SHELL == null) { + SHELL = new SocketConsoleApplicationShell(3300, autoClose, + ConsoleTestApplication.EXIT); + 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/pom.xml b/gclc-swt/pom.xml index c800141..a41023e 100644 --- a/gclc-swt/pom.xml +++ b/gclc-swt/pom.xml @@ -61,7 +61,7 @@ fr.bigeon collections - 1.0.1-SNAPSHOT + 1.0.1 2015 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 index b1d24e5..3ece870 100644 --- a/gclc-swt/src/main/java/fr/bigeon/gclc/swt/SWTConsole.java +++ b/gclc-swt/src/main/java/fr/bigeon/gclc/swt/SWTConsole.java @@ -52,15 +52,75 @@ 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 { +public class SWTConsole extends Composite implements ConsoleManager { + /** A key listener to validate commands and manage the history of commands + * + * @author Emmanuel Bigeon */ + public static final class HistoryTextKeyListener extends KeyAdapter { + + /** The size of commands history */ + private static final int DEFAULT_HISTORY_SIZE = 10; + /** The history ribbon */ + private final Ribbon commands; + /** The current index in history search */ + private int currentIndex = 0; + /** The console to write the commands in */ + private final Text consoleInput; + /** The console to notify of command validation */ + private final SWTConsole console; + + /** @param console the console + * @param consoleInput the text to write commands in */ + public HistoryTextKeyListener(SWTConsole console, Text consoleInput) { + super(); + this.console = console; + this.consoleInput = consoleInput; + this.commands = new ArrayRibbon<>(DEFAULT_HISTORY_SIZE); + } + + @Override + public void keyPressed(KeyEvent e) { + // Enter validates the command if prompting + if (e.keyCode == '\r') { + commands.add(consoleInput.getText()); + console.validateInput(); + currentIndex = -1; + } + + // Upper arrow retrieves previous commands + if (e.keyCode == SWT.ARROW_UP && + 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()); + } + } + } + } + + /** + * + */ + private static final int LAYOUT_NB_COLUMNS = 2; /** The cmd prefix in the output console */ private static final String CMD_PREFIX = "[CMD] "; //$NON-NLS-1$ /** The console output text field */ @@ -78,25 +138,19 @@ public class SWTConsole extends Composite implements ConsoleManager, CommandRequ /** 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(LAYOUT_NB_COLUMNS, false)); - 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 = 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, + LAYOUT_NB_COLUMNS, 1)); consoleOutput.setRedraw(true); consoleOutput.addFocusListener(new FocusAdapter() { @SuppressWarnings("synthetic-access") @@ -110,45 +164,28 @@ public class SWTConsole extends Composite implements ConsoleManager, CommandRequ 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(); - } - } + consoleInput.setLayoutData( + new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1)); + consoleInput.addKeyListener( + new HistoryTextKeyListener(this, consoleInput)); - // 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()); - } - } + /** + * + */ + protected void validateInput() { + if (prompting) { + synchronized (promptLock) { + command = consoleInput.getText(); + prompting = false; + consoleInput.setText(new String()); + consoleInput.setEnabled(false); + consoleOutput + .append(CMD_PREFIX + command + System.lineSeparator()); + promptLock.notifyAll(); } - }); - + } } @Override @@ -156,21 +193,6 @@ public class SWTConsole extends Composite implements ConsoleManager, CommandRequ // 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 @@ -178,76 +200,6 @@ public class SWTConsole extends Composite implements ConsoleManager, CommandRequ 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 @@ -274,6 +226,78 @@ public class SWTConsole extends Composite implements ConsoleManager, CommandRequ }); } + /* (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#prompt() */ + @Override + public String prompt() { + synchronized (promptLock) { + try { + Display.getDefault().syncExec(new Runnable() { + @SuppressWarnings("synthetic-access") + @Override + public void run() { + consoleInput.setEnabled(true); + consoleInput.setFocus(); + } + }); + prompting = true; + promptLock.wait(); + } catch (final 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.setEnabled(true); + consoleInput.setFocus(); + } + }); + prompting = true; + promptLock.wait(); + } catch (final 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 org.eclipse.swt.widgets.Composite#setFocus() */ @Override @@ -282,11 +306,18 @@ public class SWTConsole extends Composite implements ConsoleManager, CommandRequ } /* (non-Javadoc) - * @see - * fr.bigeon.gclc.CommandRequestListener#commandRequest(java.lang.String) */ + * @see fr.bigeon.gclc.ConsoleManager#setPrompt(java.lang.String) */ @Override - public void commandRequest(String request) { - commands.add(request); + 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(); + } + }); } } 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 index 257545d..6946522 100644 --- a/gclc-swt/src/main/java/fr/bigeon/gclc/swt/SWTConsoleShell.java +++ b/gclc-swt/src/main/java/fr/bigeon/gclc/swt/SWTConsoleShell.java @@ -43,49 +43,40 @@ 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; + private 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 } + /** Create contents of the shell. */ + protected void createContents() { + console = new SWTConsole(this, SWT.NONE); + setText("Console Application"); //$NON-NLS-1$ + } + /** @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 index 15e96e8..f706949 100644 --- a/gclc-swt/src/test/java/fr/bigeon/gclc/swt/AppTest.java +++ b/gclc-swt/src/test/java/fr/bigeon/gclc/swt/AppTest.java @@ -38,45 +38,55 @@ 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 +/** Unit test for simple App. */ +public class AppTest extends TestCase { + protected static final long TWO_SECONDS = 2000; + + /** Create the test case * - * @param testName name of the test case - */ - public AppTest( String testName ) - { - super( testName ); + * @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 ); + /** @return the suite of tests being tested */ + public static Test suite() { + return new TestSuite(AppTest.class); } - /** - * Rigourous Test :-) - */ + /** Rigourous Test :-) */ @SuppressWarnings("static-method") - public void testApp() - { + 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)); +// swtConsole.setLayoutData( +// new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1)); // final ConsoleApplication appl = new ConsoleApplication(swtConsole, -// "exit", -// "Hello", "See you"); +// "exit", "Hello", "See you"); +// try { +// appl.add(new Command("long") { +// +// @Override +// public String tip() { +// return "a long running command"; +// } +// +// @Override +// public void execute(String... args) { +// try { +// Thread.sleep(TWO_SECONDS); +// } catch (InterruptedException e) { +// // TODO Auto-generated catch block +// e.printStackTrace(); +// } +// } +// }); +// } catch (InvalidCommandName e) { +// // TODO Auto-generated catch block +// e.printStackTrace(); +// } // shell.pack(); // shell.open(); // Thread applThread = new Thread(new Runnable() { @@ -94,9 +104,11 @@ public class AppTest // }); // applThread.start(); // while (!shell.isDisposed()) { -// if (!display.readAndDispatch()) display.sleep(); +// if (!display.readAndDispatch()) { +// display.sleep(); +// } // } // display.dispose(); - assertTrue( true ); + assertTrue(true); } } diff --git a/gclc/src/main/java/fr/bigeon/gclc/CommandRequestListener.java b/gclc/src/main/java/fr/bigeon/gclc/CommandRequestListener.java index 3a0a5be..6917341 100644 --- a/gclc/src/main/java/fr/bigeon/gclc/CommandRequestListener.java +++ b/gclc/src/main/java/fr/bigeon/gclc/CommandRequestListener.java @@ -45,7 +45,7 @@ package fr.bigeon.gclc; * @author Emmanuel Bigeon */ public interface CommandRequestListener { /** Indicates that the given command was requested to the application - * + * * @param command the command */ void commandRequest(String command); } diff --git a/gclc/src/main/java/fr/bigeon/gclc/ConsoleApplication.java b/gclc/src/main/java/fr/bigeon/gclc/ConsoleApplication.java index f42fce7..49c171b 100644 --- a/gclc/src/main/java/fr/bigeon/gclc/ConsoleApplication.java +++ b/gclc/src/main/java/fr/bigeon/gclc/ConsoleApplication.java @@ -39,26 +39,31 @@ package fr.bigeon.gclc; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; -import fr.bigeon.gclc.command.Command; import fr.bigeon.gclc.command.HelpExecutor; +import fr.bigeon.gclc.command.ICommand; import fr.bigeon.gclc.command.ICommandProvider; import fr.bigeon.gclc.command.SubedCommand; import fr.bigeon.gclc.command.UnrecognizedCommand; +import fr.bigeon.gclc.exception.CommandParsingException; import fr.bigeon.gclc.exception.CommandRunException; import fr.bigeon.gclc.exception.InvalidCommandName; +import fr.bigeon.gclc.i18n.Messages; +import fr.bigeon.gclc.manager.ConsoleManager; +import fr.bigeon.gclc.manager.SystemConsoleManager; import fr.bigeon.gclc.prompt.CLIPrompterMessages; -import fr.bigeon.gclc.system.SystemConsoleManager; /**

* A {@link ConsoleApplication} is an application that require the user to input * commands. *

* A typical use case is the following: - * + * *

  * {@link ConsoleApplication} app = new {@link ConsoleApplication#ConsoleApplication(String, String, String) ConsoleApplication("exit", "welcome", "see you latter")};
- * app.{@link ConsoleApplication#add(Command) add}("my_command", new {@link Command MyCommand()});
+ * app.{@link ConsoleApplication#add(ICommand) add}("my_command", new {@link ICommand MyCommand()});
  * app.start();
  * 
*

@@ -70,6 +75,9 @@ import fr.bigeon.gclc.system.SystemConsoleManager; * @author Emmanuel BIGEON */ public class ConsoleApplication implements ICommandProvider { + /** The class logger */ + private static final Logger LOGGER = Logger + .getLogger(ConsoleApplication.class.getName()); /** The welcome message */ private final String header; /** The good bye message */ @@ -98,132 +106,42 @@ public class ConsoleApplication implements ICommandProvider { * @param exit the keyword for the exit command of this application * @param welcome the header message to display on launch of this * application - * @param goodbye the message to display on exit */ + * @param goodbye the message to display on exit + * @throws InvalidCommandName if the exit command name is invalid */ public ConsoleApplication(ConsoleManager manager, String exit, - String welcome, String goodbye) { + String welcome, String goodbye) throws InvalidCommandName { this(manager, welcome, goodbye); - try { - root.add(new ExitCommand(exit, this)); - } catch (InvalidCommandName e) { - throw new RuntimeException("Invalid exit command name", e); //$NON-NLS-1$ - } + root.add(new ExitCommand(exit, this)); } /** @param exit the keyword for the exit command of this application * @param welcome the header message to display on launch of this * application - * @param goodbye the message to display on exit */ - public ConsoleApplication(String exit, String welcome, String goodbye) { + * @param goodbye the message to display on exit + * @throws InvalidCommandName if the exit command name is invalid */ + public ConsoleApplication(String exit, String welcome, + String goodbye) throws InvalidCommandName { this(new SystemConsoleManager(), welcome, goodbye); - try { - root.add(new ExitCommand(exit, this)); - } catch (InvalidCommandName e) { - throw new RuntimeException("Invalid exit command name", e); //$NON-NLS-1$ - } + root.add(new ExitCommand(exit, this)); } @Override - public final boolean add(Command cmd) throws InvalidCommandName { + public final boolean add(ICommand cmd) throws InvalidCommandName { return root.add(cmd); } - /** Launches the prompting application */ - public final void start() { - if (header != null) manager.println(header); - running = true; - do { - String cmd = manager.prompt(); - if (cmd.isEmpty()) { - continue; - } - for (CommandRequestListener listener : listeners) { - listener.commandRequest(cmd); - } - interpretCommand(cmd); - } while (running); - if (footer != null) manager.println(footer); - } - - /** @param cmd the command to interpret */ - public final void interpretCommand(String cmd) { - List args = new ArrayList<>(); - // parse the string to separate arguments - int index = 0; - int startIndex = 0; - boolean escaped = false; - boolean inString = false; - while (index < cmd.length()) { - if (escaped) { - escaped = false; - } else if (cmd.charAt(index) == '\\') { - escaped = true; - } else if (cmd.charAt(index) == ' ') { - if (!inString) { - if (startIndex != index) { - String arg = cmd.substring(startIndex, index); - if (!arg.isEmpty()) { - args.add(arg); - } - } - startIndex = index + 1; - } - } else if (cmd.charAt(index) == '"') { - if (inString) { - inString = false; - args.add(cmd.substring(startIndex + 1, index)); - startIndex = index + 2; - index++; - if (index < cmd.length() && cmd.charAt(index) != ' ') { - manager.println("Command line cannot be parsed"); //$NON-NLS-1$ - return; - } - } else if (startIndex == index) { - inString = true; - } - } - index++; - } - if (startIndex < cmd.length()) { - String arg = cmd.substring(startIndex, index); - if (!arg.isEmpty()) { - args.add(arg); - } - } - if (args.size() > 0) { - try { - executeSub( - args.get(0), - Arrays.copyOfRange(args.toArray(new String[0]), 1, - args.size())); - } catch (CommandRunException e) { - manager.println("The command '" + cmd + "' failed due to:"); //$NON-NLS-1$ //$NON-NLS-2$ - manager.println(e.getLocalizedMessage()); - } - } - } - - /** Exit this running application before next command prompt */ - public final void exit() { - running = false; - } - /** Adds help command on the given key - * + * * @param cmd the handle for help - * @return if the help command was added */ - public final boolean addHelpCommand(String cmd) { - try { - return root.add(new HelpExecutor(cmd, manager, root)); - } catch (InvalidCommandName e) { - return false; - } + * @return if the help command was added + * @throws InvalidCommandName if the help command was not valid */ + public final boolean addHelpCommand(String cmd) throws InvalidCommandName { + return root.add(new HelpExecutor(cmd, manager, root)); } - /* (non-Javadoc) - * @see fr.bigeon.gclc.command.ICommandProvider#get(java.lang.String) */ - @Override - public final Command get(String command) { - return root.get(command); + /** @param listener the listener to remove. */ + public final void addListener(CommandRequestListener listener) { + listeners.add(listener); } /* (non-Javadoc) @@ -235,14 +153,100 @@ public class ConsoleApplication implements ICommandProvider { root.executeSub(command, args); } + /** Exit this running application before next command prompt */ + public final void exit() { + running = false; + } + + /* (non-Javadoc) + * @see fr.bigeon.gclc.command.ICommandProvider#get(java.lang.String) */ + @Override + public final ICommand get(String command) { + return root.get(command); + } + /** @return the manager */ public final ConsoleManager getManager() { return manager; } - /** @param listener the listener to remove. */ - public final void addListener(CommandRequestListener listener) { - listeners.add(listener); + /** @param cmd the command to interpret */ + public final void interpretCommand(String cmd) { + List args; + try { + args = splitCommand(cmd); + } catch (CommandParsingException e1) { + manager.println("Command line cannot be parsed"); //$NON-NLS-1$ + LOGGER.log(Level.INFO, "Invalid user command " + cmd, e1); //$NON-NLS-1$ + return; + } + if (!args.isEmpty()) { + try { + executeSub(args.get(0), Arrays.copyOfRange( + args.toArray(new String[0]), 1, args.size())); + } catch (final CommandRunException e) { + LOGGER.log(Level.WARNING, "Command failed: " + cmd, e); //$NON-NLS-1$ + manager.println(Messages + .getString("ConsoleApplication.cmd.failed", cmd)); //$NON-NLS-1$ + manager.println(e.getLocalizedMessage()); + } + } + } + + /** Splits a command in the diferrent arguments + * + * @param cmd the command to split in its parts + * @return the list of argument preceded by the command name + * @throws CommandParsingException if the parsing of the command failed */ + private static List splitCommand(String cmd) throws CommandParsingException { + final List args = new ArrayList<>(); + // parse the string to separate arguments + int index = 0; + int startIndex = 0; + boolean escaped = false; + boolean inString = false; + while (index < cmd.length()) { + char c = cmd.charAt(index); + index++; + if (escaped || c == '\\') { + escaped = !escaped; + continue; + } + if (c == ' ' && !inString) { + final String arg = cmd.substring(startIndex, index - 1); + if (!arg.isEmpty()) { + args.add(arg); + } + startIndex = index; + } else if (c == '"') { + if (inString) { + inString = false; + args.add(endOfString(cmd, startIndex, index)); + index++; + startIndex = index; + } + inString = startIndex == index - 1; + } + } + final String arg = cmd.substring(startIndex, cmd.length()); + if (!arg.isEmpty()) { + args.add(arg); + } + return args; + } + + /** @param cmd the command to parse + * @param startIndex the starting point of the parsing + * @param index the index of the current position + * @return the argument + * @throws CommandParsingException if the end of string does not mark end of + * command and is not followed by a space */ + private static String endOfString(String cmd, int startIndex, + int index) throws CommandParsingException { + if (index + 1 < cmd.length() && cmd.charAt(index + 1) != ' ') { + throw new CommandParsingException("Misplaced quote"); //$NON-NLS-1$ + } + return cmd.substring(startIndex + 1, index - 1); } /** @param listener the listener to remove */ @@ -254,36 +258,53 @@ public class ConsoleApplication implements ICommandProvider { } } } + + /** Launches the prompting application */ + public final void start() { + if (header != null) { + manager.println(header); + } + running = true; + do { + final String cmd = manager.prompt(); + if (cmd.isEmpty()) { + continue; + } + for (final CommandRequestListener listener : listeners) { + listener.commandRequest(cmd); + } + interpretCommand(cmd); + } while (running); + if (footer != null) { + manager.println(footer); + } + } } /**

* A command to exit a {@link ConsoleApplication}. * * @author Emmanuel BIGEON */ -class ExitCommand extends Command { +class ExitCommand implements ICommand { /** The exit command manual message key */ private static final String EXIT_MAN = "exit.man"; //$NON-NLS-1$ /** The tip of the exit command */ private static final String EXIT = "exit.tip"; //$NON-NLS-1$ /** The application that will be exited when this command runs */ private final ConsoleApplication app; + /** The exit command name */ + private final String name; /** @param name the name of the command * @param app the application to exit */ public ExitCommand(String name, ConsoleApplication app) { - super(name); + this.name = name; this.app = app; } - @Override - public String tip() { - return CLIPrompterMessages.getString(EXIT); - } - - @Override - public void help(ConsoleManager manager, String... args) { - manager.println(CLIPrompterMessages - .getString(EXIT_MAN, (Object[]) args)); + /** The actions to take before exiting */ + public void beforeExit() { + // Do nothing by default } @Override @@ -292,8 +313,21 @@ class ExitCommand extends Command { app.exit(); } - /** The actions to take before exiting */ - public void beforeExit() { - // Do nothing by default + /* (non-Javadoc) + * @see fr.bigeon.gclc.command.ICommand#getCommandName() */ + @Override + public String getCommandName() { + return name; + } + + @Override + public void help(ConsoleManager manager, String... args) { + manager.println( + CLIPrompterMessages.getString(EXIT_MAN, (Object[]) args)); + } + + @Override + public String tip() { + return CLIPrompterMessages.getString(EXIT); } } 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 6e24c45..2fba973 100644 --- a/gclc/src/main/java/fr/bigeon/gclc/command/Command.java +++ b/gclc/src/main/java/fr/bigeon/gclc/command/Command.java @@ -38,7 +38,7 @@ */ package fr.bigeon.gclc.command; -import fr.bigeon.gclc.ConsoleManager; +import fr.bigeon.gclc.manager.ConsoleManager; /**

* A command to execute. It is mandatory that it has a name and that name cannot @@ -59,12 +59,12 @@ import fr.bigeon.gclc.ConsoleManager; *

* 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 { +public abstract class Command implements ICommand { /** - * + * */ private static final String EOL_LINUX = "\n"; //$NON-NLS-1$ /** The name of the command */ @@ -76,41 +76,34 @@ public abstract class Command { this.name = name; } - /** @return the command's name */ + /** @return a brief description of the command */ + protected String brief() { + return " " + tip(); //$NON-NLS-1$ + } + + /* (non-Javadoc) + * @see fr.bigeon.gclc.command.ICommand#getCommandName() */ + @Override public final String getCommandName() { return name; } - /** @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. - *

- * 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 */ - public void help(ConsoleManager manager, String... args) { + /* (non-Javadoc) + * @see fr.bigeon.gclc.command.ICommand#help(fr.bigeon.gclc.ConsoleManager, + * java.lang.String) */ + @Override + public final void help(ConsoleManager manager, String... args) { manager.println(getCommandName()); manager.println(brief()); manager.println(); manager.println("Usage:"); //$NON-NLS-1$ manager.println(usagePattern()); manager.println(); - String details = usageDetail(); + final String details = usageDetail(); if (details != null && !details.isEmpty()) { manager.print(details); - if (!(details.endsWith(EOL_LINUX) || details.endsWith(System.lineSeparator()))) { + if (!(details.endsWith(EOL_LINUX) || + details.endsWith(System.lineSeparator()))) { manager.println(); } } @@ -119,7 +112,7 @@ public abstract class Command { /**

* 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() { @@ -129,17 +122,9 @@ public abstract class Command { /**

* 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(); } - - /** @return a brief description of the command */ - protected String brief() { - return " " + tip(); //$NON-NLS-1$ - } - - /** @return a tip on the command */ - public abstract String tip(); } diff --git a/gclc/src/main/java/fr/bigeon/gclc/command/CommandParameters.java b/gclc/src/main/java/fr/bigeon/gclc/command/CommandParameters.java index 968c440..aec0b64 100644 --- a/gclc/src/main/java/fr/bigeon/gclc/command/CommandParameters.java +++ b/gclc/src/main/java/fr/bigeon/gclc/command/CommandParameters.java @@ -42,6 +42,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Set; /**

@@ -50,10 +51,14 @@ import java.util.Set; * * @author Emmanuel BIGEON */ public class CommandParameters { + /** + * + */ + private static final int STRINGARG_NUMBER_OF_ELEMENTS = 2; /** Boolean arguments */ - private final HashMap boolArgs = new HashMap<>(); + private final Map boolArgs = new HashMap<>(); /** String arguments */ - private final HashMap stringArgs = new HashMap<>(); + private final Map stringArgs = new HashMap<>(); /** Arguments restriction on the named ones */ private final boolean strict; /** additional (unnamed) parameters */ @@ -65,49 +70,26 @@ public class CommandParameters { @SuppressWarnings("boxing") public CommandParameters(Set bools, Set strings, boolean strict) { - for (String string : bools) { + for (final String string : bools) { boolArgs.put(string, false); } - for (String string : strings) { + for (final String string : strings) { stringArgs.put(string, null); } this.strict = strict; } - /** @param args the arguments to parse - * @return if the arguments were parsed */ - @SuppressWarnings("boxing") - public boolean parseArgs(String... args) { - int i = 0; - while (i < args.length) { - String name = args[i]; - if (name.startsWith("-")) { //$NON-NLS-1$ - name = name.substring(1); - if (boolArgs.containsKey(name)) { - boolArgs.put(name, true); - } else if (stringArgs.containsKey(name)) { - i++; - if (!(args.length > i)) { - return false; - } - stringArgs.put(name, args[i]); - } else if (strict) { - return false; - } else { - additional.add(name); - } - } - i++; - } - return true; - } - /** @param key the key * @return the associated value, null if it was not specified */ public String get(String key) { return stringArgs.get(key); } + /** @return additional non parsed parameters */ + public List getAdditionals() { + return Collections.unmodifiableList(additional); + } + /** @param key the key * @return if the key was specified */ @SuppressWarnings("boxing") @@ -115,17 +97,63 @@ public class CommandParameters { return boolArgs.get(key); } - /** @param string the key - * @param value the value */ - public void set(String string, String value) { - if (stringArgs.containsKey(string)) { - stringArgs.put(string, value); + /** @param args the arguments to parse + * @return if the arguments were parsed */ + public boolean parseArgs(String... args) { + int i = 0; + while (i < args.length) { + String next = null; + if (i < args.length - 1) { + next = args[i + 1]; + } + int p = parseArg(args[i], next); + if (p == 0) { + return false; + } + i += p; } + return true; + } - /** @return additional non parsed parameters */ - public List getAdditionals() { - return Collections.unmodifiableList(additional); + /** Attempt to parse an argument. + *

+ * This method return 0 if the parsing was incorrect, or the number of + * parsed elements. + * + * @param arg the argument + * @param next the next element + * @return the number of element read */ + private int parseArg(String arg, String next) { + if (!arg.startsWith("-")) { //$NON-NLS-1$ + return 1; + } + String name = arg.substring(1); + if (boolArgs.containsKey(name)) { + boolArgs.put(name, Boolean.TRUE); + return 1; + } + if (stringArgs.containsKey(name)) { + return parseStringArg(name, next); + } + if (strict) { + return 0; + } + additional.add(name); + return 1; + } + + /** Add a string arg value + * + * @param name the string arg name + * @param next the string arg value + * @return 2 or 0 if next is invalid */ + private int parseStringArg(String name, String next) { + if (next == null) { + return 0; + } + stringArgs.put(name, next); + return STRINGARG_NUMBER_OF_ELEMENTS; } /** @param string the key @@ -136,4 +164,12 @@ public class CommandParameters { boolArgs.put(string, value); } } + + /** @param string the key + * @param value the value */ + public void set(String string, String value) { + if (stringArgs.containsKey(string)) { + stringArgs.put(string, value); + } + } } diff --git a/gclc/src/main/java/fr/bigeon/gclc/command/CommandProvider.java b/gclc/src/main/java/fr/bigeon/gclc/command/CommandProvider.java index d0cdec0..e9152a7 100644 --- a/gclc/src/main/java/fr/bigeon/gclc/command/CommandProvider.java +++ b/gclc/src/main/java/fr/bigeon/gclc/command/CommandProvider.java @@ -46,37 +46,29 @@ import fr.bigeon.gclc.exception.InvalidCommandName; * * @author Emmanuel BIGEON */ public class CommandProvider implements ICommandProvider { + /** The minus character */ + private static final String MINUS = "-"; //$NON-NLS-1$ + /** The space character */ + private static final String SPACE = " "; //$NON-NLS-1$ /** The commands map */ - protected final Set commands; + protected final Set commands; /** The error command to be executed when the command isn't recognized */ - protected final Command error; + protected final ICommand error; /** @param error the error command */ - public CommandProvider(Command error) { + public CommandProvider(ICommand error) { super(); commands = new HashSet<>(); this.error = error; } - /* (non-Javadoc) - * @see fr.bigeon.gclc.command.ICommandProvider#get(java.lang.String) */ - @Override - public Command get(String commandName) { - for (Command command : commands) { - if (command.getCommandName().equals(commandName)) { - return command; - } - } - return null; - } - /* (non-Javadoc) * @see fr.bigeon.gclc.command.ICommandProvider#add(java.lang.String, * fr.bigeon.gclc.command.Command) */ @Override - public boolean add(Command value) throws InvalidCommandName { - String name = value.getCommandName(); - if (name == null || name.startsWith("-") || name.contains(" ")) { //$NON-NLS-1$ //$NON-NLS-2$ + public boolean add(ICommand value) throws InvalidCommandName { + final String name = value.getCommandName(); + if (name == null || name.startsWith(MINUS) || name.contains(SPACE)) { throw new InvalidCommandName(); } return commands.add(value); @@ -84,7 +76,7 @@ public class CommandProvider implements ICommandProvider { @Override public void executeSub(String cmd, String... args) { - for (Command command : commands) { + for (final ICommand command : commands) { if (command.getCommandName().equals(cmd)) { command.execute(args); return; @@ -92,4 +84,16 @@ public class CommandProvider implements ICommandProvider { } error.execute(cmd); } + + /* (non-Javadoc) + * @see fr.bigeon.gclc.command.ICommandProvider#get(java.lang.String) */ + @Override + public ICommand get(String commandName) { + for (final ICommand command : commands) { + if (command.getCommandName().equals(commandName)) { + return command; + } + } + return null; + } } 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 bc6245d..ccaa55f 100644 --- a/gclc/src/main/java/fr/bigeon/gclc/command/HelpExecutor.java +++ b/gclc/src/main/java/fr/bigeon/gclc/command/HelpExecutor.java @@ -38,27 +38,32 @@ */ package fr.bigeon.gclc.command; -import fr.bigeon.gclc.ConsoleManager; +import fr.bigeon.gclc.manager.ConsoleManager; import fr.bigeon.gclc.prompt.CLIPrompterMessages; -/**

- * TODO +/** A command to print help of an other command. + *

+ * This command will display the help of an other command * * @author Emmanuel BIGEON */ public class HelpExecutor extends Command { /** The command to execute the help of */ - private final Command cmd; + private final ICommand cmd; /** The console manager */ private final ConsoleManager consoleManager; /** @param cmdName the command name * @param consoleManager the manager for the console * @param cmd the command to execute the help of */ - public HelpExecutor(String cmdName, ConsoleManager consoleManager, Command cmd) { + public HelpExecutor(String cmdName, ConsoleManager consoleManager, + ICommand 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; } @@ -70,15 +75,17 @@ public class HelpExecutor extends Command { } /* (non-Javadoc) - * @see fr.bigeon.gclc.command.Command#help() */ + * @see fr.bigeon.gclc.command.Command#brief() */ @Override - public void help(ConsoleManager manager, String... args) { - manager.println(getCommandName()); - manager.println(" A command to get help for other commands"); //$NON-NLS-1$ - manager.println(); - manager.println("Usage"); //$NON-NLS-1$ - manager.println(" " + getCommandName() + " "); //$NON-NLS-1$ //$NON-NLS-2$ - manager.println(); + protected String brief() { + return " A command to get help for other commands"; //$NON-NLS-1$ + } + + /* (non-Javadoc) + * @see fr.bigeon.gclc.command.Command#usagePattern() */ + @Override + protected String usagePattern() { + return getCommandName() + " "; //$NON-NLS-1$ } /* (non-Javadoc) diff --git a/gclc/src/main/java/fr/bigeon/gclc/command/ICommand.java b/gclc/src/main/java/fr/bigeon/gclc/command/ICommand.java new file mode 100644 index 0000000..263eb84 --- /dev/null +++ b/gclc/src/main/java/fr/bigeon/gclc/command/ICommand.java @@ -0,0 +1,77 @@ +/* + * Copyright Bigeon Emmanuel (2014) + * + * emmanuel@bigeon.fr + * + * This software is a computer program whose purpose is to + * provide a generic framework 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:fr.bigeon.gclc.command.ICommand.java + * Created on: May 31, 2016 + */ +package fr.bigeon.gclc.command; + +import fr.bigeon.gclc.manager.ConsoleManager; + +/** The contract of commands + *

+ * This interface describe the contract of commands + * + * @author Emmanuel Bigeon */ +public interface ICommand { + + /** @param args the arguments of the command (some expect an empty array) */ + void execute(String... args); + + /** @return the command's name */ + String getCommandName(); + + /** 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 */ + void help(ConsoleManager manager, String... args); + + /** @return a tip on the command */ + String tip(); + +} \ No newline at end of file diff --git a/gclc/src/main/java/fr/bigeon/gclc/command/ICommandProvider.java b/gclc/src/main/java/fr/bigeon/gclc/command/ICommandProvider.java index b4f888c..f217aa9 100644 --- a/gclc/src/main/java/fr/bigeon/gclc/command/ICommandProvider.java +++ b/gclc/src/main/java/fr/bigeon/gclc/command/ICommandProvider.java @@ -45,25 +45,14 @@ import fr.bigeon.gclc.exception.InvalidCommandName; * @author Emmanuel BIGEON */ public interface ICommandProvider { - /**

- * This method provide the command with the given name found. If no command - * with this name is found, an error command is usually returned. If there - * are several commands with the same name, the behavior is unspecified. - * Depending on the implementation, it may return an error command or the - * first command with this name found. - * - * @param command the name of the command the user wishes to execute - * @return the command to execute */ - public Command get(String command); - /**

* Adds a command to this provider, if no command was associated with the * given key - * + * * @param value the command to execute * @return if the command was added * @throws InvalidCommandName if the command name is invalid */ - public boolean add(Command value) throws InvalidCommandName; + public boolean add(ICommand value) throws InvalidCommandName; /**

* This method executes the command with the given name found. If no command @@ -71,9 +60,20 @@ public interface ICommandProvider { * are several commands with the same name, the behavior is unspecified. * Depending on the implementation, it may run an error command or prompt * the user for a choice. - * + * * @param command the name of the command the user wishes to execute * @param args the arguments for the command */ public void executeSub(String command, String... args); + /**

+ * This method provide the command with the given name found. If no command + * with this name is found, an error command is usually returned. If there + * are several commands with the same name, the behavior is unspecified. + * Depending on the implementation, it may return an error command or the + * first command with this name found. + * + * @param command the name of the command the user wishes to execute + * @return the command to execute */ + public ICommand get(String command); + } \ No newline at end of file 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 e72ed85..141f7fa 100644 --- a/gclc/src/main/java/fr/bigeon/gclc/command/ParametrizedCommand.java +++ b/gclc/src/main/java/fr/bigeon/gclc/command/ParametrizedCommand.java @@ -43,7 +43,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import fr.bigeon.gclc.ConsoleManager; +import fr.bigeon.gclc.manager.ConsoleManager; /**

* A command relying on the {@link CommandParameters} to store parameters values @@ -51,7 +51,8 @@ import fr.bigeon.gclc.ConsoleManager; * @author Emmanuel BIGEON */ public abstract class ParametrizedCommand extends Command { - /** If the command may use interactive prompting for required parameters that + /** If the command may use interactive prompting for required parameters + * that * were not provided on execution */ private boolean interactive = true; /** The manager */ @@ -62,7 +63,8 @@ public abstract class ParametrizedCommand extends Command { private final Map stringParams = new HashMap<>(); /** The parameters mandatory status */ private final Map params = new HashMap<>(); - /** The restriction of provided parameters on execution to declared paramters + /** The restriction of provided parameters on execution to declared + * paramters * in the status maps. */ private final boolean strict; @@ -85,7 +87,7 @@ public abstract class ParametrizedCommand extends Command { /**

* Add a parameter to the defined parameters - * + * * @param param the parameter identification * @param stringOrBool if the parameter is a parameter with an argument * @param needed if the parameter is required */ @@ -106,27 +108,32 @@ public abstract class ParametrizedCommand extends Command { } } + /** @param parameters the command parameters */ + protected abstract void doExecute(CommandParameters parameters); + /* (non-Javadoc) * @see fr.bigeon.gclc.command.Command#execute(java.lang.String[]) */ @SuppressWarnings("boxing") @Override public final void execute(String... args) { - CommandParameters parameters = new CommandParameters( + final CommandParameters parameters = new CommandParameters( boolParams.keySet(), stringParams.keySet(), strict); if (!parameters.parseArgs(args)) { // ERROR the parameters could not be correctly parsed manager.println("Unable to read arguments"); //$NON-NLS-1$ return; } - List toProvide = new ArrayList<>(); - for (String string : params.keySet()) { + final List toProvide = new ArrayList<>(); + for (final String string : params.keySet()) { if (params.get(string) && parameters.get(string) == null) { - if (!interactive) return; + if (!interactive) { + return; + } toProvide.add(string); } } // for each needed parameters that is missing, prompt the user. - for (String string : toProvide) { + for (final String string : toProvide) { String value = manager.prompt(string); while (value.isEmpty()) { value = manager.prompt(string); @@ -135,7 +142,4 @@ public abstract class ParametrizedCommand extends Command { } doExecute(parameters); } - - /** @param parameters the command parameters */ - protected abstract void doExecute(CommandParameters parameters); } diff --git a/gclc/src/main/java/fr/bigeon/gclc/command/SubedCommand.java b/gclc/src/main/java/fr/bigeon/gclc/command/SubedCommand.java index 478f3c2..3709c5a 100644 --- a/gclc/src/main/java/fr/bigeon/gclc/command/SubedCommand.java +++ b/gclc/src/main/java/fr/bigeon/gclc/command/SubedCommand.java @@ -38,123 +38,91 @@ package fr.bigeon.gclc.command; import java.util.Arrays; -import fr.bigeon.gclc.ConsoleManager; import fr.bigeon.gclc.exception.InvalidCommandName; +import fr.bigeon.gclc.manager.ConsoleManager; /**

* A subed command is a command that can execute sub commands depending on the * first argument. * * @author Emmanuel BIGEON */ -public class SubedCommand extends Command implements ICommandProvider { +public class SubedCommand implements ICommandProvider, ICommand { + /** The tab character */ + private static final String TAB = "\t"; //$NON-NLS-1$ /**

* The command to execute when this command is called with no sub arguments. * This may be null, in which case the command should have arguments. */ - private final Command noArgCommand; + private final ICommand noArgCommand; /** A tip on this command. */ private final String tip; /** The provider */ private final CommandProvider provider; + /** The name of the command */ + private final String name; /** @param name the name of the command * @param error the error to execute when called with wrong usage */ - public SubedCommand(String name, Command error) { - super(name); + public SubedCommand(String name, ICommand error) { + this.name = name; provider = new CommandProvider(error); noArgCommand = null; tip = null; } - /** @param name the name of the command - * @param error the error to execute when called with wrong usage - * @param tip the help tip associated */ - public SubedCommand(String name, Command error, String tip) { - super(name); - provider = new CommandProvider(error); - noArgCommand = null; - this.tip = tip; - } - - /** @param name the name of the command - * @param noArgCommand the command to execute - * @param error the error to execute when called with wrong usage - * @param tip the help tip associated */ - public SubedCommand(String name, Command error, Command noArgCommand, - String tip) { - super(name); - provider = new CommandProvider(error); - this.noArgCommand = noArgCommand; - this.tip = tip; - } - /** @param name the name of the command * @param noArgCommand the command to execute when no extra parameter are * provided * @param error the error to execute when called with wrong usage */ - public SubedCommand(String name, Command error, Command noArgCommand) { - super(name); + public SubedCommand(String name, ICommand error, ICommand noArgCommand) { + this.name = name; provider = new CommandProvider(error); this.noArgCommand = noArgCommand; tip = null; } + /** @param name the name of the command + * @param noArgCommand the command to execute + * @param error the error to execute when called with wrong usage + * @param tip the help tip associated */ + public SubedCommand(String name, ICommand error, ICommand noArgCommand, + String tip) { + this.name = name; + provider = new CommandProvider(error); + this.noArgCommand = noArgCommand; + this.tip = tip; + } + + /** @param name the name of the command + * @param error the error to execute when called with wrong usage + * @param tip the help tip associated */ + public SubedCommand(String name, ICommand error, String tip) { + this.name = name; + provider = new CommandProvider(error); + noArgCommand = null; + this.tip = tip; + } + + @Override + public boolean add(ICommand value) throws InvalidCommandName { + return provider.add(value); + } + /* (non-Javadoc) * @see fr.bigeon.acide.Command#execute(java.lang.String[]) */ @Override public void execute(String... args) { if (args.length == 0 || args[0].startsWith("-")) { //$NON-NLS-1$ - if (noArgCommand != null) + if (noArgCommand != null) { noArgCommand.execute(args); - else provider.error.execute(args); - } else { - executeSub(args[0], Arrays.copyOfRange(args, 1, args.length)); - } - } - - /* (non-Javadoc) - * @see fr.bigeon.gclc.command.Command#help() */ - @Override - public void help(ConsoleManager manager, String... args) { - if (args.length != 0 && !args[0].startsWith("-")) { //$NON-NLS-1$ - // Specific - Command c = get(args[0]); - if (c != null) - c.help(manager, Arrays.copyOfRange(args, 1, args.length)); - else { + } else { provider.error.execute(args); } } else { - // Generic - if (noArgCommand != null) - if (noArgCommand.tip() != null) - manager.println("\t" + noArgCommand.tip()); //$NON-NLS-1$ - for (Command cmd : provider.commands) { - if (cmd.tip() == null) - manager.println("\t" + cmd.getCommandName()); //$NON-NLS-1$ - else manager.println("\t" + cmd.getCommandName() + ": " + //$NON-NLS-1$ //$NON-NLS-2$ - cmd.tip()); - } + executeSub(args[0], Arrays.copyOfRange(args, 1, args.length)); } } - /* (non-Javadoc) - * @see fr.bigeon.gclc.command.Command#tip() */ - @Override - public String tip() { - return tip; - } - - @Override - public Command get(String commandName) { - return provider.get(commandName); - } - - @Override - public boolean add(Command value) throws InvalidCommandName { - return provider.add(value); - } - /* (non-Javadoc) * @see * fr.bigeon.gclc.command.ICommandProvider#executeSub(java.lang.String, @@ -164,6 +132,53 @@ public class SubedCommand extends Command implements ICommandProvider { provider.executeSub(command, args); } + @Override + public ICommand get(String commandName) { + return provider.get(commandName); + } + + /* (non-Javadoc) + * @see fr.bigeon.gclc.command.ICommand#getCommandName() */ + @Override + public String getCommandName() { + return name; + } + + /* (non-Javadoc) + * @see fr.bigeon.gclc.command.Command#help() */ + @Override + public void help(ConsoleManager manager, String... args) { + if (args.length != 0 && !args[0].startsWith("-")) { //$NON-NLS-1$ + // Specific + final ICommand c = get(args[0]); + if (c != null) { + c.help(manager, Arrays.copyOfRange(args, 1, args.length)); + } else { + provider.error.execute(args); + } + } else { + // Generic + if (noArgCommand != null && noArgCommand.tip() != null) { + manager.println(TAB + noArgCommand.tip()); + } + for (final ICommand cmd : provider.commands) { + if (cmd.tip() == null) { + manager.println(TAB + cmd.getCommandName()); + } else { + manager.println(TAB + cmd.getCommandName() + ": " + //$NON-NLS-1$ + cmd.tip()); + } + } + } + } + + /* (non-Javadoc) + * @see fr.bigeon.gclc.command.Command#tip() */ + @Override + public String tip() { + return tip; + } + /* (non-Javadoc) * @see java.lang.Object#toString() */ @Override diff --git a/gclc/src/main/java/fr/bigeon/gclc/command/UnrecognizedCommand.java b/gclc/src/main/java/fr/bigeon/gclc/command/UnrecognizedCommand.java index c9222f2..fc82832 100644 --- a/gclc/src/main/java/fr/bigeon/gclc/command/UnrecognizedCommand.java +++ b/gclc/src/main/java/fr/bigeon/gclc/command/UnrecognizedCommand.java @@ -38,14 +38,14 @@ */ package fr.bigeon.gclc.command; -import fr.bigeon.gclc.ConsoleManager; +import fr.bigeon.gclc.manager.ConsoleManager; import fr.bigeon.gclc.prompt.CLIPrompterMessages; /**

* The error message for unrecognized commands * * @author Emmanuel BIGEON */ -public final class UnrecognizedCommand extends Command { +public final class UnrecognizedCommand implements ICommand { /** The unrecognized command key */ private static final String UNRECOGNIZED_CMD = "unrecognized.cmd"; //$NON-NLS-1$ /** The unrecognized command key */ @@ -55,7 +55,6 @@ public final class UnrecognizedCommand extends Command { /** @param manager the console manager to use */ public UnrecognizedCommand(ConsoleManager manager) { - super(new String()); if (manager == null) { throw new NullPointerException("The argument cannot be null"); //$NON-NLS-1$ } @@ -63,8 +62,20 @@ public final class UnrecognizedCommand extends Command { } @Override - public String tip() { - return null; + public void execute(String... args) { + if (args.length > 0) { + manager.println(CLIPrompterMessages.getString(UNRECOGNIZED_CMD, + (Object[]) args)); + } else { + manager.println(CLIPrompterMessages.getString(EXPECTED_CMD)); + } + } + + /* (non-Javadoc) + * @see fr.bigeon.gclc.command.ICommand#getCommandName() */ + @Override + public String getCommandName() { + return ""; //$NON-NLS-1$ } @Override @@ -75,10 +86,7 @@ public final class UnrecognizedCommand extends Command { } @Override - public void execute(String... args) { - if (args.length > 0) - manager.println(CLIPrompterMessages.getString(UNRECOGNIZED_CMD, - (Object[]) args)); - else manager.println(CLIPrompterMessages.getString(EXPECTED_CMD)); + public String tip() { + return null; } } \ No newline at end of file diff --git a/gclc/src/main/java/fr/bigeon/gclc/command/package-info.java b/gclc/src/main/java/fr/bigeon/gclc/command/package-info.java index f1dfae2..d24d6b3 100644 --- a/gclc/src/main/java/fr/bigeon/gclc/command/package-info.java +++ b/gclc/src/main/java/fr/bigeon/gclc/command/package-info.java @@ -35,8 +35,17 @@ /** gclc:fr.bigeon.gclc.command.package-info.java * Created on: Sep 6, 2014 */ -/**

- * TODO +/** This package groups elements related to + * {@link fr.bigeon.gclc.command.ICommand} + *

+ * There are some implementations, such as the + * {@link fr.bigeon.gclc.command.ParametrizedCommand} for commands with a + * predefined set of flags and option taking a string as value, the + * {@link fr.bigeon.gclc.command.SubedCommand} for a command that is declined in + * a set of sub commands, the {@link fr.bigeon.gclc.command.HelpExecutor} for + * help display of other commands and the + * {@link fr.bigeon.gclc.command.UnrecognizedCommand} that should not be + * directly accessible to the user, but will be used in case of error in typing. * * @author Emmanuel BIGEON */ package fr.bigeon.gclc.command; \ No newline at end of file diff --git a/gclc/src/main/java/fr/bigeon/gclc/system/package-info.java b/gclc/src/main/java/fr/bigeon/gclc/exception/CommandParsingException.java similarity index 68% rename from gclc/src/main/java/fr/bigeon/gclc/system/package-info.java rename to gclc/src/main/java/fr/bigeon/gclc/exception/CommandParsingException.java index 445b10f..2091f6a 100644 --- a/gclc/src/main/java/fr/bigeon/gclc/system/package-info.java +++ b/gclc/src/main/java/fr/bigeon/gclc/exception/CommandParsingException.java @@ -33,14 +33,33 @@ * knowledge of the CeCILL license and that you accept its terms. */ /** - * gclc:fr.bigeon.gclc.system.package-info.java - * Created on: Dec 19, 2014 + * gclc:fr.bigeon.gclc.exception.CommandParsingException.java + * Created on: Jun 1, 2016 */ -/** - *

- * The basic system based console manager elements +package fr.bigeon.gclc.exception; + +/** An exception raised during command parsing * - * @author Emmanuel BIGEON - * - */ -package fr.bigeon.gclc.system; \ No newline at end of file + * @author Emmanuel Bigeon */ +public class CommandParsingException extends Exception { + + /** svuid */ + private static final long serialVersionUID = 1L; + + /** @param message an explaination + * @param cause the cause */ + public CommandParsingException(String message, Throwable cause) { + super(message, cause); + } + + /** @param message an explaination */ + public CommandParsingException(String message) { + super(message); + } + + /** @param cause the cause */ + public CommandParsingException(Throwable cause) { + super(cause); + } + +} diff --git a/gclc/src/main/java/fr/bigeon/gclc/exception/CommandRunException.java b/gclc/src/main/java/fr/bigeon/gclc/exception/CommandRunException.java index 8a31ae5..35a14a5 100644 --- a/gclc/src/main/java/fr/bigeon/gclc/exception/CommandRunException.java +++ b/gclc/src/main/java/fr/bigeon/gclc/exception/CommandRunException.java @@ -45,21 +45,21 @@ package fr.bigeon.gclc.exception; public class CommandRunException extends RuntimeException { /** - * + * */ private static final long serialVersionUID = 1L; + /** @param message a message */ + public CommandRunException(String message) { + super(message); + } + /** @param message a message * @param cause the cause */ public CommandRunException(String message, Throwable cause) { super(message, cause); } - /** @param message a message */ - public CommandRunException(String message) { - super(message); - } - /* (non-Javadoc) * @see java.lang.Throwable#getLocalizedMessage() */ @Override diff --git a/gclc/src/main/java/fr/bigeon/gclc/exception/InvalidCommandName.java b/gclc/src/main/java/fr/bigeon/gclc/exception/InvalidCommandName.java index 2d2cd54..8e7d46f 100644 --- a/gclc/src/main/java/fr/bigeon/gclc/exception/InvalidCommandName.java +++ b/gclc/src/main/java/fr/bigeon/gclc/exception/InvalidCommandName.java @@ -46,7 +46,7 @@ package fr.bigeon.gclc.exception; public class InvalidCommandName extends Exception { /** - * + * */ private static final long serialVersionUID = 1L; diff --git a/gclc/src/main/java/fr/bigeon/gclc/i18n/Messages.java b/gclc/src/main/java/fr/bigeon/gclc/i18n/Messages.java new file mode 100644 index 0000000..097b828 --- /dev/null +++ b/gclc/src/main/java/fr/bigeon/gclc/i18n/Messages.java @@ -0,0 +1,81 @@ +/* + * Copyright Bigeon Emmanuel (2014) + * + * emmanuel@bigeon.fr + * + * This software is a computer program whose purpose is to + * provide a generic framework 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:fr.bigeon.gclc.i18n.Messages.java + * Created on: Jun 1, 2016 + */ +package fr.bigeon.gclc.i18n; + +import java.text.MessageFormat; +import java.util.MissingResourceException; +import java.util.ResourceBundle; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** Internationalization class. + * + * @author Emmanuel Bigeon */ +public class Messages { + /** The resource bundle name */ + private static final String BUNDLE_NAME = "fr.bigeon.gclc.l10n.messages"; //$NON-NLS-1$ + + /** The resource bundle */ + private static final ResourceBundle RESOURCE_BUNDLE = ResourceBundle + .getBundle(BUNDLE_NAME); + + /** The class logger */ + private static final Logger LOGGER = Logger + .getLogger(Messages.class.getName()); + + /** Utility class */ + private Messages() { + // Utility class + } + + /** Get formatted internationalized messages + * + * @param key the message key + * @param args the formatting arguments + * @return the formatted internationalized message */ + public static String getString(String key, Object... args) { + try { + return MessageFormat.format(RESOURCE_BUNDLE.getString(key), args); + } catch (MissingResourceException e) { + LOGGER.log(Level.WARNING, + "Unrecognized internationalization message key: " + key, e); //$NON-NLS-1$ + return '!' + key + '!'; + } + } +} diff --git a/gclc/src/main/java/fr/bigeon/gclc/ConsoleManager.java b/gclc/src/main/java/fr/bigeon/gclc/manager/ConsoleManager.java similarity index 93% rename from gclc/src/main/java/fr/bigeon/gclc/ConsoleManager.java rename to gclc/src/main/java/fr/bigeon/gclc/manager/ConsoleManager.java index 24ce4a1..37cc72e 100644 --- a/gclc/src/main/java/fr/bigeon/gclc/ConsoleManager.java +++ b/gclc/src/main/java/fr/bigeon/gclc/manager/ConsoleManager.java @@ -36,7 +36,9 @@ * gclc:fr.bigeon.gclc.ConsoleManager.java * Created on: Dec 19, 2014 */ -package fr.bigeon.gclc; +package fr.bigeon.gclc.manager; + +import java.io.IOException; /**

* A console manager is in charge of the basic prompts and prints on a console. @@ -47,28 +49,31 @@ package fr.bigeon.gclc; * @author Emmanuel BIGEON */ public interface ConsoleManager { - /**

- * Set a prompting prefix. - * - * @param prompt the prompt */ - void setPrompt(String prompt); - /** @return the prompt prefix */ String getPrompt(); + /** @param text the message to print (without line break at the end). */ + void print(String text); + + /** Prints an end of line */ + void println(); + + /** @param message the message to print */ + void println(String message); + /** @return the user inputed string */ String prompt(); /** @param message the message to prompt the user * @return the user inputed string */ String prompt(String message); - - /** @param message the message to print */ - void println(String message); - - /** Prints an end of line */ - void println(); - /** @param text the message to print (without line break at the end). */ - void print(String text); + /**

+ * Set a prompting prefix. + * + * @param prompt the prompt */ + void setPrompt(String prompt); + + /** @throws IOException if the close raised an exception */ + void close() throws IOException; } diff --git a/gclc/src/main/java/fr/bigeon/gclc/system/SystemConsoleManager.java b/gclc/src/main/java/fr/bigeon/gclc/manager/SystemConsoleManager.java similarity index 64% rename from gclc/src/main/java/fr/bigeon/gclc/system/SystemConsoleManager.java rename to gclc/src/main/java/fr/bigeon/gclc/manager/SystemConsoleManager.java index 8012ce3..286edb6 100644 --- a/gclc/src/main/java/fr/bigeon/gclc/system/SystemConsoleManager.java +++ b/gclc/src/main/java/fr/bigeon/gclc/manager/SystemConsoleManager.java @@ -36,33 +36,107 @@ * gclc:fr.bigeon.gclc.system.SystemConsoleManager.java * Created on: Dec 19, 2014 */ -package fr.bigeon.gclc.system; +package fr.bigeon.gclc.manager; import java.io.IOException; +import java.io.InputStream; +import java.io.PrintStream; +import java.util.logging.Level; +import java.util.logging.Logger; -import fr.bigeon.gclc.ConsoleManager; - -/** +/** A console using the input stream and print stream. *

- * TODO + * The default constructor will use the system standart input and output. * - * @author Emmanuel BIGEON - * - */ -public class SystemConsoleManager implements ConsoleManager { + * @author Emmanuel BIGEON */ +public class SystemConsoleManager implements ConsoleManager { // NOSONAR /** The default prompt */ public static final String DEFAULT_PROMPT = ">"; //$NON-NLS-1$ + /** The logger */ + private static final Logger LOGGER = Logger + .getLogger(SystemConsoleManager.class.getName()); + /** The command prompt. It can be changed. */ private String prompt = DEFAULT_PROMPT; + /** The print stream */ + private final PrintStream out; + /** The input stream */ + private final InputStream in; + + /** This default constructor relies on the system defined standart output + * and input stream. */ + public SystemConsoleManager() { + out = System.out; // NOSONAR + in = System.in; + } + + /** @param out the output stream + * @param in the input stream */ + public SystemConsoleManager(PrintStream out, InputStream in) { + super(); + this.out = out; + this.in = in; + } + /** @return the prompt */ @Override public String getPrompt() { return prompt; } + /* (non-Javadoc) + * @see fr.bigeon.gclc.ConsoleManager#print(java.lang.Object) */ + @Override + public void print(String object) { + out.print(object); + } + + /* (non-Javadoc) + * @see fr.bigeon.gclc.ConsoleManager#println() */ + @Override + public void println() { + out.println(); + } + + /* (non-Javadoc) + * @see fr.bigeon.gclc.ConsoleManager#println(java.lang.Object) */ + @Override + public void println(String object) { + out.println(object); + } + + /* (non-Javadoc) + * @see fr.bigeon.gclc.ConsoleManager#prompt() */ + @Override + public String prompt() { + return prompt(new String() + prompt); + } + + /* (non-Javadoc) + * @see fr.bigeon.gclc.ConsoleManager#prompt(java.lang.String) */ + @Override + public String prompt(String message) { + String result = new String(); + out.print(message + ' '); + char c; + try { + c = (char) in.read(); + while (c != System.lineSeparator().charAt(0)) { + result += c; + c = (char) in.read(); + } + while (in.available() != 0) { + in.read(); + } + } catch (final IOException e) { + LOGGER.log(Level.SEVERE, "Unable to read prompt", e); //$NON-NLS-1$ + } + return result; + } + /** @param prompt the prompt to set */ @Override public void setPrompt(String prompt) { @@ -70,56 +144,10 @@ public class SystemConsoleManager implements ConsoleManager { } /* (non-Javadoc) - * @see fr.bigeon.gclc.ConsoleManager#prompt() - */ + * @see fr.bigeon.gclc.manager.ConsoleManager#close() */ @Override - public String prompt() { - return prompt(new String() + prompt); - } - - /* (non-Javadoc) - * @see fr.bigeon.gclc.ConsoleManager#prompt(java.lang.String) - */ - @Override - public String prompt(String message) { - String result = new String(); - System.out.print(message + ' '); - char c; - try { - c = (char) System.in.read(); - while (c != System.lineSeparator().charAt(0)) { - result += c; - c = (char) System.in.read(); - } - while (System.in.available() != 0) - System.in.read(); - } catch (IOException e) { - e.printStackTrace(); - } - return result; - } - - /* (non-Javadoc) - * @see fr.bigeon.gclc.ConsoleManager#println(java.lang.Object) - */ - @Override - public void println(String object) { - System.out.println(object); - } - - /* (non-Javadoc) - * @see fr.bigeon.gclc.ConsoleManager#print(java.lang.Object) - */ - @Override - public void print(String object) { - System.out.print(object); - } - - /* (non-Javadoc) - * @see fr.bigeon.gclc.ConsoleManager#println() */ - @Override - public void println() { - System.out.println(); + public void close() throws IOException { + in.close(); } } 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 3f03cbc..adf5d43 100644 --- a/gclc/src/main/java/fr/bigeon/gclc/prompt/CLIPrompter.java +++ b/gclc/src/main/java/fr/bigeon/gclc/prompt/CLIPrompter.java @@ -39,8 +39,10 @@ package fr.bigeon.gclc.prompt; import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; -import fr.bigeon.gclc.ConsoleManager; +import fr.bigeon.gclc.manager.ConsoleManager; /**

* The {@link CLIPrompter} class is a utility class that provides method to @@ -49,6 +51,14 @@ import fr.bigeon.gclc.ConsoleManager; * @author Emmanuel BIGEON */ public class CLIPrompter { + /** message key for format error in prompting a choice */ + private static final String PROMPTCHOICE_FORMATERR = "promptchoice.formaterr"; //$NON-NLS-1$ + /** message key for out of bound error in prompting a choice */ + private static final String PROMPTCHOICE_OUTOFBOUNDS = "promptchoice.outofbounds"; //$NON-NLS-1$ + /** message key for first form of no in prompting a choice */ + private static final String PROMPTBOOL_CHOICES_NO1 = "promptbool.choices.no1"; //$NON-NLS-1$ + /** message key for first form of yes in prompting a choice */ + private static final String PROMPTBOOL_CHOICES_YES1 = "promptbool.choices.yes1"; //$NON-NLS-1$ @SuppressWarnings("javadoc") private static final String BOOL_CHOICES = "promptbool.choices"; //$NON-NLS-1$ @SuppressWarnings("javadoc") @@ -57,203 +67,67 @@ public class CLIPrompter { 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$ + /** The class logger */ + private static final Logger LOGGER = Logger + .getLogger(CLIPrompter.class.getName()); - /** @param manager the manager - * @param prompt the prompting message - * @param reprompt the prompting message after empty input - * @return the non empty input */ - public static String promptNonEmpty(ConsoleManager manager, String prompt, - String reprompt) { - String res = manager.prompt(prompt); - while (res.isEmpty()) - res = manager.prompt(reprompt); - return res; - } - - /** Prompt for a text with several lines. - * - * @param manager the manager - * @param message the prompting message - * @param ender the ender character - * @return the text */ - public static String promptLongText(ConsoleManager manager, String message, - String ender) { - manager.println(message + - CLIPrompterMessages.getString( - "promptlongtext.exit.dispkey", ender)); //$NON-NLS-1$ - String res = manager.prompt(PROMPT); - String line = res; - while (!line.equals(ender)) { - line = manager.prompt(PROMPT); - if (!line.equals(ender)) res += System.lineSeparator() + line; - } - return res.equals(ender) ? "" : res; //$NON-NLS-1$ - } - - /** Prompt for a text with several lines. - * - * @param manager the manager - * @param message the prompting message - * @return the text */ - public static String promptLongText(ConsoleManager manager, String message) { - return promptLongText(manager, message, - CLIPrompterMessages.getString("promptlongtext.exit.defaultkey")); //$NON-NLS-1$ + /** Utility class */ + private CLIPrompter() { + // Utility class } /** @param manager the manager - * @param message the prompt message - * @return the integer */ - public static int promptInteger(ConsoleManager manager, String message) { - boolean still = true; - int r = 0; - while (still) { - String result = manager.prompt(message); - try { - if (result.isEmpty()) { - still = true; - continue; - } - r = Integer.parseInt(result); - still = false; - } catch (Exception e) { - still = true; - } + * @param choices the choices + * @param cancel the cancel option if it exists + * @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; + for (final U u : choices) { + manager.println(index++ + ") " + u); //$NON-NLS-1$ } - return r; + if (cancel != null) { + manager.println(index + ") " + cancel); //$NON-NLS-1$ + } + return index; } /** @param manager the manager * @param message the prompting message * @return the choice */ - public static boolean promptBoolean(ConsoleManager manager, String message) { - String result = manager.prompt(message + - CLIPrompterMessages.getString(BOOL_CHOICES)); + public static boolean promptBoolean(ConsoleManager manager, + String message) { + String result = manager + .prompt(message + CLIPrompterMessages.getString(BOOL_CHOICES)); boolean first = true; - String choices = CLIPrompterMessages - .getString("promptbool.choices.yes1") + //$NON-NLS-1$ - ", " + //$NON-NLS-1$ - CLIPrompterMessages - .getString("promptbool.choices.no1"); //$NON-NLS-1$ - while (!(result.equalsIgnoreCase(CLIPrompterMessages - .getString("promptbool.choices.yes1")) || //$NON-NLS-1$ - CLIPrompterMessages - .getString("promptbool.choices.no1").equalsIgnoreCase( //$NON-NLS-1$ - result) || - CLIPrompterMessages - .getString("promptbool.choices.no2").equalsIgnoreCase( //$NON-NLS-1$ - result) || CLIPrompterMessages.getString( - "promptbool.choices.yes2").equalsIgnoreCase(result))) { //$NON-NLS-1$ + final String choices = CLIPrompterMessages + .getString(PROMPTBOOL_CHOICES_YES1) + ", " + //$NON-NLS-1$ + CLIPrompterMessages + .getString(PROMPTBOOL_CHOICES_NO1); + while (!(result.equalsIgnoreCase( + CLIPrompterMessages.getString(PROMPTBOOL_CHOICES_YES1)) || + CLIPrompterMessages.getString(PROMPTBOOL_CHOICES_NO1) + .equalsIgnoreCase(result) || + CLIPrompterMessages.getString("promptbool.choices.no2") //$NON-NLS-1$ + .equalsIgnoreCase(result) || + CLIPrompterMessages.getString("promptbool.choices.yes2") //$NON-NLS-1$ + .equalsIgnoreCase(result))) { if (!first) { - - manager.println(CLIPrompterMessages.getString( - "promptbool.choices.invalid", choices)); //$NON-NLS-1$ - result = manager.prompt(message + - CLIPrompterMessages.getString(BOOL_CHOICES)); + + manager.println(CLIPrompterMessages + .getString("promptbool.choices.invalid", choices)); //$NON-NLS-1$ + result = manager.prompt( + message + CLIPrompterMessages.getString(BOOL_CHOICES)); } first = false; } - return result.equalsIgnoreCase(CLIPrompterMessages - .getString("promptbool.choices.yes1")) || //$NON-NLS-1$ + return result.equalsIgnoreCase( + CLIPrompterMessages.getString(PROMPTBOOL_CHOICES_YES1)) || result.equalsIgnoreCase(CLIPrompterMessages .getString("promptbool.choices.yes2")); //$NON-NLS-1$ } - /** @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 - * @param cancel the cancel option if it exists (null otherwise) - * @return the chosen object */ - @SuppressWarnings("boxing") - public static T promptChoice(ConsoleManager manager, - List choices, - Map choicesMap, - String message, String cancel) { - manager.println(message); - int index = listChoices(manager, choices, cancel); - String result = ""; //$NON-NLS-1$ - boolean keepOn = true; - int r = -1; - while (keepOn) { - result = manager.prompt(CLIPrompterMessages.getString(PROMPT)); - try { - r = Integer.parseInt(result); - if (r >= 0 && r <= index) - keepOn = false; - else { - manager.println(CLIPrompterMessages.getString( - "promptchoice.outofbounds", 0, index)); //$NON-NLS-1$ - listChoices(manager, choices, cancel); - } - - } catch (NumberFormatException e) { - keepOn = true; - manager.println(CLIPrompterMessages.getString( - "promptchoice.formaterr", 0, index)); //$NON-NLS-1$ - listChoices(manager, choices, cancel); - } - } - if (r == index && cancel != null) return null; - return choicesMap.get(choices.get(r)); - } - - /** @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 - * @param cancel the cancel option (or null) - * @return the chosen object */ - public static T promptChoice(ConsoleManager manager, - Map choicesMap, - String message, - String cancel) { - return promptChoice(manager, new ArrayList<>(choicesMap.keySet()), - choicesMap, - message, cancel); - } - - /** @param manager the manager - * @param the type of choices - * @param choices the list of choices - * @param message the prompting message - * @param cancel the cancel option, or null - * @return the index of the choice */ - @SuppressWarnings("boxing") - public static Integer promptChoice(ConsoleManager manager, - List choices, - String message, - String cancel) { - manager.println(message); - int index = listChoices(manager, choices, cancel); - String result = ""; //$NON-NLS-1$ - boolean keepOn = true; - int r = -1; - while (keepOn) { - result = manager.prompt(CLIPrompterMessages.getString(PROMPT)); - try { - r = Integer.parseInt(result); - if (r >= 0 && r <= index) - keepOn = false; - else { - manager.println(CLIPrompterMessages.getString( - "promptchoice.outofbounds", 0, index)); //$NON-NLS-1$ - listChoices(manager, choices, cancel); - } - - } catch (NumberFormatException e) { - keepOn = true; - manager.println(CLIPrompterMessages.getString( - "promptchoice.formaterr", 0, index)); //$NON-NLS-1$ - listChoices(manager, choices, cancel); - } - } - if (r == index && cancel != null) return null; - return r; - } - /** @param manager the manager * @param keys the keys to be printed * @param choices the real choices @@ -263,10 +137,12 @@ public class CLIPrompter { * @return the choice */ @SuppressWarnings("boxing") public static U promptChoice(ConsoleManager manager, List keys, - List choices, - String message, String cancel) { - Integer index = promptChoice(manager, keys, message, cancel); - if (index == null) return null; + List choices, String message, + String cancel) { + final Integer index = promptChoice(manager, keys, message, cancel); + if (index == null) { + return null; + } return choices.get(index); } @@ -276,15 +152,53 @@ public class CLIPrompter { * @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)); + * @param cancel the cancel option if it exists (null otherwise) + * @return the chosen object */ + public static T promptChoice(ConsoleManager manager, List choices, + Map choicesMap, String message, + String cancel) { + return choicesMap.get(choices.get( + promptChoice(manager, choices, message, cancel).intValue())); + } + + /** @param manager the manager + * @param the type of choices + * @param choices the list of choices + * @param message the prompting message + * @param cancel the cancel option, or null + * @return the index of the choice */ + @SuppressWarnings("boxing") + public static Integer promptChoice(ConsoleManager manager, + List choices, String message, + String cancel) { + manager.println(message); + final int index = listChoices(manager, choices, cancel); + String result = ""; //$NON-NLS-1$ + boolean keepOn = true; + int r = -1; + while (keepOn) { + result = manager.prompt(CLIPrompterMessages.getString(PROMPT)); + try { + r = Integer.parseInt(result); + if (r >= 0 && r <= index) { + keepOn = false; + } else { + manager.println(CLIPrompterMessages + .getString(PROMPTCHOICE_OUTOFBOUNDS, 0, index)); + listChoices(manager, choices, cancel); + } + + } catch (final NumberFormatException e) { + keepOn = true; + manager.println(CLIPrompterMessages + .getString(PROMPTCHOICE_FORMATERR, 0, index)); + listChoices(manager, choices, cancel); + } } - return userChoices; + if (r == index && cancel != null) { + return null; + } + return r; } /** @param manager the manager @@ -292,50 +206,101 @@ public class CLIPrompter { * @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 cancel the cancel option (or null) + * @return the chosen object */ + public static T promptChoice(ConsoleManager manager, + Map choicesMap, String message, + String cancel) { + return promptChoice(manager, new ArrayList<>(choicesMap.keySet()), + choicesMap, message, cancel); } /** @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()) { + * @param message the prompt message + * @return the integer */ + public static int promptInteger(ConsoleManager manager, String message) { + boolean still = true; + int r = 0; + while (still) { + final String result = manager.prompt(message); + try { + if (result.isEmpty()) { + still = true; 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; - } + r = Integer.parseInt(result); + still = false; + } catch (final NumberFormatException e) { + LOGGER.log(Level.INFO, + "User input a non parsable integer: " + result, e); //$NON-NLS-1$ + still = true; } } - return chs; + return r; + } + + /** This methods prompt the user for a list of elements + * + * @param manager the manager + * @param message the message + * @return the list of user inputs */ + public static List promptList(ConsoleManager manager, + String message) { + return promptList(manager, message, + CLIPrompterMessages.getString("promptlist.exit.defaultkey")); //$NON-NLS-1$ + } + + /** This methods prompt the user for a list of elements + * + * @param manager the manager + * @param message the message + * @param ender the ending sequence for the list + * @return the list of user inputs */ + public static List promptList(ConsoleManager manager, + String message, String ender) { + final List strings = new ArrayList<>(); + manager.println( + message + CLIPrompterMessages.getString(LIST_DISP_KEY, ender)); + String res = null; + while (!ender.equals(res)) { + res = manager.prompt(CLIPrompterMessages.getString(PROMPT)); + if (!res.equals(ender)) { + strings.add(res); + } + } + return strings; + } + + /** Prompt for a text with several lines. + * + * @param manager the manager + * @param message the prompting message + * @return the text */ + public static String promptLongText(ConsoleManager manager, + String message) { + return promptLongText(manager, message, CLIPrompterMessages + .getString("promptlongtext.exit.defaultkey")); //$NON-NLS-1$ + } + + /** Prompt for a text with several lines. + * + * @param manager the manager + * @param message the prompting message + * @param ender the ender character + * @return the text */ + public static String promptLongText(ConsoleManager manager, String message, + String ender) { + manager.println(message + CLIPrompterMessages + .getString("promptlongtext.exit.dispkey", ender)); //$NON-NLS-1$ + String res = manager.prompt(PROMPT); + String line = res; + while (!line.equals(ender)) { + line = manager.prompt(PROMPT); + if (!line.equals(ender)) { + res += System.lineSeparator() + line; + } + } + return res.equals(ender) ? "" : res; //$NON-NLS-1$ } /** @param manager the manager @@ -344,61 +309,120 @@ public class CLIPrompter { * @param message the message * @param the type of elements * @return the choice */ - public static List promptMultiChoice(ConsoleManager manager, List keys, List choices, + 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) { + final List indices = promptMultiChoice(manager, keys, message); + final List userChoices = new ArrayList<>(); + for (final 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 (or the number of choices if there - * is a cancel) */ - private static int listChoices(ConsoleManager manager, List choices, - String cancel) { - int index = 0; - for (U u : choices) { - manager.println((index++) + ") " + u); //$NON-NLS-1$ + * @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) { + final List chs = promptMultiChoice(manager, choices, message); + final List userChoices = new ArrayList<>(); + for (final Integer integer : chs) { + userChoices.add(choicesMap.get(integer)); } - if (cancel != null) { - manager.println((index) + ") " + cancel); //$NON-NLS-1$ - } - return index; + return userChoices; } - /** This methods prompt the user for a list of elements - * - * @param manager the manager - * @param message the message - * @param ender the ending sequence for the list - * @return the list of user inputs */ - public static List promptList(ConsoleManager manager, - String message, - String ender) { - List strings = new ArrayList<>(); - manager.println(message + - CLIPrompterMessages.getString(LIST_DISP_KEY, ender)); - String res = null; - while (!ender.equals(res)) { - res = manager.prompt(CLIPrompterMessages.getString(PROMPT)); - if (!res.equals(ender)) strings.add(res); + /** @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); + final int index = listChoices(manager, choices, null); + String result = ""; //$NON-NLS-1$ + boolean keepOn = true; + final List chs = new ArrayList<>(); + while (keepOn) { + keepOn = false; + result = manager.prompt(CLIPrompterMessages.getString(PROMPT)); + final String[] vals = result + .split(CLIPrompterMessages.getString(LIST_CHOICE_SEP)); + for (final String val : vals) { + boolean added; + try { + added = addUserChoice(val, chs, index); + } catch (final NumberFormatException e) { + keepOn = true; + manager.println(CLIPrompterMessages + .getString(PROMPTCHOICE_FORMATERR, 0, index)); + listChoices(manager, choices, null); + break; + } + if (!added) { + manager.println(CLIPrompterMessages + .getString(PROMPTCHOICE_OUTOFBOUNDS, 0, index)); + listChoices(manager, choices, null); + keepOn = true; + } + } } - return strings; + return chs; } - /** This methods prompt the user for a list of elements - * - * @param manager the manager - * @param message the message - * @return the list of user inputs */ - public static List promptList(ConsoleManager manager, String message) { - return promptList(manager, message, - CLIPrompterMessages.getString("promptlist.exit.defaultkey")); //$NON-NLS-1$ + /** @param val the string to parse + * @param chs the list of integers + * @param index the max index of choice + * @return if the parsing was done correctly */ + private static boolean addUserChoice(String val, List chs, + int index) { + if (val.isEmpty()) { + return true; + } + final int r; + r = Integer.parseInt(val); + if (r >= 0 && r < index) { + chs.add(Integer.valueOf(r)); + return true; + } + return false; + } + + /** @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 prompt the prompting message + * @param reprompt the prompting message after empty input + * @return the non empty input */ + public static String promptNonEmpty(ConsoleManager manager, String prompt, + String reprompt) { + String res = manager.prompt(prompt); + while (res.isEmpty()) { + res = manager.prompt(reprompt); + } + return res; } } diff --git a/gclc/src/main/java/fr/bigeon/gclc/prompt/CLIPrompterMessages.java b/gclc/src/main/java/fr/bigeon/gclc/prompt/CLIPrompterMessages.java index a4c1072..c03c6a5 100644 --- a/gclc/src/main/java/fr/bigeon/gclc/prompt/CLIPrompterMessages.java +++ b/gclc/src/main/java/fr/bigeon/gclc/prompt/CLIPrompterMessages.java @@ -41,41 +41,40 @@ package fr.bigeon.gclc.prompt; import java.text.MessageFormat; import java.util.MissingResourceException; import java.util.ResourceBundle; +import java.util.logging.Level; +import java.util.logging.Logger; -/** - *

+/**

* Utility class for the messages of the CLIPrompter * - * @author Emmanuel BIGEON - */ + * @author Emmanuel BIGEON */ public class CLIPrompterMessages { - /** - * The resource name - */ + /** The resource name */ private static final String BUNDLE_NAME = "fr.bigeon.gclc.messages"; //$NON-NLS-1$ - /** - * The resource - */ + /** The resource */ private static final ResourceBundle RESOURCE_BUNDLE = ResourceBundle .getBundle(BUNDLE_NAME); - /** - * Utility class - */ - private CLIPrompterMessages() {} + /** The logger */ + private static final Logger LOGGER = Logger + .getLogger(CLIPrompterMessages.class.getName()); - /** - * Return the formatted message corresponding to the given key - * + /** Utility class */ + private CLIPrompterMessages() { + // Utility constructor + } + + /** Return the formatted message corresponding to the given key + * * @param key the message's key * @param args the arguments - * @return the formatted message - */ + * @return the formatted message */ public static String getString(String key, Object... args) { try { return MessageFormat.format(RESOURCE_BUNDLE.getString(key), args); - } catch (MissingResourceException e) { + } catch (final MissingResourceException e) { + LOGGER.log(Level.WARNING, "Unrecognized key: " + key, e); //$NON-NLS-1$ return '!' + key + '!'; } } diff --git a/gclc/src/main/java/fr/bigeon/gclc/prompt/package-info.java b/gclc/src/main/java/fr/bigeon/gclc/prompt/package-info.java index 754630f..85ed457 100644 --- a/gclc/src/main/java/fr/bigeon/gclc/prompt/package-info.java +++ b/gclc/src/main/java/fr/bigeon/gclc/prompt/package-info.java @@ -34,8 +34,14 @@ */ /** gclc:fr.bigeon.gclc.prompt.package-info.java * Created on: Sep 6, 2014 */ -/**

- * TODO +/** Client prompting related objects. + *

+ * This package is used for the formatting of prompts for the user. The + * {@link fr.bigeon.gclc.prompt.CLIPrompter} class provides utility methods to + * retrieve certain basic type of data from the user or to give list choices. + *

+ * The {@link fr.bigeon.gclc.prompt.CLIPrompterMessages} class is used for + * internationalization of the prompting methods. * * @author Emmanuel BIGEON */ package fr.bigeon.gclc.prompt; \ No newline at end of file diff --git a/gclc/src/main/java/fr/bigeon/gclc/tools/PrintUtils.java b/gclc/src/main/java/fr/bigeon/gclc/tools/PrintUtils.java index 50533a1..0a138fb 100644 --- a/gclc/src/main/java/fr/bigeon/gclc/tools/PrintUtils.java +++ b/gclc/src/main/java/fr/bigeon/gclc/tools/PrintUtils.java @@ -41,27 +41,35 @@ package fr.bigeon.gclc.tools; import java.util.ArrayList; import java.util.List; -/** - *

- * TODO +/** A tool class for printing text in a console. * - * @author Emmanuel BIGEON - * - */ + * @author Emmanuel BIGEON */ public class PrintUtils { + + /** The continuation dot string */ + private static final String CONT_DOT = "..."; //$NON-NLS-1$ + /** The continuation dot string length */ + private static final int CONT_DOT_LENGTH = CONT_DOT.length(); + + /** Utility class */ + private PrintUtils() { + // Utility class + } + /** @param text the text to print * @param nbCharacters the number of characters of the resulting text * @param indicateTooLong if an indication shell be given that the text * didn't fit * @return the text to print (will be of exactly nbCharacters). */ public static String print(String text, int nbCharacters, - boolean indicateTooLong) { + boolean indicateTooLong) { String res = text; if (res.length() > nbCharacters) { // Cut if (indicateTooLong) { // With suspension dots - res = res.substring(0, nbCharacters - 3) + "..."; //$NON-NLS-1$ + res = res.substring(0, nbCharacters - CONT_DOT_LENGTH) + + CONT_DOT; } else { res = res.substring(0, nbCharacters); } @@ -77,9 +85,10 @@ public class PrintUtils { * @param i the length of the wrap * @return the list of resulting strings */ public static List wrap(String description, int i) { - String[] originalLines = description.split(System.lineSeparator()); - List result = new ArrayList<>(); - for (String string : originalLines) { + final String[] originalLines = description + .split(System.lineSeparator()); + final List result = new ArrayList<>(); + for (final String string : originalLines) { String toCut = string; while (toCut.length() > i) { int index = toCut.lastIndexOf(" ", i); //$NON-NLS-1$ diff --git a/gclc/src/main/resources/fr/bigeon/gclc/l10n/messages.properties b/gclc/src/main/resources/fr/bigeon/gclc/l10n/messages.properties new file mode 100644 index 0000000..4b2f309 --- /dev/null +++ b/gclc/src/main/resources/fr/bigeon/gclc/l10n/messages.properties @@ -0,0 +1 @@ +ConsoleApplication.cmd.failed=The command '{0}' failed due to :