- 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.
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);
}
}
}
}
