MediCare Hospital Patient Admission System
The complete, compiling application, built one real commit at a time. Every code block is the full file at that point in history, not an excerpt. Copy any block straight into your editor.
Project scaffold
A minimal Maven project: JUnit 5 for testing, the exec plugin so mvn exec:java runs the app directly.
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>medicare</groupId> <artifactId>medicare-patient-admission</artifactId> <version>1.0.0</version> <packaging>jar</packaging> <properties> <maven.compiler.release>17</maven.compiler.release> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <junit.version>5.10.2</junit.version> </properties> <dependencies> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter</artifactId> <version>${junit.version}</version> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>3.2.5</version> </plugin> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>exec-maven-plugin</artifactId> <version>3.2.0</version> <configuration> <mainClass>medicare.MediCareApp</mainClass> </configuration> </plugin> </plugins> </build> </project>
git add pom.xml && git commit -m "chore: scaffold Maven project with JUnit 5"
The PatientCategory enum
A one-shot file. There's no partial version of this worth committing separately, it's three names and nothing else.
package medicare; /** * The three categories MediCare Hospital admits patients under. * Only INPATIENT ever occupies a hospital bed. */ public enum PatientCategory { INPATIENT, OUTPATIENT, EMERGENCY }
git add src/main/java/medicare/PatientCategory.java && git commit -m "feat: add PatientCategory enum"
Patient: fields and constructor
Every field private. The constructor takes all seven values and assigns them directly, no validation yet, that comes next commit.
package medicare; public class Patient { private final String patientId; private String firstName; private String lastName; private int age; private String gender; private String medicalCondition; private final PatientCategory category; public Patient(String patientId, String firstName, String lastName, int age, String gender, String medicalCondition, PatientCategory category) { this.patientId = patientId; this.firstName = firstName; this.lastName = lastName; this.age = age; this.gender = gender; this.medicalCondition = medicalCondition; this.category = category; } public String getPatientId() { return patientId; } public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getGender() { return gender; } public void setGender(String gender) { this.gender = gender; } public String getMedicalCondition() { return medicalCondition; } public void setMedicalCondition(String medicalCondition) { this.medicalCondition = medicalCondition; } public PatientCategory getCategory() { return category; } }
git add src/main/java/medicare/Patient.java && git commit -m "feat: add Patient class with encapsulated fields and constructor"
Patient: validation and display
setAge now guards against nonsense values, and the constructor routes through it so bad data can't sneak in at creation time either. displayDetails() prints one formatted row.
package medicare; /** * Base class for every person MediCare Hospital registers. * Outpatient and Emergency patients are represented directly as Patient; * Inpatient extends this class to add bed information. */ public class Patient { private final String patientId; private String firstName; private String lastName; private int age; private String gender; private String medicalCondition; private final PatientCategory category; public Patient(String patientId, String firstName, String lastName, int age, String gender, String medicalCondition, PatientCategory category) { this.patientId = patientId; this.firstName = firstName; this.lastName = lastName; setAge(age); this.gender = gender; this.medicalCondition = medicalCondition; this.category = category; } public String getPatientId() { return patientId; } public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } public int getAge() { return age; } public void setAge(int age) { if (age < 0 || age > 120) { throw new IllegalArgumentException("Age must be between 0 and 120."); } this.age = age; } public String getGender() { return gender; } public void setGender(String gender) { this.gender = gender; } public String getMedicalCondition() { return medicalCondition; } public void setMedicalCondition(String medicalCondition) { this.medicalCondition = medicalCondition; } public PatientCategory getCategory() { return category; } /** * Prints one row of patient details. Inpatient overrides this to append * ward and bed information after calling super.displayDetails(). */ public void displayDetails() { System.out.printf("%-8s %-12s %-12s %-4d %-8s %-18s %-10s%n", patientId, firstName, lastName, age, gender, medicalCondition, category); } }
this.age = age; directly, new Patient(..., -5, ...) slips straight past the guard.git add src/main/java/medicare/Patient.java && git commit -m "feat: validate age and add displayDetails() to Patient"
Inpatient extends Patient
The ward has one ward number, so it's a constant. Bed number starts null, a patient is registered before a bed exists for them.
package medicare; public class Inpatient extends Patient { private static final int WARD_NUMBER = 1; private String bedNumber; public Inpatient(String patientId, String firstName, String lastName, int age, String gender, String medicalCondition) { super(patientId, firstName, lastName, age, gender, medicalCondition, PatientCategory.INPATIENT); this.bedNumber = null; } public int getWardNumber() { return WARD_NUMBER; } public String getBedNumber() { return bedNumber; } public void setBedNumber(String bedNumber) { this.bedNumber = bedNumber; } public boolean hasBed() { return bedNumber != null; } }
git add src/main/java/medicare/Inpatient.java && git commit -m "feat: add Inpatient class extending Patient via super()"
Inpatient: override displayDetails()
Calls super.displayDetails() first, then appends the bed line. A Patient-typed reference holding an Inpatient object still runs this version, that's polymorphism.
package medicare; /** * A patient admitted to a hospital bed. The hospital has one ward, so * wardNumber is fixed; bedNumber stays null until a bed is allocated. */ public class Inpatient extends Patient { private static final int WARD_NUMBER = 1; private String bedNumber; public Inpatient(String patientId, String firstName, String lastName, int age, String gender, String medicalCondition) { super(patientId, firstName, lastName, age, gender, medicalCondition, PatientCategory.INPATIENT); this.bedNumber = null; } public int getWardNumber() { return WARD_NUMBER; } public String getBedNumber() { return bedNumber; } public void setBedNumber(String bedNumber) { this.bedNumber = bedNumber; } public boolean hasBed() { return bedNumber != null; } @Override public void displayDetails() { super.displayDetails(); String bedInfo = hasBed() ? "Ward " + WARD_NUMBER + ", Bed " + bedNumber : "Ward " + WARD_NUMBER + ", Bed: not allocated"; System.out.println(" -> " + bedInfo); } }
git add src/main/java/medicare/Inpatient.java && git commit -m "feat: override displayDetails() in Inpatient"
A checked exception for a full ward
Checked, on purpose: whoever calls allocateBed() has to either catch this or declare it. That's harder to ignore than a null return.
package medicare; /** * Thrown when the ward has no free bed left to allocate. * Checked, on purpose: callers must decide what to do when the ward is full * instead of silently receiving null. */ public class NoBedAvailableException extends Exception { public NoBedAvailableException(String message) { super(message); } }
git add src/main/java/medicare/NoBedAvailableException.java && git commit -m "feat: add NoBedAvailableException"
Ward: the 4×5 bed grid
Two parallel grids: bedIds holds the fixed labels (B01 to B20), occupant holds either a patient ID or null. This checkpoint only builds and labels the grid.
package medicare; public class Ward { private static final int ROWS = 4; private static final int COLS = 5; private final String[][] bedIds = new String[ROWS][COLS]; private final String[][] occupant = new String[ROWS][COLS]; // patientId, or null if free public Ward() { initialiseBeds(); } private void initialiseBeds() { int bedNumber = 1; for (int row = 0; row < ROWS; row++) { for (int col = 0; col < COLS; col++) { bedIds[row][col] = String.format("B%02d", bedNumber); bedNumber++; } } } }
git add src/main/java/medicare/Ward.java && git commit -m "feat: initialise 4x5 ward bed grid"
Ward: allocate and release beds
Allocation walks the grid until it finds a free cell. Release walks it again looking for the matching bed ID. Both reuse the same nested-loop shape from checkpoint 8.
package medicare; public class Ward { private static final int ROWS = 4; private static final int COLS = 5; private final String[][] bedIds = new String[ROWS][COLS]; private final String[][] occupant = new String[ROWS][COLS]; public Ward() { initialiseBeds(); } private void initialiseBeds() { int bedNumber = 1; for (int row = 0; row < ROWS; row++) { for (int col = 0; col < COLS; col++) { bedIds[row][col] = String.format("B%02d", bedNumber); bedNumber++; } } } /** * Finds the first free bed, assigns it to the patient, and returns the * bed ID. Throws NoBedAvailableException if the ward is full. */ public String allocateBed(Inpatient patient) throws NoBedAvailableException { if (patient.hasBed()) { throw new IllegalStateException( "Patient " + patient.getPatientId() + " already occupies bed " + patient.getBedNumber() + "."); } for (int row = 0; row < ROWS; row++) { for (int col = 0; col < COLS; col++) { if (occupant[row][col] == null) { occupant[row][col] = patient.getPatientId(); patient.setBedNumber(bedIds[row][col]); return bedIds[row][col]; } } } throw new NoBedAvailableException("No beds available in the ward."); } /** * Frees the bed a patient occupies, if any. */ public void releaseBed(Inpatient patient) { if (!patient.hasBed()) { return; } for (int row = 0; row < ROWS; row++) { for (int col = 0; col < COLS; col++) { if (bedIds[row][col].equals(patient.getBedNumber())) { occupant[row][col] = null; } } } patient.setBedNumber(null); } }
occupant[row][col] == null only inside the inner loop and forgetting the return lets the search keep going and silently overwrite a later bed instead of stopping at the first free one.git add src/main/java/medicare/Ward.java && git commit -m "feat: implement allocateBed() and releaseBed()"
Ward: layout, availability and occupancy reports
The final version of Ward. Everything below the exception handling is the same nested-loop shape once more, aimed at a different question each time.
package medicare; /** * The hospital's one ward: 20 beds arranged in a 4x5 layout (B01-B20). * Tracks which bed each Inpatient occupies and reports on occupancy. */ public class Ward { private static final int ROWS = 4; private static final int COLS = 5; private final String[][] bedIds = new String[ROWS][COLS]; private final String[][] occupant = new String[ROWS][COLS]; public Ward() { initialiseBeds(); } private void initialiseBeds() { int bedNumber = 1; for (int row = 0; row < ROWS; row++) { for (int col = 0; col < COLS; col++) { bedIds[row][col] = String.format("B%02d", bedNumber); bedNumber++; } } } public String allocateBed(Inpatient patient) throws NoBedAvailableException { if (patient.hasBed()) { throw new IllegalStateException( "Patient " + patient.getPatientId() + " already occupies bed " + patient.getBedNumber() + "."); } for (int row = 0; row < ROWS; row++) { for (int col = 0; col < COLS; col++) { if (occupant[row][col] == null) { occupant[row][col] = patient.getPatientId(); patient.setBedNumber(bedIds[row][col]); return bedIds[row][col]; } } } throw new NoBedAvailableException("No beds available in the ward."); } public void releaseBed(Inpatient patient) { if (!patient.hasBed()) { return; } for (int row = 0; row < ROWS; row++) { for (int col = 0; col < COLS; col++) { if (bedIds[row][col].equals(patient.getBedNumber())) { occupant[row][col] = null; } } } patient.setBedNumber(null); } public void displayWardLayout() { System.out.println("Ward layout (4 x 5):"); for (int row = 0; row < ROWS; row++) { StringBuilder line = new StringBuilder(); for (int col = 0; col < COLS; col++) { String status = occupant[row][col] == null ? "FREE" : "OCC "; line.append(String.format("[%s:%s] ", bedIds[row][col], status)); } System.out.println(line.toString().trim()); } } public void displayAvailableBeds() { System.out.println("Available beds:"); for (int row = 0; row < ROWS; row++) { for (int col = 0; col < COLS; col++) { if (occupant[row][col] == null) { System.out.println(" " + bedIds[row][col]); } } } } public void displayOccupiedBeds() { System.out.println("Occupied beds:"); for (int row = 0; row < ROWS; row++) { for (int col = 0; col < COLS; col++) { if (occupant[row][col] != null) { System.out.println(" " + bedIds[row][col] + " -> Patient " + occupant[row][col]); } } } } public int totalBeds() { return ROWS * COLS; } public int countAvailableBeds() { int count = 0; for (String[] row : occupant) { for (String cell : row) { if (cell == null) { count++; } } } return count; } public int countOccupiedBeds() { return totalBeds() - countAvailableBeds(); } public double occupancyPercentage() { return (countOccupiedBeds() / (double) totalBeds()) * 100.0; } }
countOccupiedBeds() / totalBeds() without the (double) cast performs integer division and always returns 0. The cast has to happen before the division, not after.git add src/main/java/medicare/Ward.java && git commit -m "feat: add ward layout, availability and occupancy reports"
App: menu skeleton
Run the shell before filling it in. The switch statement exists, but most cases are stubs, run this once to prove the loop and the menu work before wiring in real logic.
package medicare; import java.util.ArrayList; import java.util.List; import java.util.Scanner; public class MediCareApp { private final List<Patient> patients = new ArrayList<>(); private final Ward ward = new Ward(); public static void main(String[] args) { new MediCareApp().run(); } public void run() { Scanner scanner = new Scanner(System.in); boolean running = true; while (running) { printMenu(); System.out.print("Choose an option: "); int choice = Integer.parseInt(scanner.nextLine().trim()); switch (choice) { case 13 -> running = false; default -> System.out.println("Not wired up yet."); } } System.out.println("Goodbye."); scanner.close(); } private void printMenu() { System.out.println(); System.out.println("========== MediCare Hospital Patient Admission System =========="); System.out.println(" 1. Register patient"); System.out.println(" 2. Search for patient"); System.out.println(" 3. Update patient details"); System.out.println(" 4. Delete patient"); System.out.println(" 5. Display all patients"); System.out.println(" 6. Allocate bed to inpatient"); System.out.println(" 7. Release bed"); System.out.println(" 8. Display ward layout"); System.out.println(" 9. Display available beds"); System.out.println("10. Display occupied beds"); System.out.println("11. Display reports"); System.out.println("12. Sort patients"); System.out.println("13. Exit"); System.out.println("=================================================================="); } }
git add src/main/java/medicare/MediCareApp.java && git commit -m "feat: add MediCareApp skeleton with menu loop"
App: register patients
registerPatient and findPatient are kept free of Scanner, so they can be unit tested directly. The interactive wrapper collects input and calls them.
public boolean registerPatient(Patient newPatient) { if (findPatient(newPatient.getPatientId()) != null) { return false; } patients.add(newPatient); return true; } public Patient findPatient(String patientId) { for (Patient p : patients) { if (p.getPatientId().equals(patientId)) { return p; } } return null; } private void registerPatientInteractive(Scanner scanner) { System.out.print("Patient ID: "); String id = scanner.nextLine().trim(); if (findPatient(id) != null) { System.out.println("A patient with ID " + id + " already exists."); return; } System.out.print("First name: "); String firstName = scanner.nextLine().trim(); System.out.print("Last name: "); String lastName = scanner.nextLine().trim(); System.out.print("Age: "); int age = Integer.parseInt(scanner.nextLine().trim()); System.out.print("Gender: "); String gender = scanner.nextLine().trim(); System.out.print("Medical condition: "); String condition = scanner.nextLine().trim(); System.out.println("Category: 1) Inpatient 2) Outpatient 3) Emergency"); System.out.print("Choose category: "); int categoryChoice = Integer.parseInt(scanner.nextLine().trim()); Patient patient = switch (categoryChoice) { case 1 -> new Inpatient(id, firstName, lastName, age, gender, condition); case 2 -> new Patient(id, firstName, lastName, age, gender, condition, PatientCategory.OUTPATIENT); case 3 -> new Patient(id, firstName, lastName, age, gender, condition, PatientCategory.EMERGENCY); default -> null; }; if (patient == null) { System.out.println("Invalid category choice."); return; } registerPatient(patient); System.out.println("Patient " + id + " registered."); }
registerPatient(Patient) separate from registerPatientInteractive(Scanner) is what makes checkpoint 15's tests possible without simulating keyboard input.git add src/main/java/medicare/MediCareApp.java && git commit -m "feat: implement patient registration with duplicate ID check"
App: search, update, delete
Delete has one extra responsibility: if the patient being removed is an Inpatient with a bed, release it first, or the bed grid would keep a phantom occupant.
public boolean updatePatient(String patientId, String firstName, String lastName, int age, String gender, String medicalCondition) { Patient patient = findPatient(patientId); if (patient == null) { return false; } patient.setFirstName(firstName); patient.setLastName(lastName); patient.setAge(age); patient.setGender(gender); patient.setMedicalCondition(medicalCondition); return true; } public boolean deletePatient(String patientId) { Patient patient = findPatient(patientId); if (patient == null) { return false; } if (patient instanceof Inpatient inpatient && inpatient.hasBed()) { ward.releaseBed(inpatient); } return patients.remove(patient); } public void displayAllPatients() { if (patients.isEmpty()) { System.out.println("No patients registered yet."); return; } System.out.println("All registered patients:"); for (Patient p : patients) { p.displayDetails(); } } public int getPatientCount() { return patients.size(); } public Ward getWard() { return ward; }
git add src/main/java/medicare/MediCareApp.java && git commit -m "feat: implement search, update and delete patient operations"
App: beds, reports and sorting, fully wired
The complete, final file: every menu case now calls something real. This is what actually ships.
package medicare; import java.util.ArrayList; import java.util.Comparator; import java.util.List; import java.util.Scanner; /** * Console-based Hospital Patient Admission System for MediCare Hospital. * Manages patient records and one 20-bed ward, entirely in memory. */ public class MediCareApp { private final List<Patient> patients = new ArrayList<>(); private final Ward ward = new Ward(); public static void main(String[] args) { new MediCareApp().run(); } public void run() { Scanner scanner = new Scanner(System.in); boolean running = true; while (running) { printMenu(); int choice = readInt(scanner, "Choose an option: "); switch (choice) { case 1 -> registerPatientInteractive(scanner); case 2 -> searchPatientInteractive(scanner); case 3 -> updatePatientInteractive(scanner); case 4 -> deletePatientInteractive(scanner); case 5 -> displayAllPatients(); case 6 -> allocateBedInteractive(scanner); case 7 -> releaseBedInteractive(scanner); case 8 -> ward.displayWardLayout(); case 9 -> ward.displayAvailableBeds(); case 10 -> ward.displayOccupiedBeds(); case 11 -> displayReports(); case 12 -> sortPatientsInteractive(scanner); case 13 -> running = false; default -> System.out.println("Invalid option, try again."); } } System.out.println("Goodbye."); scanner.close(); } private void printMenu() { System.out.println(); System.out.println("========== MediCare Hospital Patient Admission System =========="); System.out.println(" 1. Register patient"); System.out.println(" 2. Search for patient"); System.out.println(" 3. Update patient details"); System.out.println(" 4. Delete patient"); System.out.println(" 5. Display all patients"); System.out.println(" 6. Allocate bed to inpatient"); System.out.println(" 7. Release bed"); System.out.println(" 8. Display ward layout"); System.out.println(" 9. Display available beds"); System.out.println("10. Display occupied beds"); System.out.println("11. Display reports"); System.out.println("12. Sort patients"); System.out.println("13. Exit"); System.out.println("=================================================================="); } public boolean registerPatient(Patient newPatient) { if (findPatient(newPatient.getPatientId()) != null) { return false; } patients.add(newPatient); return true; } public Patient findPatient(String patientId) { for (Patient p : patients) { if (p.getPatientId().equals(patientId)) { return p; } } return null; } public boolean updatePatient(String patientId, String firstName, String lastName, int age, String gender, String medicalCondition) { Patient patient = findPatient(patientId); if (patient == null) { return false; } patient.setFirstName(firstName); patient.setLastName(lastName); patient.setAge(age); patient.setGender(gender); patient.setMedicalCondition(medicalCondition); return true; } public boolean deletePatient(String patientId) { Patient patient = findPatient(patientId); if (patient == null) { return false; } if (patient instanceof Inpatient inpatient && inpatient.hasBed()) { ward.releaseBed(inpatient); } return patients.remove(patient); } public void displayAllPatients() { if (patients.isEmpty()) { System.out.println("No patients registered yet."); return; } System.out.println("All registered patients:"); for (Patient p : patients) { p.displayDetails(); } } public int getPatientCount() { return patients.size(); } public List<Patient> getPatients() { return patients; } public Ward getWard() { return ward; } public void sortBySurname() { patients.sort(Comparator.comparing(Patient::getLastName)); } public void sortByPatientId() { patients.sort(Comparator.comparing(Patient::getPatientId)); } public void displayReports() { System.out.println("---- Patient report ----"); System.out.println("Total registered patients: " + getPatientCount()); displayAllPatients(); System.out.println(); System.out.println("---- Bed occupancy report ----"); System.out.println("Total beds: " + ward.totalBeds()); System.out.println("Available beds: " + ward.countAvailableBeds()); System.out.println("Occupied beds: " + ward.countOccupiedBeds()); System.out.printf("Occupancy: %.1f%%%n", ward.occupancyPercentage()); } private void registerPatientInteractive(Scanner scanner) { System.out.print("Patient ID: "); String id = scanner.nextLine().trim(); if (findPatient(id) != null) { System.out.println("A patient with ID " + id + " already exists."); return; } System.out.print("First name: "); String firstName = scanner.nextLine().trim(); System.out.print("Last name: "); String lastName = scanner.nextLine().trim(); int age = readInt(scanner, "Age: "); System.out.print("Gender: "); String gender = scanner.nextLine().trim(); System.out.print("Medical condition: "); String condition = scanner.nextLine().trim(); System.out.println("Category: 1) Inpatient 2) Outpatient 3) Emergency"); int categoryChoice = readInt(scanner, "Choose category: "); Patient patient; try { patient = switch (categoryChoice) { case 1 -> new Inpatient(id, firstName, lastName, age, gender, condition); case 2 -> new Patient(id, firstName, lastName, age, gender, condition, PatientCategory.OUTPATIENT); case 3 -> new Patient(id, firstName, lastName, age, gender, condition, PatientCategory.EMERGENCY); default -> null; }; } catch (IllegalArgumentException e) { System.out.println("Could not register patient: " + e.getMessage()); return; } if (patient == null) { System.out.println("Invalid category choice."); return; } registerPatient(patient); System.out.println("Patient " + id + " registered."); } private void searchPatientInteractive(Scanner scanner) { System.out.print("Patient ID to search for: "); String id = scanner.nextLine().trim(); Patient patient = findPatient(id); if (patient == null) { System.out.println("No patient found with ID " + id + "."); } else { patient.displayDetails(); } } private void updatePatientInteractive(Scanner scanner) { System.out.print("Patient ID to update: "); String id = scanner.nextLine().trim(); Patient patient = findPatient(id); if (patient == null) { System.out.println("No patient found with ID " + id + "."); return; } System.out.print("First name [" + patient.getFirstName() + "]: "); String firstName = orDefault(scanner.nextLine(), patient.getFirstName()); System.out.print("Last name [" + patient.getLastName() + "]: "); String lastName = orDefault(scanner.nextLine(), patient.getLastName()); int age = readInt(scanner, "Age [" + patient.getAge() + "]: "); System.out.print("Gender [" + patient.getGender() + "]: "); String gender = orDefault(scanner.nextLine(), patient.getGender()); System.out.print("Medical condition [" + patient.getMedicalCondition() + "]: "); String condition = orDefault(scanner.nextLine(), patient.getMedicalCondition()); updatePatient(id, firstName, lastName, age, gender, condition); System.out.println("Patient " + id + " updated."); } private void deletePatientInteractive(Scanner scanner) { System.out.print("Patient ID to delete: "); String id = scanner.nextLine().trim(); if (deletePatient(id)) { System.out.println("Patient " + id + " deleted."); } else { System.out.println("No patient found with ID " + id + "."); } } private void allocateBedInteractive(Scanner scanner) { System.out.print("Inpatient ID to allocate a bed to: "); String id = scanner.nextLine().trim(); Patient patient = findPatient(id); if (!(patient instanceof Inpatient inpatient)) { System.out.println("No inpatient found with ID " + id + "."); return; } try { String bed = ward.allocateBed(inpatient); System.out.println("Allocated bed " + bed + " to patient " + id + "."); } catch (NoBedAvailableException e) { System.out.println("Cannot admit patient: " + e.getMessage()); } catch (IllegalStateException e) { System.out.println(e.getMessage()); } } private void releaseBedInteractive(Scanner scanner) { System.out.print("Inpatient ID to release a bed from: "); String id = scanner.nextLine().trim(); Patient patient = findPatient(id); if (!(patient instanceof Inpatient inpatient)) { System.out.println("No inpatient found with ID " + id + "."); return; } ward.releaseBed(inpatient); System.out.println("Bed released for patient " + id + "."); } private void sortPatientsInteractive(Scanner scanner) { System.out.println("Sort by: 1) Surname 2) Patient ID"); int choice = readInt(scanner, "Choose: "); if (choice == 1) { sortBySurname(); } else { sortByPatientId(); } displayAllPatients(); } private static String orDefault(String input, String fallback) { return input.isBlank() ? fallback : input.trim(); } private static int readInt(Scanner scanner, String prompt) { while (true) { System.out.print(prompt); String line = scanner.nextLine().trim(); try { return Integer.parseInt(line); } catch (NumberFormatException e) { System.out.println("Please enter a whole number."); } } } }
git add src/main/java/medicare/MediCareApp.java && git commit -m "feat: wire up bed allocation, release and sorting from the menu"
The JUnit test suite
14 tests across two files. Every one runs against the real classes above, no mocking. Confirmed green with junit-platform-console-standalone before this guide was published.
package medicare; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; class WardTest { private Ward ward; @BeforeEach void setUp() { ward = new Ward(); } @Test void allocatesFirstFreeBedInReadingOrder() throws NoBedAvailableException { Inpatient patient = new Inpatient("P001", "Amy", "Ndlovu", 34, "F", "Flu"); String bed = ward.allocateBed(patient); assertEquals("B01", bed); assertEquals("B01", patient.getBedNumber()); assertEquals(19, ward.countAvailableBeds()); } @Test void releaseBedFreesItForReuse() throws NoBedAvailableException { Inpatient first = new Inpatient("P001", "Amy", "Ndlovu", 34, "F", "Flu"); ward.allocateBed(first); ward.releaseBed(first); assertFalse(first.hasBed()); assertEquals(20, ward.countAvailableBeds()); } @Test void throwsWhenAllTwentyBedsAreTaken() throws NoBedAvailableException { for (int i = 1; i <= 20; i++) { ward.allocateBed(new Inpatient("P" + i, "First" + i, "Last" + i, 30, "F", "Observation")); } Inpatient patient21 = new Inpatient("P021", "Twenty", "First", 40, "M", "Overflow"); assertThrows(NoBedAvailableException.class, () -> ward.allocateBed(patient21)); } @Test void occupancyPercentageReflectsOccupiedBeds() throws NoBedAvailableException { for (int i = 1; i <= 5; i++) { ward.allocateBed(new Inpatient("P" + i, "First" + i, "Last" + i, 30, "F", "Observation")); } assertEquals(25.0, ward.occupancyPercentage(), 0.001); } @Test void cannotAllocateASecondBedToTheSamePatient() throws NoBedAvailableException { Inpatient patient = new Inpatient("P001", "Amy", "Ndlovu", 34, "F", "Flu"); ward.allocateBed(patient); assertThrows(IllegalStateException.class, () -> ward.allocateBed(patient)); } }
package medicare; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; class MediCareAppTest { private MediCareApp app; @BeforeEach void setUp() { app = new MediCareApp(); } @Test void registersANewPatient() { Patient patient = new Patient("P001", "Amy", "Ndlovu", 34, "F", "Flu", PatientCategory.OUTPATIENT); boolean registered = app.registerPatient(patient); assertTrue(registered); assertEquals(1, app.getPatientCount()); } @Test void duplicatePatientIdIsRejected() { Patient p1 = new Patient("P001", "Amy", "Ndlovu", 34, "F", "Flu", PatientCategory.OUTPATIENT); Patient p2 = new Patient("P001", "Tumi", "Sithole", 41, "M", "Asthma", PatientCategory.OUTPATIENT); app.registerPatient(p1); boolean secondRegistered = app.registerPatient(p2); assertFalse(secondRegistered); assertEquals(1, app.getPatientCount()); } @Test void findsAPatientByIdAfterRegistration() { Patient patient = new Patient("P001", "Amy", "Ndlovu", 34, "F", "Flu", PatientCategory.OUTPATIENT); app.registerPatient(patient); Patient found = app.findPatient("P001"); assertNotNull(found); assertEquals("Ndlovu", found.getLastName()); } @Test void searchingForAnUnknownIdReturnsNull() { assertNull(app.findPatient("does-not-exist")); } @Test void updatesAnExistingPatientsDetails() { app.registerPatient(new Patient("P001", "Amy", "Ndlovu", 34, "F", "Flu", PatientCategory.OUTPATIENT)); boolean updated = app.updatePatient("P001", "Amy", "Ndlovu", 35, "F", "Recovered"); assertTrue(updated); assertEquals(35, app.findPatient("P001").getAge()); assertEquals("Recovered", app.findPatient("P001").getMedicalCondition()); } @Test void deletingAPatientRemovesThemFromTheList() { app.registerPatient(new Patient("P001", "Amy", "Ndlovu", 34, "F", "Flu", PatientCategory.OUTPATIENT)); boolean deleted = app.deletePatient("P001"); assertTrue(deleted); assertEquals(0, app.getPatientCount()); } @Test void deletingAnInpatientAlsoReleasesTheirBed() throws NoBedAvailableException { Inpatient inpatient = new Inpatient("P001", "Amy", "Ndlovu", 34, "F", "Flu"); app.registerPatient(inpatient); app.getWard().allocateBed(inpatient); app.deletePatient("P001"); assertEquals(20, app.getWard().countAvailableBeds()); } @Test void rejectsAnInvalidAge() { assertThrows(IllegalArgumentException.class, () -> new Patient("P001", "Amy", "Ndlovu", -5, "F", "Flu", PatientCategory.OUTPATIENT)); } @Test void sortsPatientsBySurname() { app.registerPatient(new Patient("P002", "Ben", "Zulu", 40, "M", "Cold", PatientCategory.OUTPATIENT)); app.registerPatient(new Patient("P001", "Amy", "Ndlovu", 34, "F", "Flu", PatientCategory.OUTPATIENT)); app.sortBySurname(); assertEquals("Ndlovu", app.getPatients().get(0).getLastName()); assertEquals("Zulu", app.getPatients().get(1).getLastName()); } }
git add src/test && git commit -m "test: add JUnit tests for patient CRUD, bed allocation and validation"
What the finished commit log looks like
If you commit at each checkpoint above, git log --oneline reads as a build order a marker (or a future you) can follow without watching the video again.
javac and all 14 tests pass under junit-platform-console-standalone. Nothing in this guide is untested.