Monday, March 31, 2008

How to create OpenMI based JGrass models

As some of you probably know, the JGrass console engine has been designed to automagically link together OpenMI based models.

This post should really be a big big document and at some point it will be usefull also for first-time-openmi-developers, but as I start now, it is some growing documentation for the JGrass team members, so that we all follow same way of doing things.

So dear guest, if you feel this all sounds strange, don't worry, it has to.


Alright, let's start with some sparse thoughts:

1) for now I will assume you are extending the eu.hydrologis.jgrass.models.ModelsBackbone class
I don't have to tell you that you should not touch that class, right? :)

2) the out and err printstreams have been moved to the ModelsBackbone class. So do not re-declare them in your model's class.
You will be able to access out and err streams from your class, since they are declared protected in the ModelsBackbone.

Do not have such as the following lines in your model, else you will get troubles:

private PrintStream out;
private PrintStream err;

3) implement safeGetValues and safeInitialize instead of getValues and initialize

Two methods have been added to ModelsBackbone to trap exceptions and give the user a feedback on what is going on:
- safeGetValues
- safeInitialize

Those two methods are called from within the interface methods getValues and initialize and are wrapped inside a try-catch block that traps three types of Exceptions:

  • OutOfMemory: this one is trapped and sends a standard message to the user on the console, suggesting to add memory to the JGrass process. This is an error that we know very well, how often the active region is too big or the resolution to high and some analysis eats all the memory? With this trap the user will finally be warned.
  • ModelSematicException: this one is the most important for models developers. It takes a message for the user (passed directly as error to the console) and when thrown, it exits the safeGetValues or safeInitialize method and prints the message to console. That way semantic problems of a model can be told to the user. An example could be a wrong input flag in the models arguments or a non existing map warning. When processing the exception, also the variable isOkToGo is set to false. This is a protected boolean in the ModelsBackbone class, so it can again be used everywhere in the model to stop execution. If for example in the initialize method something goes wrong, the developer can throw a ModelSematicException and from that moment on the isOkToGo boolean is false and can be checked as first thing in the safeGetValues method.
  • Exception: every other exception thrown is sent to console, prefixed by a standard message introducing the real problem. Users will not love the message, but they will at least have something to report :)

4) throw ModelSematicException if there are problems

Inside your model, if something goes wrong and you want to write something usefull to the user, throw a ModelSemanticException passing it your message. It will take care of it.

5) do not call safeGetValues and safeInitialize directly

Always call the OpenMI interface methods getValues and initialize! Else you will lose beeing OpenMI based!!

6) to finish for now, a small code example:

The following shows a model (like the one you are writing if you read until now) in which a check on the passed parameters is done. If in troubles, the exception with a nice message is thrown:

public void safeInitialize( IArgument[] properties ) throws Exception {

String grassDb = null;
String location = null;
String mapset = null;
if (properties != null) {
for( IArgument argument : properties ) {
String key = argument.getKey();
if (key.compareTo(ModelsConstants.GRASSDB) == 0) {
grassDb = argument.getValue();
} else if (key.compareTo(ModelsConstants.LOCATION) == 0) {
location = argument.getValue();
} else if (key.compareTo(ModelsConstants.MAPSET) == 0) {
mapset = argument.getValue();
} else if (key.compareTo("activefield") == 0) { //$NON-NLS-1$
activeField = argument.getValue();
if (activeField == null) {
isOkToGo = false;
String pattern = "Parameter {0} supposed to be used but not supplied.";
Object[] args = new Object[]{key};
pattern = MessageFormat.format(pattern, args);
throw new ModelsSematicException(pattern);
}
} else {
isOkToGo = false;
String pattern = "Parameter {0} not recognized for model h.netshape2flow.";
Object[] args = new Object[]{key};
pattern = MessageFormat.format(pattern, args);
throw new ModelsSematicException(pattern);
}
}
}



Behind the coulisse, in the ModelsBackbone class, the exception is trapped and the isOkToGo variable is set to false:


try {
safeInitialize(properties);
} catch (OutOfMemoryError e) {
err.println(Messages.getString("ModelsBackbone.outofmemory")); //$NON-NLS-1$
e.printStackTrace();
isOkToGo = false;
return null;
} catch (ModelsSematicException e) {
err.println(e.getLocalizedMessage());
isOkToGo = false;
return null;
}
//... etc. etc.


Again in the ModelsBackbone class, when it comes to do the job, which is in the getValues method, before launching the safeGetValues method, a check is done if before that moment everything was ok, in order to decide wheather to stop or go on:


public IValueSet getValues( ITime time, String linkID ) {
// if there were problems, stop and return null
if (!isOkToGo) {
return null;
}
// if everything ok, do your stuff
try {
return safeGetValues(time, linkID);
}
//... etc. etc.



The same can be done eveywhere inside your own code (i.e. not in ModelsBackbone).

No comments: