Step 4: Take initial model data from a file

Rather than hard-code the initial model state, we will read scenario data from a JSON file.

Reading a Scenario from an external JSON file

First, add a new dependency to the pom.xml file:

pom.xml, json dependency
<!-- JSON -->
<dependency>
    <groupId>org.json</groupId>
    <artifactId>json</artifactId>
    <version>20230227</version>
</dependency>

Create a ScenarioParser class with the following code:

ScenarioParser.java
package tutorial.scenario;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import org.json.JSONObject;

public class ScenarioParser {

    public static Scenario parseScenario(JSONObject jsonScenario) {

        LocalDateTime beginDate = parseLocalDateTime(jsonScenario, "beginDate");
        LocalDateTime endDate = parseLocalDateTime(jsonScenario, "endDate");

        double intervalBetweenRequestHrs = parseDouble(jsonScenario, "intervalBetweenRequestHrs");
        double maxDeliveryTimeHrs = parseDouble(jsonScenario, "maxDeliveryTimeHrs");

        Map<String, Node> nodes = new HashMap<>();

        List<Truck> trucks = new ArrayList<>();
        List<Arc> arcs = new ArrayList<>();
        List<Warehouse> warehouses = new ArrayList<>();
        List<Store> stores = new ArrayList<>();

        jsonScenario.getJSONArray("nodes").forEach(elem -> {
            JSONObject json = (JSONObject) elem;
            String id = parseString(json, "id");
            nodes.put(id, new Node(id, parseDouble(json, "x"), parseDouble(json, "y")));
        });

        jsonScenario.getJSONArray("arcs").forEach(elem -> {
            JSONObject json = (JSONObject) elem;
            Node source = parseNode(nodes, json, "source");
            Node dest = parseNode(nodes, json, "dest");
            if (source.equals(dest)) {
                String message = "Arc source and destination should be different Nodes.\n";
                System.err.println(message);
                throw new RuntimeException(message);
            }
            List<Point> points = new ArrayList<>();
            json.getJSONArray("points").forEach(pointObj  -> {
                JSONObject pointJson = (JSONObject) pointObj;
                points.add(new Point(pointJson.getDouble("x"), pointJson.getDouble("y")));
            });
            arcs.add(new Arc(source, dest, points));
        });

        jsonScenario.getJSONArray("trucks").forEach(elem -> {
            JSONObject json = (JSONObject) elem;
            trucks.add(new Truck(parseString(json, "id"), parseString(json, "name"), parseDouble(json, "speed"), parseNode(nodes, json, "initialNode")));
        });

        jsonScenario.getJSONArray("warehouses").forEach(elem -> {
            JSONObject json = (JSONObject) elem;
            warehouses.add(new Warehouse(parseString(json, "id"), parseString(json, "name"), parseNode(nodes, json, "node")));
        });

        jsonScenario.getJSONArray("stores").forEach(elem -> {
            JSONObject json = (JSONObject) elem;
            stores.add(new Store(parseString(json, "id"), parseString(json, "name"), parseNode(nodes, json, "node")));
        });

        return new Scenario(trucks, intervalBetweenRequestHrs, maxDeliveryTimeHrs,
                nodes.values().stream().toList(), arcs, warehouses, stores, beginDate, endDate);
    }

    private static LocalDateTime parseLocalDateTime(JSONObject jsonScenario, String str) {
        return LocalDateTime.parse(jsonScenario.getString(str));
    }

    private static Node parseNode(Map<String, Node> nodes, JSONObject object, String nodeFieldName) {
        String nodeFieldValue = object.getString(nodeFieldName);
        Node node = nodes.get(nodeFieldValue);
        if (Objects.nonNull(node)) {
            return node;
        } else {
            String message = "Node with id '" + nodeFieldValue + "' does not exist.\n";
            System.err.println(message);
            throw new RuntimeException(message);
        }
    }

    private static String parseString(JSONObject object, String str) {
        return object.getString(str);
    }

    private static double parseDouble(JSONObject object, String str) {
        return object.getDouble(str);
    }
}

Prepare one example scenario

Create a new scenarios folder in the project folder.

Place the following 'scenario.json' file into the scenarios folder:

scenario.json
{
  "name": "scenario",
  "beginDate": "2023-01-01T00:00:00",
  "endDate": "2023-01-01T12:00:00",
  "intervalBetweenRequestHrs": 0.5,
  "maxDeliveryTimeHrs": 6,
  "trucks": [
    {
      "id": "truck-1",
      "name": "Truck 1",
      "speed": 40.0,
      "initialNode": "node3"
    }
  ],
  "nodes": [
    {
      "id": "node1",
      "x": 0,
      "y": 0
    },
    {
      "id": "node2",
      "x": 0,
      "y": 30
    },
    {
      "id": "node3",
      "x": 40,
      "y": 30
    },
    {
      "id": "node4",
      "x": 40,
      "y": 0
    }
  ],
  "arcs": [
    {
      "source": "node1",
      "dest": "node3",
      "points": []
    },
    {
      "source": "node1",
      "dest": "node4",
      "points": []
    },
    {
      "source": "node2",
      "dest": "node3",
      "points": []
    },
    {
      "source": "node2",
      "dest": "node4",
      "points": []
    }
  ],
  "warehouses": [
    {
      "id": "wh1",
      "name": "Warehouse 1",
      "node": "node1"
    },
    {
      "id": "wh2",
      "name": "Warehouse 2",
      "node": "node1"
    },
    {
      "id": "wh3",
      "name": "Warehouse 3",
      "node": "node2"
    }
  ],
  "stores": [
    {
      "id": "st1",
      "name": "Store 1",
      "node": "node3"
    },
    {
      "id": "st2",
      "name": "Store 2",
      "node": "node3"
    },
    {
      "id": "st1",
      "name": "Store 3",
      "node": "node4"
    }
  ]
}

Note that this scenario file contains exactly the same scenario data as in the static{} section of the Main class. Simulated period is taken from the createAndRunOneExperimentWithStats() method (12 hours).

Changes to the Main class

Get to the Main class.

Add new 'import' statements:

Main.java, new import statements
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.Objects;
import org.json.JSONObject;
import tutorial.scenario.ScenarioParser;

Add a new field:

Main.java, new field
private static final String SCENARIOS_PATH = "scenarios/";

Add a new method:

Main.java, runScenariosFromFiles() method
private static void runScenariosFromFiles() {
    File folder = new File(SCENARIOS_PATH);
    for (File file : folder.listFiles()) {
        JSONObject jsonScenario = null;
        try {
            jsonScenario = new JSONObject(Files.readString(file.toPath()));
        } catch (IOException e) {
            e.printStackTrace();
        }
        if (Objects.nonNull(jsonScenario)) {
            try {
                Scenario scenario = ScenarioParser.parseScenario(jsonScenario);
                runExperimentWithStats(scenario, file.getName());
            } catch (Exception e) {
                System.err.printf("File '%s' contains error or has incorrect format\n", file.getName());
                e.printStackTrace();
            }
        }
    }
}

Replace the main() method to use the runScenariosFromFiles() method:

Main.java, updated main() method
public static void main(String[] args) {
    runScenariosFromFiles();
}

Check the result

Run the program and see if you get the following result in the console output:

Scenario            	Trucks count	SL	Expenses	Expenses/SL
scenario.json       	           1	57,14%	$ 420,00	$ 7,35

This is exactly the same result for "12 hours" simulation as we had in the previous step.