diff --git a/PetCareScheduler/build.gradle b/PetCareScheduler/build.gradle new file mode 100644 index 0000000..e69de29 diff --git a/PetCareScheduler/src/main/java/Appointment.java b/PetCareScheduler/src/main/java/Appointment.java new file mode 100644 index 0000000..5a67fd1 --- /dev/null +++ b/PetCareScheduler/src/main/java/Appointment.java @@ -0,0 +1,67 @@ +import java.time.LocalDate; +import java.time.LocalTime; + +import static java.util.Objects.requireNonNull; + +public final class Appointment { + + private Type type; + private LocalDate date; + private LocalTime time; + private String notes; + + public Appointment(Type type, LocalDate date, LocalTime time, String notes) { + this.type = requireNonNull(type, "Type must not be null"); + this.date = requireNonNull(date, "date must not be null"); + this.time = requireNonNull(time, "time must not be null"); + this.notes = requireNonNull(notes, "note must not be null"); + } + + public Type getType() { + return type; + } + + public LocalDate getDate() { + return date; + } + + public LocalTime getTime() { + return time; + } + + public String getNotes() { + return notes; + } + + public void setType(Type type) { + this.type = requireNonNull(type, "Type must not be null"); + } + + public void setDate(LocalDate date) { + this.date = requireNonNull(date, "date must not be null"); + } + + public void setTime(LocalTime time) { + this.time = requireNonNull(time, "time must not be null"); + } + + public void setNotes(String notes) { + this.notes = requireNonNull(notes, "note must not be null"); + } + + @Override + public String toString() { + return "Appointment{" + + "type=" + type + + ", date=" + date + + ", time=" + time + + ", notes='" + notes + '\'' + + '}'; + } + + public enum Type { + VET_VISIT, + VACCINATION, + GROOMING + } +} \ No newline at end of file diff --git a/PetCareScheduler/src/main/java/Pet.java b/PetCareScheduler/src/main/java/Pet.java new file mode 100644 index 0000000..8555c34 --- /dev/null +++ b/PetCareScheduler/src/main/java/Pet.java @@ -0,0 +1,110 @@ +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import static java.util.Objects.requireNonNull; + +public final class Pet { + + private String id; + private String name; + private String owner; + private String contactInformation; + private String breed; + private int age; + private LocalDate registrationDate; + private final List appointments = new ArrayList<>(); + + public Pet(String id, String name, String owner, String contactInformation, String breed, int age, LocalDate registrationDate) { + + if (age < 0) throw new IllegalArgumentException("Age must be over 0"); + + this.age = age; + this.id = requireNonNull(id, "id must not be null"); + this.name = requireNonNull(name, "Name must not be null"); + this.owner = requireNonNull(owner, "Owner must not be null"); + this.contactInformation = requireNonNull(contactInformation, "Contact information must not be null"); + this.breed = requireNonNull(breed, "Breed must not be null"); + this.registrationDate = requireNonNull(registrationDate, "Registration date must not be null"); + } + + public void addAppointment(Appointment appointment) { + this.appointments.add(requireNonNull(appointment, "Appointment must not be null")); + } + + public String getId() { + return id; + } + + public String getName() { + return name; + } + + public String getOwner() { + return owner; + } + + public String getContactInformation() { + return contactInformation; + } + + public String getBreed() { + return breed; + } + + public int getAge() { + return age; + } + + public LocalDate getRegistrationDate() { + return registrationDate; + } + + public List getAppointments() { + return Collections.unmodifiableList(appointments); + } + + public void setId(String id) { + this.id = requireNonNull(id, "id must not be null"); + } + + public void setName(String name) { + this.name = requireNonNull(name, "Name must not be null"); + } + + public void setOwner(String owner) { + this.owner = requireNonNull(owner, "Owner must not be null"); + } + + public void setContactInformation(String contactInformation) { + this.contactInformation =requireNonNull(contactInformation, "Contact information must not be null"); + } + + public void setBreed(String breed) { + this.breed = requireNonNull(breed, "Breed must not be null"); + } + + public void setAge(int age) { + if (age < 0) throw new IllegalArgumentException("Age must be over 0"); + this.age = age; + } + + public void setRegistrationDate(LocalDate registrationDate) { + this.registrationDate = requireNonNull(registrationDate, "Registration date must not be null"); + } + + @Override + public String toString() { + return "Pet{" + + "id='" + id + '\'' + + ", name='" + name + '\'' + + ", owner='" + owner + '\'' + + ", contactInformation='" + contactInformation + '\'' + + ", breed='" + breed + '\'' + + ", age=" + age + + ", registrationDate=" + registrationDate + + ", appointments=" + appointments + + '}'; + } +} \ No newline at end of file diff --git a/PetCareScheduler/src/main/java/PetCareScheduler.java b/PetCareScheduler/src/main/java/PetCareScheduler.java new file mode 100644 index 0000000..a052970 --- /dev/null +++ b/PetCareScheduler/src/main/java/PetCareScheduler.java @@ -0,0 +1,379 @@ +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.PrintWriter; +import java.nio.file.Files; +import java.nio.file.OpenOption; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.time.DayOfWeek; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.time.temporal.TemporalAdjusters; +import java.util.*; +import java.util.stream.Collectors; + +/** + * Pet Care Scheduler + * + * @author John Ahlroos / 2025 + * + * Allows the user to manage Pet care appointments + */ +public class PetCareScheduler { + + private final List pets; + private final String dataFileName; + private final Scanner scanner; + + public PetCareScheduler(Scanner scanner, String dataFileName) { + this.dataFileName = dataFileName; + this.scanner = scanner; + this.pets = new ArrayList<>(); + } + + public static void main(String[] args) { + + System.out.println("PET CARE SCHEDULER v1.0"); + System.out.println("================================================================="); + System.out.println(); + + try (Scanner scanner = new Scanner(System.in)) { + + PetCareScheduler scheduler = new PetCareScheduler(scanner, "data.txt"); + + boolean loaded = scheduler.loadPetsFromFile(); + if (!loaded) { + System.err.println("Failed to load data file."); + return; + } + + System.out.println(); + + + boolean exited = false; + while (!exited) { + System.out.println("Actions:"); + System.out.println("\t1: Register Pet"); + System.out.println("\t2: Schedule Appointment"); + System.out.println("\t3: List Pets"); + System.out.println("\t4: Print Report"); + System.out.println("\t5: Save & Exit"); + System.out.println(); + System.out.print("Action: "); + + try { + int action = Integer.parseInt(scanner.nextLine()); + switch (action) { + case 1: + scheduler.registerPet(); + break; + case 2: + scheduler.addAppointment(); + break; + case 3: + scheduler.list(); + break; + case 4: + scheduler.report(); + break; + case 5: + boolean saved = scheduler.savePetsToFile(); + if (!saved) { + System.err.println("Failed to save data file! Latest changes were lost."); + System.out.print("Exit anyway (yes/no)?"); + String option = scanner.nextLine(); + exited = option.equals("yes"); + } else { + exited = true; + } + break; + default: + System.out.println("Not a valid option!"); + } + } catch (NumberFormatException nfe) { + System.err.println("Action must be a number!"); + } + } + } catch (RuntimeException re) { + System.err.println(re.getMessage()); + } + } + + /** + * Register a new pet with the system + */ + public void registerPet() { + System.out.println("================================================================="); + + String id = UUID.randomUUID().toString(); + LocalDate registration = LocalDate.now(); + System.out.print("Name: "); + String name = scanner.nextLine(); + if (name.isBlank()) { + System.err.println("Name must not be blank!"); + return; + } + + System.out.print("Owner: "); + String owner = scanner.nextLine(); + if (owner.isBlank()) { + System.err.println("Owner must not be blank!"); + return; + } + + System.out.print("Contact Information: "); + String contact = scanner.nextLine(); + System.out.print("Breed: "); + String breed = scanner.nextLine(); + System.out.print("Age: "); + int age; + try{ + age = Integer.parseInt(scanner.nextLine()); + } catch (NumberFormatException nfe) { + System.err.println("Not a valid number!"); + return; + } + + Pet pet = new Pet(id,name,owner,contact,breed,age,registration); + pets.add(pet); + + System.out.printf("New pet %s registered with id %s%n", pet.getName(), pet.getId()); + System.out.println("================================================================="); + System.out.println(); + } + + /** + * Add a new appointment for a pet + */ + public void addAppointment() { + System.out.println("================================================================="); + System.out.print("Pet ID: "); + String petId = scanner.nextLine(); + + Optional pet = pets.stream().filter(p -> p.getId().equals(petId)).findFirst(); + if (pet.isEmpty()) { + System.err.printf("No Pet with id %s%n", petId); + return; + } + + System.out.printf("Type (%s): ", Arrays.stream(Appointment.Type.values()).map(Enum::name).collect(Collectors.joining("|"))); + String typeStr = scanner.nextLine().toUpperCase(); + if (Arrays.stream(Appointment.Type.values()).map(Enum::name).noneMatch(t -> t.equals(typeStr))) { + System.err.printf("%s is not a valid appointment type%n", typeStr); + return; + } + + System.out.print("Date (dd/mm/yyyy): "); + String dateStr = scanner.nextLine(); + LocalDate date; + try { + date = LocalDate.parse(dateStr, DateTimeFormatter.ofPattern("dd/MM/yyyy")); + } catch (DateTimeParseException dtpe) { + System.err.printf("%s is not a valid date%n", dateStr); + return; + } + + System.out.print("Time (hh:mm): "); + String timeStr = scanner.nextLine(); + LocalTime time; + try { + time = LocalTime.parse(timeStr, DateTimeFormatter.ofPattern("HH:mm")); + } catch (DateTimeParseException dtpe) { + System.err.printf("%s is not a valid time%n", timeStr); + return; + } + + System.out.print("Notes: "); + String notes = scanner.nextLine(); + + Appointment appointment = new Appointment(Appointment.Type.valueOf(typeStr), date, time, notes); + pet.get().addAppointment(appointment); + + System.out.printf("Appointment added for pet %s at %s%n", pet.get().getName(), appointment.getDate().atTime(appointment.getTime())); + System.out.println("================================================================="); + System.out.println(); + } + + /** + * List all pets in the system + */ + public void list() { + System.out.println("================================================================="); + System.out.println(); + + for (Pet pet: pets) { + System.out.printf("Id:\t\t\t%s%n", pet.getId()); + System.out.printf("Name:\t\t%s%n", pet.getName()); + System.out.printf("Owner:\t\t%s%n", pet.getOwner()); + System.out.printf("Contact:\t%s%n", pet.getContactInformation()); + System.out.printf("Breed:\t\t%s%n", pet.getBreed()); + System.out.printf("Age:\t\t%d%n", pet.getAge()); + System.out.printf("Registered:\t%s%n", pet.getRegistrationDate()); + + System.out.println("Upcoming Appointments:"); + for (Appointment appointment : pet.getAppointments()) { + if (appointment.getDate().atTime(appointment.getTime()).isAfter(LocalDateTime.now())) { + System.out.printf("\t- %s %s %s%n", + appointment.getDate().atTime(appointment.getTime()), + appointment.getType(), + appointment.getNotes()); + } + } + + System.out.println("Past Appointments:"); + for (Appointment appointment : pet.getAppointments()) { + if (appointment.getDate().atTime(appointment.getTime()).isBefore(LocalDateTime.now())) { + System.out.printf("\t- %s %s %s%n", + appointment.getDate().atTime(appointment.getTime()), + appointment.getType(), + appointment.getNotes()); + } + } + + System.out.println(); + } + + System.out.println("================================================================="); + System.out.println(); + } + + /** + * Generate a report of future and past appointments + */ + public void report() { + System.out.println("================================================================="); + System.out.println("Next Week: "); + LocalDateTime nextMonday = LocalDate.now().with(TemporalAdjusters.next(DayOfWeek.MONDAY)).atTime(LocalTime.MIDNIGHT); + LocalDateTime nextSunday = nextMonday.with(TemporalAdjusters.next(DayOfWeek.MONDAY)); + for (Pet pet : pets) { + for (Appointment appointment : pet.getAppointments()) { + LocalDateTime dt = appointment.getDate().atTime(appointment.getTime()); + if (dt.isAfter(nextMonday) && dt.isBefore(nextSunday)) { + System.out.printf("%s: %s %s %s%n", pet.getId(), dt, pet.getName(), pet.getOwner()); + } + } + } + + System.out.println(); + + System.out.println("Due Visits: "); + LocalDateTime dueDate = LocalDate.now().minusMonths(6).atTime(LocalTime.MIDNIGHT); + for (Pet pet : pets) { + for (Appointment appointment : pet.getAppointments()) { + LocalDateTime dt = appointment.getDate().atTime(appointment.getTime()); + if (dt.isBefore(dueDate)) { + System.out.printf("%s: %s %s %s%n", pet.getId(), dt, pet.getName(), pet.getOwner()); + } + } + } + + System.out.println("================================================================="); + System.out.println(); + } + + /** + * Load pets from file + */ + private boolean loadPetsFromFile() { + File dataFile = new File(".", dataFileName); + if (!dataFile.exists()) { + System.out.println("No data file exists, loading empty pet database"); + return true; + } + + try (BufferedReader reader = Files.newBufferedReader(dataFile.toPath())) { + String line; + while((line = reader.readLine()) != null) { + StringTokenizer petDeserializer = new StringTokenizer(line, "|"); + Pet pet = new Pet( + petDeserializer.nextToken(), // id + petDeserializer.nextToken(), // name + petDeserializer.nextToken(), // owner + petDeserializer.nextToken(), // contactInformation + petDeserializer.nextToken(), // breed + Integer.parseInt(petDeserializer.nextToken()), //age + LocalDate.parse(petDeserializer.nextToken(), DateTimeFormatter.ISO_DATE) // registrationDate + ); + + while((line = reader.readLine()) != null) { + if (line.isEmpty()) { + break; + } + + StringTokenizer appointmentDeserializer = new StringTokenizer(line, "|"); + Appointment appointment = new Appointment( + Appointment.Type.valueOf(appointmentDeserializer.nextToken()), // type + LocalDate.parse(appointmentDeserializer.nextToken(), DateTimeFormatter.ISO_DATE), // date + LocalTime.parse(appointmentDeserializer.nextToken(), DateTimeFormatter.ISO_TIME), // time + appointmentDeserializer.hasMoreTokens() ? appointmentDeserializer.nextToken() : "" // notes + ); + pet.addAppointment(appointment); + } + + pets.add(pet); + } + System.out.printf("%d pets loaded successfully from data file %s.%n", pets.size(), dataFileName); + return true; + } catch (IOException | RuntimeException e) { + System.err.printf("Failed to read data file. %s%n", e.getMessage()); + return false; + } + } + + /** + * Save pets to file + */ + private boolean savePetsToFile() { + if (pets == null || pets.isEmpty()) { + System.out.println("No pets to save"); + return true; + } + + File dataFile = new File(".", dataFileName); + + try { + Path parent = dataFile.toPath().getParent(); + if (parent != null) { + Files.createDirectories(parent); + } + } catch (IOException e) { + System.err.println("Failed to create data directory"); + return false; + } + + OpenOption[] fileOptions = new OpenOption[]{ StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING }; + try (PrintWriter writer = new PrintWriter(Files.newBufferedWriter(dataFile.toPath(), fileOptions))) { + for (Pet pet : pets) { + StringJoiner petSerializer = new StringJoiner("|"); + petSerializer.add(Objects.toString(pet.getId(), UUID.randomUUID().toString())); + petSerializer.add(Objects.toString(pet.getName()," ")); + petSerializer.add(Objects.toString(pet.getOwner(), " ")); + petSerializer.add(Objects.toString(pet.getContactInformation(), "None")); + petSerializer.add(Objects.toString(pet.getBreed(), "Unknown")); + petSerializer.add(String.valueOf(pet.getAge())); + petSerializer.add(Optional.ofNullable(pet.getRegistrationDate()).orElse(LocalDate.now()).format(DateTimeFormatter.ISO_DATE)); + writer.println(petSerializer); + + for (Appointment appointment : pet.getAppointments()) { + StringJoiner appointmentSerializer = new StringJoiner("|"); + appointmentSerializer.add(appointment.getType().name()); + appointmentSerializer.add(appointment.getDate().format(DateTimeFormatter.ISO_DATE)); + appointmentSerializer.add(appointment.getTime().format(DateTimeFormatter.ISO_TIME)); + appointmentSerializer.add(appointment.getNotes()); + writer.println(appointmentSerializer); + } + + writer.println(); + } + System.out.printf("%d pets saved successfully to fata file %s.%n", pets.size(), dataFileName); + return true; + } catch (IOException | RuntimeException e) { + System.err.printf("Failed to write to file. %s%n", e.getMessage()); + return false; + } + } +} \ No newline at end of file