Last updated on 16th July 2024
In order to create a model with the Simudyne SDK, you must generate the java
files yourself, which can be time-consuming and is prone to small errors if you are not familiar with java.
The model scaffolding allows you to avoid having to create those files manually. This plugin can turn correct JSON files into a basic model structure. The simudyne-maven-plugin is the only component you need to make it work. This is currently only supported for models built with maven.
The plugin should be added to the POM in the build section:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
... Model properties here...
<dependencies>
... dependencies here ...
</dependencies>
<build>
<plugins>
... other plugins here ...
<plugin>
<groupId>simudyne</groupId>
<artifactId>simudyne-maven-plugin</artifactId>
<version>1.0-SNAPSHOT</version>
<configuration>
<overwrite>false</overwrite>
</configuration>
</plugin>
</plugins>
</build>
</project>
In order to work, a correct JSON representation of a model must be provided to the plugin. Each representation must be held in one JSON file, but the plugin can be called to generate the models from several files at the same time.
The default location the plugin will look for JSON files is in the src/main/json/models
(inside the current directory). You can manually set the input directory by passing the input
parameter to the plugin.
<configuration>
<input>my/new/path</input>
</configuration>
Inside the input directory, put the JSON files you want to use to generate models. Each file will generate a different model and must therefore contain all the necessary information to the creation of a model. The name of the json file will be the package name in which the generated files are put.
{
"name": "myModel",
"variables": {},
"constants": {},
"inputs": {},
"fields": {},
"longAccumulators": {},
"doubleAccumulators": {},
"globals": {},
"agents": {},
"messages": {},
"links": {}
}
A JSON file representing a model must provide the name of the model. It can also define accumulators, model fields, the global states fields, inputs, constants, variables, the agents of the model, the links and the messages that will be used in the model. The name of the JSON file will be the name of the model package.
{
"variables": {},
"constants": {},
"inputs": {},
"fields": {}
}
The keys variables
, constants
, inputs
, fields
all represent java
variables in the model. The only difference is their annotation.
A variable defined in inputs
will have the annotation @Input
, those in variables
will have @Variable
, those in constants
will have @Constant
and the ones in fields
will have no annotations.
Required fields for a java
variable are
java
variable are {
"inputs": {
"fieldWithDefault": "int = 1",
"basicField": "String",
"fieldWithDisplayName": "long = 2 , myField"
}
}
The code above will generate the following java code inside your model:
@Input
int fieldWithDefault = 1;
@Input
String basicField;
@Input(name = "myField")
long fieldWithDisplayName = 2;
The member in the JSON can also be defined in a more explicit way as follow :
{
"inputs": {
"fieldName": {
"type": "int",
"default": 5,
"displayName": " this will be displayed"
}
}
}
Note that a member of fields
does not have any annotation and therefore cannot have a display name.
The globals
key in the model allows you to define the variables, constants, inputs and fields that will be in the GlobalState
of the model.
{
"globals" : {
"variables": {},
"constants": {},
"inputs": {},
"fields": {}
}
}
The code above will output the correct fields with the right annotations inside the GlobalState
object on your model.
Accumulators must be defined separately in the JSON file depending on their type (Double or Long accumulator).
An accumulator only needs a name, but will also accept a display name if provided.
The keys doubleAccumulators
and longAccumulators
are used to define the accumulators.
To generate accumulators without display names:
{
"longAccumulators": ["name1", "name2", "name3"],
"doubleAccumulators": ["name4", "name5"]
}
To use with the display names :
{
"longAccumulators": {
"name1": "display name1",
"name2": "display name2",
"name3": "display name3"
},
"doubleAccumulators": {
"name4": "display name4",
"name5": "display name5"
}
}
The messages and links must be defined on the same JSON file as the model, but they will be output as different files. A message/link only needs a name, but it can also have a type and fields.
There are 3 ways to define a message/link.
If the message/link takes fields :
{
"messages": {
"MyMessage": {
"fields": {}
}
},
"links": {
"MyLink": {
"inputs": {}
}
}
}
If the link/message is empty :
{
"messages": {
"MyMessage": {}
},
"links": {
"MyLink": {}
}
}
This will create a link/message with the specified name but no other attribute (no fields, no type).
Messages can also have a type. A message type is used when the message is used to represent a single data field. For example a message might only have a single Long field, so would be a message of type Long. If a message doesn't have any fields, its good practice to define the message as being of type Empty.
{
"messages": {
"MyMessage": "Empty"
}
}
For instance, the following code :
{
"messages": {
"Mg1": {
"fields": {},
"type": "Empty"
},
"Msg2": "Long",
"Msg3": {}
},
"links": {
"MyLink": {}
}
}
will output the Messages.java
file:
import simudyne.core.graph.Message;
public class Messages {
public static class Msg1 extends Message.Empty {
}
public static class Msg2 extends Message.Long {
}
public static class Msg3 extends Message {
}
}
and the Links.java
file:
import simudyne.core.graph.Link;
public class Links {
public static class MyLink extends Link {
}
}
Agents are defined with their fields and the names of their actions. The actions generated will not contain any logic, they must be completed before running the code.
{
"agents": {
"Agent1": {
"fields": {},
"actions": ["action1_name", "action2_name"]
}
}
}
The JSON above will for instance output the following java file agent1.java
:
package myImports;
import simudyne.core.abm.Action;
import simudyne.core.abm.Agent;
public class Agent1 extends Agent<myModel.Globals> {
public static Action<agent1> action1_name =
Action.create(
Agent1.class,
agent1 -> {
// enter the action's body here
});
public static Action<agent1> action2_name =
Action.create(
Agent1.class,
agent1 -> {
// enter the action's body here
});
}
Once the JSON files representing models are placed at the right location, the generation of the java
files can begin.
In order to generate the java
files from the JSON, run
mvn simudyne:jsonGen
This will fetch the JSON into the specified input directory and extract the information.
The java
files will be generated into the directory src/main/java/models
inside the Simudyne project.
The files corresponding to a model will be output in a subfolder with the same name as the input JSON file.
When running the mvn simudyne:jsonGen
, you may encounter issues.
If some fields, actions, classes or files were not generated, please check the syntax and make sure the JSON file does not contain spelling errors.
If the plugin finds duplicate names in your model, an exception will be thrown and the conflicting names will be displayed, as well as their location.
If the plugin tries to create a file that already exists and the overwrite
flag is not set on true
, the plugin will throw an exception.
The overwrite
flag must be set to true
to enable the old files to be replaced. This can be done in the POM :
<configuration>
<overwrite>true</overwrite>
</configuration>