diff --git a/Programs/studentManagement/Student.java b/Programs/studentManagement/Student.java new file mode 100644 index 0000000..4bcb288 --- /dev/null +++ b/Programs/studentManagement/Student.java @@ -0,0 +1,100 @@ +//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,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 + //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.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 + 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; + } + } + diff --git a/Programs/studentManagement/StudentManager.java b/Programs/studentManagement/StudentManager.java new file mode 100644 index 0000000..b60ce34 --- /dev/null +++ b/Programs/studentManagement/StudentManager.java @@ -0,0 +1,120 @@ +//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.trim()); + } + + 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 + } + + //duplicate name (case-insensitive) + if (s.getName() != null && findByName(s.getName()) != null) { + return false; + } + + 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 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."); + } + 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 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; + } + + 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(); + } + + public String normalizedName(String name) { + if (name == null) return null; + return name.trim().toLowerCase(); + } +} 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. + diff --git a/Programs/studentManagement/studentManagementSystem.java b/Programs/studentManagement/studentManagementSystem.java new file mode 100644 index 0000000..6a0348a --- /dev/null +++ b/Programs/studentManagement/studentManagementSystem.java @@ -0,0 +1,252 @@ +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); + 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); + + 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()); + 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()); + + 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(); + } + +}