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");
}
}
Related
I'm writing a program that needs to have Command Objects. A Command contains a String for its name, and an AbstractAction that represents what the Command actually does. Furthermore, a Command has a method, init(), used higher up in the program's hierarchy that instantiates variables for the Command's use (to provide access to the GUI, network, and so on), and a method, execute(), that executes the AbstractAction on a special Thread. Here is an example of creating and using a Command:
Command c = new Command("Test",
new AbstractAction() {
public void actionPerformed(ActionEvent a) {
System.out.println("Hello world!");
}
});
At this point, calling "c.execute();" will print out "Hello world!", as expected.
My goal is to have a text file with pairs of values, which can be parsed to generate a String name and an AbstractAction action. Once that has been done, another class will go through the found names and actions, create Command Objects for each one, and add them to the list of commands in the program, where they can then be used as normal.
Right now, my problem is that I read in a String that represents the body of the private AbstractAction above- but there isn't an easy way to actually convert the String into an actual AbstractAction Object.
One potential idea was creating a temporary java file with the AbstractAction String representation, compiling it, creating a new AbstractAction from it, and then get that reference using reflection, but that seems like overkill. Another was to directly modify the source of the file that parses through the file, so that it would have the code of the AbstractAction written out, but again, this is a bit crazy.
I've tried a few other implementations, including forcing the user to create a subclass of Command, putting their source into a special program folder, and then creating the Commands on initialisation, but this ended up being a lot of work for the user (lots of redundant code).
Please let me know if there's a better way to implement what I want to do- or if there's an easier way to turn the String of the source into an inner Object as above.
Edit 1:
Here is an example of what the text file would look like:
//Anything outside of quotes is a comment
"Foo", "System.out.println("Hello world!");"
"Bar", "network.sendOverAFile(new File("test.txt"));"
From here, the parser (on startup) would read through the file and extract "Foo" as a String name, and "System ... ;" as a String action. I need to turn action into the code in the body of the AbstractAction, as seen above when creating the Command.
The same would be done for Bar; Bar uses one of the variables passed by init().
As for the subclass implementation I tried, the user would have to create their own subclass of Command, and put it into a source folder. A subclass would look something like this:
public class TestCommand extends Command {
public TestCommand() {
super("Test", new AbstractAction() {
public void actionPerformed(ActionEvent a) {
System.out.println("Hello!");
}
});
}
}
This would then be put into a source directory, among every other subclassed Command, and compiled. The parser would go through the compiled code segments, and add the relevant information to an array. Every time a Command would normally be executed, the parser scans through the list of all names, and if there is a match, execute the relevant AbstractAction. This works, but involves a ton of references to external classes (which will probably slow down the program with dozens of commands), and is two or three times as much work for the users making the plugin. As a result, I felt it would be much easier to use the text file technique above, but I don't know how to turn a String representation of the code into the code itself; Ergo my initial question.
This sounds like a case of overengineering. Do you really need this much flexibility at runtime, or do you simply have a lot of commands and you want an easy way to refer to them in a file?
If it's the latter, your text file doesn't need to contain the code; it just needs to contain symbolic identifiers corresponding to that code. Those identifiers should exist in your code as enum constants:
public enum Command { FOO, BAR }
You should create all of your actions in code, and place those actions in a Map using the enum constants as keys. Your file can then refer to the actions by those enum constants:
public List<Action> parseActions(Path file)
throws IOException {
List<Action> actions = new ArrayList<>();
try (BufferedReader reader =
Files.newBufferedReader(file, Charset.defaultCharset())) {
String line;
while ((line = reader.readLine()) != null) {
Command command = Command.valueOf(line);
Action action = getAction(command);
actions.add(action);
}
}
return actions;
}
private Map<Command, Action> allActions;
private Action getAction(Command command) {
Objects.requireNonNull(command, "Command cannot be null");
if (allActions == null) {
allActions = new EnumMap<>(Command.class);
allActions.put(Command.FOO, new AbstractAction() {
public void actionPerformed(ActionEvent event) {
System.out.println("Hello world!");
}
};
allActions.put(Command.BAR, new AbstractAction() {
public void actionPerformed(ActionEvent event) {
network.sendOverAFile(new File("test.txt"));
}
};
// Safety check
if (!allActions.keySet().containsAll(
EnumSet.allOf(Command.class))) {
throw new RuntimeException(
"Not every Command constant has an associated Action");
}
}
return allActions.get(command);
}
To conform to the above, your text file would simply contain:
FOO
BAR
If you really and truly need fully dynamic code that can be read from a text file, bear in mind that it is a tremendous security hole. In fact, it is the very definition of code injection: anyone can place arbitrary code (including things like Runtime.getRuntime().exec("rd /s/q C:\\Windows\\System32") or Runtime.getRuntime().exec("rm -rf ~")) in a file and your program will gladly run it.
If you're still sure that you want to do it, you'd probably want to use the JavaScript engine that comes with every Java runtime:
public List<Action> parseActions(Path file)
throws IOException {
List<Action> actions = new ArrayList<>();
final ScriptEngine engine =
new ScriptEngineManager().getEngineByName("JavaScript");
Bindings bindings = engine.getBindings(ScriptContext.ENGINE_SCOPE);
bindings.put("network", myNetwork);
try (BufferedReader reader =
Files.newBufferedReader(file, Charset.defaultCharset())) {
String line;
while ((line = reader.readLine()) != null) {
String[] nameAndCode = line.split("\\s+", 2);
String name = nameAndCode[0];
final String code = nameAndCode[1];
Action action = new AbstractAction() {
public void actionPerformed(ActionEvent event) {
engine.eval(code);
}
};
actions.add(action);
}
}
return actions;
}
Each line in your file would contain a command name followed by JavaScript code. So it might look like this:
Foo importClass(java.lang.System); System.out.println('Hello world!');
Bar importClass(java.io.File); network.sendOverAFile(new File('test.txt'));
Another major disadvantage of doing this, in my opinion, is that the code won't benefit from compiler checks, and you certainly can't set breakpoints in that code from a debugger. All in all, it will be a considerable headache to debug and maintain.
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
...
}
The Java tutorials recommend using the Preferences API over Properties files.
Properties files and ResourceBundles are the recommended way to handle Internalization requirements in applications.
I am considering using both for a desktop application that will display preferences in a locale specific way.
Can anyone point out problems with this approach?
Maybe I should just use Properties files period?
I am considering using both for a desktop application that will display preferences in a locale specific way.
OK, so what you want is translated configuration file in form of:
some_translated_key=some_value
Well, unless you want to support MUI at some point it should not be a big deal. However, if you do, so that different users on the same computer could use different languages, or user might be able to switch language, you would have troubles in matching key to a property. You would have to scan all translations while reading the key, and you would surely end up with multiple entries for the same key. How to resolve that? Well, that's a good question.
From my experience, configuration files should be language-independent (neutral culture) and should never be edited by hand (that is translating keys doesn't really matter).
I thought there could be a problem with character encoding, but following code snippet works without an issue (files are UTF-8 encoded):
public class Main {
private static final String FILE_NAME = "i18ned.properties";
private File propertiesFile;
private Properties properties;
public Main() {
properties = new Properties();
propertiesFile = new File(FILE_NAME);
if (propertiesFile.exists()) {
try {
properties.load(new BufferedReader(new FileReader(
propertiesFile)));
} catch (FileNotFoundException e) {
// not likely, but should be logged either way
} catch (IOException e) {
// logger should be used instead
e.printStackTrace();
}
}
}
public void saveProperties() {
try {
properties
.store(new BufferedWriter(new FileWriter(propertiesFile)), "");
} catch (IOException e) {
// oops, use logger instead
e.printStackTrace();
}
}
public static void main(String[] args) {
Main main = new Main();
main.storeSome();
main.readSome();
}
private void readSome() {
String highAsciiKey = "żółć";
String value = properties.getProperty(highAsciiKey);
System.out.println(value);
}
private void storeSome() {
String highAsciiKey = "żółć";
String highAsciiValue = "łąkę";
properties.setProperty(highAsciiKey, highAsciiValue);
saveProperties();
}
}
Using resource bundle for localizing applications is the standard way in java. The problems of this way are:
there is no compile time check of number and type of parameters required by resource.
It is hard to hold files clean, e.g. there is no mechanism the helps to remove unused strings
It is hard to make all texts translated to all supported languages.
etc....
The probably better internationalization mechanism is suggested by Google in their GWT. They generate class with method per string.
For example if you have text Hello, {0} they will generate method
String hello(String name);
So, you cannot pass neither 0 nor 2 arguments to this method. Only one.
This partially solves the second problem also. It is easier to see if method is not used in whole project. It does not solve the 3rd problem anyway.
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
}