In this part, you make your new behavior accessible to the user.
Note: The Java GUI is complicated. If your needs cannot be covered with the procedure below, please seek Lora Murphy's help.
The Java code package responsible for creating the parameter file (and thus where behaviors go) is the javawrapper
package. Behaviors are packaged together by type into classes descended from BehaviorTypeBase
. For instance, there are the classes GrowthBehaviors
, MortalityBehaviors
, etc.
These classes descended from BehaviorTypeBase
are responsible for all the sorts of things that individual behaviors do for themselves in the C++ code: getting values from the parameter file, validating parameter values, etc. When you are adding a new behavior, you determine into which of these classes it fits.
In the case of our example biomass calculation behavior, we would add it to the class Analysis Behaviors
.
The BehaviorTypeBase
class children contain all of the parameters needed by any of their behaviors. You first need to add any parameters not already defined.
Each parameter is defined by a class descended from the ModelData
class. Each child class is tailored for a different type of data: a species-specific array (ModelVector
), a single float (ModelFloat
), etc. These classes contain everything you need to know about a parameter: what kind of data it is, a descriptive name to display to the user, its XML tags, etc.
The parameters become new members of the BehaviorTypeBase
child class containing your new behavior. When you add it, make sure that your comment for the parameter indicate to which behavior it belongs. Look carefully at all your constructor options for the parameter's class to make sure you set it up correctly. There are often several flags you can set which precisely describe the parameter.
Our example biomass calculation behavior had two parameters: "slope of biomass equation (a)" and "exponent of biomass equation (d)". They are both species-specific, so we would add two new ModelVector
objects to the AnalysisBehaviors
class. Here's the code we could write:
/**Biomass behavior - slope of biomass equation (a) (look, I'm a Doxygen comment!)*/
protected ModelVector mp_fSlopeOfBiomassEq = new ModelVector(
"Biomass Calculator - Slope of Biomass Equation (a)", //descriptive name to display to user
"an_slopeBiomassEq", //XML tag for the parameter
"an_sbeVal", //XML tag for individual species values
0, //initial size of the java.util.Vector within this class - 0 is always a good choice
ModelVector.FLOAT, //type of data within the array
false); //optional flag indicating that the parameter is not forced to apply to all species
/**Biomass behavior - exponent of biomass equation (d)*/
protected ModelVector mp_fExpOfBiomassEq = new ModelVector(
"Biomass Calculator - Exponent of Biomass Equation (d)",
"an_exponentBiomassEq",
"an_ebeVal",
0,
ModelVector.FLOAT);
We also need to add our parameters to the list of all parameters. In the constructor, you will see where parameters are added. We will put our new parameters in the list:
mp_oAllData.add(mp_fSlopeOfBiomassEq);
mp_oAllData.add(mp_fExpOfBiomassEq);
Tips for your descriptive parameter name, which will be displayed to the user:
The class which contains the behaviors is Behavior
. A Behavior
object contains information about the behavior and how the user is allowed to use it, along with what trees the user has assigned to it if it has been included in the current run.
The list of behaviors is held in an array in the BehaviorTypeBase
child class. Be sure to increase the size of the array before adding your new behavior.
It is easiest to show the code rather than explain it beforehand. Here's the code for our new example biomass behavior, which we would put in the constructor of the AnalysisBehaviors
class:
//Create the new behavior
mp_oChildBehaviors[iIndex] = new Behavior("biomass_map", //short key that we can use to find this behavior in the code
"Biomass Map Calculator", //name to display to users
"sample biomass behavior", //string to put in the parameter file in the behavior list
(float) 1, //version number
(float) 1); //optional - minimum version number supported
//Assign the appropriate parameters to this behavior
mp_oChildBehaviors[iIndex].AddRequiredData(mp_fSlopeOfBiomassEq);
mp_oChildBehaviors[iIndex].AddRequiredData(mp_fExpOfBiomassEq);
//At this point we could also: prevent this behavior from being assigned to certain kinds of trees, list new tree data members our behavior would create, etc.
Our behavior creates a grid object, so we need to tell the AnalysisBehaviors
class about that too. This way it will show up in the list of grids for the user. The list of grids is also kept in an array. After properly sizing the grid array to make room for our new grid, we would add the following code to the constructor:
//Add the "Biomass Map" grid
//Create the data member array for our two data members
DataMember[] p_oDataMembers = new DataMember[2];
//"count" int data member"
p_oDataMembers[0] = new DataMember("Adult Count", //data member name to display to user
"count", //data member name in the C++ code
DataMember.INTEGER); //data member type
//"avg" float data member"
p_oDataMembers[1] = new DataMember("Average Biomass",
"avg",
DataMember.FLOAT);
//Create the new grid
mp_oGrids[iIndex] = new Grid("Biomass Map", //name of the grid
p_oDataMembers, //list of grid's data members
null, //list of package data members - none in our case
8, 8); //default grid cell resolution - 8 X 8 is always safe
//Assign this grid to our new behavior
GetBehaviorByKey("biomass_map").AddGrid(mp_oGrids[iIndex]);
As in the C++ code, you want to make sure that you reject invalid parameter values. To do this, add code to the ValidateData
method of your BehaviorTypeBase
child class. This code runs when parameters are being entered in the parameter window, so users are not allowed to enter bad values, and before a run begins.
For our example biomass calculation behavior, negative values of our parameter a are illegal. We would add the following code to ValidateData
:
//Only proceed if our biomass behavior is enabled for the run - otherwise we don't care if there are invalid values
if (GetBehaviorByKey("biomass_map").m_bIsEnabled) {
//Get a list of species to which this behavior is applied so we only validate values for those species
boolean[] p_bAppliesTo = GetBehaviorByKey("biomass_map").GetWhichSpeciesUsed(oPop);
//There's a method available for making sure values are positive - use it
MakeSureAllPositive(mp_fSlopeOfBiomassEq, p_bAppliesTo);
}
There are JUnit tests for many classes in the GUI. Run the JUnit test for the class to which you added. Fix anything that you broke. Add new test conditions for any tests of methods you added to, like ValidateData
.
The very last step is to update the documentation so people know how to use your behavior. Add your new parameters and behavior to the page devoted to the type of behavior you've made. Tips: