Sunday, November 15, 2009

Its starting to work, GroovyScriptCommandFactory is live.

Now that GroovyScriptCommand is mostly working I did more work on GroovyScriptCommandFactory. I added the code to configure the script engine with the default script directories where it will look for scripts to execute. I’ll be adding the mechanisms for customizing the script directories later. With that the factory does most of what it needs to do to function as a CommandFactory to find scripts in the script directories.

The next big step was to change the PredefinedCommandFactory to look up commands in more than just the TypeMapCommandFactory. In the original code the CommandCenter, which is where the main execution happens, created a new PredefinedCommandFactory which would delegate to a TypeMapCommandFactory to create the static Command objects. I added logic to PredefinedCommandFactory to have a List instead of a single CommandFactory delegate. Each CommandFactory is added in turn and each one is checked to see if it can create the command requested when createCommand(String) is called on the PredefinedCommandFactory. The behavior of has changed slightly in that all of the CommandFactories are given the chance to create a new Command and only if all of them fail is an IllegalArgumentException thrown.

Because of this, the order that the CommandFactories are added to the list of delegates is important as it is also the order that they will be given a chance to create a Command object. Right now the static TypeMapCommandFactory is added first and checked first. This way there is no way to have a script called that is the same name as an existing static Command. I’m not sure how I feel about this. Its easy to change so I’ll see how I like it as I continue testing.

All of this works in new unit tests and I tried it on the command line. It was pretty cool to see my test scripts getting run from the interactive shell. Its starting to come together. I don’t think the script engine integration part I’m doing now is going to be all that hard. I think that designing and implementing the script API is going to be tougher, mostly because I’ll probably be changing it a lot as I start to use it myself to write scripts. No one is a tougher critic on your code than your first user.

Monday, November 9, 2009

Working Code: Running scripts from GroovyScriptCommand

Working off of the examples of the existing CommandFactories and Commands in the code, I wrote the GroovyScriptCommandFactory and the GroovyScriptCommand as a wrapper for all script commands. There are some assumptions made in the existing code that instances of Commands will be known at compile time so that information about them, such as annotations on the class, can be used for help output and command names. I'm not crazy about this as it makes doing dynamic commands much harder. I would have preferred to have a more standard abstract method or something that can be called so that I could, for example, return the description of the script from the script itself which is known at runtime when the method would be called. As it is, there are helper classes which reflect on the Command subclass and output the description from the annotation.

I think my two options here are
  1. Go with a single GroovyScriptCommand that has a lame description defined in the annotation like "To get help for this script, run it with 'help' as the only argument"
  2. Do some crazy runtime class modification to add annotations to the subclass when the script name is known and load the description from the script if it is available and return some standard "no help" message if the script does not implement a description. I'm not yet sure how the script would do this. That is left as an exercise for future me.
Right now I'm going with #1 as its quick and easy. We'll see how hard #2 is later.

I'm actually pretty happy with the GroovyScriptCommandFactory. This is the subclass of CommandFactory that is responsible for creating the instances of Command based on a string name. This is pretty easy as the string name of the command will be the name of the groovy command without the .groovy at the end. I decided to return a Command object even if the script requested doesn't exist so that the exception can be thrown when the Command.execute() method is called. Since CommandFactory.createCommand(String) does not throw exceptions I could only return null which isn't hugely helpful.

Unit tests work and I can read variables set in the script binding and write new ones which can be read by the unit tests. There's still a little more work to have the GroovyScriptCommandFactory get created on startup and be asked to create Commands. Right now there is only one hard coded CommandFactory called and it is configured by a property file. I'm not sure how flexible this needs to be or if I can just change it to create the original CommandFactory and the groovy one and call one or the other first. My gut reaction is to have the scripts override the built in commands as that's how bash works but I could easily see it going the other way as overwriting builtin commands is crazy making. Its like monkey patching in dynamic languages. Not for the faint of heart.

Monday, November 2, 2009

Steeping in the code

I've been staring at the code for a while peppering with TODO statements where changes need to be made to allow the Groovy script engine to do its thing.

During startup, a CommandCenter object is created that does most of the delegating of work. It passes commands off to the CommandFactory to get the Command object, creates an instance of the Command and then runs it returning the result.

Right now the CommandFactory is hard coded to create a PredefinedCommandFactory object. This seems to just parse a properties file which contains names of commands and their corresponding classnames. These are put in a map of name to classname and used to create another factory called TypeMapCommandFactory which creates instances of commands based on the name requested and the classname in the map, throwing exceptions if they are missing.

In PredefinedCommandFactory, the TypeMapCommandFactory is held as an instance field called "delagate". There may have been grander plans for the PredefinedCommandFactory and the CommandFactory in general as it seems overly abstract, but it does provide a great place to put in my own CommandFactory.

My CommandFactory will be able to take in a command name and look up whether a Groovy script exists with that name in the scripts directory, however it was configured. I'm not sure whether or not built in commands or scripts will take precedence during name collisions. That might even be an exception as its too weird to want to do on purpose.

I intend to leave PredefinedCommandFactory in place and change its implementation to check with both the GroovyScriptCommandFactory and the TypeMapCommandFactory to return the Command.

Next up, besides actually doing that, is to create a base GroovyScriptCommand from which the scripts will inherit.