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 :