diff --git a/lab-02/pom.xml b/lab-02/pom.xml
new file mode 100644
index 0000000..b909448
--- /dev/null
+++ b/lab-02/pom.xml
@@ -0,0 +1,29 @@
+
+
+ 4.0.0
+
+ by.polina_kostyukovich.docks_and_hobos
+ lab-02
+ 1.0-SNAPSHOT
+
+
+ com.googlecode.json-simple
+ json-simple
+ 1.1
+
+
+ commons-io
+ commons-io
+ 2.6
+
+
+
+
+ 19
+ 19
+ UTF-8
+
+
+
\ No newline at end of file
diff --git a/lab-02/src/main/java/by/polina_kostyukovich/docks_and_hobos/Main.java b/lab-02/src/main/java/by/polina_kostyukovich/docks_and_hobos/Main.java
new file mode 100644
index 0000000..8f344c8
--- /dev/null
+++ b/lab-02/src/main/java/by/polina_kostyukovich/docks_and_hobos/Main.java
@@ -0,0 +1,17 @@
+package by.polina_kostyukovich.docks_and_hobos;
+
+import by.polina_kostyukovich.docks_and_hobos.controllers.Controller;
+import org.json.simple.parser.ParseException;
+
+import java.io.IOException;
+
+public class Main {
+ public static void main(String[] args) throws IOException, ParseException {
+ if (args.length != 1) {
+ throw new IllegalArgumentException("Illegal number of arguments");
+ }
+
+ Controller controller = new Controller(args[0]);
+ controller.startRunning();
+ }
+}
diff --git a/lab-02/src/main/java/by/polina_kostyukovich/docks_and_hobos/Model.java b/lab-02/src/main/java/by/polina_kostyukovich/docks_and_hobos/Model.java
new file mode 100644
index 0000000..a18f854
--- /dev/null
+++ b/lab-02/src/main/java/by/polina_kostyukovich/docks_and_hobos/Model.java
@@ -0,0 +1,88 @@
+package by.polina_kostyukovich.docks_and_hobos;
+
+import by.polina_kostyukovich.docks_and_hobos.actors.*;
+import by.polina_kostyukovich.docks_and_hobos.controllers.HoboController;
+import org.json.simple.JSONArray;
+import org.json.simple.JSONObject;
+import org.json.simple.parser.JSONParser;
+import org.json.simple.parser.ParseException;
+
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.*;
+
+public class Model {
+ public void readData(String filename) throws IOException, ParseException {
+ JSONParser parser = new JSONParser();
+ JSONObject info = (JSONObject) parser.parse(new FileReader(filename));
+
+ final int maxShipsInTunnel = (int) (long) info.get("Max ships in tunnel");
+ tunnel = new Tunnel(maxShipsInTunnel);
+
+ final int generatingTime = (int) (long) info.get("Generating time");
+ final int minShipCapacity = (int) (long) info.get("Min ship capacity");
+ final int maxShipCapacity = (int) (long) info.get("Max ship capacity");
+ Object[] cargoTypesJson = ((JSONArray) info.get("Cargo types")).toArray();
+ ArrayList cargoTypes = new ArrayList<>(cargoTypesJson.length);
+ for (Object type : cargoTypesJson) {
+ cargoTypes.add((String) type);
+ }
+ shipGenerator = new ShipGenerator(generatingTime, cargoTypes, minShipCapacity, maxShipCapacity, tunnel);
+
+ final int numberOfDocks = (int) (long) info.get("Number of docks");
+ final int unloadingSpeed = (int) (long) info.get("Unloading speed");
+ final int dockCapacity = (int) (long) info.get("Dock capacity");
+ docks = new ArrayList<>(numberOfDocks);
+ for (int i = 0; i < numberOfDocks; ++i) {
+ docks.add(new Dock(unloadingSpeed, dockCapacity, i));
+ }
+
+ final int numberOfHobos = (int) (long) info.get("Number of hobos");
+ Object[] ingredientsCountJson = ((JSONArray) info.get("Ingredients count")).toArray();
+ Map ingredientsCount = new HashMap<>(ingredientsCountJson.length);
+ for (Object ingredientCount : ingredientsCountJson) {
+ JSONObject ingredientCountJson = (JSONObject) ingredientCount;
+ ingredientsCount.put((String) ingredientCountJson.get("Cargo type"),
+ (int) (long) ingredientCountJson.get("Cargo amount"));
+ }
+ final int stealingTime = (int) (long) info.get("Stealing time");
+ final int eatingTime = (int) (long) info.get("Eating time");
+ ArrayList hobos = new ArrayList<>(numberOfHobos);
+ for (int i = 0; i < numberOfHobos; ++i) {
+ hobos.add(new Hobo(stealingTime, i));
+ }
+ hoboController = new HoboController(hobos, ingredientsCount, docks, eatingTime, cargoTypes);
+ }
+
+ public ShipGenerator getShipGenerator() {
+ return shipGenerator;
+ }
+
+ public Tunnel getTunnel() {
+ return tunnel;
+ }
+
+ public ArrayList getDocks() {
+ return docks;
+ }
+
+ public HoboController getHoboController() {
+ return hoboController;
+ }
+
+ public Dock getFreeDock() {
+ return docks.stream()
+ .filter(Dock::isFree)
+ .findAny().orElse(null);
+ }
+
+ public boolean hasFreeDock() {
+ return docks.stream()
+ .anyMatch(Dock::isFree);
+ }
+
+ private ShipGenerator shipGenerator;
+ private Tunnel tunnel;
+ private ArrayList docks;
+ private HoboController hoboController;
+}
diff --git a/lab-02/src/main/java/by/polina_kostyukovich/docks_and_hobos/actors/Cargo.java b/lab-02/src/main/java/by/polina_kostyukovich/docks_and_hobos/actors/Cargo.java
new file mode 100644
index 0000000..0115389
--- /dev/null
+++ b/lab-02/src/main/java/by/polina_kostyukovich/docks_and_hobos/actors/Cargo.java
@@ -0,0 +1,27 @@
+package by.polina_kostyukovich.docks_and_hobos.actors;
+
+public class Cargo {
+ private final String type;
+ private int amount;
+
+ public Cargo(String type, int amount) {
+ if (type == null) {
+ throw new IllegalArgumentException("Parameter type is null");
+ }
+
+ this.type = type;
+ this.amount = amount;
+ }
+
+ public void setAmount(int amount) {
+ this.amount = amount;
+ }
+
+ public int getAmount() {
+ return amount;
+ }
+
+ public String getType() {
+ return type;
+ }
+}
diff --git a/lab-02/src/main/java/by/polina_kostyukovich/docks_and_hobos/actors/Dock.java b/lab-02/src/main/java/by/polina_kostyukovich/docks_and_hobos/actors/Dock.java
new file mode 100644
index 0000000..444087e
--- /dev/null
+++ b/lab-02/src/main/java/by/polina_kostyukovich/docks_and_hobos/actors/Dock.java
@@ -0,0 +1,79 @@
+package by.polina_kostyukovich.docks_and_hobos.actors;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+public class Dock implements Runnable {
+ public Dock(int unloadingTime, int capacity, int id) {
+ this.unloadingSpeed = unloadingTime;
+ this.capacity = capacity;
+ this.id = id;
+ }
+
+ @Override
+ public void run() {
+ Cargo addedCargo = new Cargo(cargoToAdd.getType(), 0);
+ while (cargoToAdd.getAmount() > 0 && getTotalCargoAmount() < capacity) {
+ try {
+ Thread.sleep(1000L);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ int addedAmount = Math.min(
+ Math.min(capacity - getTotalCargoAmount(), cargoToAdd.getAmount()), unloadingSpeed);
+ cargos.computeIfPresent(cargoToAdd.getType(), (type, count) -> count + addedAmount);
+ cargos.putIfAbsent(cargoToAdd.getType(), addedAmount);
+ cargoToAdd.setAmount(cargoToAdd.getAmount() - addedAmount);
+ addedCargo.setAmount(addedCargo.getAmount() + addedAmount);
+ }
+ isFree = true;
+ String strToLog = "Dock #" + id + " ended unloading " + addedCargo.getAmount() + " ones of "
+ + addedCargo.getType();
+ LOGGER.log(Level.INFO, strToLog);
+ }
+
+ public Map getCargos() {
+ return cargos;
+ }
+
+ public int getUnloadingSpeed() {
+ return unloadingSpeed;
+ }
+
+ public boolean isFree() {
+ return isFree && getTotalCargoAmount() < capacity;
+ }
+
+ public void setCargoToAdd(Cargo cargoToAdd) {
+ if (cargoToAdd == null) {
+ throw new IllegalArgumentException("Parameter cargoToAdd is null");
+ }
+
+ this.cargoToAdd = cargoToAdd;
+ }
+
+ public void setFree(boolean isFree) {
+ this.isFree = isFree;
+ }
+
+ public int getTotalCargoAmount() {
+ return cargos.values().stream()
+ .mapToInt(n -> n)
+ .sum();
+ }
+
+ public static Logger getLogger() {
+ return LOGGER;
+ }
+
+ private final int id;
+ private final int unloadingSpeed;
+ private final int capacity;
+ private final ConcurrentMap cargos = new ConcurrentHashMap<>();
+ private boolean isFree = true;
+ private Cargo cargoToAdd;
+ private static final Logger LOGGER = Logger.getLogger(Dock.class.getName());
+}
diff --git a/lab-02/src/main/java/by/polina_kostyukovich/docks_and_hobos/actors/Hobo.java b/lab-02/src/main/java/by/polina_kostyukovich/docks_and_hobos/actors/Hobo.java
new file mode 100644
index 0000000..b00cf54
--- /dev/null
+++ b/lab-02/src/main/java/by/polina_kostyukovich/docks_and_hobos/actors/Hobo.java
@@ -0,0 +1,52 @@
+package by.polina_kostyukovich.docks_and_hobos.actors;
+
+import by.polina_kostyukovich.docks_and_hobos.controllers.HoboController;
+
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+public class Hobo implements Runnable {
+ public Hobo(int stealingTime, int id) {
+ this.stealingTime = stealingTime;
+ this.id = id;
+ }
+
+ @Override
+ public void run() {
+ try {
+ Thread.sleep(stealingTime * 1000L);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ cargoToBeStolen.getDock().getCargos().compute(cargoToBeStolen.getCargoType(), (type, count) -> count - 1);
+ cargoToBeStolen.decrementStealingHobosNumber();
+ LOGGER.log(Level.FINE, "Hobo #" + id + " stole one " + cargoToBeStolen.getCargoType());
+ isFree = true;
+ }
+
+ public boolean isFree() {
+ return isFree;
+ }
+
+ public void setCargoToBeStolen(HoboController.CargoToBeStolen cargoToBeStolen) {
+ if (cargoToBeStolen == null) {
+ throw new IllegalArgumentException("cargoToBeStolen is null");
+ }
+
+ this.cargoToBeStolen = cargoToBeStolen;
+ }
+
+ public void setFree(boolean isFree) {
+ this.isFree = isFree;
+ }
+
+ public static Logger getLogger() {
+ return LOGGER;
+ }
+
+ private final int id;
+ private final int stealingTime;
+ private boolean isFree = true;
+ private HoboController.CargoToBeStolen cargoToBeStolen;
+ private static final Logger LOGGER = Logger.getLogger(Hobo.class.getName());
+}
diff --git a/lab-02/src/main/java/by/polina_kostyukovich/docks_and_hobos/actors/Ship.java b/lab-02/src/main/java/by/polina_kostyukovich/docks_and_hobos/actors/Ship.java
new file mode 100644
index 0000000..5211021
--- /dev/null
+++ b/lab-02/src/main/java/by/polina_kostyukovich/docks_and_hobos/actors/Ship.java
@@ -0,0 +1,17 @@
+package by.polina_kostyukovich.docks_and_hobos.actors;
+
+public class Ship {
+ private final Cargo cargo;
+
+ public Ship(Cargo cargo) {
+ if (cargo == null) {
+ throw new IllegalArgumentException("Parameter cargo is null");
+ }
+
+ this.cargo = cargo;
+ }
+
+ public Cargo getCargo() {
+ return cargo;
+ }
+}
diff --git a/lab-02/src/main/java/by/polina_kostyukovich/docks_and_hobos/actors/ShipGenerator.java b/lab-02/src/main/java/by/polina_kostyukovich/docks_and_hobos/actors/ShipGenerator.java
new file mode 100644
index 0000000..6e77940
--- /dev/null
+++ b/lab-02/src/main/java/by/polina_kostyukovich/docks_and_hobos/actors/ShipGenerator.java
@@ -0,0 +1,62 @@
+package by.polina_kostyukovich.docks_and_hobos.actors;
+
+import java.util.ArrayList;
+import java.util.Random;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+public class ShipGenerator implements Runnable {
+ public ShipGenerator(int generatingTime, ArrayList cargoTypes, int minShipCapacity, int maxShipCapacity,
+ Tunnel tunnel) {
+ if (cargoTypes == null || tunnel == null) {
+ throw new IllegalArgumentException("Some parameter in ShipGenerator constructor is null");
+ }
+
+ this.generatingTime = generatingTime;
+ this.cargoTypes = cargoTypes;
+ this.minShipCapacity = minShipCapacity;
+ this.maxShipCapacity = maxShipCapacity;
+ this.tunnel = tunnel;
+ }
+
+ @Override
+ public void run() {
+ while (true) {
+ try {
+ Thread.sleep(1000L * generatingTime);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ Ship newShip = generateShip();
+ String strToLog;
+ if (tunnel.addShip(newShip)) {
+ strToLog = "Added ship: type - " + newShip.getCargo().getType() + ", amount - "
+ + newShip.getCargo().getAmount();
+ } else {
+ strToLog = "Killed ship :(";
+ }
+ LOGGER.log(Level.INFO, strToLog);
+ }
+ }
+
+ private Ship generateShip() {
+ int capacity = (int) (Math.random() * (maxShipCapacity - minShipCapacity + 1) + minShipCapacity);
+ String type = cargoTypes.get((new Random()).nextInt(cargoTypes.size()));
+ return new Ship(new Cargo(type, capacity));
+ }
+
+ public int getGeneratingTime() {
+ return generatingTime;
+ }
+
+ public static Logger getLogger() {
+ return LOGGER;
+ }
+
+ private final int generatingTime;
+ private final ArrayList cargoTypes;
+ private final int minShipCapacity;
+ private final int maxShipCapacity;
+ private final Tunnel tunnel;
+ private static final Logger LOGGER = Logger.getLogger(ShipGenerator.class.getName());
+}
diff --git a/lab-02/src/main/java/by/polina_kostyukovich/docks_and_hobos/actors/Tunnel.java b/lab-02/src/main/java/by/polina_kostyukovich/docks_and_hobos/actors/Tunnel.java
new file mode 100644
index 0000000..400970e
--- /dev/null
+++ b/lab-02/src/main/java/by/polina_kostyukovich/docks_and_hobos/actors/Tunnel.java
@@ -0,0 +1,23 @@
+package by.polina_kostyukovich.docks_and_hobos.actors;
+
+import java.util.concurrent.LinkedBlockingQueue;
+
+public class Tunnel {
+ private final LinkedBlockingQueue ships;
+
+ public Tunnel(int maxShipsNumber) {
+ ships = new LinkedBlockingQueue<>(maxShipsNumber);
+ }
+
+ public Ship takeShip() {
+ return ships.remove();
+ }
+
+ public boolean addShip(Ship ship) {
+ return ships.offer(ship);
+ }
+
+ public boolean hasShips() {
+ return !ships.isEmpty();
+ }
+}
diff --git a/lab-02/src/main/java/by/polina_kostyukovich/docks_and_hobos/controllers/Controller.java b/lab-02/src/main/java/by/polina_kostyukovich/docks_and_hobos/controllers/Controller.java
new file mode 100644
index 0000000..327b9c2
--- /dev/null
+++ b/lab-02/src/main/java/by/polina_kostyukovich/docks_and_hobos/controllers/Controller.java
@@ -0,0 +1,35 @@
+package by.polina_kostyukovich.docks_and_hobos.controllers;
+
+import by.polina_kostyukovich.docks_and_hobos.Model;
+import by.polina_kostyukovich.docks_and_hobos.actors.*;
+import org.json.simple.parser.ParseException;
+
+import java.io.IOException;
+
+public class Controller {
+ public Controller(String filenameForReadingData) throws IOException, ParseException {
+ this.model = new Model();
+ model.readData(filenameForReadingData);
+ }
+
+ public void startRunning() {
+ LoggersController loggersController = new LoggersController();
+ loggersController.configureLoggers();
+
+ new Thread(loggersController).start();
+ new Thread(model.getShipGenerator()).start();
+ new Thread(model.getHoboController()).start();
+
+ while (true) {
+ while (model.hasFreeDock() && model.getTunnel().hasShips()) {
+ Ship nextShip = model.getTunnel().takeShip();
+ Dock freeDock = model.getFreeDock();
+ freeDock.setFree(false);
+ freeDock.setCargoToAdd(nextShip.getCargo());
+ new Thread(freeDock).start();
+ }
+ }
+ }
+
+ private final Model model;
+}
diff --git a/lab-02/src/main/java/by/polina_kostyukovich/docks_and_hobos/controllers/HoboController.java b/lab-02/src/main/java/by/polina_kostyukovich/docks_and_hobos/controllers/HoboController.java
new file mode 100644
index 0000000..3f8a93b
--- /dev/null
+++ b/lab-02/src/main/java/by/polina_kostyukovich/docks_and_hobos/controllers/HoboController.java
@@ -0,0 +1,222 @@
+package by.polina_kostyukovich.docks_and_hobos.controllers;
+
+import by.polina_kostyukovich.docks_and_hobos.actors.Dock;
+import by.polina_kostyukovich.docks_and_hobos.actors.Hobo;
+
+import java.util.*;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+public class HoboController implements Runnable {
+ public HoboController(ArrayList hobos, Map ingredientsCount, List docks,
+ int eatingTime, List allCargoTypes) {
+ if (hobos == null || ingredientsCount == null || docks == null || allCargoTypes == null) {
+ throw new IllegalArgumentException("Some parameter in HoboController constructor is null");
+ }
+ this.hobos = hobos;
+ hobos.remove(hobos.size() - 1);
+ hobos.remove(hobos.size() - 1);
+ this.ingredientsCount = ingredientsCount;
+ this.eatingTime = eatingTime;
+
+ cargos = new ArrayList<>();
+ for (String cargoType : allCargoTypes) {
+ for (Dock dock : docks) {
+ cargos.add(new CargoToBeStolen(dock, cargoType));
+ }
+ }
+ }
+
+ @Override
+ public void run() {
+ while (true) {
+ goStealingAndEating();
+ }
+ }
+
+ private void goStealingAndEating() {
+ while (areAllDocksEmpty()) {}
+
+ Map curIngrCount = new HashMap<>(ingredientsCount.size());
+ curIngrCount.putAll(ingredientsCount);
+ checkStock(curIngrCount);
+
+ while (containsNotOnlyZeros(curIngrCount)) {
+ while (hasFreeHobo()) {
+ tryHoboToStealCargo(curIngrCount);
+ }
+ }
+
+ while (!areAllHobosFree()) {}
+ try {
+ LOGGER.log(Level.INFO, "Hobos started eating");
+ Thread.sleep(eatingTime * 1000L);
+ LOGGER.log(Level.INFO, "Hobos finished eating");
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private void tryHoboToStealCargo(Map curIngrCount) {
+ while (areAllDocksEmpty()) {
+ if (!containsNotOnlyZeros(curIngrCount)) return;
+ }
+
+ List neededCargoTypes = curIngrCount.entrySet().stream()
+ .filter(entry -> entry.getValue() > 0)
+ .map(Map.Entry::getKey)
+ .toList();
+
+ boolean wasStealingDetected = false;
+ for (String cargoType : neededCargoTypes) {
+ Optional availableCargo = findAvailableCertainCargo(cargoType);
+ if (availableCargo.isEmpty()) continue;
+
+ stealCargo(availableCargo.get());
+ curIngrCount.compute(cargoType, (type, count) -> count - 1);
+
+ wasStealingDetected = true;
+ break;
+ }
+
+ if (!wasStealingDetected) {
+ Optional stolenCargoType = tryToStealRandomCargo();
+ if (stolenCargoType.isEmpty()) return;
+ replaceRandomCargoInRecipe(curIngrCount, stolenCargoType.get());
+ }
+ }
+
+ private Optional findAvailableCertainCargo(String cargoType) {
+ return cargos.stream()
+ .filter(cargo -> cargo.getCargoType().equals(cargoType))
+ .filter(cargo -> cargo.getDock().getCargos().containsKey(cargo.getCargoType())
+ && cargo.getDock().getCargos().get(cargo.getCargoType()) != null)
+ .filter(cargo -> cargo.getStealingHobosNumber()
+ < cargo.getDock().getCargos().get(cargoType))
+ .findAny();
+ }
+
+ private Optional tryToStealRandomCargo() {
+ Optional stolenCargo = cargos.stream()
+ .filter(cargo -> cargo.getDock().getCargos().containsKey(cargo.getCargoType())
+ && cargo.getDock().getCargos().get(cargo.getCargoType()) != null
+ && cargo.getDock().getCargos().get(cargo.getCargoType()) > cargo.getStealingHobosNumber())
+ .findAny();
+ if (stolenCargo.isEmpty()) {
+ return Optional.empty();
+ }
+ stealCargo(stolenCargo.get());
+ return Optional.of(stolenCargo.get().getCargoType());
+ }
+
+ private void stealCargo(CargoToBeStolen cargo) {
+ Hobo freeHobo = getFreeHobo();
+ freeHobo.setCargoToBeStolen(cargo);
+ cargo.incrementStealingHobosNumber();
+ freeHobo.setFree(false);
+ new Thread(freeHobo).start();
+ }
+
+ private void replaceRandomCargoInRecipe(Map curIngrCount, String stolenCargoType) {
+ Optional randomCargoType = curIngrCount.entrySet().stream()
+ .filter(entry -> entry.getValue() > 0)
+ .map(Map.Entry::getKey)
+ .findAny();
+ if (randomCargoType.isPresent()) {
+ curIngrCount.compute(randomCargoType.get(), (type, count) -> count - 1);
+ } else {
+ stock.computeIfPresent(stolenCargoType, (type, count) -> count + 1);
+ stock.putIfAbsent(stolenCargoType, 1);
+ }
+ }
+
+ private boolean areAllDocksEmpty() {
+ return cargos.stream()
+ .filter(cargo -> cargo.getDock().getCargos().containsKey(cargo.getCargoType())
+ && cargo.getDock().getCargos().get(cargo.getCargoType()) != null)
+ .noneMatch(cargo ->
+ cargo.getDock().getCargos().get(cargo.getCargoType()) > cargo.getStealingHobosNumber());
+ }
+
+ private void checkStock(Map curIngrCount) {
+ stock.entrySet().stream()
+ .filter(entry -> entry.getValue() != null && entry.getValue() > 0)
+ .filter(entry -> curIngrCount.containsKey(entry.getKey()) && curIngrCount.get(entry.getKey()) != null)
+ .forEach(entry -> {
+ int amountToTakeFromStock = Math.min(entry.getValue(), curIngrCount.get(entry.getKey()));
+ stock.compute(entry.getKey(), (type, count) -> count - amountToTakeFromStock);
+ curIngrCount.compute(entry.getKey(), (type, count) -> count - amountToTakeFromStock);
+ String strToLog = amountToTakeFromStock + " ones of " + entry.getKey() + " were taken from stock";
+ LOGGER.log(Level.FINE, strToLog);
+ });
+ }
+
+ private boolean containsNotOnlyZeros(Map map) {
+ return map.values().stream()
+ .mapToInt(n -> n)
+ .anyMatch(n -> n != 0);
+ }
+
+ private boolean hasFreeHobo() {
+ return hobos.stream()
+ .anyMatch(Hobo::isFree);
+ }
+
+ private Hobo getFreeHobo() {
+ return hobos.stream()
+ .filter(Hobo::isFree)
+ .findAny().orElse(null);
+ }
+
+ private boolean areAllHobosFree() {
+ return hobos.stream()
+ .allMatch(Hobo::isFree);
+ }
+
+ public static Logger getLogger() {
+ return LOGGER;
+ }
+
+ private final ArrayList hobos;
+ private final Map ingredientsCount;
+ private final int eatingTime;
+ private final Map stock = new HashMap<>();
+ private final ArrayList cargos;
+ private static final Logger LOGGER = Logger.getLogger(HoboController.class.getName());
+
+ public static class CargoToBeStolen {
+ private final Dock dock;
+ private final String cargoType;
+ private final AtomicInteger stealingHobosNumber = new AtomicInteger(0);
+
+ public CargoToBeStolen(Dock dock, String cargoType) {
+ if (dock == null || cargoType == null) {
+ throw new IllegalArgumentException("Some parameter in CargoToBeStolen constructor is null");
+ }
+
+ this.dock = dock;
+ this.cargoType = cargoType;
+ }
+
+ public Dock getDock() {
+ return dock;
+ }
+
+ public String getCargoType() {
+ return cargoType;
+ }
+
+ public void incrementStealingHobosNumber() {
+ stealingHobosNumber.incrementAndGet();
+ }
+
+ public void decrementStealingHobosNumber() {
+ stealingHobosNumber.decrementAndGet();
+ }
+
+ public int getStealingHobosNumber() {
+ return stealingHobosNumber.get();
+ }
+ }
+}
diff --git a/lab-02/src/main/java/by/polina_kostyukovich/docks_and_hobos/controllers/LoggersController.java b/lab-02/src/main/java/by/polina_kostyukovich/docks_and_hobos/controllers/LoggersController.java
new file mode 100644
index 0000000..ccefae0
--- /dev/null
+++ b/lab-02/src/main/java/by/polina_kostyukovich/docks_and_hobos/controllers/LoggersController.java
@@ -0,0 +1,120 @@
+package by.polina_kostyukovich.docks_and_hobos.controllers;
+
+import by.polina_kostyukovich.docks_and_hobos.actors.Dock;
+import by.polina_kostyukovich.docks_and_hobos.actors.Hobo;
+import by.polina_kostyukovich.docks_and_hobos.actors.ShipGenerator;
+import by.polina_kostyukovich.docks_and_hobos.utils.LogFormatter;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.logging.*;
+
+import org.apache.commons.io.FileUtils;
+
+public class LoggersController implements Runnable {
+ public LoggersController() {
+ if (ShipGenerator.getLogger() == null || Dock.getLogger() == null || Hobo.getLogger() == null
+ || HoboController.getLogger() == null) {
+ throw new NullPointerException("Some logger is null");
+ }
+ loggers.add(ShipGenerator.getLogger());
+ loggers.add(Dock.getLogger());
+ loggers.add(Hobo.getLogger());
+ loggers.add(HoboController.getLogger());
+
+ try {
+ FileUtils.cleanDirectory(new File(PATH_TO_DIRECTORY));
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public void configureLoggers() {
+ for (Logger logger : loggers) {
+ logger.setUseParentHandlers(false);
+ }
+
+ ConsoleHandler consoleHandler = new ConsoleHandler();
+ consoleHandler.setFormatter(formatter);
+ consoleHandler.setLevel(Level.INFO);
+ addHandler(consoleHandler);
+
+ String filename = getNewFilenameAndCreateFile();
+ try {
+ handler = new FileHandler(filename);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ handler.setFormatter(formatter);
+
+ for (Logger logger : loggers) {
+ logger.setLevel(Level.FINE);
+ }
+ addHandler(handler);
+ }
+
+ @Override
+ public void run() {
+ while (true) {
+ try {
+ Thread.sleep(FILE_CHANGING_INTERVAL * 1000L);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ String filename = getNewFilenameAndCreateFile();
+ changeHandlers(filename);
+ }
+ }
+
+ private synchronized void changeHandlers(String filename) {
+ if (handler != null) {
+ handler.flush();
+ }
+
+ for (Logger logger : loggers) {
+ logger.removeHandler(handler);
+ }
+ if (handler != null) {
+ handler.close();
+ }
+
+ try {
+ handler = new FileHandler(filename);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ handler.setFormatter(formatter);
+
+ addHandler(handler);
+ }
+
+ private void addHandler(Handler handler) {
+ for (Logger logger : loggers) {
+ logger.addHandler(handler);
+ }
+ }
+
+ private String getNewFilenameAndCreateFile() {
+ File file;
+ String filename;
+ try {
+ do {
+ filename = PATH_TO_DIRECTORY + "/log file #" + numberOfNextFile + ".txt";
+ file = new File(filename);
+ ++numberOfNextFile;
+ } while (!file.createNewFile());
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ return filename;
+ }
+
+ private final List loggers = new ArrayList<>();
+ private final static int FILE_CHANGING_INTERVAL = 40;
+ private final LogFormatter formatter = new LogFormatter();
+ private Handler handler;
+ private int numberOfNextFile = 1;
+ private final String PATH_TO_DIRECTORY = "src/main/java/by/polina_kostyukovich/docks_and_hobos/logs";
+}
diff --git a/lab-02/src/main/java/by/polina_kostyukovich/docks_and_hobos/logs/log file #1.txt b/lab-02/src/main/java/by/polina_kostyukovich/docks_and_hobos/logs/log file #1.txt
new file mode 100644
index 0000000..2f95880
--- /dev/null
+++ b/lab-02/src/main/java/by/polina_kostyukovich/docks_and_hobos/logs/log file #1.txt
@@ -0,0 +1,55 @@
+2022-11-30 19:36:53: Added ship: type - tomato, amount - 19
+2022-11-30 19:36:57: Hobo #1 stole one tomato
+2022-11-30 19:36:57: Hobo #2 stole one tomato
+2022-11-30 19:36:57: Hobo #0 stole one tomato
+2022-11-30 19:36:57: Added ship: type - cheese, amount - 13
+2022-11-30 19:36:58: Hobo #3 stole one tomato
+2022-11-30 19:36:58: Hobo #4 stole one tomato
+2022-11-30 19:37:00: Dock #0 ended unloading 19 ones of tomato
+2022-11-30 19:37:00: Hobo #1 stole one tomato
+2022-11-30 19:37:00: Hobo #2 stole one tomato
+2022-11-30 19:37:00: Hobo #0 stole one tomato
+2022-11-30 19:37:01: Hobo #3 stole one tomato
+2022-11-30 19:37:01: Hobo #4 stole one tomato
+2022-11-30 19:37:01: Added ship: type - cheese, amount - 15
+2022-11-30 19:37:02: Dock #1 ended unloading 13 ones of cheese
+2022-11-30 19:37:03: Hobo #1 stole one cheese
+2022-11-30 19:37:03: Hobo #0 stole one cheese
+2022-11-30 19:37:03: Hobo #2 stole one cheese
+2022-11-30 19:37:04: Hobo #3 stole one cheese
+2022-11-30 19:37:04: Hobo #4 stole one cheese
+2022-11-30 19:37:05: Added ship: type - cucumber, amount - 14
+2022-11-30 19:37:06: Hobo #1 stole one cheese
+2022-11-30 19:37:06: Hobo #0 stole one cheese
+2022-11-30 19:37:06: Hobos started eating
+2022-11-30 19:37:06: Dock #0 ended unloading 15 ones of cheese
+2022-11-30 19:37:09: Added ship: type - cucumber, amount - 13
+2022-11-30 19:37:10: Dock #1 ended unloading 14 ones of cucumber
+2022-11-30 19:37:11: Hobos finished eating
+2022-11-30 19:37:13: Added ship: type - cheese, amount - 10
+2022-11-30 19:37:14: Hobo #0 stole one tomato
+2022-11-30 19:37:14: Hobo #2 stole one tomato
+2022-11-30 19:37:14: Hobo #3 stole one cucumber
+2022-11-30 19:37:14: Hobo #1 stole one tomato
+2022-11-30 19:37:14: Hobo #4 stole one cucumber
+2022-11-30 19:37:14: Dock #0 ended unloading 13 ones of cucumber
+2022-11-30 19:37:17: Hobo #0 stole one cucumber
+2022-11-30 19:37:17: Hobo #2 stole one cucumber
+2022-11-30 19:37:17: Hobo #1 stole one cheese
+2022-11-30 19:37:17: Hobo #3 stole one cheese
+2022-11-30 19:37:17: Hobo #4 stole one cheese
+2022-11-30 19:37:17: Added ship: type - tomato, amount - 17
+2022-11-30 19:37:17: Dock #1 ended unloading 10 ones of cheese
+2022-11-30 19:37:20: Hobo #0 stole one cheese
+2022-11-30 19:37:20: Hobo #2 stole one cheese
+2022-11-30 19:37:20: Hobo #1 stole one cheese
+2022-11-30 19:37:20: Hobo #3 stole one cheese
+2022-11-30 19:37:20: Hobo #4 stole one cheese
+2022-11-30 19:37:21: Added ship: type - cucumber, amount - 16
+2022-11-30 19:37:23: Hobo #0 stole one cheese
+2022-11-30 19:37:23: Hobo #2 stole one cheese
+2022-11-30 19:37:23: Hobos started eating
+2022-11-30 19:37:23: Dock #0 ended unloading 17 ones of tomato
+2022-11-30 19:37:25: Added ship: type - cucumber, amount - 12
+2022-11-30 19:37:27: Dock #1 ended unloading 16 ones of cucumber
+2022-11-30 19:37:28: Hobos finished eating
diff --git a/lab-02/src/main/java/by/polina_kostyukovich/docks_and_hobos/resources/config.json b/lab-02/src/main/java/by/polina_kostyukovich/docks_and_hobos/resources/config.json
new file mode 100644
index 0000000..13a1551
--- /dev/null
+++ b/lab-02/src/main/java/by/polina_kostyukovich/docks_and_hobos/resources/config.json
@@ -0,0 +1,46 @@
+{
+ "Generating time": 4,
+ "Min ship capacity": 10,
+ "Max ship capacity": 20,
+ "Cargo types": [
+ "bread",
+ "ham",
+ "cheese",
+ "butter",
+ "cucumber",
+ "tomato"
+ ],
+ "Max ships in tunnel": 3,
+ "Number of docks": 4,
+ "Unloading speed": 3,
+ "Dock capacity": 50,
+ "Number of hobos": 7,
+ "Ingredients count": [
+ {
+ "Cargo type": "bread",
+ "Cargo amount": 2
+ },
+ {
+ "Cargo type": "ham",
+ "Cargo amount": 4
+ },
+ {
+ "Cargo type": "cheese",
+ "Cargo amount": 3
+ },
+ {
+ "Cargo type": "butter",
+ "Cargo amount": 1
+ },
+ {
+ "Cargo type": "cucumber",
+ "Cargo amount": 4
+ },
+ {
+ "Cargo type": "tomato",
+ "Cargo amount": 3
+ }
+ ],
+ "Stealing time": 3,
+ "Eating time": 5
+}
\ No newline at end of file
diff --git a/lab-02/src/main/java/by/polina_kostyukovich/docks_and_hobos/utils/LogFormatter.java b/lab-02/src/main/java/by/polina_kostyukovich/docks_and_hobos/utils/LogFormatter.java
new file mode 100644
index 0000000..3717184
--- /dev/null
+++ b/lab-02/src/main/java/by/polina_kostyukovich/docks_and_hobos/utils/LogFormatter.java
@@ -0,0 +1,19 @@
+package by.polina_kostyukovich.docks_and_hobos.utils;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.logging.LogRecord;
+import java.util.logging.SimpleFormatter;
+
+public class LogFormatter extends SimpleFormatter {
+ public static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";
+
+ @Override
+ public String format(LogRecord record) {
+ Date date = new Date(record.getMillis());
+ DateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT);
+ String strDate = dateFormat.format(date);
+ return strDate + ": " + record.getMessage() + "\n";
+ }
+}