For readability reasons I'm trying to avoid using Char based case constructs, using Java 6. I cannot switch to 7 jet...
Map<String, String> map = new HashMap<String, String>() {
{
put("foo", "--foo");
put("bar), "--bar");
...
}
private static final long serialVersionUID = 1L; // java problem
};
The serialVersionUID - as far as I know, maybe part of the problem. Currently I'm working with if constructs:
if (!map.containsValue(args[0])) {
logger.error("Unknown parameter: " + args[0]);
...
I handle ~ 30 parameters. In any case a growing number.
Is it even possible to define switch constructs with enums or HashMaps In Java 6?
If you're handling over 30 parameters in the same way, then you need some kind of loop. For example:
for (int i=0; i<args.length; i++)
{
String param = args[i];
if (!map.containsValue(param))
logger.error("Unknown parameter: " + param);
.. handle argument
}
It looks like you are parsing command line arguments. There are some good libraries available that offer flexible command line parsing, for example args4j. With args4j, you create your data model, and let it map fields in the data to command line arguments.
Using Strings in a switch statement will be available in Java 7.
For moderate or complex parsing of command line arguments I strongly recommend using Commons-CLI, it provides a great API to make this much easier for you to handle. An example of it's usage:
// create Options object
Options options = new Options();
// add t option
options.addOption("t", false, "display current time");
...
CommandLineParser parser = new PosixParser();
CommandLine cmd = parser.parse( options, args);
if(cmd.hasOption("t")) {
// print the date and time
}
else {
// print the date
}
Related
Let's say I'm writing a bot for a chat (discord, telegram, whatever). The bot can handle chat commands (e.g. !join tells it to join a voice channel on the server).
So somewhere in my code I'd have to parse the command, and I'll have something like
String userMessage = getTheMessageTextSomehow();
// Do something with the message.
I'd like to have a Command class for every one of my commands, and every command would implement a execute() method.
My question is: what's the best practice to create those command objects?
The easiest way would be to have a large CommandFactory or whatever class somwhere, that would be like
if(message.equals(JOIN_MESSAGE) {
return new JoinCommand();
}
if(message.equals(LEAVE_MESSAGE){
return new LeaveCommand();
}
//etc...
That looks like a bad practice and code smell to me.
Is there a better way to do it?
You might want to rely on a Map of Commands.
I'll make it clear that for this usecase, using the Function or Supplier, or whatever standard functional interface is not idiomatic at all. Avoid it.
We can start by building a Command interface
interface Command {
Result execute();
}
Or if you need to accept an argument
interface Command {
Result execute(final Input input);
}
Which will have the required implementations
class JoinCommand implements Command { ... }
class LeaveCommand implements Command { ... }
class NoopCommand implements Command { ... }
And so on.
You'll now need to store those definitions in a key (the command) - value (the implementation) data structure. A Map is perfect for that.
As your command definition will be a String, then
static final Map<String, Command> COMMANDS = new HashMap<>(8);
static {
COMMANDS.put("join", new JoinCommand());
COMMANDS.put("leave", new LeaveCommand());
// And so on
}
The usage is pretty simple
final String userMessage = getTheMessageTextSomehow();
final String commandStr = extractCommand(userMessage);
final Command command = COMMANDS.getOrDefault(commandStr, NOOP_COMMAND);
command.execute();
Or if you'll have to accept an argument
command.execute(yourInput);
You'll also notice I used NOOP_COMMAND, that's just a no-op implementation for Command to avoid dealing with null. It might be, or it might be not, appropriate.
If you're on Java 9+, the Map could also be created using
Map.of(
"join", new JoinCommand(),
"leave", new LeaveCommand(),
// And so on.
)
Usually, it is implemented via mapping. It would be much clearer and readable to implement this with simple Map.
For example:
Map<String, Command> strategies = new HashMap<String, Command>(){{
put(JOIN_MESSAGE, new JoinCommand());
put(LEAVE_MESSAGE, new LeaveCommand());
}};
And its usage:
Command command = strategies.get(messageType);
Moreover, you can define creation strategies (factories) since Java 8 if you need to construct commands depending on some parameters.
Map<String, Function<String, Command>> strategies = new HashMap<String, Command>(){{
put(JOIN_MESSAGE, param -> new JoinCommand(param)); // or JoinCommand::new
put(LEAVE_MESSAGE, param -> new LeaveCommand(param)); // or LeaveCommand::new
}};
And its usage:
Command command = strategies.get(messageType);
command.process(param);
Hello You try Switch Case statement for it, it's easy to understand and in future if you have any changes then it's easy to update the code.
switch(message)
{
case JOIN_MESSAGE:
return new JoinCommand();
break;
case LEAVE_MESSAGE:
return new LeaveCommand();
break;
}
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");
}
}
I want to make a simple interative shell based on the console where I can write commands like login, help, et cetera.
I first thought of using Enums, but then I didn't know how to implement them neatly without a load of if-else statements, so I decided to go with an array-approach and came up with this:
public class Parser {
private static String[] opts = new String[] {"opt0", "opt1", "opt2", "opt3" ... }
public void parse(String text) {
for(int i = 0; i < opts.length; i++) {
if(text.matches(opts[i]) {
switch(i) {
case 0:
// Do something
case 1:
// Do something-something
case 2:
// Do something else
}
return;
}
}
}
}
But I ended up seeing that this was probably the most rudimentary way of doing something like this, and that there would be problems if I wanted to change the order of the options. How could I make a simpler parser? This way it would work, but it would also have said problems. The use of the program is purely educational, not intended for any serious thing.
A simple approach is to have a HashMap with the key equal to the command text and the value is an instance of class that handle this command. Assuming that the command handler class does not take arguments (but you can easily extend this) you can just use a Runnable instance.
Example code:
Runnable helpHandler = new Runnable() {
public void run(){
// handle the command
}
}
// Define all your command handlers
HashMap<String, Runnable> commandsMap = new HashMap<>(); // Java 7 syntax
commandsMap.put("help",helpHandler);
// Add all your command handlers instances
String cmd; // read the user input
Runnable handler;
if((handler = commandsMap.get(cmd)) != null) {
handler.run();
}
You can easily extend this approach to accept argument by implementing your own interface and subclass it. It is good to use variable arguments if you know the data type e.g. void execute(String ... args)
One solution that comes to mind is actually using Design patterns. You could use the input from the user, as the discriminator for a Factory class.
This factory class will generate an object, with an "execute" method, based on the input. This is called a Command object.
Then you can simply call the method of the object returned from the factory.
No need for a switch statement. If the object is null, then you know the user entered an invalid option, and it abstracts the decision logic away from your input parser.
Hopefully this will help :)
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.