Writing a cleaner and more modular command-parser - java

I'm writing a debugger for a Z80-emulator we are writing in a school project, using Java. The debugger reads a command from the user, executes it, reads another command, etc.
Commands can either be argument less, have optional arguments, or take an unlimited amount of arguments. Arguments are mostly integers, but occasionally they're strings.
Currently, we're using the Scanner-class for reading and parsing input. The read-method looks kinda like like this (I'm writing this off the top of my head, not paying attention to syntax nor correctness).
This was a kludge written in the beginning of the project, which quickly got out of hand as we added more and more commands to the debugger.
The major issues I have with this code is the large amount of repetition, the highlevel of if/else-nestedness, and the all around uglyness.
I would like suggestions on how to make this code more beautiful and modular, and what kind of patterns that are suitable for this kind of program.
I would also like more general suggestions on code style.

yup, there is a simpler/better way, especially in Java or other OO languages.
The basic insight, first, is that your command parser is a finite state machine: the START state is an empty line (or index at the start of a line).
Let's think about echo:
$ echo foo bat "bletch quux"
tokenize the line into pieces:
"echo" "foo" "bar" "bletch quux"
in a shell, the grammar is usually verb noun noun noun... so interpret it that way. You CAN do it with a sequence of if-else if things, but a hash is better. You load the hash with strings as indices, and index something else. It could be just a number, which would go into a switch:
(this is pseudocode):
Hashtable cmds = new Hashtable();
enum cmdindx { ECHO=1, LS=2, ...}
cmds.add("echo", ECHO); // ...
// get the token into tok
switch (cmds.get(tok)) {
case ECHO: // process it
// get each successor token and copy it to stdout
break;
...
default:
// didn't recognize the token
// process errors
}
EVEN better, you can apply the Command and Object Factory patterns. Now you have a class Command
public interface Command {
public void doThis(String[] nouns) ;
public Command factory();
}
public class Echo implements Command {
public void doThis(String[] nouns){
// the code is foreach noun in nouns, echo it
}
public Command factory(){
// this clones the object and returns it
}
}
Now, your code becomes
// Load the hash
Hashtable cmds = new Hashtable();
cmds.add("echo", new Echo()); // one for each command
// token is in tok
// the "nouns" or "arguments are in a String[] nouns
((cmds.get(tok)).factory()).doThis(nouns);
See how this works? You look up the object in the hash. You call the factory method to get a new copy. You then invoke the processing for that command using the doThis method.
Update
This may be a bit too general, in that it uses the Factory pattern. Why have a factory method? Mainly, you'd use that so that each time you execute a command, the "verb" object (like the instance of Echo) can have its own internal state. If you don't need state to persist for a long time, you can simplify this to
(cmds.get(tok)).doThis(nouns);
Now it simply gets and uses the Echo object you created when you instanciated it with cmds.add("echo", new Echo());.

Have you looked at doing the dispatching with a Map? A hashmap would be pretty easy to put in there. Just make the key the command and make an interface or abstract class that is a command like this:
interface Commmand {
void execute(String args);
}
Or even better you could chop up the arguments in advance:
interface Commmand {
void execute(String[] args);
}
Then your you would use HashMap<String,Command>.

Related

Possible side effects when several CommandLine instance "work" on the same instance of an annotated class?

picoCLI's #-file mechanism is almost what I need, but not exactly. The reason is that I want to control the exact location of additional files parsed -- depending on previous option values.
Example: When called with the options
srcfolder=/a/b optionfile=of.txt, my program should see the additional options read from /a/b/of.txt, but when called with srcfolder=../c optionfile=of.txt, it should see those from ../c/of.txt.
The #-file mechanism can't do that, because it expands ALL the option files (always relative to the current folder, if they're relative) prior to processing ANY option values.
So I'd like to have picoCLI...
process options "from left to right",
recursively parse an option file when it's mentioned in an optionfile option,
and after that continue with the following options.
I might be able to solve this by recursively starting to parse from within the annotated setter method:
...
Config cfg = new Config();
CommandLine cmd = new CommandLine(cfg);
cmd.parseArgs(a);
...
public class Config {
#Option(names="srcfolder")
public void setSrcfolder(String path) {
this.srcfolder=path;
}
#Option(names="optionfile")
public void parseOptionFile(String pathAndName) {
// validate path, do some other housekeeping...
CommandLine cmd = new CommandLine(this /* same Config instance! */ );
cmd.parseArgs(new String[] { "#"+this.srcfolder + pathAndName });
}
...
This way several CommandLine instances would call setter methods on the same Config instance, recursively "interrupting" each other. Now comes the actual question: Is that a problem?
Of course my Config class has state. But do CommandLine instances also have state that might get messed up if other CommandLine instances also modify cfg "in between options"?
Thanks for any insights!
Edited to add: I tried, and I'm getting an UnmatchedArgumentException on the #-file option:
Exception in thread "main" picocli.CommandLine$UnmatchedArgumentException: Unmatched argument at index 0: '#/path/to/configfile'
at picocli.CommandLine$Interpreter.validateConstraints(CommandLine.java:13490)
...
So first I have to get around this: Obviously picoCLI doesn't expand the #-file option unless it's coming directly from the command line.
I did get it to work: several CommandLine instance can indeed work on the same instance of an annotated class, without interfering with each other.
There are some catches and I had to work around a strange picoCLI quirk, but that's not exactly part of an answer to this question, so I explain them in this other question.

What is the best way to process arguments passed to main method in java?

I wanted to know the best way to process arguments passed to the main method.
User pass the arguments from command line. i.e. I have a shell script which will invoke my java program. I am using this java program to invoke web service.
For Example,The format of the input is as follows
"Ram,ABC,XYZ,null,null,27-04-15" "Raj,EFG,DEF,null,null,25-04-15" "Kiran,IJK,LMN,null,null,20-04-15"
as you see, within each string there are different attribute values(comma separated). and each set of input is space separated. And web service provides two methods which are as follows.
public void processArg(name,addr1,addr2,info1,info2,dob){
}
public void processArg2(name,addr1,addr2){
}
here first method processArg will be used to submit data for each set. Once this method returns success then i need invoke second method processArg2 which will check the status of the submission i.e whether it is success or not.
What is the best way to achieve this? Please let me know if i am not clearly explained.
Thanks
You can use string tokenizer to solve this issue.Following code can solve you problem.
public static void main(String[] args){
for(int i=0;i<args.length;i++){
String dataString=args[i];
String[] splittedData=dataString.trim().split(",");
processArg(splittedData[0],splittedData[1],splittedData[2],splittedData[3],splittedData[4]);
processArg2(splittedData[0],splittedData[1],splittedData[2]);
}
}

Multiple output path (Java - Hadoop - MapReduce)

I do two MapReduce job, and I want for the second job to be able to write my result into two different files, in two different directories.
I would like something similar to FileInputFormat.addInputPath(.., multiple input path) in a sense, but for the output.
I'm completely new to MapReduce, and I have a specificity to write my code in Hadoop 0.21.0
I use context.write(..) in my Reduce step, but I don't see how to control multiple output paths...
Thanks for your time !
My reduceCode from my first job, to show you I only know how to output (it goes into a /../part* file. But now what I would like is to be able to specify two precises files for different output, depending on the key) :
public static class NormalizeReducer extends Reducer<LongWritable, NetflixRating, LongWritable, NetflixUser> {
public void reduce(LongWritable key, Iterable<NetflixRating> values, Context context) throws IOException, InterruptedException {
NetflixUser user = new NetflixUser(key.get());
for(NetflixRating r : values) {
user.addRating(new NetflixRating(r));
}
user.normalizeRatings();
user.reduceRatings();
context.write(key, user);
}
}
EDIT: so I did the method in the last comment as you mentioned, Amar. I don't know if it's works, I have other problem with my HDFS, but before I forget let's put here my discoveries for the sake of civilization :
http://archive.cloudera.com/cdh/3/hadoop-0.20.2+228/api/org/apache/hadoop/mapreduce/lib/output/MultipleOutputs.html
MultipleOutputs DOES NOT act in place of FormatOutputFormat. You define one output path with FormatOutputFormat, and then you can add many more with multiple MultipleOutputs.
addNamedOutput method: String namedOutput is just a word who describe.
You define the path actually in the write method, the String baseOutputPath arg.
so I did the method in the last comment as you mentioned, Amar. I don't know if it's works, I have other problem with my HDFS, but before I forget let's put here my discoveries for the sake of civilization :
http://archive.cloudera.com/cdh/3/hadoop-0.20.2+228/api/org/apache/hadoop/mapreduce/lib/output/MultipleOutputs.html
MultipleOutputs DOES NOT act in place of FormatOutputFormat. You define one output path with FormatOutputFormat, and then you can add many more with multiple MultipleOutputs.
addNamedOutput method: String namedOutput is just a word who describe.
You define the path actually in the write method, the String baseOutputPath arg.

It's possible to unit test modifications in windows environment variable?

My process simply add some content to the system variable PATH. Actually I'm doing this with a Process that use the setx.exe:
public void changePath(String newPath ) {
String path = System.getenv("PATH") + ";";
String[] cmd = new String[]{"C:\\Windows\\System32\\setx.exe", "PATH",
path+newPath, "-m"};
ProcessBuilder builder = new ProcessBuilder(cmd);
...
}
So I tried to write a test case to it.
Class UpdatePathTest {
#Test
public void testUpdatePath() {
//call the method that update the path
changePath("C:\\somebin");
assertTrue(System.getenv("PATH").contains("C:\\somebin")); //fails
// ProcessBuilder with command String[]{"cmd", "/C", "echo", "%PATH%"}; will fail too.
//and the above in a new Thread will fail too.
}
}
So, is there any way to get the new PATH value? Writing the new path is the only option, because I'm developing a jar that will install a desktop application.
I'm not sure changing the path is a good idea in a unit test. What if the test fails? You will have to make sure you do all the relevant tidy up.
Consider inverting your dependencies and use dependency injection.
This article explains it quite well I think.
So instead of having a method that does:
public void method() {
String path = System.getenv("PATH") + ";";
//do stuff on path
}
consider doing:
public void method(String path) {
//do stuff on path
}
which allows you to stub the path. If you cannot change the signature of the method then consider using the factory pattern and using a test factory to get the path.
EDIT: after update to question
What you have to think about here is what you are testing. When you call C:\Windows\System32\setx.exe you have read the API docs and are calling it with the correct parameters. This is much like calling another method on a java API. For example if you are manipulating a list you "know" it is zero based. You do not need to test this you just read the API and the community backs you up on this. For testing changePath I think you probably what to test what is going into ProcessBuilder. Again you have read the API docs and you have to assume that you are passing in the correct variables. (See //1 at bottom) And again you have to assume that ProcessBuilder works properly and that the Oracle (or most likely Sun) guys have implemented it to the API documents.
So what you want to do is check that you are passing variables to ProcessBuilder that match the specification as you understand it. For this you can mock ProcessBuilder and then verify that you are passing the correct parameters and calling the correct method on this class.
In general it is a hard one to test because you don't want to test the windows functions but want to test java's interaction with them.
//1 The main problem I have had with calling this external commands is understanding the API documents correctly or setting up the command. Usually you have to get the command line out and check that you are using methods correctly (esp cmd functions). This can mean that you work out how to use the cmd function, code it into ProcessBuilder and then write a test (or vice versa on the ProcessBuilder/test) Not the ideal way but sometimes documents are hard to understand.

How to run vbscript function from java?

From java code i am able to run the vbscript by using this code
Runtime.getRuntime().exec("wscript C:\\ppt\\test1.vbs ");
But want to know how to call the method of vbscript from java..for example in test1.vbs
Set objPPT = CreateObject("PowerPoint.Application")
objPPT.Visible = True
Set objPresentation = objPPT.Presentations.Open("C:\ppt\Labo.ppt")
Set objSlideShow = objPresentation.SlideShowSettings.Run.View
sub ssn1()
objPPT.Run "C:\ppt\Labo.ppt!.SSN"
End sub
how to call only ssn1() method from java.Otherwise can we run the macro of a power point from java code..kindly help!!
This should make you happy :) Go to the WScript section : http://technet.microsoft.com/library/ee156618.aspx
Here's my idea... in your vbscript file, make your script listen to a command line parameter that would specify which method to call. Then, in Java, you could only have to use this parameter whenever you want to call a specific method in the file.
Otherwise, if you want to access powerpoint in java, you will need to access its API like you did in vbscript, which is possible if vbscript can do it but the approach / syntax may change.
I'm not so much into the visual basic script side, but if you can expose your visual basic script as a COM object, the you can access the methods of it from java by usage of frameworks such as for example com4j:
http://com4j.java.net/
The PowerPoint application object's .Run method lets you call any public subroutine or function in any open presentation or loaded add-in
This post answers the OP's question:
Otherwise can we run the macro of a power point from java code..kindly help!!
(but does not address the original vbscript question)
There's the JACOB library, which stands for Java COM Bridge, you can find here: http://sourceforge.net/projects/jacob-project/?source=directory
With it you can invoke Excel, Word, Outlook, PowerPoint application object model methods.
I've tried this with Excel but not PowerPoint. (This is just some sample code, one might want to make it more object oriented.)
public class Excel {
private static ActiveXComponent xl = null;
public static Init() {
try {
ComThread.InitSTA();
xl = ActiveXComponent.connectToActiveInstance("Excel.Application.14");
// 14 is Office 2010, if you don't know what version you can do "Excel.Application"
if (xl==null) {
// code to launch Excel if not running:
xl = new ActiveXComponent("Excel.Application");
Dispatch.put(xl, "Visible", Constants.kTrue);
}
}
catch (Exception e) {
ComThread.Release();
}
}
public static String Run(String vbName) {
// Variant v = Dispatch.call(xl, "Run", vbName); // using string name lookup
Variant v = Dispatch.call(xl, 0x103, vbName); // using COM offset
// return Dispatch.get(this, "Name").getString();
return v.getString();
}
public static Variant Run1p(String vbName, Object param) {
// Variant v = Dispatch.call(xl, "Run", vbName, param);
return Dispatch.call(xl, 0x103, vbName, param);
// return Dispatch.get(this, "Name").getString();
}
public static Worksheet GetActiveWorksheet () {
// Dispatch d = xl.getProperty("ActiveSheet").toDispatch();
Dispatch d = Dispatch.get(xl, 0x133).toDispatch ();
return d; // you may want to put a wrapper around this...
}
}
Notes:
For Excel, at least, to get Run to invoke a VBA macro/subroutine several things have to be true:
The Excel workbook containing the macro must be "Active" (i.e. must
be the ActiveWorkbook) otherwise Run will not find the VBA subroutine. (However the workbook does not have to be
screen visible!! This means you can call a VBA Macro that is in an add-in!).
You can then pass the name of the macro using the following syntax as a string literal:
VBAProjectName.VBAModuleName.SubroutineName
For COM object invocations, you can use the name lookup version or the id number version. The id numbers come from the published COM interfaces (which you can find in C++ header files, or possibly have JACOB look them up for you).
If you successfully did the connection to Excel, be sure to call ComThread.Release() when you're done. Put it in some appropriately surrounding finally. If the process of your Java code terminates without calling it, the COM reference count on Excel will be wrong, and the Excel process will never terminate, even after you exit the Excel application. Once that happens, needless to say, Excel starts to behave screwy then (when you try to use it next, it runs but will fail to load any plug-ins/add-ons). If that happens (as it can during debugging esp. if you are bypassing finally's for better debugging) you have to use the task manager to kill the Excel process.

Categories

Resources