- User Override Control Loop
- Automatic Control Loop
In this post, the Train class contains a few characteristics such as distance travelled from Origin and its speed, which can be abstracted into a separate class and can be handled in a more centralized manner inside TrainController class.
Also a few more features such as directionality of train movement and maximum distance from origin will be introduced.
Codepackage com.example.lib; import java.util.ArrayList; import java.util.List; import java.util.Scanner; public class TrainController { private final ListmTrains = new ArrayList<>(); private final List mManuallyStoppedTrains = new ArrayList<>(); private final InstructionParser mInstructionParser = new InstructionParser(); public static void main(String[] args) { final TrainController trainController = new TrainController(); trainController.createAndStartTrains(5); // This is the main controller loop. // 1. It monitors the distance between the pairs of trains and sends stop and start signals appropriately. // 2. This is run in a separate thread so that the user input can be captured in the main thread. Runnable controllerRunnable = new Runnable() { @Override public void run() { while (true) { trainController.checkAndControlTrains(); } } }; Thread controllerThread = new Thread(controllerRunnable); controllerThread.start(); trainController.printAllCommands(); // Accept user input in a loop. while (true) { Scanner scanner = new Scanner(System.in); int option = scanner.nextInt(); trainController.executeOption(option); System.out.println("Waiting for the next input"); } } public void printAllCommands() { System.out.println("Choose from the below options"); System.out.println("1. Start 5 trains"); System.out.println("2. Get status of all trains"); System.out.println("3. Stop all trains"); System.out.println("4. Start all trains"); System.out.println("5. Exit"); System.out.println("6. Control individual train"); System.out.println("7. Print all commands"); } public void executeOption(final int option) { switch (option) { case 1: createAndStartTrains(5); break; case 2: printCurrentStateOfAllTrains(); break; case 3: stopAllTrains(); break; case 4: startAllTrains(); break; case 5: stopAllTrains(); System.exit(0); case 6: String instruction = System.console().readLine(); System.out.println("Instruction received: " + instruction); if (instruction.toLowerCase().split(" ").length == 2) { try { int index = mInstructionParser.getIndex(instruction); switch (instruction.toLowerCase().split(" ")[0]) { case "start": startTrain(index); break; case "stop": stopTrain(index); break; } } catch (IllegalArgumentException e) { // Do nothing. } } break; case 7: printAllCommands(); break; } } /** * Initialize trains with different speeds and start them on different threads. * @param count of trains to start */ public void createAndStartTrains(final int count) { final String trainName = "Train "; for (int i = 0; i < count; i++) { Train train = new Train(trainName + i, i * 1000); mTrains.add(train); train.startTrain(); } } /** * Checks that the distance between successive trains is more than 1000 units. * Whenever the distance between successive trains is less than 1000 units, it calls {@link Train#stopTrain()} on the rear train. * Whenever the distance between successive trains is more than 1000 units, it calls {@link Train#startTrain()} on the rear train. */ private void checkAndControlTrains() { // Loop to stop a train if it is within 1000 units of the train ahead of it. for (int i = 0; i < mTrains.size() - 1; i++) { if (mTrains.get(i + 1).getDistance() - mTrains.get(i).getDistance() - 3 * mTrains.get(i).getSpeed() <= 1000) { mTrains.get(i).stopTrain(); } } // Loop to start a train if it is more than 1000 units away from the train ahead of it and it was not stopped manually. for (int i = 0; i < mTrains.size() - 1; i++) { if (mTrains.get(i + 1).getDistance() - mTrains.get(i).getDistance() - mTrains.get(i).getSpeed() > 1000) { Train train = mTrains.get(i); if (!mManuallyStoppedTrains.contains(train) && !train.isRunning()) { train.startTrain(); } } } } private void printCurrentStateOfAllTrains() { if (mTrains.size() == 0) { System.out.println("No train is in running state"); return; } System.out.println("Printing status of all trains"); for (Train train : mTrains) { train.printCurrentState(); } } private void stopAllTrains() { for (Train train : mTrains) { train.stopTrain(); } } private void startAllTrains() { for (Train train : mTrains) { train.startTrain(); } } /** * Stop the train with the provided index. * @param trainIndex the index of the train to stop */ private void stopTrain(int trainIndex) { // First add the train to the list of manually stopped trains. Otherwise the checkAndControl loop will start the train again. mManuallyStoppedTrains.add(mTrains.get(trainIndex)); mTrains.get(trainIndex).stopTrain(); } /** * Start the train with the provided index safely. * This checks if the train ahead is more than 1000 units ahead before starting the train. * @param trainIndex the index of the train to start */ private void startTrain(int trainIndex) { if (trainIndex < mTrains.size() - 1) { if (mTrains.get(trainIndex + 1).getDistance() - mTrains.get(trainIndex).getDistance() <= 1000) { System.out.println("Cannot start train " + trainIndex + " since the train ahead is within 1000"); return; } System.out.println("Starting train " + trainIndex + " since the train ahead is more than 1000"); } // First remove the train from the list of manually stopped trains. Otherwise the checkAndControl loop will not start the train again. mManuallyStoppedTrains.remove(mTrains.get(trainIndex)); mTrains.get(trainIndex).startTrain(); } private static class InstructionParser { public int getIndex(final String instruction) { String lowerCaseInstruction = instruction.toLowerCase(); if (!lowerCaseInstruction.startsWith("start") && !lowerCaseInstruction.startsWith("stop")) { System.out.println("Invalid instruction"); throw new IllegalArgumentException("Invalid instruction provided. Cannot proceed"); } return Integer.parseInt(lowerCaseInstruction.split(" ")[1]); } } private static class Train extends Thread { private final int mSpeed; private boolean mIsRunning; private int mDistance = 0; private int dummyLooper = 0; private boolean mIsInitialized; public Train(String name, int speed) { super(name); mSpeed = speed; } @Override public void run() { while (true) { dummyLooper++; // dummyLooper is just to keep this thread from becoming No-op when mIsRunning is false. while (mIsRunning) { dummyLooper = 0; try { // The train runs at mSpeed units per second. Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } mDistance += mSpeed; } if (dummyLooper > 0) { mIsRunning = false; } } } public int getDistance() { return mDistance; } public int getSpeed() { return mSpeed; } public boolean isRunning() { return mIsRunning; } /** * Sets the mIsRunning to true starts this thread. */ public void startTrain() { mIsRunning = true; // Calling start always causes {@link IllegalThreadStateException}. Hence mIsInitialized is used as a check. if (!mIsInitialized) { mIsInitialized = true; start(); } } public void stopTrain() { mIsRunning = false; } public void printCurrentState() { if (mIsRunning) { System.out.println(getName() + " is running and has reached " + mDistance); } else { System.out.println(getName() + " is stopped and has reached " + mDistance); } } } }