diff --git a/Contest/Assignment/resources/log4j2.xml b/Contest/Assignment/resources/log4j2.xml
new file mode 100644
index 0000000..1c946b1
--- /dev/null
+++ b/Contest/Assignment/resources/log4j2.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Contest/Assignment/src/org/togetherjava/event/elevator/Main.java b/Contest/Assignment/src/org/togetherjava/event/elevator/Main.java
index e9e4159..4c1f0ca 100644
--- a/Contest/Assignment/src/org/togetherjava/event/elevator/Main.java
+++ b/Contest/Assignment/src/org/togetherjava/event/elevator/Main.java
@@ -1,10 +1,13 @@
package org.togetherjava.event.elevator;
-import org.togetherjava.event.elevator.elevators.Elevator;
-import org.togetherjava.event.elevator.humans.Human;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.togetherjava.event.elevator.simulation.MoreSimulations;
import org.togetherjava.event.elevator.simulation.Simulation;
+import org.togetherjava.event.elevator.util.LogUtils;
public final class Main {
+ private static final Logger logger = LogManager.getLogger();
/**
* Starts the application.
*
@@ -19,28 +22,47 @@ public static void main(final String[] args) {
// Eventually try out the randomly generated systems. If you want to debug a problem you encountered
// with one of them, note down the seed that it prints at the beginning and then use the variant that takes this seed.
// That way, it will generate the same system again, and you can repeat the test.
- Simulation simulation = Simulation.createSingleElevatorSingleHumanSimulation();
- // Simulation simulation = Simulation.createSimpleSimulation();
- // Simulation simulation = Simulation.createRandomSimulation(5, 50, 10);
- // Simulation simulation = Simulation.createRandomSimulation(putDesiredSeedHere, 5, 50, 10);
+// Simulation simulation = Simulation.createSingleElevatorSingleHumanSimulation();
+// Simulation simulation = Simulation.createSimpleSimulation();
+// Simulation simulation = Simulation.createRandomSimulation(5, 50, 10);
+// Simulation simulation = Simulation.createRandomSimulation(putDesiredSeedHere, 5, 50, 10);
+ Simulation simulation = MoreSimulations.createMegaSimulation();
+// Simulation simulation = MoreSimulations.createMegaAdvancedSimulation();
+// Simulation simulation = MoreSimulations.createSimpleFailingSimulation();
+// Simulation simulation = MoreSimulations.createSimpleSucceedingSimulation();
+// Simulation simulation = MoreSimulations.createSimpleThreeStepSimulation();
+// Simulation simulation = Simulation.createRandomSimulation(1, 100, 1000, 50);
+// Simulation simulation = MoreSimulations.createSimplePaternosterSimulation();
+// Simulation simulation = MoreSimulations.createSimpleAdvancedSimulation();
+// Simulation simulation = MoreSimulations.createNotMarkoSimulation();
+// Simulation simulation = MoreSimulations.createGoodPaternosterSimulation();
- simulation.printSummary();
+ if (simulation.shouldPrintSummary()) {
+ simulation.printSummary();
+ }
- System.out.println("Starting simulation...");
+ long simulationStart = System.nanoTime();
+ logger.info("Starting simulation...");
simulation.start();
- simulation.prettyPrint();
+ if (simulation.shouldPrint()) {
+ simulation.prettyPrint();
+ }
while (!simulation.isDone()) {
- System.out.println("\tSimulation step " + simulation.getStepCount());
- simulation.step();
- simulation.prettyPrint();
+ logger.info("Simulation step " + simulation.getStepCount());
+ LogUtils.measure("Simulation step", simulation::step);
+ if (simulation.shouldPrint()) {
+ simulation.prettyPrint();
+ }
+ simulation.printCurrentStatistics();
if (simulation.getStepCount() >= 100_000) {
throw new IllegalStateException("Simulation aborted. All humans should have arrived"
+ " by now, but they did not. There is likely a bug in your code.");
}
}
- System.out.println("Simulation is done.");
+ long simulationEnd = System.nanoTime();
+ logger.info("Simulation completed in %.3f seconds.%n".formatted((simulationEnd - simulationStart) / 1e9));
simulation.printResult();
}
diff --git a/Contest/Assignment/src/org/togetherjava/event/elevator/elevators/CommonElevator.java b/Contest/Assignment/src/org/togetherjava/event/elevator/elevators/CommonElevator.java
new file mode 100644
index 0000000..212f7d8
--- /dev/null
+++ b/Contest/Assignment/src/org/togetherjava/event/elevator/elevators/CommonElevator.java
@@ -0,0 +1,225 @@
+package org.togetherjava.event.elevator.elevators;
+
+import org.jetbrains.annotations.Nullable;
+import org.togetherjava.event.elevator.humans.Passenger;
+import org.togetherjava.event.elevator.util.CollectionUtils;
+
+import java.util.ArrayDeque;
+import java.util.Comparator;
+import java.util.Deque;
+
+/**
+ * A single elevator that can serve a given amount of floors.
+ *
+ * This elevator can take floor requests from either humans or the elevator system itself.
+ * The elevator will eventually move towards the requested floor and transport humans to their destinations.
+ */
+public final class CommonElevator extends Elevator {
+ /**
+ * Creates a new elevator.
+ *
+ * @param minFloor the minimum floor that the elevator can serve, must be greater than or equal to 1.
+ * @param floorsServed the amount of floors served in total by this elevator, must be greater than or equal to 2.
+ * Together with the minFloor this forms a consecutive range of floors with no gaps in between.
+ * @param currentFloor the floor the elevator starts at, must be within the defined range of floors served by the elevator
+ */
+ public CommonElevator(int minFloor, int floorsServed, int currentFloor) {
+ super(minFloor, floorsServed, currentFloor);
+ }
+
+ @Override
+ public boolean canRequestDestinationFloor() {
+ return true;
+ }
+
+ /**
+ * This represents a human or the elevator system
+ * itself requesting this elevator to eventually move to the given floor.
+ * The elevator is supposed to memorize the destination in a way that
+ * it can ensure to eventually reach it.
+ *
+ * If this method is called by a passenger, it passes itself as the second parameter,
+ * which helps the elevator selection algorithm.
+ */
+ @Override
+ public synchronized void requestDestinationFloor(int destinationFloor, @Nullable Passenger passenger) {
+ rangeCheck(destinationFloor);
+
+ if (passenger != null) {
+ potentialTargets.remove(passenger);
+ }
+
+ // Let's check if the work queue already contains the desired floor
+ if (!willVisitFloor(destinationFloor)) {
+ addTargetFloor(destinationFloor);
+ }
+ }
+
+ /**
+ * Add a floor to the task queue of this elevator. Either as a new element at the end of the queue,
+ * or by modifying the last element if it's possible to do so without changing elevator semantics.
+ * After that, try to find an optimal path through all targets and repopulate the targets' collection, if necessary.
+ * It is expected that this method is called in a synchronized context.
+ */
+ private void addTargetFloor(int targetFloor) {
+ targets.add(targetFloor);
+
+ var optimalTargetsRecord = rearrangeTargets(currentFloor, targets);
+ var optimalTargets = compressTargets(optimalTargetsRecord.targets());
+
+ if (!CollectionUtils.equals(targets, optimalTargets)) {
+ logger.debug(() -> "Elevator %d on floor %d is rearranging targets after receiving new floor %d, would be %s, new queue %s, potential targets %s, queue length in turns is %d"
+ .formatted(id, currentFloor, targetFloor, targets, optimalTargets, potentialTargets.values(), optimalTargetsRecord.cost()));
+ targets.clear();
+ targets.addAll(optimalTargets);
+ } else {
+ logger.debug(() -> "Elevator %d on floor %d has added floor %d to the queue, the queue is now %s, potential targets %s, queue length in turns is %d"
+ .formatted(id, currentFloor, targetFloor, targets, potentialTargets.values(), optimalTargetsRecord.cost()));
+ }
+ }
+
+ /**
+ * A recursive method that tries to find an optimal path from a specified starting point through all the targets
+ * in the specified deque.
+ * It is assumed that the input deque is in synchronized context and does not contain duplicates.
+ * No guarantee is made that the deque reference contained in the returned record will be the same
+ * or different as the input deque. However, if it's the same, it won't get mutated.
+ */
+ private static OptimalTargetsAndCost rearrangeTargets(int from, Deque targets) {
+ int size = targets.size();
+ if (size == 0) {
+ // No targets - no need to move
+ return new OptimalTargetsAndCost(from, targets, 0);
+ } else if (size == 1) {
+ // One target - calculate distance to it
+ return new OptimalTargetsAndCost(from, targets, Math.abs(targets.getFirst() - from));
+ } else if (size == 2) {
+ // Two targets - simple enough to do a manual calculation on them
+ int e1 = targets.getFirst();
+ int e2 = targets.getLast();
+ // c1 represents cost as is, c2 represents cost if the two elements were flipped
+ int c1 = Math.abs(e2 - e1) + Math.abs(e1 - from);
+ int c2 = Math.abs(e1 - e2) + Math.abs(e2 - from);
+ int cost;
+ var newDeque = new ArrayDeque(2);
+ if (c2 < c1) {
+ // Flip the two elements
+ cost = c2;
+ newDeque.addFirst(e2);
+ newDeque.addLast(e1);
+ } else {
+ cost = c1;
+ newDeque.addFirst(e1);
+ newDeque.addLast(e2);
+ }
+ return new OptimalTargetsAndCost(from, newDeque, cost);
+ } else {
+ // Anything with N targets, where N > 2, gets decomposed into N recursive method calls,
+ // where each element becomes the new starting point and the rest act as new targets
+ return targets.stream()
+ // First off, decompose and do recursive calls
+ .map(nextFrom -> {
+ var recursiveTargets = new ArrayDeque(targets.size() - 1);
+ boolean encounteredSameElement = false;
+ for (Integer e : targets) {
+ if (!e.equals(nextFrom)) {
+ recursiveTargets.addLast(e);
+ } else if (!encounteredSameElement) {
+ encounteredSameElement = true;
+ } else {
+ throw new RuntimeException("Input queue contains a duplicate");
+ }
+ }
+ return rearrangeTargets(nextFrom, recursiveTargets);
+ })
+ // Then, filter out the result with the smallest potential cost
+ .min(Comparator.comparingInt(r -> r.cost() + Math.abs(r.from() - from)))
+ // Finally, perform the object creation since there's just one element left
+ .map(r -> {
+ var oldTargets = r.targets();
+ var newTargets = new ArrayDeque(oldTargets.size() + 1);
+ newTargets.addAll(oldTargets);
+ newTargets.addFirst(r.from());
+ return new OptimalTargetsAndCost(from, newTargets, r.cost() + Math.abs(r.from() - from));
+ })
+ .orElseThrow(() -> new RuntimeException("Target sorting is functioning incorrectly"));
+ }
+ }
+
+ /**
+ * A record that holds intermediate or terminal search results.
+ *
+ * @param from starting point
+ * @param targets a deque holding points to visit, sorted in the optimal order
+ * @param cost the amount of turns it would take to visit all points
+ */
+ private record OptimalTargetsAndCost(int from, Deque targets, int cost) {}
+
+ /**
+ * Compress a given target deque, returning a new deque with removed unnecessary intermediate targets.
+ * No guarantee is made that the deque reference contained in the returned record will be the same
+ * or different as the input deque. However, if it's the same, it won't get mutated.
+ * It is expected that this method is called in a synchronized context.
+ */
+ private Deque compressTargets(Deque deque) {
+ int size = deque.size();
+ if (size <= 1) {
+ // There is nothing to compress
+ return deque;
+ }
+ boolean wasCompressed = false;
+ var newDeque = new ArrayDeque(size);
+ var itr = deque.iterator();
+ int first = currentFloor;
+ int second = itr.next();
+ newDeque.addLast(second);
+ int third;
+ while (itr.hasNext()) {
+ third = itr.next();
+ if (Integer.compare(first, second) == Integer.compare(second, third)) {
+ // Compression takes place, so we do not advance first
+ newDeque.removeLast();
+ wasCompressed = true;
+ } else {
+ // No compression, advance first
+ first = second;
+ }
+ newDeque.addLast(third);
+ second = third;
+ }
+ if (wasCompressed) {
+ logger.debug(() -> "Elevator queue was compressed, start at %d, previous %s, new %s".formatted(currentFloor, deque, newDeque));
+ }
+ return newDeque;
+ }
+
+ @Override
+ protected void modifyTargetsOnArrival() {
+ targets.remove();
+ }
+
+ /**
+ * @return whether this elevator is currently on the specified floor
+ * or will at some point visit that floor before all its tasks are done.
+ */
+ public boolean willVisitFloor(int floor) {
+ if (!canServe(floor)) {
+ return false;
+ }
+
+ if (floor == currentFloor) {
+ return true;
+ }
+
+ int min = currentFloor;
+ int max = currentFloor;
+ for (int nextTarget : targets) {
+ min = Math.min(min, nextTarget);
+ max = Math.max(max, nextTarget);
+ if (min <= floor && floor <= max) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/Contest/Assignment/src/org/togetherjava/event/elevator/elevators/Elevator.java b/Contest/Assignment/src/org/togetherjava/event/elevator/elevators/Elevator.java
index 51333b2..6e88fc7 100644
--- a/Contest/Assignment/src/org/togetherjava/event/elevator/elevators/Elevator.java
+++ b/Contest/Assignment/src/org/togetherjava/event/elevator/elevators/Elevator.java
@@ -1,21 +1,52 @@
package org.togetherjava.event.elevator.elevators;
-import java.util.StringJoiner;
+import lombok.Getter;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.jetbrains.annotations.Nullable;
+import org.togetherjava.event.elevator.humans.ElevatorListener;
+import org.togetherjava.event.elevator.humans.Passenger;
+
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
/**
- * A single elevator that can serve a given amount of floors.
+ * Common superclass for all elevators.
*
- * An elevator can take floor requests from either humans or the elevator system itself.
- * The elevator will eventually move towards the requested floor and transport humans to their destinations.
+ * An elevator may be able to take floor requests from either humans or the elevator system itself. In that case,
+ * the elevator will eventually move towards the requested floor and transport humans to their destinations.
*/
-public final class Elevator implements ElevatorPanel {
+public abstract class Elevator implements ElevatorPanel {
+ protected static final Logger logger = LogManager.getLogger();
private static final AtomicInteger NEXT_ID = new AtomicInteger(0);
- private final int id;
- private final int minFloor;
- private final int floorsServed;
- private int currentFloor;
+ @Getter protected final int id;
+ @Getter protected final int minFloor;
+ @Getter protected final int maxFloor;
+
+ /**
+ * Currently boarded passengers.
+ */
+ @Getter protected final Collection passengers = ConcurrentHashMap.newKeySet();
+
+ /**
+ * Target queue which holds floors that this elevator must visit.
+ */
+ protected final Deque targets = new ArrayDeque<>();
+
+ /**
+ * A map which holds guesses of next potential targets, populated by the {@link ElevatorSystem}.
+ * This is the whole reason to make the system aware of passengers who make elevator calls.
+ * It's not necessary, but is believed to ever so slightly improve the elevator selection algorithm
+ * over just a simple set/list of potential targets without owners.
+ */
+ protected final Map potentialTargets = new LinkedHashMap<>();
+ @Getter protected int currentFloor;
+ /**
+ * An elevator should be aware of the system it belongs to.
+ */
+ protected ElevatorSystem elevatorSystem;
/**
* Creates a new elevator.
@@ -26,65 +57,242 @@ public final class Elevator implements ElevatorPanel {
* @param currentFloor the floor the elevator starts at, must be within the defined range of floors served by the elevator
*/
public Elevator(int minFloor, int floorsServed, int currentFloor) {
- if (minFloor <= 0 || floorsServed < 2) {
- throw new IllegalArgumentException("Min floor must at least 1, floors served at least 2.");
+ if (minFloor < 1) {
+ throw new IllegalArgumentException("Minimum floor must at least 1, got " + minFloor);
+ }
+ if (floorsServed < 2) {
+ throw new IllegalArgumentException("Amount of served floors must be at least 2, got " + floorsServed);
}
- if (currentFloor < minFloor || currentFloor >= minFloor + floorsServed) {
- throw new IllegalArgumentException("The current floor must be between the floors served by the elevator.");
+ int maxFloor = minFloor + floorsServed - 1;
+ if (currentFloor < minFloor || maxFloor < currentFloor) {
+ throw new IllegalArgumentException("The current floor for this elevator must be between %d and %d, got %d".formatted(minFloor, maxFloor, currentFloor));
}
this.id = NEXT_ID.getAndIncrement();
this.minFloor = minFloor;
+ this.maxFloor = maxFloor;
this.currentFloor = currentFloor;
- this.floorsServed = floorsServed;
}
- @Override
- public int getId() {
- return id;
+ public int getFloorsServed() {
+ return maxFloor - minFloor + 1;
}
- public int getMinFloor() {
- return minFloor;
+ public int getTaskCount() {
+ return targets.size();
}
- public int getFloorsServed() {
- return floorsServed;
+ void setElevatorSystem(ElevatorSystem elevatorSystem) {
+ if (elevatorSystem == null) {
+ throw new IllegalArgumentException("Elevator system must not be null");
+ }
+ this.elevatorSystem = elevatorSystem;
}
- @Override
- public int getCurrentFloor() {
- return currentFloor;
+ public void boardPassenger(Passenger passenger) {
+ if (elevatorSystem == null) {
+ throw new IllegalStateException("Elevator is not connected to an elevator system");
+ }
+ if (passengers.contains(passenger)) {
+ throw new IllegalArgumentException("Attempt to add a passenger which is already in the elevator");
+ }
+ passengers.add(passenger);
+ elevatorSystem.passengerEnteredElevator(passenger);
+ }
+
+ public void removePassenger(Passenger passenger, boolean arrived) {
+ if (elevatorSystem == null) {
+ throw new IllegalStateException("Elevator is not connected to an elevator system");
+ }
+ if (!passengers.contains(passenger)) {
+ throw new IllegalArgumentException("Attempt to remove a passenger which is not in the elevator");
+ }
+ passengers.remove(passenger);
+ elevatorSystem.passengerLeftElevator(passenger, arrived);
}
+ /**
+ * Whether this elevator accepts requests to move to a floor.
+ * For example, a paternoster elevator does not, because his movement pattern is predetermined forever.
+ * @see #requestDestinationFloor(int, Passenger)
+ */
+ public abstract boolean canRequestDestinationFloor();
+
+ /**
+ * This represents a human or the elevator system
+ * itself requesting this elevator to eventually move to the given floor.
+ * The elevator is supposed to memorize the destination in a way that
+ * it can ensure to eventually reach it.
+ *
+ * @throws UnsupportedOperationException if the operation is not supported by this elevator
+ * @see #canRequestDestinationFloor()
+ */
@Override
- public void requestDestinationFloor(int destinationFloor) {
- // TODO Implement. This represents a human or the elevator system
- // itself requesting this elevator to eventually move to the given floor.
- // The elevator is supposed to memorize the destination in a way that
- // it can ensure to eventually reach it.
- System.out.println("Request for destination floor received");
+ public abstract void requestDestinationFloor(int destinationFloor, @Nullable Passenger passenger);
+
+ /**
+ * Add a potential future target for this elevator. Improves the elevator selection algorithm.
+ */
+ synchronized void addPotentialTarget(int potentialTarget, ElevatorListener listener) {
+ if (!potentialTargets.containsKey(listener) && !potentialTargets.containsValue(potentialTarget)) {
+ potentialTargets.put(listener, clampFloor(potentialTarget));
+ logger.debug(() -> "Elevator %d on floor %d has added potential target %d, the queue is now %s, potential targets %s".formatted(id, currentFloor, potentialTarget, targets, potentialTargets.values()));
+ }
}
+ /**
+ * Essentially there are three possibilities:
+ *
+ * - move up one floor
+ * - move down one floor
+ * - stand still
+ *
+ * The elevator is supposed to move in a way that it will eventually reach
+ * the floors requested by Humans via {@link #requestDestinationFloor(int, Passenger)}, ideally "fast" but also "fair",
+ * meaning that the average time waiting (either in corridor or inside the elevator)
+ * is minimized across all humans.
+ * It is essential that this method updates the currentFloor field accordingly.
+ */
public void moveOneFloor() {
- // TODO Implement. Essentially there are three possibilities:
- // - move up one floor
- // - move down one floor
- // - stand still
- // The elevator is supposed to move in a way that it will eventually reach
- // the floors requested by Humans via requestDestinationFloor(), ideally "fast" but also "fair",
- // meaning that the average time waiting (either in corridor or inside the elevator)
- // is minimized across all humans.
- // It is essential that this method updates the currentFloor field accordingly.
- System.out.println("Request to move a floor received");
+ if (!targets.isEmpty()) {
+ int target = targets.element();
+ if (currentFloor < target) {
+ currentFloor++;
+ } else if (currentFloor > target) {
+ currentFloor--;
+ } else {
+ throw new IllegalArgumentException("Elevator has current floor as next target, this is a bug");
+ }
+ if (currentFloor == target) {
+ // We arrived at the next target
+ modifyTargetsOnArrival();
+ }
+ } else if (!potentialTargets.isEmpty()) {
+ logger.debug(() -> "Elevator %d on floor %d is idling and will clear its potential targets".formatted(id, currentFloor));
+ potentialTargets.clear();
+ }
}
+ /**
+ * The action to perform once the elevator reaches a target.
+ */
+ protected abstract void modifyTargetsOnArrival();
+
@Override
public synchronized String toString() {
- return new StringJoiner(", ", Elevator.class.getSimpleName() + "[", "]").add("id=" + id)
+ return new StringJoiner(", ", getClass().getSimpleName() + "[", "]").add("id=" + id)
.add("minFloor=" + minFloor)
- .add("floorsServed=" + floorsServed)
+ .add("maxFloor=" + maxFloor)
.add("currentFloor=" + currentFloor)
+ .add("passengers=" + passengers.size())
.toString();
}
+
+ @Override
+ public int hashCode() {
+ return id;
+ }
+
+ /**
+ * @return whether this elevator can serve all the specified floors.
+ */
+ public boolean canServe(int... floors) {
+ for (int floor : floors) {
+ if (floor < minFloor || floor > maxFloor) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * @throws IllegalArgumentException if the specified floor cannot be served by this elevator.
+ */
+ protected void rangeCheck(int floor) {
+ if (!canServe(floor)) {
+ throw new IllegalArgumentException("Elevator cannot serve floor %d, only %d to %d are available".formatted(floor, minFloor, maxFloor));
+ }
+ }
+
+ /**
+ * Returns a floor value clamped between max and min floors.
+ */
+ protected int clampFloor(int floor) {
+ return Math.max(Math.min(floor, maxFloor), minFloor);
+ }
+
+ /**
+ * @return whether this elevator is currently on the specified floor
+ * or will at some point visit that floor before all its tasks are done.
+ */
+ public abstract boolean willVisitFloor(int floor);
+
+ /**
+ * @return the minimum amount of turns it would take for this elevator to visit a specified sequence of floors
+ * (either indirectly by passing by or by creating new tasks),
+ * taking into account all potential targets of this elevator,
+ * or -1 if it's impossible or of the input array is empty
+ * @implNote a choice was made to use {@code int} over {@link OptionalInt OptionalInt}
+ * since the amount of turns cannot be negative
+ */
+ public synchronized int turnsToVisit(int... floors) {
+ if (floors.length == 0) {
+ return -1;
+ }
+
+ int count = 0;
+ int previousTarget = currentFloor;
+
+ Collection allTargets = new ArrayList<>(targets.size() + potentialTargets.size());
+ allTargets.addAll(targets);
+ allTargets.addAll(potentialTargets.values());
+ Iterator targetItr = allTargets.iterator();
+ Iterator floorItr = Arrays.stream(floors).iterator();
+
+ int nextFloor = floorItr.next();
+ if (!canServe(nextFloor)) {
+ return -1;
+ }
+
+ while (targetItr.hasNext()) {
+ int nextTarget = targetItr.next();
+
+ // While the next floor we're interested in lies on the path,
+ // we "chop off" part of the path, adding the length of that part to count
+ // we also advance the floor iterator, or return if the floor was last
+ while (previousTarget <= nextFloor && nextFloor <= nextTarget || previousTarget >= nextFloor && nextFloor >= nextTarget) {
+ count += Math.abs(nextFloor - previousTarget);
+ previousTarget = nextFloor;
+ if (floorItr.hasNext()) {
+ nextFloor = floorItr.next();
+ if (!canServe(nextFloor)) {
+ return -1;
+ }
+ } else {
+ return count;
+ }
+ }
+ // If there are more floors remaining to check, add what's left of currently inspected path
+ count += Math.abs(nextTarget - previousTarget);
+
+ previousTarget = nextTarget;
+ }
+
+ // The floor currently at nextFloor is guaranteed to be unprocessed
+ count += Math.abs(nextFloor - previousTarget);
+ previousTarget = nextFloor;
+ // If after traversing the queue we haven't covered all floors that we wanted,
+ // simulate adding them to the queue
+ while (floorItr.hasNext()) {
+ nextFloor = floorItr.next();
+ if (!canServe(nextFloor)) {
+ return -1;
+ }
+
+ count += Math.abs(nextFloor - previousTarget);
+ previousTarget = nextFloor;
+ }
+
+ return count;
+ }
}
diff --git a/Contest/Assignment/src/org/togetherjava/event/elevator/elevators/ElevatorPanel.java b/Contest/Assignment/src/org/togetherjava/event/elevator/elevators/ElevatorPanel.java
index 386ec77..cfee5ab 100644
--- a/Contest/Assignment/src/org/togetherjava/event/elevator/elevators/ElevatorPanel.java
+++ b/Contest/Assignment/src/org/togetherjava/event/elevator/elevators/ElevatorPanel.java
@@ -1,5 +1,8 @@
package org.togetherjava.event.elevator.elevators;
+import org.jetbrains.annotations.Nullable;
+import org.togetherjava.event.elevator.humans.Passenger;
+
/**
* The system inside an elevator which provides information about the elevator and can be
* used to request a destination floor.
@@ -20,9 +23,60 @@ public interface ElevatorPanel {
int getCurrentFloor();
/**
- * Requesting the elevator to eventually move to the given destination floor, for humans to exit.
+ * Whether this elevator accepts requests to move to a floor.
+ * For example, a paternoster elevator does not, because his movement patters is predetermined forever.
+ * @see #requestDestinationFloor(int)
+ */
+ boolean canRequestDestinationFloor();
+
+ /**
+ * Requesting the elevator to eventually move to the given destination floor, for humans to exit or enter.
+ * By default, behaves the same way as {@link #requestDestinationFloor(int, Passenger)} with
+ * {@code null} as the second argument.
+ *
+ * @param destinationFloor the desired destination, must be within the range served by this elevator
+ * @throws UnsupportedOperationException if the operation is not supported by this elevator
+ * @see #canRequestDestinationFloor()
+ */
+ default void requestDestinationFloor(int destinationFloor) {
+ requestDestinationFloor(destinationFloor, null);
+ }
+
+ // New methods below
+
+ /**
+ * The lowest floor the elevator can travel to.
+ */
+ int getMinFloor();
+
+ /**
+ * The highest floor the elevator can travel to.
+ */
+ int getMaxFloor();
+
+ /**
+ * Ask the elevator to accept this passenger and to tell the system to remove it from the floor.
+ * @param passenger the passenger
+ */
+ void boardPassenger(Passenger passenger);
+
+ /**
+ * Ask the elevator to remove this passenger and to tell the system to add it to the floor, if necessary.
+ * @param passenger the passenger
+ * @param arrived whether the passenger has reached its final destination
+ * @throws IllegalArgumentException if the elevator does not have the specified passenger
+ */
+ void removePassenger(Passenger passenger, boolean arrived);
+
+ /**
+ * Requesting the elevator to eventually move to the given destination floor, for humans to exit or enter.
+ * Calling the method with {@code null} as second argument will mean that the request was made by the system.
*
* @param destinationFloor the desired destination, must be within the range served by this elevator
+ * @param passenger passenger that requested this operation, null if the operation was requested by the system itself,
+ * sanctioned by not marko
+ * @throws UnsupportedOperationException if the operation is not supported by this elevator
+ * @see #canRequestDestinationFloor()
*/
- void requestDestinationFloor(int destinationFloor);
+ void requestDestinationFloor(int destinationFloor, @Nullable Passenger passenger);
}
diff --git a/Contest/Assignment/src/org/togetherjava/event/elevator/elevators/ElevatorSystem.java b/Contest/Assignment/src/org/togetherjava/event/elevator/elevators/ElevatorSystem.java
index fadfe56..0b223f4 100644
--- a/Contest/Assignment/src/org/togetherjava/event/elevator/elevators/ElevatorSystem.java
+++ b/Contest/Assignment/src/org/togetherjava/event/elevator/elevators/ElevatorSystem.java
@@ -1,9 +1,11 @@
package org.togetherjava.event.elevator.elevators;
import org.togetherjava.event.elevator.humans.ElevatorListener;
+import org.togetherjava.event.elevator.humans.Passenger;
+import org.togetherjava.event.elevator.util.ConcurrentUtils;
+import org.togetherjava.event.elevator.util.LogUtils;
-import java.util.ArrayList;
-import java.util.List;
+import java.util.*;
/**
* System controlling all elevators of a building.
@@ -13,37 +15,185 @@
* the system can be made ready using {@link #ready()}.
*/
public final class ElevatorSystem implements FloorPanelSystem {
- private final List elevators = new ArrayList<>();
- private final List elevatorListeners = new ArrayList<>();
+ private final Collection elevators = new HashSet<>();
+ private final NavigableMap floors = new TreeMap<>();
public void registerElevator(Elevator elevator) {
elevators.add(elevator);
+ elevator.setElevatorSystem(this);
+
+ for (int i = elevator.getMinFloor(); i <= elevator.getMaxFloor(); i++) {
+ floors.computeIfAbsent(i, Floor::new);
+ }
+
+ floors.get(elevator.getCurrentFloor()).addElevator(elevator);
}
public void registerElevatorListener(ElevatorListener listener) {
- elevatorListeners.add(listener);
+ if (listener instanceof Passenger passenger && passenger.wantsToMove()) {
+ floors.get(passenger.getCurrentFloor()).addPassenger(passenger);
+ }
}
/**
- * Upon calling this, the system is ready to receive elevator requests. Elevators may now start moving.
+ * Upon calling this, the system is ready to receive elevator requests. Elevators may now start moving.
+ *
+ * Additionally, elevator arrival events are fired so that humans can immediately enter them.
*/
public void ready() {
- elevatorListeners.forEach(listener -> listener.onElevatorSystemReady(this));
+ LogUtils.measure("Elevator requests", () -> ConcurrentUtils.performTasksInParallel(floors.values(), f -> f.fireElevatorRequestEvents(this)));
+ LogUtils.measure("Elevator arrivals", () -> ConcurrentUtils.performTasksInParallel(floors.values(), Floor::fireElevatorArrivalEvents));
}
+ void passengerEnteredElevator(Passenger passenger) {
+ floors.get(passenger.getCurrentFloor()).removePassenger(passenger);
+ }
+
+ void passengerLeftElevator(Passenger passenger, boolean arrived) {
+ if (!arrived) {
+ floors.get(passenger.getCurrentFloor()).addPassenger(passenger);
+ }
+ }
+
+ /**
+ * This represents a human standing in the corridor,
+ * requesting that an elevator comes to pick them up for travel into the given direction.
+ * The system is supposed to make sure that an elevator will eventually reach this floor to pick up the human.
+ * The human can then enter the elevator and request their actual destination within the elevator.
+ * Ideally this has to select the best elevator among all which can reduce the time
+ * for the human spending waiting (either in corridor or in the elevator itself).
+ *
+ * @param atFloor the floor to pick up the human at, must be within the range served by the system
+ * @param desiredTravelDirection the direction the human wants to travel into,
+ * can be used for determination of the best elevator
+ * @param listener (NEW) the listener that requested the operation, only used to slightly
+ * improve the elevator selection algorithm, think an array of surveillance cameras
+ * and fingerprint sensors in buttons (scary to think about though), sanctioned by
+ * not marko
+ * @return the id of the elevator that was recommended by the system
+ */
@Override
- public void requestElevator(int atFloor, TravelDirection desiredTravelDirection) {
- // TODO Implement. This represents a human standing in the corridor,
- // requesting that an elevator comes to pick them up for travel into the given direction.
- // The system is supposed to make sure that an elevator will eventually reach this floor to pick up the human.
- // The human can then enter the elevator and request their actual destination within the elevator.
- // Ideally this has to select the best elevator among all which can reduce the time
- // for the human spending waiting (either in corridor or in the elevator itself).
- System.out.println("Request for elevator received");
+ public int requestElevator(int atFloor, TravelDirection desiredTravelDirection, ElevatorListener listener) {
+ Elevator elevator;
+
+ int target = calculateAverageTarget(atFloor, desiredTravelDirection)
+ .orElseThrow(() -> new IllegalArgumentException("Impossible to travel %s from floor %d".formatted(desiredTravelDirection.name(), atFloor)));
+
+ synchronized (elevators) {
+ if (elevators.isEmpty()) {
+ throw new IllegalStateException("An elevator was requested, but there are none registered in the system");
+ }
+
+ elevator = elevators.stream()
+ .filter(e -> e.canServe(atFloor, atFloor + (desiredTravelDirection == TravelDirection.UP ? 1 : -1)))
+ .min((e1, e2) -> {
+ // Calculate the time it would take for both elevators to reach the request floor and the target
+ int t1 = e1.turnsToVisit(atFloor, target);
+ int t2 = e2.turnsToVisit(atFloor, target);
+ // If they both can actually reach it, just compare the numbers
+ if (t1 >= 0 && t2 >= 0) {
+ return Integer.compare(t1, t2);
+ }
+ // If one of them cannot reach it, prefer the one that can
+ else if (t1 >= 0) {
+ return -1;
+ } else if (t2 >= 0) {
+ return 1;
+ }
+ // At this point, the target lies outside the range of both elevators
+ // In this case, choose the elevator which boundaries lie closest to the target
+ return Integer.compare(
+ Math.min(Math.abs(e1.getMaxFloor() - target), Math.abs(e1.getMinFloor() - target)),
+ Math.min(Math.abs(e2.getMaxFloor() - target), Math.abs(e2.getMinFloor() - target))
+ );
+ })
+ .orElseThrow(() -> new IllegalStateException("No elevators can go %s from floor %d".formatted(desiredTravelDirection.name(), atFloor)));
+
+ }
+
+ if (elevator.canRequestDestinationFloor()) {
+ elevator.requestDestinationFloor(atFloor);
+ elevator.addPotentialTarget(target, listener);
+ }
+
+ return elevator.getId();
+ }
+
+ public int getFloorAmount() {
+ return floors.size();
+ }
+
+ public int getMinFloor() {
+ return floors.firstEntry().getKey();
+ }
+
+ public int getMaxFloor() {
+ return floors.lastEntry().getKey();
+ }
+
+ /**
+ * A helper method to determine whether the simulation is still running.
+ * Created to avoid streaming the entire Human registry.
+ */
+ public boolean hasActivePassengers() {
+ return floors.values().stream()
+ .map(Floor::getActivePassengersCount)
+ .reduce(0, Integer::sum) > 0;
}
public void moveOneFloor() {
- elevators.forEach(Elevator::moveOneFloor);
- elevators.forEach(elevator -> elevatorListeners.forEach(listener -> listener.onElevatorArrivedAtFloor(elevator)));
+ LogUtils.measure("Moving elevators", this::moveElevators);
+ LogUtils.measure("Listener firing", this::fireFloorListeners);
+ }
+
+ /**
+ * Estimate average target floor that a user might select given a starting floor and a direction.
+ * Picks the middle floor between the starting floor and the one farthest in the given direction.
+ * @return {@link OptionalInt} describing the calculated floor number, or empty if the request doesn't make sense
+ * (going up from the topmost floor or down from the bottom floor)
+ */
+ private OptionalInt calculateAverageTarget(int floorFrom, TravelDirection desiredTravelDirection) {
+ if (desiredTravelDirection == TravelDirection.UP) {
+ int maxFloor = floors.lastEntry().getKey();
+ if (floorFrom >= maxFloor) {
+ return OptionalInt.empty();
+ } else {
+ int delta = maxFloor - floorFrom;
+ delta = delta % 2 == 0 ? delta / 2 : delta / 2 + 1;
+ return OptionalInt.of(floorFrom + delta);
+ }
+ } else {
+ int minFloor = floors.firstEntry().getKey();
+ if (floorFrom <= minFloor) {
+ return OptionalInt.empty();
+ } else {
+ int delta = floorFrom - minFloor;
+ delta = delta % 2 == 0 ? delta / 2 : delta / 2 + 1;
+ return OptionalInt.of(floorFrom - delta);
+ }
+ }
+ }
+
+ private void moveElevators() {
+ ConcurrentUtils.performTasksInParallel(elevators, e -> {
+ floors.get(e.getCurrentFloor()).removeElevator(e);
+ e.moveOneFloor();
+ floors.get(e.getCurrentFloor()).addElevator(e);
+ });
+ }
+
+ /**
+ * Elevator passengers are notified first, giving them a chance to exit and potentially remove themselves
+ * from tracking if they have reached their destination. In the majority of cases, this will be a performance
+ * improvement compared to firing waiting passenger events first.
+ * Lastly, once everyone who wanted to board did so, notify any remaining idle passengers that they can request
+ * an elevator. This helps to introduce new passengers to the system.
+ */
+ private void fireFloorListeners() {
+ ConcurrentUtils.performTasksInParallel(floors.values(), f -> {
+ f.fireElevatorPassengerEvents();
+ f.fireElevatorArrivalEvents();
+ f.fireElevatorRequestEvents(this);
+ });
}
}
diff --git a/Contest/Assignment/src/org/togetherjava/event/elevator/elevators/Floor.java b/Contest/Assignment/src/org/togetherjava/event/elevator/elevators/Floor.java
new file mode 100644
index 0000000..557d35e
--- /dev/null
+++ b/Contest/Assignment/src/org/togetherjava/event/elevator/elevators/Floor.java
@@ -0,0 +1,93 @@
+package org.togetherjava.event.elevator.elevators;
+
+import org.togetherjava.event.elevator.humans.ElevatorListener;
+import org.togetherjava.event.elevator.humans.Passenger;
+
+import java.util.Collection;
+import java.util.StringJoiner;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Class that holds data for each floor, package-private as technically all this is considered
+ * implementation detail.
+ */
+class Floor {
+ private final int number;
+ /**
+ * Passengers that currently wait for an elevator, should not include passengers that have arrived
+ * at their destination.
+ */
+ private final Collection passengers = ConcurrentHashMap.newKeySet();
+ /**
+ * Elevators currently stopping at this floor.
+ */
+ private final Collection elevators = ConcurrentHashMap.newKeySet();
+
+ Floor(int number) {
+ this.number = number;
+ }
+
+ @Override
+ public String toString() {
+ return new StringJoiner(", ", Floor.class.getSimpleName() + "[", "]")
+ .add("number=" + number)
+ .toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return number;
+ }
+
+ void addPassenger(Passenger passenger) {
+ passengers.add(passenger);
+ }
+
+ void removePassenger(Passenger passenger) {
+ passengers.remove(passenger);
+ }
+
+ void addElevator(Elevator elevator) {
+ elevators.add(elevator);
+ }
+
+ void removeElevator(Elevator elevator) {
+ elevators.remove(elevator);
+ }
+
+ synchronized int getActivePassengersCount() {
+ return passengers.size() + elevators.stream().map(e -> e.getPassengers().size()).reduce(0, Integer::sum);
+ }
+
+
+ /**
+ * Notify all passengers of all elevators on this floor that they may exit the elevator if they wish.
+ */
+ synchronized void fireElevatorPassengerEvents() {
+ for (Elevator elevator : elevators) {
+ for (ElevatorListener passenger : elevator.getPassengers()) {
+ passenger.onElevatorArrivedAtFloor(elevator);
+ }
+ }
+ }
+
+ /**
+ * Notify all passengers on this floor that they may enter an elevator if they wish.
+ */
+ synchronized void fireElevatorArrivalEvents() {
+ for (Passenger passenger : passengers) {
+ for (Elevator elevator : elevators) {
+ passenger.onElevatorArrivedAtFloor(elevator);
+ }
+ }
+ }
+
+ /**
+ * Notify all passengers on this floor that they may request an elevator if they wish.
+ */
+ synchronized void fireElevatorRequestEvents(FloorPanelSystem floorPanelSystem) {
+ for (Passenger passenger : passengers) {
+ passenger.onElevatorSystemReady(floorPanelSystem);
+ }
+ }
+}
diff --git a/Contest/Assignment/src/org/togetherjava/event/elevator/elevators/FloorPanelSystem.java b/Contest/Assignment/src/org/togetherjava/event/elevator/elevators/FloorPanelSystem.java
index 8043228..d4c9718 100644
--- a/Contest/Assignment/src/org/togetherjava/event/elevator/elevators/FloorPanelSystem.java
+++ b/Contest/Assignment/src/org/togetherjava/event/elevator/elevators/FloorPanelSystem.java
@@ -1,5 +1,7 @@
package org.togetherjava.event.elevator.elevators;
+import org.togetherjava.event.elevator.humans.ElevatorListener;
+
/**
* The system in corridors that allows requesting elevators to the current floor.
*/
@@ -10,8 +12,10 @@ public interface FloorPanelSystem {
* @param atFloor the floor to pick up the human at, must be within the range served by the system
* @param desiredTravelDirection the direction the human wants to travel into,
* can be used for determination of the best elevator
+ * @param listener (NEW) the listener that requested the operation
+ * @return the id of the elevator that was recommended by the system
* @apiNote This represents a human standing in the corridor, pressing a button on the wall,
* requesting that an elevator comes to pick them up for travel into the given direction.
*/
- void requestElevator(int atFloor, TravelDirection desiredTravelDirection);
+ int requestElevator(int atFloor, TravelDirection desiredTravelDirection, ElevatorListener listener);
}
diff --git a/Contest/Assignment/src/org/togetherjava/event/elevator/elevators/PaternosterElevator.java b/Contest/Assignment/src/org/togetherjava/event/elevator/elevators/PaternosterElevator.java
new file mode 100644
index 0000000..f9d2a6f
--- /dev/null
+++ b/Contest/Assignment/src/org/togetherjava/event/elevator/elevators/PaternosterElevator.java
@@ -0,0 +1,75 @@
+package org.togetherjava.event.elevator.elevators;
+
+import org.jetbrains.annotations.Nullable;
+import org.togetherjava.event.elevator.humans.Passenger;
+
+/**
+ * A Paternoster lift which endlessly travels in a range between two floors. It cannot take requests.
+ */
+public final class PaternosterElevator extends Elevator {
+ /**
+ * Creates a new Paternoster elevator.
+ *
+ * @param minFloor the minimum floor that the elevator can serve, must be greater than or equal to 1.
+ * @param floorsServed the amount of floors served in total by this elevator, must be greater than or equal to 2.
+ * Together with the minFloor this forms a consecutive range of floors with no gaps in between.
+ * @param currentFloor the floor the elevator starts at, must be within the defined range of floors served by the elevator
+ */
+ public PaternosterElevator(int minFloor, int floorsServed, int currentFloor) {
+ this(minFloor, floorsServed,currentFloor, TravelDirection.UP);
+ }
+
+ /**
+ * Creates a new Paternoster elevator.
+ *
+ * @param minFloor the minimum floor that the elevator can serve, must be greater than or equal to 1.
+ * @param floorsServed the amount of floors served in total by this elevator, must be greater than or equal to 2.
+ * Together with the minFloor this forms a consecutive range of floors with no gaps in between.
+ * @param currentFloor the floor the elevator starts at, must be within the defined range of floors served by the elevator
+ * @param startingDirection desired starting direction, will be overridden if the elevator is currently
+ * at the end of the path and cannot move further in that direction
+ */
+ public PaternosterElevator(int minFloor, int floorsServed, int currentFloor, TravelDirection startingDirection) {
+ super(minFloor, floorsServed, currentFloor);
+ if (this.currentFloor == this.minFloor || this.currentFloor != this.maxFloor && startingDirection == TravelDirection.UP) {
+ targets.add(this.maxFloor);
+ targets.add(this.minFloor);
+ } else {
+ targets.add(this.minFloor);
+ targets.add(this.maxFloor);
+ }
+ }
+
+ /**
+ * Cannot request floors since this elevator has predetermined movement
+ */
+ @Override
+ public boolean canRequestDestinationFloor() {
+ return false;
+ }
+
+ @Override
+ public synchronized void requestDestinationFloor(int destinationFloor, @Nullable Passenger passenger) {
+ throw new UnsupportedOperationException("Paternoster elevator does not accept requests");
+ }
+
+ /**
+ * Reinsert the target at the back of the queue
+ */
+ @Override
+ protected void modifyTargetsOnArrival() {
+ targets.add(targets.remove());
+ }
+
+ /**
+ * Since this is a Paternoster elevator, it will always be able to visit floor in his range,
+ * and will never visit other floors.
+ *
+ * @return whether this elevator is currently on the specified floor
+ * or will at some point visit that floor before all its tasks are done.
+ */
+ @Override
+ public boolean willVisitFloor(int floor) {
+ return canServe(floor);
+ }
+}
diff --git a/Contest/Assignment/src/org/togetherjava/event/elevator/elevators/TravelDirection.java b/Contest/Assignment/src/org/togetherjava/event/elevator/elevators/TravelDirection.java
index b1c01c0..114a5b8 100644
--- a/Contest/Assignment/src/org/togetherjava/event/elevator/elevators/TravelDirection.java
+++ b/Contest/Assignment/src/org/togetherjava/event/elevator/elevators/TravelDirection.java
@@ -2,5 +2,6 @@
public enum TravelDirection {
UP,
- DOWN,
+ DOWN
+
}
diff --git a/Contest/Assignment/src/org/togetherjava/event/elevator/humans/Human.java b/Contest/Assignment/src/org/togetherjava/event/elevator/humans/Human.java
index 0af2511..580bf95 100644
--- a/Contest/Assignment/src/org/togetherjava/event/elevator/humans/Human.java
+++ b/Contest/Assignment/src/org/togetherjava/event/elevator/humans/Human.java
@@ -1,10 +1,13 @@
package org.togetherjava.event.elevator.humans;
+import lombok.Getter;
import org.togetherjava.event.elevator.elevators.ElevatorPanel;
import org.togetherjava.event.elevator.elevators.FloorPanelSystem;
+import org.togetherjava.event.elevator.elevators.TravelDirection;
import java.util.OptionalInt;
import java.util.StringJoiner;
+import java.util.concurrent.atomic.AtomicInteger;
/**
* A single human that starts at a given floor and wants to
@@ -13,15 +16,21 @@
* The class mainly acts upon given elevator events it listens to,
* for example requesting an elevator, eventually entering and exiting them.
*/
-public final class Human implements ElevatorListener {
- private State currentState;
- private final int startingFloor;
- private final int destinationFloor;
+public final class Human implements Passenger {
+ private static final AtomicInteger NEXT_ID = new AtomicInteger(0);
+
+ @Getter private final int id;
+ @Getter private final int startingFloor;
+ @Getter private final int destinationFloor;
+ @Getter private State currentState;
+ @Getter private int currentFloor;
+ private int nextDestination;
+ private int expectedElevatorId = -1;
/**
- * If the human is currently inside an elevator, this is its unique ID.
+ * If the human is currently inside an elevator, this the reference to it.
* Otherwise, this is {@code null} to indicate that the human is currently on the corridor.
*/
- private Integer currentEnteredElevatorId;
+ private ElevatorPanel currentEnteredElevator;
/**
* Creates a new human.
@@ -37,46 +46,115 @@ public Human(int startingFloor, int destinationFloor) {
throw new IllegalArgumentException("Floors must be at least 1");
}
+ this.id = NEXT_ID.getAndIncrement();
this.startingFloor = startingFloor;
+ this.currentFloor = startingFloor;
this.destinationFloor = destinationFloor;
- currentState = State.IDLE;
+ if (startingFloor == destinationFloor) {
+ currentState = State.ARRIVED;
+ } else {
+ currentState = State.IDLE;
+ }
}
- public State getCurrentState() {
- return currentState;
+ /**
+ * The system is now ready and the human should leave
+ * their initial IDLE state, requesting an elevator by clicking on the buttons of
+ * the floor panel system. The human will now enter the WAITING_FOR_ELEVATOR state.
+ */
+ @Override
+ public synchronized void onElevatorSystemReady(FloorPanelSystem floorPanelSystem) {
+ if (currentState == State.IDLE) {
+ TravelDirection direction;
+ if (currentFloor < destinationFloor) {
+ direction = TravelDirection.UP;
+ } else if (currentFloor > destinationFloor) {
+ direction = TravelDirection.DOWN;
+ } else {
+ // Do nothing. Why did this human come to the elevator hall? :thinking:
+ System.out.printf("A human has matching source and destination floors, it will be counted as arrived: %d and %d%n", startingFloor, destinationFloor);
+ currentState = State.ARRIVED;
+ return;
+ }
+ expectedElevatorId = floorPanelSystem.requestElevator(currentFloor, direction, this);
+ currentState = State.WAITING_FOR_ELEVATOR;
+ }
}
- public int getStartingFloor() {
- return startingFloor;
- }
+ /**
+ * If the human is currently waiting for an elevator and
+ * this event represents arrival at the humans current floor, the human can now enter the
+ * elevator and request their actual destination floor. The state has to change to TRAVELING_WITH_ELEVATOR.
+ * If the human is currently traveling with this elevator and the event represents
+ * arrival at the human's destination floor, the human can now exit the elevator.
+ */
+ @Override
+ public synchronized void onElevatorArrivedAtFloor(ElevatorPanel elevatorPanel) {
+ if (shouldBoardElevator(elevatorPanel)) {
+ if (currentFloor < destinationFloor) {
+
+ int elevatorMaxFloor = elevatorPanel.getMaxFloor();
+ if (elevatorMaxFloor <= currentFloor) {
+ // This human wants to go up, but the elevator cannot go up, skip
+ return;
+ }
+
+ elevatorPanel.boardPassenger(this);
+ nextDestination = Math.min(elevatorMaxFloor, destinationFloor);
- public int getDestinationFloor() {
- return destinationFloor;
+ if (elevatorPanel.canRequestDestinationFloor()) {
+ elevatorPanel.requestDestinationFloor(nextDestination, this);
+ }
+ } else if (currentFloor > destinationFloor) {
+
+ int elevatorMinFloor = elevatorPanel.getMinFloor();
+ if (elevatorMinFloor >= currentFloor) {
+ // This human wants to go down, but the elevator cannot go down, skip
+ return;
+ }
+
+ elevatorPanel.boardPassenger(this);
+ nextDestination = Math.max(elevatorMinFloor, destinationFloor);
+
+ if (elevatorPanel.canRequestDestinationFloor()) {
+ elevatorPanel.requestDestinationFloor(nextDestination, this);
+ }
+ } else {
+ throw new RuntimeException("A human's current floor matches destination floor, but they are waiting for an elevator, this is a bug");
+ }
+
+ currentEnteredElevator = elevatorPanel;
+ currentState = State.TRAVELING_WITH_ELEVATOR;
+ } else if (isInElevator(elevatorPanel)) {
+ currentFloor = elevatorPanel.getCurrentFloor();
+
+ if (currentFloor == nextDestination) {
+ boolean arrived = nextDestination == destinationFloor;
+ elevatorPanel.removePassenger(this, arrived);
+ currentEnteredElevator = null;
+ expectedElevatorId = -1;
+ if (arrived) {
+ currentState = State.ARRIVED;
+ } else {
+ currentState = State.IDLE;
+ }
+ }
+ }
}
- @Override
- public void onElevatorSystemReady(FloorPanelSystem floorPanelSystem) {
- // TODO Implement. The system is now ready and the human should leave
- // their initial IDLE state, requesting an elevator by clicking on the buttons of
- // the floor panel system. The human will now enter the WAITING_FOR_ELEVATOR state.
- System.out.println("Ready-event received");
+ private boolean shouldBoardElevator(ElevatorPanel elevatorPanel) {
+ return currentState == State.WAITING_FOR_ELEVATOR && elevatorPanel.getId() == expectedElevatorId;
}
- @Override
- public void onElevatorArrivedAtFloor(ElevatorPanel elevatorPanel) {
- // TODO Implement. If the human is currently waiting for an elevator and
- // this event represents arrival at the humans current floor, the human can now enter the
- // elevator and request their actual destination floor. The state has to change to TRAVELING_WITH_ELEVATOR.
- // If the human is currently traveling with this elevator and the event represents
- // arrival at the human's destination floor, the human can now exit the elevator.
- System.out.println("Arrived-event received");
+ private boolean isInElevator(ElevatorPanel elevatorPanel) {
+ return currentState == State.TRAVELING_WITH_ELEVATOR && elevatorPanel.equals(currentEnteredElevator);
}
public OptionalInt getCurrentEnteredElevatorId() {
- return currentEnteredElevatorId == null
+ return currentEnteredElevator == null
? OptionalInt.empty()
- : OptionalInt.of(currentEnteredElevatorId);
+ : OptionalInt.of(currentEnteredElevator.getId());
}
@Override
@@ -84,11 +162,17 @@ public String toString() {
return new StringJoiner(", ", Human.class.getSimpleName() + "[", "]")
.add("currentState=" + currentState)
.add("startingFloor=" + startingFloor)
+ .add("currentFloor=" + currentFloor)
.add("destinationFloor=" + destinationFloor)
- .add("currentEnteredElevatorId=" + currentEnteredElevatorId)
+ .add("currentEnteredElevatorId=" + (currentEnteredElevator == null ? "null" : currentEnteredElevator.getId()))
.toString();
}
+ @Override
+ public int hashCode() {
+ return id;
+ }
+
public enum State {
IDLE,
WAITING_FOR_ELEVATOR,
diff --git a/Contest/Assignment/src/org/togetherjava/event/elevator/humans/Passenger.java b/Contest/Assignment/src/org/togetherjava/event/elevator/humans/Passenger.java
new file mode 100644
index 0000000..62eaa84
--- /dev/null
+++ b/Contest/Assignment/src/org/togetherjava/event/elevator/humans/Passenger.java
@@ -0,0 +1,28 @@
+package org.togetherjava.event.elevator.humans;
+
+/**
+ * A passenger that wants to travel from one floor to another.
+ */
+public interface Passenger extends ElevatorListener {
+ /**
+ * Where is the passenger travelling from.
+ */
+ int getStartingFloor();
+
+ /**
+ * Where the passenger wants to travel to.
+ */
+ int getDestinationFloor();
+
+ /**
+ * Passenger's current floor.
+ */
+ int getCurrentFloor();
+
+ /**
+ * Whether this passenger currently wants to move to a different floor.
+ */
+ default boolean wantsToMove() {
+ return getCurrentFloor() != getDestinationFloor();
+ }
+}
diff --git a/Contest/Assignment/src/org/togetherjava/event/elevator/simulation/AdvancedSimulation.java b/Contest/Assignment/src/org/togetherjava/event/elevator/simulation/AdvancedSimulation.java
new file mode 100644
index 0000000..16cf62f
--- /dev/null
+++ b/Contest/Assignment/src/org/togetherjava/event/elevator/simulation/AdvancedSimulation.java
@@ -0,0 +1,46 @@
+package org.togetherjava.event.elevator.simulation;
+
+import org.togetherjava.event.elevator.elevators.Elevator;
+import org.togetherjava.event.elevator.humans.Human;
+
+import java.util.List;
+import java.util.concurrent.ThreadLocalRandom;
+
+/**
+ * A simulation that adds humans while it's running.
+ */
+public final class AdvancedSimulation extends Simulation {
+
+ public AdvancedSimulation(List extends Elevator> elevators, List humans) {
+ super(elevators, humans);
+ }
+
+ public static AdvancedSimulation createRandomSimulation(long seed, int amountOfElevators, int amountOfHumans, int floorsServed) {
+ return Simulation.createRandomSimulation(seed, amountOfElevators, amountOfHumans, floorsServed, AdvancedSimulation::new);
+ }
+
+ @Override
+ public void step() {
+ if (stepCount > 0 && stepCount % elevatorSystem.getFloorAmount() == 0) {
+// addMoreHumans(elevators.size() / 2);
+ addMoreHumans(1);
+ }
+ super.step();
+ }
+
+ private void addMoreHumans(int amount) {
+ var random = ThreadLocalRandom.current();
+
+ int maxFloor = elevatorSystem.getMaxFloor();
+ int minFloor = elevatorSystem.getMinFloor();
+ for (int i = 0; i < amount; i++) {
+ Human human = new Human(
+ random.nextInt(maxFloor - minFloor + 1) + minFloor,
+ random.nextInt(maxFloor - minFloor + 1) + minFloor);
+ elevatorSystem.registerElevatorListener(human);
+ humans.add(human);
+ humanStatistics.add(new HumanStatistics(human));
+ System.out.printf("Added a new human at floor %d which wants to travel to floor %d%n", human.getCurrentFloor(), human.getDestinationFloor());
+ }
+ }
+}
diff --git a/Contest/Assignment/src/org/togetherjava/event/elevator/simulation/HumanStatistics.java b/Contest/Assignment/src/org/togetherjava/event/elevator/simulation/HumanStatistics.java
index 9c2c778..7913e3e 100644
--- a/Contest/Assignment/src/org/togetherjava/event/elevator/simulation/HumanStatistics.java
+++ b/Contest/Assignment/src/org/togetherjava/event/elevator/simulation/HumanStatistics.java
@@ -1,12 +1,13 @@
package org.togetherjava.event.elevator.simulation;
+import lombok.Getter;
import org.togetherjava.event.elevator.humans.Human;
import java.util.EnumMap;
import java.util.Map;
final class HumanStatistics {
- private final Human human;
+ @Getter private final Human human;
private final Map stateToStepCount = new EnumMap<>(Human.State.class);
HumanStatistics(Human human) {
diff --git a/Contest/Assignment/src/org/togetherjava/event/elevator/simulation/MoreSimulations.java b/Contest/Assignment/src/org/togetherjava/event/elevator/simulation/MoreSimulations.java
new file mode 100644
index 0000000..586e356
--- /dev/null
+++ b/Contest/Assignment/src/org/togetherjava/event/elevator/simulation/MoreSimulations.java
@@ -0,0 +1,80 @@
+package org.togetherjava.event.elevator.simulation;
+
+import org.togetherjava.event.elevator.elevators.CommonElevator;
+import org.togetherjava.event.elevator.elevators.PaternosterElevator;
+import org.togetherjava.event.elevator.humans.Human;
+
+import java.util.List;
+import java.util.stream.IntStream;
+
+/**
+ * Separate file to avoid bloating {@link Simulation} with static methods
+ */
+public class MoreSimulations {
+
+ public static Simulation createSimpleFailingSimulation() {
+ return new Simulation(
+ List.of(
+ new CommonElevator(6, 5, 10),
+ new CommonElevator(1, 5, 5)),
+ List.of(
+ new Human(1, 7)));
+ }
+
+ public static Simulation createSimpleSucceedingSimulation() {
+ return new Simulation(
+ List.of(
+ new CommonElevator(6, 5, 10),
+ new CommonElevator(1, 6, 5)),
+ List.of(
+ new Human(1, 8)));
+ }
+
+ public static Simulation createSimpleThreeStepSimulation() {
+ return new Simulation(
+ List.of(
+ new CommonElevator(7, 4, 10),
+ new CommonElevator(4, 5, 5),
+ new CommonElevator(1, 5, 3)),
+ List.of(
+ new Human(1, 10)));
+ }
+
+ public static Simulation createSimplePaternosterSimulation() {
+ return new Simulation(
+ List.of(
+ new CommonElevator(6, 5, 10),
+ new PaternosterElevator(1, 8, 5),
+ new PaternosterElevator(8, 3, 10)),
+ List.of(
+ new Human(1, 7)));
+ }
+
+ public static AdvancedSimulation createSimpleAdvancedSimulation() {
+ return new AdvancedSimulation(
+ List.of(
+ new CommonElevator(1, 10, 10),
+ new CommonElevator(1, 10, 8)),
+ List.of(
+ new Human(1, 9)));
+ }
+
+ public static Simulation createMegaSimulation() {
+ return Simulation.createRandomSimulation(3, 100, 100_000, 100);
+ }
+
+ public static AdvancedSimulation createMegaAdvancedSimulation() {
+ return AdvancedSimulation.createRandomSimulation(3, 100, 100_000, 100);
+ }
+
+ public static Simulation createNotMarkoSimulation() {
+ return Simulation.createRandomSimulation(2, 4, 1_000, 50);
+ }
+
+ public static Simulation createGoodPaternosterSimulation() {
+ return new Simulation(
+ IntStream.rangeClosed(1, 4).mapToObj(i -> new PaternosterElevator(1, 50, (int) Math.ceil(Math.random() * 50))).toList(),
+ IntStream.rangeClosed(1, 1_000).mapToObj(i -> new Human((int) Math.ceil(Math.random() * 50), (int) Math.ceil(Math.random() * 50))).toList()
+ );
+ }
+}
diff --git a/Contest/Assignment/src/org/togetherjava/event/elevator/simulation/Simulation.java b/Contest/Assignment/src/org/togetherjava/event/elevator/simulation/Simulation.java
index 6f462c6..387d633 100644
--- a/Contest/Assignment/src/org/togetherjava/event/elevator/simulation/Simulation.java
+++ b/Contest/Assignment/src/org/togetherjava/event/elevator/simulation/Simulation.java
@@ -1,27 +1,34 @@
package org.togetherjava.event.elevator.simulation;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.togetherjava.event.elevator.elevators.CommonElevator;
import org.togetherjava.event.elevator.elevators.Elevator;
import org.togetherjava.event.elevator.elevators.ElevatorSystem;
import org.togetherjava.event.elevator.humans.Human;
+import org.togetherjava.event.elevator.util.LogUtils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;
+import java.util.stream.Collectors;
import java.util.stream.LongStream;
import java.util.stream.Stream;
-public final class Simulation {
- private final List humans;
- private final List elevators;
- private final ElevatorSystem elevatorSystem;
+public class Simulation {
+ private static final Logger logger = LogManager.getLogger();
+
+ protected final List humans;
+ protected final List elevators;
+ protected final ElevatorSystem elevatorSystem;
private final View view;
- private long stepCount;
- private final List humanStatistics;
+ protected long stepCount;
+ protected final List humanStatistics;
public static Simulation createSingleElevatorSingleHumanSimulation() {
- return new Simulation(List.of(new Elevator(1, 10, 5)),
+ return new Simulation(List.of(new CommonElevator(1, 10, 5)),
List.of(new Human(1, 10)));
}
@@ -31,8 +38,8 @@ public static Simulation createSimpleSimulation() {
return new Simulation(
List.of(
- new Elevator(minFloor, floorsServed, 1),
- new Elevator(minFloor, floorsServed, 6)),
+ new CommonElevator(minFloor, floorsServed, 1),
+ new CommonElevator(minFloor, floorsServed, 6)),
List.of(
new Human(1, 2),
new Human(1, 5),
@@ -46,14 +53,18 @@ public static Simulation createRandomSimulation(int amountOfElevators, int amoun
}
public static Simulation createRandomSimulation(long seed, int amountOfElevators, int amountOfHumans, int floorsServed) {
- System.out.println("Seed for random simulation is: " + seed);
+ return createRandomSimulation(seed, amountOfElevators, amountOfHumans, floorsServed, Simulation::new);
+ }
+
+ public static S createRandomSimulation(long seed, int amountOfElevators, int amountOfHumans, int floorsServed, SimulationFactory factory) {
+ logger.info("Seed for random simulation is: " + seed);
Random random = new Random(seed);
int minFloor = 1;
- List elevators = Stream.generate(() -> {
+ List extends Elevator> elevators = Stream.generate(() -> {
int currentFloor = minFloor + random.nextInt(floorsServed);
- return new Elevator(minFloor, floorsServed, currentFloor);
+ return new CommonElevator(minFloor, floorsServed, currentFloor);
}).limit(amountOfElevators).toList();
List humans = Stream.generate(() -> {
@@ -62,10 +73,14 @@ public static Simulation createRandomSimulation(long seed, int amountOfElevators
return new Human(startingFloor, destinationFloor);
}).limit(amountOfHumans).toList();
- return new Simulation(elevators, humans);
+ return factory.createSimulation(elevators, humans);
}
- public Simulation(List elevators, List humans) {
+ interface SimulationFactory {
+ S createSimulation(List extends Elevator> elevators, List humans);
+ }
+
+ public Simulation(List extends Elevator> elevators, List humans) {
this.elevators = new ArrayList<>(elevators);
this.humans = new ArrayList<>(humans);
@@ -73,7 +88,7 @@ public Simulation(List elevators, List humans) {
this.elevators.forEach(elevatorSystem::registerElevator);
this.humans.forEach(elevatorSystem::registerElevatorListener);
- humanStatistics = this.humans.stream().map(HumanStatistics::new).toList();
+ humanStatistics = this.humans.stream().map(HumanStatistics::new).collect(Collectors.toCollection(() -> new ArrayList<>(humans.size())));
view = new View(this);
}
@@ -91,20 +106,20 @@ public void startAndExecuteUntilDone(int stepLimit) {
}
public void start() {
- elevatorSystem.ready();
+ LogUtils.measure("Ready", elevatorSystem::ready);
}
public void step() {
elevatorSystem.moveOneFloor();
-
- humanStatistics.forEach(HumanStatistics::step);
+ LogUtils.measure("Statistics update", () -> humanStatistics.forEach(HumanStatistics::step));
stepCount++;
}
public boolean isDone() {
- return humans.stream()
- .map(Human::getCurrentState)
- .allMatch(Human.State.ARRIVED::equals);
+// return humans.stream()
+// .map(Human::getCurrentState)
+// .allMatch(Human.State.ARRIVED::equals);
+ return !elevatorSystem.hasActivePassengers();
}
public long getStepCount() {
@@ -152,4 +167,17 @@ public int getAverageTimePercentageSpendForState(Human.State state) {
long medianPercentage = 100 * medianSteps / stepCount;
return (int) medianPercentage;
}
+
+ public void printCurrentStatistics() {
+ logger.trace(() -> humanStatistics.stream()
+ .collect(Collectors.toMap((HumanStatistics stat) -> stat.getHuman().getCurrentState(), s -> 1, Integer::sum)));
+ }
+
+ public boolean shouldPrintSummary() {
+ return elevators.size() <= 100 && humans.size() <= 100;
+ }
+
+ public boolean shouldPrint() {
+ return elevatorSystem.getFloorAmount() <= 10 && elevators.size() <= 20;
+ }
}
diff --git a/Contest/Assignment/src/org/togetherjava/event/elevator/simulation/View.java b/Contest/Assignment/src/org/togetherjava/event/elevator/simulation/View.java
index e61eb63..2f5ed61 100644
--- a/Contest/Assignment/src/org/togetherjava/event/elevator/simulation/View.java
+++ b/Contest/Assignment/src/org/togetherjava/event/elevator/simulation/View.java
@@ -101,7 +101,7 @@ private List corridorForFloorToLines(int floor) {
.filter(Human.State.ARRIVED::equals)
.count();
long humansWaiting = simulation.getHumans().stream()
- .filter(human -> human.getStartingFloor() == floor)
+ .filter(human -> human.getCurrentFloor() == floor)
.map(Human::getCurrentState)
.filter(state -> state.equals(Human.State.IDLE) || state.equals(Human.State.WAITING_FOR_ELEVATOR))
.count();
diff --git a/Contest/Assignment/src/org/togetherjava/event/elevator/util/CollectionUtils.java b/Contest/Assignment/src/org/togetherjava/event/elevator/util/CollectionUtils.java
new file mode 100644
index 0000000..e5fe80c
--- /dev/null
+++ b/Contest/Assignment/src/org/togetherjava/event/elevator/util/CollectionUtils.java
@@ -0,0 +1,40 @@
+package org.togetherjava.event.elevator.util;
+
+import java.util.Collection;
+import java.util.Objects;
+
+public final class CollectionUtils {
+ private CollectionUtils() {}
+
+ /**
+ * Equality check for collections that {@link java.util.ArrayDeque do not} override {@link #equals(Object) equals} for some reason.
+ * Will fall back to checking {@link #equals(Object) equals} first before doing the manual check,
+ * so it's the caller responsibility to determine whether they actually need this method
+ * and avoid potential double iteration.
+ * Not thread-safe, requires external synchronization,
+ * exception generation is delegated to iterators themselves.
+ */
+ public static boolean equals(Collection> c1, Collection> c2) {
+ if (c1 == null || c2 == null) {
+ return c1 == c2;
+ }
+
+ if (c1.equals(c2)) {
+ return true;
+ }
+
+ if (c1.size() != c2.size()) {
+ return false;
+ }
+
+ var i1 = c1.iterator();
+ var i2 = c2.iterator();
+ while (i1.hasNext()) {
+ if (!Objects.equals(i1.next(), i2.next())) {
+ return false;
+ }
+ }
+
+ return !i2.hasNext();
+ }
+}
diff --git a/Contest/Assignment/src/org/togetherjava/event/elevator/util/ConcurrentUtils.java b/Contest/Assignment/src/org/togetherjava/event/elevator/util/ConcurrentUtils.java
new file mode 100644
index 0000000..3f7f721
--- /dev/null
+++ b/Contest/Assignment/src/org/togetherjava/event/elevator/util/ConcurrentUtils.java
@@ -0,0 +1,25 @@
+package org.togetherjava.event.elevator.util;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.ForkJoinTask;
+import java.util.concurrent.RecursiveAction;
+import java.util.function.Consumer;
+
+public final class ConcurrentUtils {
+ private ConcurrentUtils() {}
+
+ /**
+ * Construct a {@link ForkJoinTask} for each member of the specified collection that performs the specified action,
+ * then submit them to the common {@link java.util.concurrent.ForkJoinPool ForkJoinPool} and wait for their completion.
+ */
+ public static void performTasksInParallel(Collection targets, Consumer action) {
+ List extends ForkJoinTask>> tasks = targets.stream().map(target -> new RecursiveAction() {
+ @Override
+ protected void compute() {
+ action.accept(target);
+ }
+ }).toList();
+ ForkJoinTask.invokeAll(tasks);
+ }
+}
diff --git a/Contest/Assignment/src/org/togetherjava/event/elevator/util/LogUtils.java b/Contest/Assignment/src/org/togetherjava/event/elevator/util/LogUtils.java
new file mode 100644
index 0000000..72c7cdb
--- /dev/null
+++ b/Contest/Assignment/src/org/togetherjava/event/elevator/util/LogUtils.java
@@ -0,0 +1,37 @@
+package org.togetherjava.event.elevator.util;
+
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+public final class LogUtils {
+ private static final Logger logger = LogManager.getLogger();
+
+ private LogUtils() {}
+ public static void measure(String name, Runnable toLog) {
+ measure(name, toLog, Level.INFO);
+ }
+
+ /**
+ * If the specified log level is enabled, measure the execution time of the specified runnable,
+ * then output the result at that logging level. Otherwise, just execute the runnable.
+ */
+ public static void measure(String name, Runnable toLog, Level level) {
+ if (logger.isEnabled(level)) {
+ long start = System.nanoTime();
+ toLog.run();
+ long end = System.nanoTime();
+ logger.log(level, "%s took %s".formatted(name, formatNanos(end - start)));
+ } else {
+ toLog.run();
+ }
+ }
+
+ private static String formatNanos(long nanos) {
+ if (nanos >= 10_000_000) {
+ return "%,.3f s".formatted(nanos / 1e9);
+ } else {
+ return "%,.3f ms".formatted(nanos / 1e6);
+ }
+ }
+}
diff --git a/Contest/Assignment/test/PreviousElevatorSystemTest.java b/Contest/Assignment/test/PreviousElevatorSystemTest.java
index 1d060e6..a09c11b 100644
--- a/Contest/Assignment/test/PreviousElevatorSystemTest.java
+++ b/Contest/Assignment/test/PreviousElevatorSystemTest.java
@@ -1,5 +1,5 @@
-import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
+import org.togetherjava.event.elevator.elevators.CommonElevator;
import org.togetherjava.event.elevator.elevators.Elevator;
import org.togetherjava.event.elevator.elevators.ElevatorSystem;
import org.togetherjava.event.elevator.elevators.FloorPanelSystem;
@@ -41,7 +41,7 @@ void testReady() {
void testMoveOneFloor() {
ElevatorSystem system = new ElevatorSystem();
- Supplier createAnyElevator = () -> new Elevator(1, 5, 2);
+ Supplier createAnyElevator = () -> new CommonElevator(1, 5, 2);
List elevators = Stream.generate(createAnyElevator).limit(3).toList();
List listeners =
diff --git a/Contest/Assignment/test/PreviousElevatorTest.java b/Contest/Assignment/test/PreviousElevatorTest.java
index 28a8b1e..3a87b8a 100644
--- a/Contest/Assignment/test/PreviousElevatorTest.java
+++ b/Contest/Assignment/test/PreviousElevatorTest.java
@@ -1,5 +1,5 @@
-import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
+import org.togetherjava.event.elevator.elevators.CommonElevator;
import org.togetherjava.event.elevator.elevators.Elevator;
import org.togetherjava.event.elevator.elevators.ElevatorPanel;
@@ -18,7 +18,7 @@ void testGetFloors() {
int expectedFloorsServed = 4;
int expectedCurrentFloor = 5;
Elevator elevator =
- new Elevator(expectedMinFloor, expectedFloorsServed, expectedCurrentFloor);
+ new CommonElevator(expectedMinFloor, expectedFloorsServed, expectedCurrentFloor);
int actualMinFloor = elevator.getMinFloor();
int actualFloorsServed = elevator.getFloorsServed();
@@ -42,7 +42,7 @@ void testGetId() {
Set elevatorIds = new HashSet<>();
for (int i = 0; i < 500; i++) {
- Elevator elevator = new Elevator(anyMinFloor, anyFloorsServed, anyCurrentFloor);
+ Elevator elevator = new CommonElevator(anyMinFloor, anyFloorsServed, anyCurrentFloor);
int id = elevator.getId();
assertFalse(elevatorIds.contains(id),
diff --git a/build.gradle b/build.gradle
index 7ed88c7..1ae9424 100644
--- a/build.gradle
+++ b/build.gradle
@@ -52,6 +52,28 @@ subprojects {
}
}
+project(':Contest-Assignment') {
+ def log4jVersion = '2.18.0'
+ def jbaVersion = '23.0.0'
+
+ dependencies {
+ annotationProcessor 'org.projectlombok:lombok:+'
+ compileOnly 'org.projectlombok:lombok:+'
+
+ annotationProcessor "org.jetbrains:annotations:$jbaVersion"
+ compileOnly "org.jetbrains:annotations:$jbaVersion"
+
+ implementation "org.apache.logging.log4j:log4j-core:$log4jVersion"
+ implementation "org.apache.logging.log4j:log4j-api:$log4jVersion"
+ }
+
+ sourceSets {
+ main {
+ resources.srcDir 'resources'
+ }
+ }
+}
+
project(':util') {
dependencies {
testImplementation 'org.mockito:mockito-core:4.0.0'