From 1c302189d0b3edfb497fbc022b0daa4825322c71 Mon Sep 17 00:00:00 2001 From: 123EFD Date: Fri, 31 Oct 2025 12:14:23 +0800 Subject: [PATCH 1/5] Add initial implementation of Student, StudentManager, and studentManagementSystem classes --- Programs/studentManagement/Student.java | 5 +++++ Programs/studentManagement/StudentManager.java | 5 +++++ Programs/studentManagement/studentManagementSystem.java | 5 +++++ 3 files changed, 15 insertions(+) create mode 100644 Programs/studentManagement/Student.java create mode 100644 Programs/studentManagement/StudentManager.java create mode 100644 Programs/studentManagement/studentManagementSystem.java diff --git a/Programs/studentManagement/Student.java b/Programs/studentManagement/Student.java new file mode 100644 index 0000000..0eb6d3c --- /dev/null +++ b/Programs/studentManagement/Student.java @@ -0,0 +1,5 @@ +package Programs.studentManagement; + +public class Student { + +} diff --git a/Programs/studentManagement/StudentManager.java b/Programs/studentManagement/StudentManager.java new file mode 100644 index 0000000..f086760 --- /dev/null +++ b/Programs/studentManagement/StudentManager.java @@ -0,0 +1,5 @@ +package Programs.studentManagement; + +public class StudentManager { + +} diff --git a/Programs/studentManagement/studentManagementSystem.java b/Programs/studentManagement/studentManagementSystem.java new file mode 100644 index 0000000..04ca3f6 --- /dev/null +++ b/Programs/studentManagement/studentManagementSystem.java @@ -0,0 +1,5 @@ +package Programs.studentManagement; + +public class studentManagementSystem { + +} From 2f47b0a915e2a7d42d34b55fb22e9f261796ac43 Mon Sep 17 00:00:00 2001 From: 123EFD Date: Mon, 3 Nov 2025 18:37:20 +0800 Subject: [PATCH 2/5] Implement Student and StudentManager classes with validation and management methods; enhance studentManagementSystem for user interaction and CRUD operations. --- Programs/studentManagement/Student.java | 102 +++++++- .../studentManagement/StudentManager.java | 81 +++++- .../studentManagementSystem.java | 235 ++++++++++++++++++ 3 files changed, 415 insertions(+), 3 deletions(-) diff --git a/Programs/studentManagement/Student.java b/Programs/studentManagement/Student.java index 0eb6d3c..fdda827 100644 --- a/Programs/studentManagement/Student.java +++ b/Programs/studentManagement/Student.java @@ -1,5 +1,103 @@ +//student data model package Programs.studentManagement; +import java.util.regex.Matcher; +import java.util.regex.Pattern; public class Student { - -} + //private accessed via getter setter + private String id; + private String name; + private int age; + private String faculty; + private String email; + + /*Email verification regex: + * Basic format: + * ^: Asserts the start of the string. + * [a-zA-Z0-9_+&*-]+: Matches one or more alphanumeric characters, plus _, +, &, *, and - for the local part (username) of the email. + (?:\\.[a-zA-Z0-9_+&*-]+)*: Optionally matches additional parts of the local part, separated by a dot (.). The (?: ... ) creates a non-capturing group. + @: Matches the literal @ symbol. + (?:[a-zA-Z0-9-]+\\.)+: Matches one or more domain components, each consisting of alphanumeric characters and hyphens, followed by a dot (.). + [a-zA-Z]{2,7}: Matches the top-level domain (TLD), which must be 2 to 7 alphabetic characters long. This range is a common practice, but specific TLDs can be longer or shorter. + $: Asserts the end of the string. + */ + private static final String EMAIL_FORMAT = "(?=^.{4,40}$)[A-Za-z0-9._%-]+@[A-Za-z0-9.-]+\\.[a-zA-Z]{2,4}$"; //compiles te given regex string into pattern that can be used to reated matcher obj. + private static final Pattern EMAIL_PATTERN = Pattern.compile(EMAIL_FORMAT, Pattern.CASE_INSENSITIVE); + + //No-arg constructor for creating obj tht will be populated later + //E.g: Student s = new Student(); + public Student() {} + + //E.g: Student s = new Student(id, name, age, email, faculty); + public Student(String id, String name, int age, String email, String faculty) { + this.id = id; + this.name = name; + //apply validation for age and email for range and format check + setAge(age); + setEmail(email); + this.age = age; + this.email = email; + this.faculty = faculty; + } + + //getter setup for each field to read values + public String getId() { + return id; + } + + public String getName() { + return name; + } + + public int getAge() { + return age; + } + + public String getEmail() { + return email; + } + + public String getFaculty() { + return faculty; + } + + //setter setup for each field to update values + public void setId(String id ) { + this.id = id; + } + + public void setName(String name) { + this.name = name; + } + + public void setAge(int age) { + if (age<=15 || age>23) { + throw new IllegalArgumentException("Age must be between 15 and 23 for college students."); + } else { + this.age = age; + } + } + + public void setEmail(String email) { + if (email == null) { + throw new IllegalArgumentException("Email must not be null."); + } + String trimmed = email.trim(); //remove leading/trailing whitespace + if (!isBasicEmail(trimmed)) { + throw new IllegalArgumentException("Email format is invalid."); + } + this.email = trimmed; + } + + public void setFaculty(String faculty) { + this.faculty = faculty; + } + + private boolean isBasicEmail(String email) { + if ( email == null) return false; + Matcher m = EMAIL_PATTERN.matcher(email); + return m.matches(); + } + + } + diff --git a/Programs/studentManagement/StudentManager.java b/Programs/studentManagement/StudentManager.java index f086760..35427be 100644 --- a/Programs/studentManagement/StudentManager.java +++ b/Programs/studentManagement/StudentManager.java @@ -1,5 +1,84 @@ +//store and manage Student objects package Programs.studentManagement; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; +import java.util.List; + public class StudentManager { - + //HashMap to store students with ID as key for quick lookup + private final Map students = new HashMap<>(); + + //check existence by ID + public boolean studentExists(String id) { + if (id == null) return false; + return students.containsKey(id); + } + + public boolean addStudent(Student s) { + if ( s == null || s.getId() == null || s.getId().trim().isEmpty()) { + throw new IllegalArgumentException("Student or Student ID cannot be null/empty."); + } + String id = s.getId().trim(); + if (students.containsKey(id) || findByEmail(s.getEmail()) != null){ + return false; //student with same ID exists + } + String email = s.getEmail(); + if ( email == null) { + return false; + } else { + students.put(id, s); + return true; //successfully added + } + } + + public Student findById(String id) { + if ( id == null) return null; + return students.get(id); + } + + //Linear search for email.Normalizes by trimming and lower-casting + public Student findByEmail(String email) { + if ( email == null) return null; + String norm = normalizedEmail(email); + for (Student s : students.values()) { + String sEmail = normalizedEmail(s.getEmail()); + if (sEmail != null && norm.equals(normalizedEmail(sEmail))) { + return s; + } + } + return null; + } + + public boolean updateStudent(Student updated) { + if ( updated == null || updated.getId() == null ) { + throw new IllegalArgumentException("Student and id must not be empty."); + } + String id = updated.getId(); + if (!students.containsKey(id)) { + return false; + } + Student updatedEmail = findByEmail(updated.getEmail()); + if (updatedEmail != null && !updatedEmail.getId().equals(id)) { + return false; //another student has this email + } + students.put(id, updated); + return true; + } + + public boolean deleteStudents(String id) { + if ( id == null ) return false; + return students.remove(id) != null; + } + + public List listAll() { + return new ArrayList<>(students.values()); + } + + //helpers method + private String normalizedEmail(String email) { + if ( email == null) return null; + return email.trim().toLowerCase(); + } } diff --git a/Programs/studentManagement/studentManagementSystem.java b/Programs/studentManagement/studentManagementSystem.java index 04ca3f6..36742e5 100644 --- a/Programs/studentManagement/studentManagementSystem.java +++ b/Programs/studentManagement/studentManagementSystem.java @@ -1,5 +1,240 @@ package Programs.studentManagement; +import java.util.List; +import java.util.Scanner; public class studentManagementSystem { + private final StudentManager manager = new StudentManager();//CRUD + private final Scanner scanner = new Scanner(System.in); + + public static void main(String[] args) { + /*run method contain loop that shows the menu and processes user choices*/ + new studentManagementSystem().run(); + } + + public void run() { + boolean running = true; + while (running) { + printMenu(); + System.out.print("Choose option: "); + String choice = scanner.nextLine().trim(); + switch (choice) { + case "1": + handleAdd(); + break; + case "2": + handleView(); + break; + case "3": + handleUpdate(); + break; + case "4": + handleDelete(); + break; + case "5": + handleList(); + break; + case "6": + running = false; + break; + + default: System.out.println("Unknown option, try again"); + } + } + scanner.close(); + } + + private void printMenu() { + System.out.println("\n--- Student Management ---"); + System.out.println("1. Add Student"); + System.out.println("2. View Student by ID"); + System.out.println("3. Update Student"); + System.out.println("4. Delete Student"); + System.out.println("5. List All Students"); + System.out.println("6. Exit"); + } + + //helper input method for non-empty strings and integers + //promptNonEmpty ensure non-empty input (id, name) + private String promptNonEmpty(String label, boolean required, String defaultValue) { + while (true) { + if (required) { + System.out.print(label + " "); + } else { + System.out.print(label + " [" + (defaultValue == null ? "" : defaultValue) + "]: "); + } + + String input = scanner.nextLine(); + if (input == null || input.trim().isEmpty()) { + return defaultValue; + } + return input.trim(); + } + } + + //loops for integer validation + private int promptInt(String label, boolean required , Integer defaultValue) { + while (true) { + if (required) { + System.out.print(label + ": "); + } else { + System.out.print(label + " [" + defaultValue + "]: "); + } + + String input = scanner.nextLine(); + if (input == null) input = ""; + input = input.trim(); + + if (input.isEmpty() && !required) { + return defaultValue; + } else if(required && input.isEmpty()) { + System.out.println(label + " must not be empty."); + continue; + } + + try { + return Integer.parseInt(input); + //convert a string to numeric type but dont have appropriate format(subclass of IllegalArgumentException) + } catch (NumberFormatException e) { + System.out.println("Error: Cannot parse ' " + label + " ' to an integer."); + System.out.println(e.getMessage()); + } + } + } + + private Student collectStudentInput(String suggestedId, boolean requiredId) { + String id; + String name = promptNonEmpty("Name: ", true, null); + int age = promptInt("Age ", true, null); + String email = promptNonEmpty("Email: ", true, null); + String faculty = promptNonEmpty("Faculty: ", true, null); + + if (suggestedId != null && !suggestedId.trim().isEmpty()) { + id = promptNonEmpty("Sugessted ID: ", false, suggestedId); + } else if (requiredId){ + id = promptNonEmpty("ID: ", true, null); + } else { + id = promptNonEmpty("ID", false, null); + } + + if ( id == null || id.trim().isEmpty()) return null; + + if (manager.findById(id) != null) { + System.out.println("Student with this id already exists."); + return null; + } else if (manager.findByEmail(email) != null) { + System.out.println("Student with this email already exists."); + return null; + } + + try { + return new Student(id, name, age, email, faculty); + } catch (IllegalArgumentException ex) { + System.out.println("Invalid Input: " + ex.getMessage()); + return null; + } + } + + private void handleAdd() { + /*System.out.println("--- Add Student ---"); + Student s = collectStudentInput(null, true); + if (s== null) return; + boolean added = manager.addStudent(s); + System.out.println(added ? "Student added." : "Add failed.");*/ + handleAdd((String) null); + } + + private void handleAdd(String suggestedId) { + System.out.println("--- Add Student ---"); + Student s = collectStudentInput(suggestedId, true); + if (s == null) return; + boolean added = manager.addStudent(s); + System.out.println(added ? "Student added." : "Add failed."); + } + + private void handleView() { + System.out.println("--View Student--"); + String id = promptNonEmpty("ID ", true, null); + Student s = manager.findById(id); + if (s == null) { + System.out.println("Student not found."); + } else { + printStudent(s); + } + } + + private void handleUpdate() { + System.out.println("--- Update Student ---"); + String id = promptNonEmpty("ID of student to update", true, null); + Student existing = manager.findById(id); + if (existing == null) { + System.out.println("Student not found."); + String answer = promptNonEmpty("Do you want to add a new student with this ID? (y/n)", true, null); + if (answer.equalsIgnoreCase("y") || answer.equalsIgnoreCase("Y")){ + // Optionally pre-fill ID into the add flow: + // You can either set a field or modify handleAdd to accept a prefilled ID. + // For simplicity, call handleAdd() and let the user enter the ID again, + // or modify handleAdd to accept an optional suggestedId parameter. + handleAdd(id); + } else { + System.out.println("Update cancelled."); + } + + return; + } + + String name = promptNonEmpty("Name", false, existing.getName()); + int age = promptInt("Age", false, existing.getAge()); + String email = promptNonEmpty("Email", false, existing.getEmail()); + String faculty = promptNonEmpty("Faculty", false, existing.getFaculty()); + + if (!normalized(email).equals(normalized(existing.getEmail()))) { + Student emailOwner = manager.findByEmail(email); + if (emailOwner != null && !emailOwner.getId().equals(id)) { + System.out.println("Another student with this email already exists. Update cancelled."); + return; + } + } + + try { + Student updated = new Student(id, name, age, email, faculty); + boolean updateCompleted = manager.updateStudent(updated); + System.out.println(updateCompleted ? "Student updated." : "Updated failed"); + } catch (IllegalArgumentException ex) { + System.out.println("Invalid Input: " + ex.getMessage()); + } + } + + private void handleDelete() { + System.out.println("---Delete Student---"); + String id = promptNonEmpty("ID", false, null); + boolean deleted = manager.deleteStudents(id); + System.out.println(deleted ? "Student deleted." : "Student not found."); + } + + private void handleList() { + System.out.println("---All Students---"); + List allStudents = manager.listAll(); + if (allStudents.isEmpty()) { + System.out.println("No students available"); + return; + } + for (Student s : allStudents) { + printStudent(s); + System.out.println("-------------------"); + } + } + + private void printStudent(Student s) { + System.out.println("ID" + s.getId()); + System.out.println("Name: " + s.getName()); + System.out.println("Age: " + s.getAge()); + System.out.println("Email: " + s.getEmail()); + System.out.println("Faculty: " + s.getFaculty()); + } + + //helper sames as manager + private String normalized(String value) { + return value == null ? "" : value.trim().toLowerCase(); + } } From 13846b54e1b16ea4e6eeabfc4b0492f13a8145b1 Mon Sep 17 00:00:00 2001 From: 123EFD Date: Mon, 3 Nov 2025 18:55:34 +0800 Subject: [PATCH 3/5] Refactor email validation in Student class: update regex for email format and enforce gmail.com domain requirement; remove redundant isBasicEmail method. --- Programs/studentManagement/Student.java | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/Programs/studentManagement/Student.java b/Programs/studentManagement/Student.java index fdda827..4bcb288 100644 --- a/Programs/studentManagement/Student.java +++ b/Programs/studentManagement/Student.java @@ -21,7 +21,7 @@ public class Student { [a-zA-Z]{2,7}: Matches the top-level domain (TLD), which must be 2 to 7 alphabetic characters long. This range is a common practice, but specific TLDs can be longer or shorter. $: Asserts the end of the string. */ - private static final String EMAIL_FORMAT = "(?=^.{4,40}$)[A-Za-z0-9._%-]+@[A-Za-z0-9.-]+\\.[a-zA-Z]{2,4}$"; //compiles te given regex string into pattern that can be used to reated matcher obj. + private static final String EMAIL_FORMAT = "(?=^.{4,40}$)[A-Za-z0-9._%+\\-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,7}$";; //compiles te given regex string into pattern that can be used to reated matcher obj. private static final Pattern EMAIL_PATTERN = Pattern.compile(EMAIL_FORMAT, Pattern.CASE_INSENSITIVE); //No-arg constructor for creating obj tht will be populated later @@ -35,8 +35,6 @@ public Student(String id, String name, int age, String email, String faculty) { //apply validation for age and email for range and format check setAge(age); setEmail(email); - this.age = age; - this.email = email; this.faculty = faculty; } @@ -83,21 +81,20 @@ public void setEmail(String email) { throw new IllegalArgumentException("Email must not be null."); } String trimmed = email.trim(); //remove leading/trailing whitespace - if (!isBasicEmail(trimmed)) { + Matcher m = EMAIL_PATTERN.matcher(trimmed); + if (!m.matches()) { throw new IllegalArgumentException("Email format is invalid."); } + // Enforce gmail.com domain to catch typos like "@gmail.xom" + String lower = trimmed.toLowerCase(); + if (!lower.endsWith("@gmail.com")) { + throw new IllegalArgumentException("Only gmail.com address are accepted."); + } this.email = trimmed; } public void setFaculty(String faculty) { this.faculty = faculty; } - - private boolean isBasicEmail(String email) { - if ( email == null) return false; - Matcher m = EMAIL_PATTERN.matcher(email); - return m.matches(); - } - } From a91105778df5808f7db331cca52dd5540ea2b6de Mon Sep 17 00:00:00 2001 From: 123EFD Date: Sat, 15 Nov 2025 21:15:00 +0800 Subject: [PATCH 4/5] Enhance student management: add duplicate name checks in add and update operations; normalize ID and name inputs. --- .../studentManagement/StudentManager.java | 40 ++++++++++++++++++- .../studentManagementSystem.java | 22 +++++++--- 2 files changed, 55 insertions(+), 7 deletions(-) diff --git a/Programs/studentManagement/StudentManager.java b/Programs/studentManagement/StudentManager.java index 35427be..b60ce34 100644 --- a/Programs/studentManagement/StudentManager.java +++ b/Programs/studentManagement/StudentManager.java @@ -13,7 +13,7 @@ public class StudentManager { //check existence by ID public boolean studentExists(String id) { if (id == null) return false; - return students.containsKey(id); + return students.containsKey(id.trim()); } public boolean addStudent(Student s) { @@ -24,6 +24,12 @@ public boolean addStudent(Student s) { if (students.containsKey(id) || findByEmail(s.getEmail()) != null){ return false; //student with same ID exists } + + //duplicate name (case-insensitive) + if (s.getName() != null && findByName(s.getName()) != null) { + return false; + } + String email = s.getEmail(); if ( email == null) { return false; @@ -31,6 +37,7 @@ public boolean addStudent(Student s) { students.put(id, s); return true; //successfully added } + } public Student findById(String id) { @@ -51,6 +58,20 @@ public Student findByEmail(String email) { return null; } + public Student findByName(String name) { + if ( name == null) return null; + String norm = normalizedName(name); + if (norm == null) return null; + for (Student s : students.values()) { + String sName = s.getName(); + if (sName != null && norm.equals(normalizedName(sName))) { + return s; + } + } + return null; + + } + public boolean updateStudent(Student updated) { if ( updated == null || updated.getId() == null ) { throw new IllegalArgumentException("Student and id must not be empty."); @@ -60,9 +81,19 @@ public boolean updateStudent(Student updated) { return false; } Student updatedEmail = findByEmail(updated.getEmail()); + if (updatedEmail != null && !updatedEmail.getId().equals(id)) { - return false; //another student has this email + return false; //another stude + } + + //name conflict nt has this email + if (updated.getName() != null) { + Student ownName = findByName(updated.getName()); + if (ownName != null && !ownName.getId().equals(id)) { + return false; + } } + students.put(id, updated); return true; } @@ -81,4 +112,9 @@ private String normalizedEmail(String email) { if ( email == null) return null; return email.trim().toLowerCase(); } + + public String normalizedName(String name) { + if (name == null) return null; + return name.trim().toLowerCase(); + } } diff --git a/Programs/studentManagement/studentManagementSystem.java b/Programs/studentManagement/studentManagementSystem.java index 36742e5..6a0348a 100644 --- a/Programs/studentManagement/studentManagementSystem.java +++ b/Programs/studentManagement/studentManagementSystem.java @@ -104,6 +104,11 @@ private int promptInt(String label, boolean required , Integer defaultValue) { private Student collectStudentInput(String suggestedId, boolean requiredId) { String id; String name = promptNonEmpty("Name: ", true, null); + if(manager.findByName(name) != null) { + System.out.println("User cannot have duplicate name."); + return null; + } + int age = promptInt("Age ", true, null); String email = promptNonEmpty("Email: ", true, null); String faculty = promptNonEmpty("Faculty: ", true, null); @@ -137,7 +142,7 @@ private Student collectStudentInput(String suggestedId, boolean requiredId) { private void handleAdd() { /*System.out.println("--- Add Student ---"); Student s = collectStudentInput(null, true); - if (s== null) return; + if (s == null) return; boolean added = manager.addStudent(s); System.out.println(added ? "Student added." : "Add failed.");*/ handleAdd((String) null); @@ -170,10 +175,10 @@ private void handleUpdate() { System.out.println("Student not found."); String answer = promptNonEmpty("Do you want to add a new student with this ID? (y/n)", true, null); if (answer.equalsIgnoreCase("y") || answer.equalsIgnoreCase("Y")){ - // Optionally pre-fill ID into the add flow: - // You can either set a field or modify handleAdd to accept a prefilled ID. - // For simplicity, call handleAdd() and let the user enter the ID again, - // or modify handleAdd to accept an optional suggestedId parameter. + /*Optionally pre-fill ID into the add flow: + You can either set a field or modify handleAdd to accept a prefilled ID. + For simplicity, call handleAdd() and let the user enter the ID again, + or modify handleAdd to accept an optional suggestedId parameter. */ handleAdd(id); } else { System.out.println("Update cancelled."); @@ -183,6 +188,13 @@ private void handleUpdate() { } String name = promptNonEmpty("Name", false, existing.getName()); + if (!normalized(name).equals(normalized(existing.getName()))) { + Student nameOwner = manager.findByName(name); + if (nameOwner != null && !nameOwner.getId().equals(id)) { + System.out.println("Another student with this name already exists. Update cancelled."); + return; + } + } int age = promptInt("Age", false, existing.getAge()); String email = promptNonEmpty("Email", false, existing.getEmail()); String faculty = promptNonEmpty("Faculty", false, existing.getFaculty()); From b7a2508262db62896a376bdb66a26a19caef1955 Mon Sep 17 00:00:00 2001 From: 123EFD Date: Sat, 15 Nov 2025 21:17:57 +0800 Subject: [PATCH 5/5] upload readme.md --- Programs/studentManagement/readme.md | 182 +++++++++++++++++++++++++++ 1 file changed, 182 insertions(+) create mode 100644 Programs/studentManagement/readme.md diff --git a/Programs/studentManagement/readme.md b/Programs/studentManagement/readme.md new file mode 100644 index 0000000..9442dc8 --- /dev/null +++ b/Programs/studentManagement/readme.md @@ -0,0 +1,182 @@ +# Student Management System — Overview & Guide + +This document describes the Student Management System (CLI) included in this project. It explains the concept, learning objectives, system design, validation rules, how to build and run the program, and how to extend or test it. + +--- + +## What is this project? + +A small command-line Student Management System (SMS) used to practice Java programming concepts: object modeling, input handling, validation, collections, and simple business rules. The CLI lets you add, view, update, delete and list students kept in memory. The system enforces basic validations (age, email format) and uniqueness rules (ID, email, and name). + +Files of interest +- Programs/studentManagement/Student.java — student data model, validation logic +- Programs/studentManagement/StudentManager.java — in-memory storage + business rules (add/update/delete/find) +- Programs/studentManagement/studentManagementSystem.java — command line interface (menu, prompts) +- (Optional) Programs/studentManagement/TestEmailValidation.java — small test script demonstrating email validation + +--- + +## Learning objectives + +By reading and running this project you will learn to: +- Model domain data using classes (Student). +- Encapsulate validation logic in setters/constructors. +- Design a simple manager/service (StudentManager) to enforce collection-level invariants (unique id, email, name). +- Build a robust console UI that collects input safely and handles exceptions. +- Normalize and compare strings (case-insensitive name/email comparison). +- Write simple unit-style test scripts for focused behavior (e.g., email validation). +- Reason about tradeoffs: linear search vs indexing for uniqueness checks. + +--- + +## High-level design + +1. Student (data model) + - Fields: id, name, age, email, faculty. + - Validation: + - Age: allowed range (e.g., 15–23). + - Email: syntactic validation via regex and (in current demo) a rule to accept only `@gmail.com` addresses (to demonstrate stricter domain enforcement). + - Validation is performed in setters and invoked by the constructor. + +2. StudentManager (service layer) + - Stores students in a Map keyed by student ID for fast lookup. + - Provides operations: + - addStudent(Student): enforces uniqueness (id, email, name) and returns boolean success. + - updateStudent(Student): ensures updates do not violate uniqueness. + - deleteStudent(id), findById(id), findByEmail(email), findByName(name), listAll(). + - Email and name comparisons are normalized consistently (trim + toLowerCase). + +3. CLI (studentManagementSystem) + - Presents a simple menu loop for CRUD operations. + - Uses prompt helpers to read required or optional inputs. + - Calls StudentManager methods; catches IllegalArgumentException from Student setters and shows friendly messages. + - Prevents duplicate IDs/emails/names before constructing/adding students and during updates. + +--- + +## Business rules enforced + +- ID must be non-null and non-empty. IDs are unique. +- Age must be within the accepted student range (currently 15–23 inclusive/exclusive depending on implementation; follow the code). +- Email must match the configured regex and (in the demo) must end with `@gmail.com`. You can relax or change this rule in `Student.setEmail(...)`. +- Email uniqueness: no two students may share the same email (case-insensitive, trimmed). +- Name uniqueness: exact match after trimming and lower-casing is considered a duplicate (so `Chin` and `CHIN` are duplicates; `Chin Er` is not). + +--- + +## How it works (user flow) + +Add student: +1. Select "Add Student" in the menu. +2. Enter Name, Age, Email, Faculty, and ID (ID may be suggested if coming from an update flow). +3. CLI checks: + - Name uniqueness (case-insensitive). + - ID uniqueness. + - Email uniqueness. +4. If all checks pass, it attempts to build a Student object. The Student constructor/setters validate age and email syntax/domain and will throw an IllegalArgumentException for invalid values. The CLI catches this and prints a message such as `Invalid Input: Email format is invalid.` + +Update student: +1. Select "Update Student", enter the student ID. +2. If ID not found, the CLI offers to add a new student using that ID. +3. When updating, empty input values are interpreted as "keep current". +4. When changing email or name, uniqueness checks prevent conflicts with other students. + +View, Delete, List: +- View by ID prints stored student details. +- Delete removes the student (and the stored email/name consequently become available for new entries). +- List prints all students in the manager. + +--- + +## How to build & run + +From your project root (where `Programs/` is a subdirectory) using the JDK: + +1. Compile: + javac Programs/studentManagement/*.java + +2. Run the CLI: + java Programs.studentManagement.studentManagementSystem + +3. (Optional) Run the email validation test: + java Programs.studentManagement.TestEmailValidation + +Notes: +- The code uses only the Java standard library. +- The program stores students in memory only; data is lost when the program exits. To persist data add file I/O or a database layer. + +--- + +## Example runs + +1) Add with invalid gmail TLD typo: +- Input: email = `user@gmail.xom` +- Behavior: Student constructor rejects the email and CLI prints: + `Invalid Input: Only gmail.com address are accepted.` + +2) Duplicate name behavior: +- Add Student A: Name = `Chin` +- Attempt to add Student B: Name = `CHIN` → CLI prints: + `User cannot have duplicate name.` (because comparison is case-insensitive) + +3) Update prevents conflicts: +- When changing an email or name, the update flow checks whether another student already owns that email/name and aborts the update with a message when a conflict exists. + +--- + +## Where the key validations live + +- Email format & domain: `Programs/studentManagement/Student.java` → `setEmail(...)` +- Age validation: `Student.setAge(...)` +- Uniqueness checks (ID, email, name): `Programs/studentManagement/StudentManager.java` +- CLI orchestration & friendly messages: `Programs/studentManagement/studentManagementSystem.java` + +--- + +## How to change the email rule + +- Currently the demo enforces gmail-only addresses. To allow other domains: + - Edit `Student.setEmail(...)`. + - Either remove the domain check (so only regex is used) or replace the domain check with a TLD whitelist or a different policy. + - Make sure `StudentManager.findByEmail(...)` normalizes the email the same way as `Student.setEmail(...)` (trim and lower-case) so uniqueness checks are consistent. + +--- + +## Extending the system + +Ideas to extend and practice: +- Persist students to disk (CSV, JSON) and load at startup. +- Replace linear `findByEmail`/`findByName` with secondary indexes (Map) for O(1) lookups. +- Add unit tests (JUnit) for Student validation, StudentManager behaviors, and CLI helpers. +- Add search/filter by faculty or age range. +- Add role-based access or an admin password for destructive operations (delete). +- Improve the CLI to accept command-line arguments or implement a simple REPL command parser. + +--- + +## Testing tips + +- Use `TestEmailValidation.java` or write JUnit tests for: + - valid/invalid emails and domain checks, + - add/update duplicate detection for id/email/name, + - age boundary conditions. + +- Manually test CLI flows: + - Add a student, attempt to add another with same id/email/name (different casing), update to conflicting values, and delete. + +--- + +## Final notes + +This project is intentionally small and instructional. It highlights good practices: +- keep validation close to the data model (`Student`), +- keep collection-level rules in the manager (`StudentManager`), +- keep user interaction logic in the UI layer (`studentManagementSystem`), +- normalize strings consistently for comparisons, +- catch and report validation exceptions in the UI. + +If you’d like, I can: +- Produce a README.md instead of read.md and commit it into the repo, +- Generate a TestNameDuplication.java script to demonstrate name-dup detection, +- Or implement persistent CSV save/load in StudentManager and wire import/export options into the CLI. +