diff --git a/.idea/misc.xml b/.idea/misc.xml index c643c23..08a0d19 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,5 +1,8 @@ - + + + + \ No newline at end of file diff --git a/src/main/java/entities/CollaborativeTask.java b/src/main/java/entities/CollaborativeTask.java index a0eae1c..5c62353 100644 --- a/src/main/java/entities/CollaborativeTask.java +++ b/src/main/java/entities/CollaborativeTask.java @@ -254,4 +254,6 @@ public void setDeclinedTeammates(ArrayList declinedTeammates) { this.declinedTeammates = declinedTeammates; } + } + diff --git a/src/main/java/scheduling_ct_screens/EnterTimeAndTask.java b/src/main/java/scheduling_ct_screens/EnterTimeAndTask.java new file mode 100644 index 0000000..172c81e --- /dev/null +++ b/src/main/java/scheduling_ct_screens/EnterTimeAndTask.java @@ -0,0 +1,65 @@ +package scheduling_ct_screens; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionListener; +import java.awt.event.ActionEvent; + +/** + * The View for the Scheduling Collaborative Tasks Use Case + */ + +public class EnterTimeAndTask extends JFrame implements ActionListener { + + ScheduleCTController scheduleCTController; + JTextField taskTitle = new JTextField(15); + JTextField startTime = new JTextField(15); + JTextField endTime = new JTextField(15); + + + public EnterTimeAndTask(ScheduleCTController scheduleCTController) { + + this.scheduleCTController = scheduleCTController; + + + JLabel title = new JLabel("Scheduling Collaborative Tasks"); + title.setAlignmentX(Component.CENTER_ALIGNMENT); + + LabelTextPanel taskInfo = new LabelTextPanel(new JLabel("Enter task title"), taskTitle); + + LabelTextPanel startInfo = new LabelTextPanel(new JLabel("Enter start time"), startTime); + + LabelTextPanel endInfo = new LabelTextPanel(new JLabel("Enter end time"), endTime); + + + JButton submit = new JButton("Submit"); + + JPanel buttons = new JPanel(); + buttons.add(submit); + + submit.addActionListener(this); + + this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); + + JPanel main = new JPanel(); + main.setLayout(new BoxLayout(main, BoxLayout.Y_AXIS)); + + main.add(title); + main.add(taskInfo); + main.add(startInfo); + main.add(endInfo); + main.add(buttons); + this.setContentPane(main); + + this.pack(); + setVisible(true); + } + + // React to button click that results in evt + public void actionPerformed(ActionEvent evt) { +// System.out.println("Click" + evt.getActionCommand()); + scheduleCTController.isConflict(taskTitle.getText(), startTime.getText(), endTime.getText()); + + + } +} diff --git a/src/main/java/scheduling_ct_screens/LabelTextPanel.java b/src/main/java/scheduling_ct_screens/LabelTextPanel.java new file mode 100644 index 0000000..d6dc19a --- /dev/null +++ b/src/main/java/scheduling_ct_screens/LabelTextPanel.java @@ -0,0 +1,16 @@ +package scheduling_ct_screens; + +import javax.swing.*; + +// Frameworks/Drivers layer + +/** + * UI helper class that creates labeled text panel + */ + +public class LabelTextPanel extends JPanel { + public LabelTextPanel(JLabel label, JTextField textField) { + this.add(label); + this.add(textField); + } +} \ No newline at end of file diff --git a/src/main/java/scheduling_ct_screens/PresentOutput.java b/src/main/java/scheduling_ct_screens/PresentOutput.java new file mode 100644 index 0000000..609433d --- /dev/null +++ b/src/main/java/scheduling_ct_screens/PresentOutput.java @@ -0,0 +1,20 @@ +package scheduling_ct_screens; + +import javax.swing.*; + +public class PresentOutput extends JFrame { + + String message; + + public PresentOutput(String message) { + this.message = message; + + JLabel text = new JLabel(message); + + JPanel main = new JPanel(); + main.setLayout(new BoxLayout(main, BoxLayout.Y_AXIS)); + + main.add(text); + setVisible(true); + } +} diff --git a/src/main/java/scheduling_ct_screens/ScheduleCTController.java b/src/main/java/scheduling_ct_screens/ScheduleCTController.java new file mode 100644 index 0000000..d0f576a --- /dev/null +++ b/src/main/java/scheduling_ct_screens/ScheduleCTController.java @@ -0,0 +1,40 @@ +package scheduling_ct_screens; +import entities.StudentUser; +import entities.Task; +import scheduling_ct_use_case.*; + +import java.util.HashMap; + +/** + * Controller for the Scheduling Collaborative Tasks Use Case + * Triggers the interactor + */ + +public class ScheduleCTController { + + final ScheduleCTInputBoundary scheduleInput; + + private final HashMap hashMap; + + private final StudentUser studentUser; + + public ScheduleCTController(ScheduleCTInputBoundary scheduleInput, HashMap hashMap, StudentUser studentUser) { + this.scheduleInput = scheduleInput; + this.hashMap = hashMap; + this.studentUser = studentUser; + } + + public HashMap getTaskMap() { + return hashMap; + } + + public StudentUser getStudentUser() { + return studentUser; + } + + public ScheduleCTResponseModel isConflict(String taskName, String startTime, String endTime) { + ScheduleCTRequestModel inputData = new ScheduleCTRequestModel(taskName, startTime, endTime, studentUser); + return scheduleInput.schedule(inputData, this.hashMap); + } + +} diff --git a/src/main/java/scheduling_ct_screens/ScheduleCTPresenter.java b/src/main/java/scheduling_ct_screens/ScheduleCTPresenter.java new file mode 100644 index 0000000..a5e0104 --- /dev/null +++ b/src/main/java/scheduling_ct_screens/ScheduleCTPresenter.java @@ -0,0 +1,23 @@ +package scheduling_ct_screens; +import scheduling_ct_use_case.*; + +/** + * Presenter for the Collaborative Scheduling Use Case + * Implements the Output Boundary as part of the dependency inversion + */ + +public class ScheduleCTPresenter implements ScheduleCTOutputBoundary { + + @Override + public ScheduleCTResponseModel prepareNoConflictView(ScheduleCTResponseModel responseModel) { + if ((!responseModel.getIsConflict())) { + responseModel.setDisplayString("Successful input"); + } + return responseModel; + } + + @Override + public ScheduleCTResponseModel prepareFailView(String error) { + throw new SchedulingTimesFailed(error); + } +} diff --git a/src/main/java/scheduling_ct_screens/SchedulingTimesFailed.java b/src/main/java/scheduling_ct_screens/SchedulingTimesFailed.java new file mode 100644 index 0000000..758bd09 --- /dev/null +++ b/src/main/java/scheduling_ct_screens/SchedulingTimesFailed.java @@ -0,0 +1,7 @@ +package scheduling_ct_screens; + +public class SchedulingTimesFailed extends RuntimeException{ + public SchedulingTimesFailed(String error){ + super(error); + } +} diff --git a/src/main/java/scheduling_ct_use_case/ScheduleCTInputBoundary.java b/src/main/java/scheduling_ct_use_case/ScheduleCTInputBoundary.java new file mode 100644 index 0000000..12b8365 --- /dev/null +++ b/src/main/java/scheduling_ct_use_case/ScheduleCTInputBoundary.java @@ -0,0 +1,14 @@ +package scheduling_ct_use_case; + +import entities.Task; +import java.util.HashMap; + +/** + * Input Boundary interface for the Scheduling Collaborative Tasks Use Case + * (inverts dependency from Controller to Interactor) + */ + +public interface ScheduleCTInputBoundary { + + ScheduleCTResponseModel schedule(ScheduleCTRequestModel scheduleCTRequestModel, HashMap hashMap); +} diff --git a/src/main/java/scheduling_ct_use_case/ScheduleCTInteractor.java b/src/main/java/scheduling_ct_use_case/ScheduleCTInteractor.java new file mode 100644 index 0000000..b959a12 --- /dev/null +++ b/src/main/java/scheduling_ct_use_case/ScheduleCTInteractor.java @@ -0,0 +1,304 @@ +package scheduling_ct_use_case; + +import entities.*; + +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.HashMap; + +/** + * Scheduling Collaborative Tasks Use Case Interactor (use case layer) + * Implements business logic on entities + */ + +public class ScheduleCTInteractor implements ScheduleCTInputBoundary { + private final ScheduleCTOutputBoundary scheduleCTOutputBoundary; + + public ScheduleCTInteractor(ScheduleCTOutputBoundary scheduleCTOutputBoundary) { + this.scheduleCTOutputBoundary = scheduleCTOutputBoundary; + } + + /** + * The main controller of this interactor that calls the helper methods + */ + @Override + public ScheduleCTResponseModel schedule(ScheduleCTRequestModel requestModel, HashMap hashMap) { + // returns that this has conflict + // otherwise will automatically schedule and return a success view + + + CollaborativeTask task = getTaskObjectFromName(requestModel.getTaskName(), hashMap); + + if (requestModel.getStudentUser() != task.getLeader()) { + return scheduleCTOutputBoundary.prepareFailView("User is not the leader. " + + "You don't have scheduling access"); + } + + ArrayList users = task.getTeammates(); + users.add(task.getLeader()); + + ArrayList unavailableUsers = new ArrayList<>(); + + LocalDateTime startTime = convertStringToLocalDateTime(requestModel.getStartTime()); + LocalDateTime endTime = convertStringToLocalDateTime(requestModel.getEndTime()); + + for (StudentUser user : users) { + ArrayList userTasks = getTaskFromId(user, hashMap); + if (!isUserAvailableAtDateTime(user, userTasks, startTime, endTime)) { + unavailableUsers.add(user); + } + } + if (!unavailableUsers.isEmpty()) { + if (task.getFrequency().equals("")) { + ArrayList> oneDate = new ArrayList<>(); + ArrayList theDate = new ArrayList<>(); + theDate.add(startTime); + theDate.add(endTime); + oneDate.add(theDate); + task.setTimeBlocks(oneDate); + } else { + ArrayList> dates = getDates(task.getFrequency(), startTime, endTime, task.getDeadline()); + task.setTimeBlocks(dates); + } + ScheduleCTResponseModel scheduleCTResponseModel = new ScheduleCTResponseModel(false); + return scheduleCTOutputBoundary.prepareNoConflictView(scheduleCTResponseModel); + } else { + ScheduleCTResponseModel scheduleCTResponseModel = new ScheduleCTResponseModel(true); + return scheduleCTOutputBoundary.prepareFailView("Cannot schedule due to conflict"); + } + } + + /** + * Finds all the dates to meet up given the frequency of a task + * @param frequency - either "daily", "weekly" or "monthly" + * @param startTime - the start time of the time block + * @param endTime - the end time of the time block + * @param deadline - the deadline of the task + * @return an array list of array lists of local date time (i.e. the dates to meet up) + */ + public ArrayList> getDates(String frequency, LocalDateTime startTime, LocalDateTime endTime, LocalDateTime deadline) { + ArrayList> times = new ArrayList<>(); + ArrayList initialTime = new ArrayList<>(); + initialTime.add(startTime); + initialTime.add(endTime); + times.add(initialTime); + + LocalDateTime currStart = startTime; + LocalDateTime currEnd = endTime; + + switch (frequency) { + case "daily": + do { + ArrayList date = new ArrayList<>(); + currStart = currStart.plusDays(1); + currEnd = currEnd.plusDays(1); + + date.add(currStart); + date.add(currEnd); + times.add(date); + } while (endTime.isBefore(deadline)); + break; + case "weekly": + do { + ArrayList date = new ArrayList<>(); + currStart = currStart.plusWeeks(1); + currEnd = currEnd.plusWeeks(1); + + date.add(currStart); + date.add(currEnd); + times.add(date); + } while (endTime.isBefore(deadline)); + break; + case "monthly": + do { + ArrayList date = new ArrayList<>(); + currStart = currStart.plusMonths(1); + currEnd = currEnd.plusMonths(1); + + date.add(currStart); + date.add(currEnd); + times.add(date); + } while (endTime.isBefore(deadline)); + break; + } + return times; + } + + /** + * Check if a user is available at a fixed date time + * @param user - the student user + * @param tasks - an array list of a user's tasks + * @param start - the start time of the time block + * @param end -the end time of the time block + * @return whether or not the user is available at this date time + */ + public boolean isUserAvailableAtDateTime(StudentUser user, ArrayList tasks, LocalDateTime start, + LocalDateTime end) { + // assume there's a method in TaskUseCase that gets all the tasks a student has + + boolean is_task_free = true; + boolean is_working_hours_free = true; + for (Task task : tasks) { + + if (task instanceof Event) { + if (((Event) task).getTimeBlock() != null) { + LocalDateTime[] timeBlock = ((Event) task).getTimeBlock(); + LocalDateTime timeBlockStart = timeBlock[0]; + LocalDateTime timeBlockEnd = timeBlock[1]; + is_task_free = is_task_free && givenTime(timeBlockStart, timeBlockEnd, start, end); + } + } else if (task instanceof Test) { + if (((Test) task).getTimeBlock() != null) { + + LocalDateTime[] timeBlock = ((Test) task).getTimeBlock(); + LocalDateTime timeBlockStart = timeBlock[0]; + LocalDateTime timeBlockEnd = timeBlock[1]; + + is_task_free = is_task_free && givenTime(timeBlockStart, timeBlockEnd, start, end); + + if (((Test) task).getPrepTimeScheduled() != null) { + ArrayList> scheduledPrep = ((Test) task).getPrepTimeScheduled(); + for (ArrayList prep : scheduledPrep) { + LocalDateTime prepStart = prep.get(0); + LocalDateTime prepEnd = prep.get(1); + + is_task_free = is_task_free && givenTime(prepStart, prepEnd, start, end); + } + + } + } + } else if (task instanceof Assignment) { + + if (((Assignment) task).getPrepTimeScheduled() != null) { + ArrayList> scheduledPrep = ((Assignment) task).getPrepTimeScheduled(); + for (ArrayList prep : scheduledPrep) { + LocalDateTime prepStart = prep.get(0); + LocalDateTime prepEnd = prep.get(1); + + is_task_free = is_task_free && givenTime(prepStart, prepEnd, start, end); + } + } + } + + } + if (user.getWorkingHours() != null) { + ArrayList workingHours = user.getWorkingHours(); + LocalTime workingHoursStart = workingHours.get(0); + LocalTime workingHoursEnd = workingHours.get(1); + is_working_hours_free = workingHoursFree(start, end, workingHoursStart, workingHoursEnd); + } + return is_task_free && is_working_hours_free; + } + + /** + * Check if a time block conflicts with a user's working hours + * @param timeBlockStart - the start of the time block + * @param timeBlockEnd - the end of the time block + * @param workingHoursStart - the start of a user's working hours + * @param workingHoursEnd - the end of a user's working hours + * @return whether or not a time block conflicts with a user's working hours + */ + public boolean workingHoursFree(LocalDateTime timeBlockStart, LocalDateTime timeBlockEnd, + LocalTime workingHoursStart, LocalTime workingHoursEnd) { + // if timeBlock is within working hours + // timeBlockStart.isAfter(ChronoLocalDateTime.from(workingHoursStart)) && + // timeBlockEnd.isBefore(ChronoLocalDateTime.from(workingHoursEnd)) + if (timeBlockStart.getHour() > workingHoursStart.getHour() && + timeBlockEnd.getHour() < workingHoursEnd.getHour()) { + return false; + // if timeBlock covers the whole period of working hours + // timeBlockStart.isBefore(ChronoLocalDateTime.from(workingHoursStart)) && + // timeBlockEnd.isAfter(ChronoLocalDateTime.from(workingHoursEnd)) + } else if (timeBlockStart.getHour() < workingHoursStart.getHour() && + timeBlockEnd.getHour() > workingHoursEnd.getHour()) { + return false; + // if timeBlockStart is before workingHoursStart and timeBlockEnd is before workingHoursEnd + } else if (timeBlockStart.getHour()< workingHoursStart.getHour() && + timeBlockEnd.getHour() < workingHoursEnd.getHour()) { + return false; + // if timeBlockStart is the same as workingHoursStart + } else if (timeBlockStart.getHour() == workingHoursStart.getHour()) { + return false; + // if timeBlockEnd is the same as workingHoursEnd + } else if (timeBlockEnd.getHour() == workingHoursEnd.getHour()) { + return false; + // if timeBlockStart is after workingHoursStart and timeBlockEnd is after workingHoursEnd + } else return !(timeBlockStart.getHour() > workingHoursStart.getHour() && + timeBlockEnd.getHour() > workingHoursEnd.getHour()); + } + + /** + * Check if two time blocks conflict + * @param timeBlockStart - the start of the time block + * @param timeBlockEnd - the end of the time block + * @param start - the start of another time block + * @param end - the end of another time block + * @return whether or not two time blocks conflict + */ + public boolean givenTime(LocalDateTime timeBlockStart, LocalDateTime timeBlockEnd, + LocalDateTime start, LocalDateTime end) { + // in the case where the time block is within the time of start and end + if (timeBlockStart.isAfter(start) && timeBlockEnd.isBefore(end)) { + return false; + // in the case where it covers the time period + } else if (timeBlockStart.isBefore(start) && timeBlockEnd.isAfter(end)) { + return false; + // in the case where timeBlockStart is before start and timeBlockEnd is before end + } else if (timeBlockStart.isBefore(start) && timeBlockEnd.isBefore(end)) { + return false; + // if the start time same as start time of block, return false + } else if (timeBlockStart.isEqual(start)) { + return false; + // if the end time same as end time of block, return false + } else if (timeBlockEnd.isEqual(end)) { + return false; + // in the case where timeBlockStart is after start time and timeBlockEnd is after end time + } else return !(timeBlockStart.isAfter(start) && timeBlockEnd.isAfter(end)); + } + + /** + * Retrieve the tasks associated with a user from the given hash map + * @param user - the student user + * @param hashMap - a hash map of all task ids to tasks + * @return an array list of tasks + */ + public ArrayList getTaskFromId(StudentUser user, HashMap hashMap) { + ArrayList userTasks = new ArrayList<>(); + + ArrayList toDoList = user.getToDoList(); + + for (String taskId : toDoList) { + Task task_value = hashMap.get(taskId); + userTasks.add(task_value); + } + return userTasks; + } + + /** + * Get the task object from the task map given the name of the task + * @param taskName - the name of the task + * @param hashMap - hashmap of task ids to tasks + * @return the task object that corresponds to the task name + */ + public CollaborativeTask getTaskObjectFromName(String taskName, HashMap hashMap) { + for (Task task : hashMap.values()) { + if (task.getTitle().equals(taskName) && task instanceof CollaborativeTask) { + return (CollaborativeTask) task; + } + } + throw new RuntimeException("Task does not exist"); + } + + /** + * Convert the given string to a LocalDateTime object + * @param stringTime - the string representation of a date time + * @return the string formatted as LocalDateTime + */ + public LocalDateTime convertStringToLocalDateTime(String stringTime) { + DateTimeFormatter format = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); + return LocalDateTime.parse(stringTime, format); + } + +} diff --git a/src/main/java/scheduling_ct_use_case/ScheduleCTOutputBoundary.java b/src/main/java/scheduling_ct_use_case/ScheduleCTOutputBoundary.java new file mode 100644 index 0000000..03c9ca0 --- /dev/null +++ b/src/main/java/scheduling_ct_use_case/ScheduleCTOutputBoundary.java @@ -0,0 +1,13 @@ +package scheduling_ct_use_case; + +/** + * Output Boundary for the Scheduling Collaborative Tasks Use Case + * (inverts dependency for interactor to presenter) + */ + +public interface ScheduleCTOutputBoundary { + + ScheduleCTResponseModel prepareNoConflictView(ScheduleCTResponseModel responseModel); + + ScheduleCTResponseModel prepareFailView(String error); +} diff --git a/src/main/java/scheduling_ct_use_case/ScheduleCTRequestModel.java b/src/main/java/scheduling_ct_use_case/ScheduleCTRequestModel.java new file mode 100644 index 0000000..7a09f6a --- /dev/null +++ b/src/main/java/scheduling_ct_use_case/ScheduleCTRequestModel.java @@ -0,0 +1,42 @@ +package scheduling_ct_use_case; + +import entities.StudentUser; + +/** + * Request Model for the Scheduling Collaborative Tasks Use Case + * Acts as the input data object in the use case layer + */ + +public class ScheduleCTRequestModel { + + private final String taskName; + + private final String startTime; + + private final String endTime; + + private final StudentUser studentUser; + + public ScheduleCTRequestModel(String taskName, String startTime, String endTime, StudentUser studentUser) { + this.taskName = taskName; + this.startTime = startTime; + this.endTime = endTime; + this.studentUser = studentUser; + } + + public String getTaskName() { + return taskName; + } + public String getStartTime() { + return startTime; + } + public String getEndTime() { + return endTime; + } + + public StudentUser getStudentUser() { + return studentUser; + } + + +} diff --git a/src/main/java/scheduling_ct_use_case/ScheduleCTResponseModel.java b/src/main/java/scheduling_ct_use_case/ScheduleCTResponseModel.java new file mode 100644 index 0000000..17c4b24 --- /dev/null +++ b/src/main/java/scheduling_ct_use_case/ScheduleCTResponseModel.java @@ -0,0 +1,28 @@ +package scheduling_ct_use_case; + +/** + * Response Model for the Scheduling Collaborative Tasks Use Case + * Acts as the output data object in the use case layer + */ + +public class ScheduleCTResponseModel { + + private final boolean isConflict; + String displayString; + + public ScheduleCTResponseModel(boolean isConflict) { + + this.isConflict = isConflict; + } + + public boolean getIsConflict() { + return isConflict; + } + + public String getDisplayString() { + return displayString; + } + public void setDisplayString(String displayString) { + this.displayString = displayString; + } +}