Showing posts with label Threads. Show all posts
Showing posts with label Threads. Show all posts

Sunday, June 27, 2021

Automatic Train Controller System Code

TrainController class starts two infinite loops in separate threads:
  1. User Override Control Loop
  2. 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.

Code
package com.example.lib;

import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

public class TrainController {
    private final List mTrains = 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);
            }
        }
    }
}