I'm writing a program for an address book in Java. The user will enter commands such as add-addressbook . The main problem I'm having is knowing which command is entered and taking the parameters from the command and putting them into methods. Not all commands have the same number of parameters. There is a space between the command and the parameters. The parameters are separated by exactly one comma. This code works when the user enters everything properly but returns an error otherwise. Is there a better way to do this?
while (!input.equals("quit")) {
String[] data = input.split(" ", 2);
ArrayList<String> listofdata = new ArrayList<String>(Arrays.asList(data[1].split(",")));
switch (data[0]) {
case "add-addressbook":
String nameofBook = listofdata.get(0);
c.addBook(nameofBook);
break;
case "add-contact":
String nameofBook2 = listofdata.get(0);
String nameofContact = listofdata.get(1);
String email = listofdata.get(2);
String phone = listofdata.get(3);
c.addPerson(nameofBook2, nameofContact, email, phone);
break;
//rest of code
}
You'd better create a common interface Command with method that accepts either String or List<String>. And several separate implementations for each command.
And then each concrete implementation will parse its parameter and either process it or throw exception (or return false or String with error message).
Related
Im working on this assignment, in which im using a command line interface. Im using a simple switch statement to create the controls to this command line interface, however when I added internationalisation support as part of my assignment, my switch statement has broken due to the variables within needing to be constant. How do I get around this?
public class Editor {
private boolean running = true;
public static ArrayList<String> command = new ArrayList<String>();
Locale enLocale = new Locale("en", "GB");
ResourceBundle messages = ResourceBundle.getBundle("BaseBundle", enLocale);
String open = messages.getString("open");
String saveas = messages.getString("saveas");
String put = messages.getString("put");
String rot90 = messages.getString("rot90");
String mono = messages.getString("mono");
String help = messages.getString("help");
String quit = messages.getString("quit");
String look = messages.getString("look");
String undo = messages.getString("undo");
private String get = messages.getString("get");
Parser parser = new Parser();
public Editor() {
}
public void run() {
System.out.println("fotoshop");
while (running) {
command = parser.readInput();
interpret(command);
}
System.out.println("fotoshop exiting");
}
public void interpret(ArrayList<String> command) {
switch (command.get(0)) {
case open: OpenCommand.execute();
case saveas: SaveCommand.execute();
case put: PutCommand.execute();
case get: GetCommand.execute();
case rot90: ApplyRot90.execute();
case mono: ApplyMono.execute();
case help: HelpCommand.execute();
case quit: running = false;
case look: LookCommand.execute();
case undo;
}
}
}
I'm not very familiar with internationalization in Java, but I think the general pattern you should be using is something like this:
Map<String, String> i18Map = new HashMap<>();
map.put("open", "open"); // English
map.put("abierto", "open"); // Spanish
map.put("ouvrir", "open"); // French
// other words/languages
In other words, maintain some sort of map which can map a command, in any language you want to support, to a command English language command. Then, refactor your switch statement to switch on those English language constants instead of variables:
String input = parser.readInput();
String command = i18Map.get(input);
switch (command.get(0)) {
case "open": OpenCommand.execute();
case "saveas": SaveCommand.execute();
case "put": PutCommand.execute();
case "get": GetCommand.execute();
// etc.
}
Use a if else statement
Example
String com = command.get(0);
if (com.equals(open)) {
OpenCommand.execute()
} else if (com.equals(saveas)) {
SaveCommand.execute();
} ...
Have you looked at the following trick to implement dynamic switch?
It might be an overkill but it is worth having a look.
You can define it with String instead of Integer to map it to the different actions in the different languages your application should support.
java-tip-3-how-to-implement-dynamic-switch
class Switcher {
private Map<Integer, Command> caseCommands;
private Command defaultCommand;
private Command getCaseCommandByCaseId(Integer caseId) {
if (caseCommands.containsKey(caseId)) {
return caseCommands.get(caseId);
} else {
return defaultCommand;
}
}
public Switcher() {
caseCommands = new HashMap<Integer, Command>();
setDefaultCaseCommand(new DoNothingCommand());
}
public void addCaseCommand(Integer caseId, Command caseCommand) {
caseCommands.put(caseId, caseCommand);
}
public void setDefaultCaseCommand(Command defaultCommand) {
if (defaultCommand != null) {
this.defaultCommand = defaultCommand;
}
}
public void on(Integer caseId) {
Command command = getCaseCommandByCaseId(caseId);
command.execute();
}
}
Your Main class:
public class Main {
public static void main(String[] args) {
Switcher switcher = new Switcher();
switcher.addCaseCommand(1, new Command() {
#Override
public void execute() {
System.out.println("Command on {id: 1}");
}
});
switcher.addCaseCommand(2, new Command() {
#Override
public void execute() {
System.out.println("Command on {id: 2}");
}
});
switcher.addCaseCommand(3, new Command() {
#Override
public void execute() {
System.out.println("Command on {id: 3}");
}
});
switcher.setDefaultCaseCommand(new Command() {
#Override
public void execute() {
System.out.println("Command on {default}");
}
});
for (int i = 1; i <= 4; i++) {
switcher.on(i);
}
}
}
// output
Command on {id: 1}
Command on {id: 2}
Command on {id: 3}
Command on {default}
Or even better to avoid having all the messages directly hardcoded in the hashmap, and by consequence having to recompile change the code when adding new language support
Internationalizing the Sample Program
If you look at the internationalized source code, you'll notice that
the hardcoded English messages have been removed. Because the messages
are no longer hardcoded and because the language code is specified at
run time, the same executable can be distributed worldwide. No
recompilation is required for localization. The program has been
internationalized.
You may be wondering what happened to the text of the messages or what
the language and country codes mean. Don't worry. You'll learn about
these concepts as you step through the process of internationalizing
the sample program.
1. Create the Properties Files
A properties file stores information about the characteristics of a
program or environment. A properties file is in plain-text format. You
can create the file with just about any text editor.
In the example the properties files store the translatable text of the
messages to be displayed. Before the program was internationalized,
the English version of this text was hardcoded in the
System.out.println statements. The default properties file, which is
called MessagesBundle.properties, contains the following lines:
greetings = Hello
farewell = Goodbye
inquiry = How are you? Now that
the messages are in a properties file, they can be translated into
various languages. No changes to the source code are required. The
French translator has created a properties file called
MessagesBundle_fr_FR.properties, which contains these lines:
greetings = Bonjour.
farewell = Au revoir.
inquiry = Comment
allez-vous? Notice that the values to the right side of the equal sign
have been translated but that the keys on the left side have not been
changed. These keys must not change, because they will be referenced
when your program fetches the translated text.
The name of the properties file is important. For example, the name of
the MessagesBundle_fr_FR.properties file contains the fr language code
and the FR country code. These codes are also used when creating a
Locale object.
2. Define the Locale
The Locale object identifies a particular language and country. The
following statement defines a Locale for which the language is English
and the country is the United States:
aLocale = new Locale("en","US"); The next example creates Locale
objects for the French language in Canada and in France:
caLocale = new Locale("fr","CA"); frLocale = new Locale("fr","FR");
The program is flexible. Instead of using hardcoded language and
country codes, the program gets them from the command line at run
time:
String language = new String(args[0]);
String country = new String(args[1]);
currentLocale = new Locale(language, country); Locale
objects are only identifiers. After defining a Locale, you pass it to
other objects that perform useful tasks, such as formatting dates and
numbers. These objects are locale-sensitive because their behavior
varies according to Locale. A ResourceBundle is an example of a
locale-sensitive object.
3. Create a ResourceBundle
ResourceBundle objects contain locale-specific objects. You use
ResourceBundle objects to isolate locale-sensitive data, such as
translatable text. In the sample program the ResourceBundle is backed
by the properties files that contain the message text we want to
display.
The ResourceBundle is created as follows:
messages = ResourceBundle.getBundle("MessagesBundle", currentLocale);
The arguments passed to the getBundle method identify which properties
file will be accessed. The first argument, MessagesBundle, refers to
this family of properties files:
MessagesBundle_en_US.properties MessagesBundle_fr_FR.properties
MessagesBundle_de_DE.properties The Locale, which is the second
argument of getBundle, specifies which of the MessagesBundle files is
chosen. When the Locale was created, the language code and the country
code were passed to its constructor. Note that the language and
country codes follow MessagesBundle in the names of the properties
files.
Now all you have to do is get the translated messages from the
ResourceBundle.
4. Fetch the Text from the ResourceBundle
The properties files contain key-value pairs. The values consist of
the translated text that the program will display. You specify the
keys when fetching the translated messages from the ResourceBundle
with the getString method. For example, to retrieve the message
identified by the greetings key, you invoke getString as follows:
String msg1 = messages.getString("greetings"); The sample program uses
the key greetings because it reflects the content of the message, but
it could have used another String, such as s1 or msg1. Just remember
that the key is hardcoded in the program and it must be present in the
properties files. If your translators accidentally modify the keys in
the properties files, getString won't be able to find the messages.
Conclusion
That's it. As you can see, internationalizing a program isn't too
difficult. It requires some planning and a little extra coding, but
the benefits are enormous. To provide you with an overview of the
internationalization process, the sample program in this lesson was
intentionally kept simple. As you read the lessons that follow, you'll
learn about the more advanced internationalization features of the
Java programming language.
Source official doc: https://docs.oracle.com/javase/tutorial/i18n/intro/steps.html
One way to do this is with a Map where the keys are the internationalised strings, and the values are the commands that you want executed. So assuming that all the various Command classes (OpenCommand, SaveCommand and so on) have a common subtype Command where the execute method is declared, you could prime the map like this.
Map<String,Command> commandsByInternationalisedString = new HashMap<String,Command>();
Locale enLocale = new Locale("en", "GB");
ResourceBundle messages = ResourceBundle.getBundle("BaseBundle", enLocale);
commandsByInternationalisedString.add(messages.getString("open"), new OpenCommand());
commandsByInternationalisedString.add(messages.getString("saveas"), new SaveAsCommand());
commandsByInternationalisedString.add(messages.getString("put"), new PutCommand());
So now you have a map with a Command object corresponding to any command the user might enter. To use it, you could do this.
public void interpret(ArrayList<String> command) {
Command toExecute = commandsByInternationalisedString.get(command.get(0));
if (toExecute != null ) {
toExecute.execute();
} else {
System.out.println("That's not a valid command");
}
}
Let me clarify the question I am asking. I have a java program I am working on that takes input from the keyboard via a readline library called JLine2. The library takes the entire line types as a command instead on breaking it up into space separated commands and arguments. What I am looking for is a safe way to break up the string that is passed as input.
I have tried using an array but since I am in the early stages of concept I don't yet know how many arguments my largest command will have so using a pre-initialized array I don't think will work. The problem I have ran into is when I check for null values in the array or when I check to see if a particular command or argument is present. Java keeps throwing an exception about the array index being out of scope or something. Because the array does not actually have a value for say array index 1 which is an argument to command in array index 0.
So what I am looking for is a way to take a string and safely split it into parts without having Java yelling at me when and array exception has occurred.
Here is the very slim code I can provide...
ConfigShell.class
package shell;
import java.io.IOException;
import configFS.ConfigFS;
import jline.console.ConsoleReader;
public class ConfigShell {
private ConfigFS config;
public ConfigShell() throws IOException {
config = new ConfigFS();
}
public void init() throws IOException {
ConsoleReader console = new ConsoleReader();
// When the program starts we want to be placed at / (root).
console.setPrompt(">> ");
// In this case an infinite loop is better than a loop based on whether line is equal to null.
// This allows line to be equal to null and still stay inside the shell.
while (true) {
String line = console.readLine();
if (line != null) {
// If pre-initialize the array I can check for null as a value for an array index.
// If I did this at time I needed the array and there were not enough index occupied the system would return an exception.
String[] cmdArgs = new String[4];
// We need to split up the incoming line because JLine2 does not do it for me.
// This allows me to evaluate the entire command piece by piece rather all at once.
cmdArgs = line.split("\\s+");
if (cmdArgs[0] != null && cmdArgs[0].equals("add")) {
if (cmdArgs[1] != null && cmdArgs[1].equals("server")) {
if (cmdArgs[2] != null) {
config.addServer(cmdArgs[2]);
System.out.println("Added server " + cmdArgs[2] + " to the configuration successfully.");
}
}
}
if (cmdArgs[0].equals("exit")) {
System.exit(0);
}
}
}
}
}
Note for testing: My Start.class main method makes a call to the init method in the above file.
You can do:
String cmdArgs = line.split("\\s+");
and then, before accessing any particular index, check the size of the array so that you do not get ArrayIndexOutOfBoundException
Something like this:
if(cmdArgs.length>=2){
//It means you have at least 2 elements
//Now its safe to access cmdArgs[0] and cmdArgs[1]
}
If all your problem is to have a storage for a variable number of strings you can use ArrayList<String> object.
You declare it like ArrayList<String> as = new ArrayList<String>();
Then when you split something from your command string you will simply use add method:
as.add(yourString);
If you need to retrieve a particular element of the ArrayList you can use its get method:
as.get(0);
You can process all elements with for each loop:
for(String str: as) {
println(str):
}
Have a look here for info and here for an example.
As I think you can use StringTokenizer class and its methods for your requirement.
see the sample code below:
if(line!=null)
{
StringTokenizer st=new StringTokenizer(line);// by default it takes space as delimiter....you can use as required as second argument in constructor...
while(st.hasMoreTokens())
{
String token1=st.nextToken();
// do your stuffs here..........
// I don't know exactly about your required logic here......
/* if(token1.equals("add"))
{
String token2=st.nextToken();
if(token2.equals("server"))
{
String token3=st.nextToken();
config.addServer(token3);
System.out.println("Added server " + token3 + " to the configuration successfully.");
}
}
*/
}// while closing...
}// outer if closing...
Or as PM 77-1 told you can use ArrayList. But as my opinion LinkedList should be a better option here.
I have the following code that defines a getParts method to find a given Part Name and Part Number in the system. Note that this code comes from our system's API, so if no one can help I'll just delete this question. I figured someone could potentially see a solution or help me along the way.
<%! private QueryResult getParts( String name, String number )
throws WTException, WTPropertyVetoException {
Class cname = wt.part.WTPart.class;
QuerySpec qs = new QuerySpec(cname);
QueryResult qr = null;
qs.appendWhere
(new SearchCondition(cname,
"master>name",
SearchCondition.EQUAL,
name,
false));
qs.appendAnd();
qs.appendWhere
(new SearchCondition(cname,
"master>number",
SearchCondition.EQUAL,
number,
false));
qr = PersistenceHelper.manager.find(qs);
System.out.println("...found: " + qr.size());
return qr;
}
%>
But I would like to allow the user more flexibility in finding these parts. So I set up conditional statements to check for a radio button. This allows them to search by part name and part number, find all, or search using a wildcard. However, I'm having trouble implementing the two latter options.
To attempt to accomplish the above, I have written the below code:
<%
String partName = request.getParameter("nameInput");
String partNumber = request.getParameter("numberInput");
String searchMethod = request.getParameter("selection");
//out.print(searchMethod);
QueryResult myResult = new QueryResult();
if(searchMethod.equals("search"))
myResult = getParts(partName, partNumber);
else if(searchMethod.equals("all"))
{
//Should I write a new function and do this?
//myResult = getAllParts();
//or is there a way I could use a for each loop to accomplish this?
}
//else if(searchMethod.equals("wildcard"))
//get parts matching %wildcard%
while(myResult.hasMoreElements())
{
out.print(myResult.nextElement().toString());
}
%>
Basically, it accepts user input and checks what type of search they would like to perform. Is there an easy way to pass all the values into the myResult object? And likewise for the wildcard search? Like I said before, it may be futile trying to help without access to the API, but hopefully it isn't.
Thanks!
You can (and should) reuse the function, but in order to do so, you will need a part name and number (as those are its input parameters). So for the multi-result options you will need to get a list/collection of part names+numbers and feed them individually to the function, then collect the result in the format that is most appropriate for your needs
Current assignment needs me to write a program to read a file with instructions in a very tiny and basic programming language (behaves a little like FORTRAN) and execute those instructions. Basically it's a simple interpreter for the language I guess. It's completely linear, with statements all being defined in sequence and it only has String and integer variables. There are 8 keywords and 4 arithmetic operators I would need to find and define if they exist within the source file, and each line must start off with one of the reserved words.
A program in this language might look something like this:
#COMMENTS
LET.... (declares variables with values)
INTEGER myINT
STRING myString
CALCULATE...
PRINT
PRINTLN
END
Can I use a switch block instead of if-loops to find and then execute all these? My concern is that switches don't work with Strings in Java 6, which is what I'm supposed to be using, but I don't see how to easily assign various int values so the switch block would work. Thanks in advance for any suggestions and advice!
If your language is so simple that every statement begins in its own line and is identified by one word only, then (as Gray pointed out in another comment) you can split the words in each line, then compare the first word against a map. However, I would suggest, instead of mapping the words to ints and then doing one big switch, to map them into objects instead, like this (suggested by Dave Newton):
interface Directive {
public void execute(String line);
}
class LetDirective implements Directive {
public void execute(String line) { ...handle LET directive here... }
}
...define other directives in the same way...
Then define the map:
private Map<String, Directive> directives = new HashMap<String, Directive>();
directives.put("LET", new LetDirective());
...
Then in your parsing method:
int firstSpace = line.indexOf(' ');
String command = line;
if (firstSpace > 0)
command = line.substring(0, firstSpace);
Directive directive = directives.get(command.toUpperCase());
if (directive != null)
directive.execute(line);
else
...show some error...
Each directive would have to parse the rest of the line on its own and handle it correctly inside its execute() method.
The benefit of this over a switch is that you can handle a larger amount of commands without ending up with one gigantic method, but instead with one smaller method per each command.
If you are talking about converting strings to integers then you could do it with an Java enumerated type:
private enum ReservedWord {
LET,
...
}
// skip blank lines and comments
String[] tokens = codeLine.split(" ");
ReservedWord keyword;
try {
keyword = ReservedWord.valueOf(tokens[0]);
} catch (IllegalArgumentException e) {
// spit out nice syntax error message
}
You could also put the processing of the line inside of the enum as a method if you'd like. You could also do it with a Map:
private final Map<String, Integer> reservedWords = new HashMap<String, Integer>();
private final int RESERVED_WORD_LET 1
...
{
reservedWords.put("LET", RESERVED_WORD_LET);
...
}
// skip blank lines and comments
String[] tokens = codeLine.split(" ");
Integer value = reservedWords.get(tokens[0]);
if (value == null) // handle error... ;
switch (value) {
case 1:
// LET
...
}
I am trying to get JLine to do tab completion so I can enter something like the following:
commandname --arg1 value1 --arg2 value2
I am using the following code:
final List<Completor> completors = Arrays.asList(
new SimpleCompletor("commandname "),
new SimpleCompletor("--arg1"),
new SimpleCompletor("--arg2"),
new NullCompletor());
consoleReader.addCompletor(new ArgumentCompletor(completors));
But after I type the value2 tab completion stops.
(Suplementary question, can I validate value1 as a date using jline?)
I had the same problem, and I solved it by creating my own classes to complete the commands with jLine. I just needed to implement my own Completor.
I am developing an application that could assist DBAs to type not only the command names, but also the parameters. I am using jLine for just for the Terminal interactions, and I created another Completor.
I have to provide the complete grammar to the Completor, and that is the objective of my application. It is called Zemucan and it is hosted in SourceForge; this application is initially focused to DB2, but any grammar could be incorporated. The example of the Completor I am using is:
public final int complete(final String buffer, final int cursor,
#SuppressWarnings("rawtypes") final List candidateRaw) {
final List<String> candidates = candidateRaw;
final String phrase = buffer.substring(0, cursor);
try {
// Analyzes the typed phrase. This is my program: Zemucan.
// ReturnOptions is an object that contains the possible options of the command.
// It can propose complete the command name, or propose options.
final ReturnOptions answer = InterfaceCore.analyzePhrase(phrase);
// The first candidate is the new phrase.
final String complete = answer.getPhrase().toLowerCase();
// Deletes extra spaces.
final String trim = phrase.trim().toLowerCase();
// Compares if they are equal.
if (complete.startsWith(trim)) {
// Takes the difference.
String diff = complete.substring(trim.length());
if (diff.startsWith(" ") && phrase.endsWith(" ")) {
diff = diff.substring(1, diff.length());
}
candidates.add(diff);
} else {
candidates.add("");
}
// There are options or phrases, then add them as
// candidates. There is not a predefined phrase.
candidates.addAll(this.fromArrayToColletion(answer.getPhrases()));
candidates.addAll(this.fromArrayToColletion(answer.getOptions()));
// Adds a dummy option, in order to prevent that
// jLine adds automatically the option as a phrase.
if ((candidates.size() == 2) && (answer.getOptions().length == 1)
&& (answer.getPhrases().length == 0)) {
candidates.add("");
}
} catch (final AbstractZemucanException e) {
String cause = "";
if (e.getCause() != null) {
cause = e.getCause().toString();
}
if (e.getCause() != null) {
final Throwable ex = e.getCause();
}
System.exit(InputReader.ASSISTING_ERROR);
}
return cursor;
This is an extract of the application. You could do a simple Completor, and you have to provide an array of options. Eventually, you will want to implement your own CompletionHandler to improve the way that the options are presented to the user.
The complete code is available here.
Create 2 completors, then use them to complete arbituary arguments. Note that not all the arguments need to be completed.
List<Completer> completors = new LinkedList<>();
// Completes using the filesystem
completors.add(new FileNameCompleter());
// Completes using random words
completors.add(new StringsCompleter("--arg0", "--arg1", "command"));
// Aggregate the above completors
AggregateCompleter aggComp = new AggregateCompleter(completors);
// Parse the buffer line and complete each token
ArgumentCompleter argComp = new ArgumentCompleter(aggComp);
// Don't require all completors to match
argComp.setStrict(false);
// Add it all together
conReader.addCompleter(argComp);
Remove the NullCompletor and you will have what you want. NullCompletor makes sure your entire command is only 3 words long.