Completed edit functionality for examinations in the calendar view.

This commit is contained in:
haxala1r
2025-12-14 19:21:14 +03:00
parent 1f279145bf
commit 24997710ef
3 changed files with 716 additions and 34 deletions

View File

@@ -0,0 +1,372 @@
package org.example.se302.controller;
import javafx.geometry.Insets;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import javafx.util.StringConverter;
import org.example.se302.model.*;
import org.example.se302.service.ConstraintValidationService;
import org.example.se302.service.ConstraintValidationService.ValidationResult;
import org.example.se302.service.DataManager;
import java.util.Optional;
/**
* Dialog for editing an exam assignment.
* Provides real-time constraint validation with detailed violation info.
*/
public class ExamEditDialog extends Dialog<ExamEditDialog.EditResult> {
private final String courseCode;
private final ScheduleState scheduleState;
private final ScheduleConfiguration config;
private final DataManager dataManager;
private final ConstraintValidationService validationService;
// UI Components
private ComboBox<Integer> dayComboBox;
private ComboBox<Integer> slotComboBox;
private ComboBox<Classroom> classroomComboBox;
private VBox validationPanel;
private Label validationStatusLabel;
private TextArea violationDetails;
private Button applyButton;
private Button applyAnywayButton;
// Current state
private ValidationResult currentValidation;
public ExamEditDialog(ExamAssignment assignment, ScheduleState scheduleState, ScheduleConfiguration config) {
this.courseCode = assignment.getCourseCode();
this.scheduleState = scheduleState;
this.config = config;
this.dataManager = DataManager.getInstance();
this.validationService = new ConstraintValidationService();
setTitle("Exam Details");
setHeaderText(courseCode);
buildUI(assignment);
setupValidation();
setupButtons();
// Initial validation
validateCurrentSelection();
}
private void buildUI(ExamAssignment assignment) {
GridPane grid = new GridPane();
grid.setHgap(15);
grid.setVgap(15);
grid.setPadding(new Insets(20));
// Course info header
Course course = dataManager.getCourse(courseCode);
Classroom currentRoom = dataManager.getClassroom(assignment.getClassroomId());
int studentCount = course != null ? course.getEnrolledStudentsCount() : 0;
Label courseLabel = new Label("Course: " + courseCode);
courseLabel.setFont(Font.font("System", FontWeight.BOLD, 14));
Label studentLabel = new Label("Students enrolled: " + studentCount);
studentLabel.setStyle("-fx-text-fill: #666;");
// Current assignment info
String currentInfo = String.format("Current: Day %d, Slot %d, Room %s",
assignment.getDay() + 1, assignment.getTimeSlotIndex() + 1, assignment.getClassroomId());
Label currentLabel = new Label(currentInfo);
currentLabel.setStyle("-fx-text-fill: #666;");
// Capacity info
String capacityInfo = "";
if (currentRoom != null) {
double utilization = (double) studentCount / currentRoom.getCapacity() * 100;
capacityInfo = String.format("Capacity: %d/%d (%.0f%% utilization)",
studentCount, currentRoom.getCapacity(), utilization);
}
Label capacityLabel = new Label(capacityInfo);
capacityLabel.setStyle("-fx-text-fill: #666;");
VBox headerBox = new VBox(5, courseLabel, studentLabel, currentLabel, capacityLabel);
headerBox.setStyle("-fx-padding: 5; -fx-background-color: #f8f9fa; -fx-background-radius: 5;");
grid.add(headerBox, 0, 0, 2, 1);
// Separator
Separator sep = new Separator();
grid.add(sep, 0, 1, 2, 1);
// Day selector
grid.add(new Label("Day:"), 0, 2);
dayComboBox = new ComboBox<>();
for (int i = 0; i < config.getNumDays(); i++) {
dayComboBox.getItems().add(i);
}
dayComboBox.setConverter(new StringConverter<>() {
@Override
public String toString(Integer day) {
if (day == null)
return "";
TimeSlot slot = config.getTimeSlot(day, 0);
String dateStr = slot != null ? " (" + slot.getDate().toString() + ")" : "";
return "Day " + (day + 1) + dateStr;
}
@Override
public Integer fromString(String string) {
return null;
}
});
dayComboBox.setValue(assignment.getDay());
dayComboBox.setMaxWidth(Double.MAX_VALUE);
grid.add(dayComboBox, 1, 2);
// Time slot selector
grid.add(new Label("Time Slot:"), 0, 3);
slotComboBox = new ComboBox<>();
for (int i = 0; i < config.getSlotsPerDay(); i++) {
slotComboBox.getItems().add(i);
}
slotComboBox.setConverter(new StringConverter<>() {
@Override
public String toString(Integer slot) {
if (slot == null)
return "";
TimeSlot ts = config.getTimeSlot(0, slot);
String timeStr = ts != null ? " (" + ts.getStartTime() + " - " + ts.getEndTime() + ")" : "";
return "Slot " + (slot + 1) + timeStr;
}
@Override
public Integer fromString(String string) {
return null;
}
});
slotComboBox.setValue(assignment.getTimeSlotIndex());
slotComboBox.setMaxWidth(Double.MAX_VALUE);
grid.add(slotComboBox, 1, 3);
// Classroom selector
grid.add(new Label("Classroom:"), 0, 4);
classroomComboBox = new ComboBox<>();
classroomComboBox.getItems().addAll(dataManager.getClassrooms());
classroomComboBox.setConverter(new StringConverter<>() {
@Override
public String toString(Classroom classroom) {
if (classroom == null)
return "";
return classroom.getClassroomId() + " (Capacity: " + classroom.getCapacity() + ")";
}
@Override
public Classroom fromString(String string) {
return null;
}
});
// Set current classroom
Classroom currentClassroom = dataManager.getClassroom(assignment.getClassroomId());
classroomComboBox.setValue(currentClassroom);
classroomComboBox.setMaxWidth(Double.MAX_VALUE);
grid.add(classroomComboBox, 1, 4);
// Column constraints
ColumnConstraints col1 = new ColumnConstraints();
col1.setMinWidth(80);
ColumnConstraints col2 = new ColumnConstraints();
col2.setMinWidth(250);
col2.setHgrow(Priority.ALWAYS);
grid.getColumnConstraints().addAll(col1, col2);
// Separator before validation
Separator sep2 = new Separator();
grid.add(sep2, 0, 5, 2, 1);
// Validation panel
validationPanel = new VBox(10);
validationPanel.setPadding(new Insets(10));
validationPanel.setStyle("-fx-background-color: #f5f5f5; -fx-background-radius: 5;");
validationStatusLabel = new Label("Checking constraints...");
validationStatusLabel.setFont(Font.font("System", FontWeight.BOLD, 12));
violationDetails = new TextArea();
violationDetails.setEditable(false);
violationDetails.setWrapText(true);
violationDetails.setPrefRowCount(4);
violationDetails.setMaxHeight(120);
violationDetails.setStyle("-fx-control-inner-background: #f5f5f5;");
validationPanel.getChildren().addAll(validationStatusLabel, violationDetails);
grid.add(validationPanel, 0, 6, 2, 1);
getDialogPane().setContent(grid);
getDialogPane().setPrefWidth(450);
}
private void setupValidation() {
// Add listeners to all inputs
dayComboBox.valueProperty().addListener((obs, old, newVal) -> validateCurrentSelection());
slotComboBox.valueProperty().addListener((obs, old, newVal) -> validateCurrentSelection());
classroomComboBox.valueProperty().addListener((obs, old, newVal) -> validateCurrentSelection());
}
private void validateCurrentSelection() {
Integer day = dayComboBox.getValue();
Integer slot = slotComboBox.getValue();
Classroom classroom = classroomComboBox.getValue();
if (day == null || slot == null || classroom == null) {
validationStatusLabel.setText("⚠️ Please select all fields");
validationStatusLabel.setTextFill(Color.ORANGE);
violationDetails.setText("");
updateButtonStates(false, false);
return;
}
// Perform validation
currentValidation = validationService.validateAssignment(
courseCode, day, slot, classroom.getClassroomId(), scheduleState);
if (currentValidation.isValid()) {
validationStatusLabel.setText("✓ No constraint violations");
validationStatusLabel.setTextFill(Color.web("#27ae60"));
violationDetails.setText("");
validationPanel.setStyle("-fx-background-color: #e8f5e9; -fx-background-radius: 5;");
updateButtonStates(true, false);
} else if (currentValidation.hasHardViolations()) {
validationStatusLabel.setText("❌ Constraint violations found");
validationStatusLabel.setTextFill(Color.web("#e74c3c"));
violationDetails.setText(currentValidation.getFormattedMessage());
validationPanel.setStyle("-fx-background-color: #ffebee; -fx-background-radius: 5;");
updateButtonStates(false, true);
} else {
validationStatusLabel.setText("⚠️ Warnings found");
validationStatusLabel.setTextFill(Color.web("#f39c12"));
violationDetails.setText(currentValidation.getFormattedMessage());
validationPanel.setStyle("-fx-background-color: #fff3e0; -fx-background-radius: 5;");
updateButtonStates(true, true);
}
}
private void updateButtonStates(boolean applyEnabled, boolean showApplyAnyway) {
if (applyButton != null) {
applyButton.setDisable(!applyEnabled);
}
if (applyAnywayButton != null) {
applyAnywayButton.setVisible(showApplyAnyway);
applyAnywayButton.setManaged(showApplyAnyway);
}
}
private void setupButtons() {
// Create custom button types
ButtonType cancelButtonType = new ButtonType("Cancel", ButtonBar.ButtonData.CANCEL_CLOSE);
ButtonType applyButtonType = new ButtonType("Apply", ButtonBar.ButtonData.OK_DONE);
getDialogPane().getButtonTypes().addAll(cancelButtonType, applyButtonType);
applyButton = (Button) getDialogPane().lookupButton(applyButtonType);
applyButton.setDefaultButton(true);
// Add "Apply Anyway" button for when there are violations
applyAnywayButton = new Button("Apply Anyway");
applyAnywayButton.setStyle("-fx-background-color: #e74c3c; -fx-text-fill: white;");
applyAnywayButton.setVisible(false);
applyAnywayButton.setManaged(false);
// Find the button bar and add our custom button
ButtonBar buttonBar = (ButtonBar) getDialogPane().lookup(".button-bar");
if (buttonBar != null) {
ButtonBar.setButtonData(applyAnywayButton, ButtonBar.ButtonData.LEFT);
buttonBar.getButtons().add(0, applyAnywayButton);
}
// Handle apply anyway
applyAnywayButton.setOnAction(e -> {
if (currentValidation != null && currentValidation.hasHardViolations()) {
// Show confirmation for hard violations
Alert confirm = new Alert(Alert.AlertType.WARNING);
confirm.setTitle("Confirm Override");
confirm.setHeaderText("Override Constraint Violations?");
confirm.setContentText(
"You are about to apply changes that violate hard constraints:\n\n" +
currentValidation.getFormattedMessage() + "\n\n" +
"This may cause scheduling conflicts for students. Are you sure?");
confirm.getButtonTypes().setAll(ButtonType.YES, ButtonType.NO);
Optional<ButtonType> result = confirm.showAndWait();
if (result.isPresent() && result.get() == ButtonType.YES) {
setResultAndClose(true);
}
} else {
setResultAndClose(true);
}
});
// Set result converter
setResultConverter(dialogButton -> {
if (dialogButton == applyButtonType) {
return createResult(false);
}
return null;
});
}
private void setResultAndClose(boolean forced) {
EditResult result = createResult(forced);
setResult(result);
close();
}
private EditResult createResult(boolean forced) {
Integer day = dayComboBox.getValue();
Integer slot = slotComboBox.getValue();
Classroom classroom = classroomComboBox.getValue();
if (day == null || slot == null || classroom == null) {
return null;
}
return new EditResult(courseCode, day, slot, classroom.getClassroomId(), forced);
}
/**
* Result of the edit dialog.
*/
public static class EditResult {
private final String courseCode;
private final int day;
private final int timeSlot;
private final String classroomId;
private final boolean forcedOverride;
public EditResult(String courseCode, int day, int timeSlot, String classroomId, boolean forcedOverride) {
this.courseCode = courseCode;
this.day = day;
this.timeSlot = timeSlot;
this.classroomId = classroomId;
this.forcedOverride = forcedOverride;
}
public String getCourseCode() {
return courseCode;
}
public int getDay() {
return day;
}
public int getTimeSlot() {
return timeSlot;
}
public String getClassroomId() {
return classroomId;
}
public boolean isForcedOverride() {
return forcedOverride;
}
}
}

View File

@@ -80,6 +80,7 @@ public class ScheduleCalendarController {
private final DataManager dataManager = DataManager.getInstance();
private ScheduleGeneratorService generatorService;
private ScheduleState currentSchedule;
private ScheduleConfiguration currentConfig;
private Thread generationThread;
@FXML
@@ -300,6 +301,7 @@ public class ScheduleCalendarController {
if (result.isSuccess()) {
currentSchedule = result.getScheduleState();
currentConfig = config;
// Save schedule to DataManager (so other views can see it)
saveScheduleToDataManager(currentSchedule, config);
@@ -474,9 +476,6 @@ public class ScheduleCalendarController {
examBox.setStyle("-fx-background-color: #3498db; -fx-background-radius: 3; -fx-padding: 3 6;");
examBox.setCursor(Cursor.HAND);
// Add click handler
examBox.setOnMouseClicked(e -> showAssignmentDetails(exam));
Label courseLabel = new Label(exam.getCourseCode());
courseLabel.setStyle("-fx-text-fill: white; -fx-font-weight: bold; -fx-font-size: 11;");
@@ -485,8 +484,18 @@ public class ScheduleCalendarController {
examBox.getChildren().addAll(courseLabel, roomLabel);
// Add tooltip
Tooltip tooltip = new Tooltip(exam.getCourseCode() + "\n" + exam.getClassroomId());
// Hover highlight
examBox.setOnMouseEntered(e -> examBox
.setStyle("-fx-background-color: #2980b9; -fx-background-radius: 3; -fx-padding: 3 6;"));
examBox.setOnMouseExited(e -> examBox
.setStyle("-fx-background-color: #3498db; -fx-background-radius: 3; -fx-padding: 3 6;"));
// Click opens the edit dialog (which also shows details)
examBox.setOnMouseClicked(e -> openEditDialog(exam));
// Tooltip with hint
Tooltip tooltip = new Tooltip(
exam.getCourseCode() + "\n" + exam.getClassroomId() + "\nClick to view/edit");
Tooltip.install(examBox, tooltip);
cell.getChildren().add(examBox);
@@ -503,35 +512,6 @@ public class ScheduleCalendarController {
return cell;
}
private void showAssignmentDetails(ExamAssignment exam) {
Course course = dataManager.getCourse(exam.getCourseCode());
Classroom room = dataManager.getClassroom(exam.getClassroomId());
Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.setTitle("Exam Details");
alert.setHeaderText(exam.getCourseCode());
StringBuilder content = new StringBuilder();
content.append("Course: ").append(exam.getCourseCode()).append("\n");
if (course != null) {
content.append("Students: ").append(course.getEnrolledStudentsCount()).append("\n");
}
content.append("\n");
content.append("Assigned Classroom: ").append(exam.getClassroomId()).append("\n");
if (room != null) {
content.append("Capacity: ").append(room.getCapacity()).append("\n");
double fillRatio = course != null ? (double) course.getEnrolledStudentsCount() / room.getCapacity() * 100
: 0;
content.append(String.format("Utilization: %.1f%%\n", fillRatio));
}
content.append("\n");
content.append("Day: ").append(exam.getDay() + 1).append("\n");
content.append("Slot: ").append(exam.getTimeSlotIndex() + 1).append("\n");
alert.setContentText(content.toString());
alert.showAndWait();
}
@FXML
private void onCancelGeneration() {
if (generatorService != null) {
@@ -549,4 +529,60 @@ public class ScheduleCalendarController {
alert.setContentText(message);
alert.showAndWait();
}
/**
* Opens the edit dialog for an exam assignment.
*/
private void openEditDialog(ExamAssignment exam) {
if (currentSchedule == null || currentConfig == null) {
showAlert(Alert.AlertType.WARNING, "Cannot Edit",
"No schedule is currently loaded. Please generate a schedule first.");
return;
}
ExamEditDialog dialog = new ExamEditDialog(exam, currentSchedule, currentConfig);
dialog.showAndWait().ifPresent(result -> {
applyExamEdit(result);
});
}
/**
* Applies an edit result to the schedule.
*/
private void applyExamEdit(ExamEditDialog.EditResult result) {
if (result == null)
return;
String courseCode = result.getCourseCode();
int newDay = result.getDay();
int newSlot = result.getTimeSlot();
String newClassroom = result.getClassroomId();
// Update the ScheduleState
boolean updated = currentSchedule.updateAssignment(courseCode, newDay, newSlot, newClassroom);
if (updated) {
// Also update the Course in DataManager
Course course = dataManager.getCourse(courseCode);
if (course != null) {
course.setExamSchedule(newDay, newSlot, newClassroom);
}
// Refresh the grid display
displayScheduleGrid(currentSchedule, currentConfig);
// Update status
String message = result.isForcedOverride() ? "⚠️ Exam moved (with override)" : "✓ Exam moved successfully";
statusLabel.setText(message);
statusLabel.setStyle(result.isForcedOverride() ? "-fx-text-fill: #f39c12;" : "-fx-text-fill: #27ae60;");
// Log the change
System.out.println("Exam edited: " + courseCode + " -> Day " + (newDay + 1) +
", Slot " + (newSlot + 1) + ", Room " + newClassroom +
(result.isForcedOverride() ? " (forced)" : ""));
} else {
showAlert(Alert.AlertType.ERROR, "Edit Failed",
"Could not update the exam assignment. The exam may be locked.");
}
}
}

View File

@@ -0,0 +1,274 @@
package org.example.se302.service;
import org.example.se302.model.*;
import java.util.*;
/**
* Service for validating exam assignment changes against constraints.
* Provides detailed violation information including affected student names.
*/
public class ConstraintValidationService {
private final DataManager dataManager;
public ConstraintValidationService() {
this.dataManager = DataManager.getInstance();
}
/**
* Validates a proposed exam assignment change.
*
* @param courseCode The course being moved
* @param newDay Proposed day (0-based)
* @param newSlot Proposed time slot (0-based)
* @param newClassroom Proposed classroom ID
* @param currentState Current schedule state (to check against other exams)
* @return ValidationResult with all violations found
*/
public ValidationResult validateAssignment(String courseCode, int newDay, int newSlot,
String newClassroom, ScheduleState currentState) {
ValidationResult result = new ValidationResult();
// Get course info
Course course = dataManager.getCourse(courseCode);
if (course == null) {
result.addViolation(new ConstraintViolation(
"Unknown Course",
true,
"Course " + courseCode + " not found",
Collections.emptyList(),
null));
return result;
}
// Check classroom capacity
Classroom classroom = dataManager.getClassroom(newClassroom);
if (classroom == null) {
result.addViolation(new ConstraintViolation(
"Unknown Classroom",
true,
"Classroom " + newClassroom + " not found",
Collections.emptyList(),
null));
} else if (classroom.getCapacity() < course.getEnrolledStudentsCount()) {
result.addViolation(new ConstraintViolation(
"Capacity Exceeded",
true,
String.format("Classroom %s has capacity %d, but course has %d students",
newClassroom, classroom.getCapacity(), course.getEnrolledStudentsCount()),
Collections.emptyList(),
null));
}
// Check classroom conflict (another exam in same classroom at same time)
String classroomConflictCourse = getClassroomConflict(courseCode, newClassroom, newDay, newSlot, currentState);
if (classroomConflictCourse != null) {
result.addViolation(new ConstraintViolation(
"Classroom Conflict",
true,
String.format("Classroom %s is already used by %s at this time",
newClassroom, classroomConflictCourse),
Collections.emptyList(),
classroomConflictCourse));
}
// Check student conflicts (students with exams at the same time)
List<StudentConflictInfo> studentConflicts = getStudentConflicts(courseCode, newDay, newSlot, currentState);
if (!studentConflicts.isEmpty()) {
// Group by conflicting course
Map<String, List<String>> conflictsByCourse = new LinkedHashMap<>();
for (StudentConflictInfo conflict : studentConflicts) {
conflictsByCourse
.computeIfAbsent(conflict.conflictingCourse, k -> new ArrayList<>())
.add(conflict.studentId);
}
for (Map.Entry<String, List<String>> entry : conflictsByCourse.entrySet()) {
String conflictCourse = entry.getKey();
List<String> students = entry.getValue();
String studentList = formatStudentList(students);
result.addViolation(new ConstraintViolation(
"Student Conflict",
true,
String.format("%d student(s) have exams for both %s and %s at this time: %s",
students.size(), courseCode, conflictCourse, studentList),
new ArrayList<>(students),
conflictCourse));
}
}
return result;
}
/**
* Gets the course that conflicts with the given classroom at the specified
* time.
*/
private String getClassroomConflict(String excludeCourse, String classroomId,
int day, int slot, ScheduleState state) {
for (ExamAssignment assignment : state.getAssignments().values()) {
if (assignment.getCourseCode().equals(excludeCourse)) {
continue; // Skip the course being moved
}
if (assignment.isAssigned() &&
assignment.getDay() == day &&
assignment.getTimeSlotIndex() == slot &&
classroomId.equals(assignment.getClassroomId())) {
return assignment.getCourseCode();
}
}
return null;
}
/**
* Gets all student conflicts for a proposed assignment.
*/
private List<StudentConflictInfo> getStudentConflicts(String courseCode, int day, int slot,
ScheduleState state) {
List<StudentConflictInfo> conflicts = new ArrayList<>();
Course course = dataManager.getCourse(courseCode);
if (course == null) {
return conflicts;
}
Set<String> enrolledStudents = new HashSet<>(course.getEnrolledStudents());
// Find all other courses at the same time
for (ExamAssignment assignment : state.getAssignments().values()) {
if (assignment.getCourseCode().equals(courseCode)) {
continue; // Skip self
}
if (!assignment.isAssigned() ||
assignment.getDay() != day ||
assignment.getTimeSlotIndex() != slot) {
continue; // Not at the same time
}
Course otherCourse = dataManager.getCourse(assignment.getCourseCode());
if (otherCourse == null) {
continue;
}
// Check for shared students
for (String studentId : otherCourse.getEnrolledStudents()) {
if (enrolledStudents.contains(studentId)) {
conflicts.add(new StudentConflictInfo(studentId, assignment.getCourseCode()));
}
}
}
return conflicts;
}
/**
* Formats a list of student IDs for display.
*/
private String formatStudentList(List<String> students) {
if (students.size() <= 5) {
return String.join(", ", students);
} else {
List<String> first5 = students.subList(0, 5);
return String.join(", ", first5) + " and " + (students.size() - 5) + " more";
}
}
// ============= Inner Classes =============
/**
* Holds information about a student conflict.
*/
private static class StudentConflictInfo {
final String studentId;
final String conflictingCourse;
StudentConflictInfo(String studentId, String conflictingCourse) {
this.studentId = studentId;
this.conflictingCourse = conflictingCourse;
}
}
/**
* Represents the result of a constraint validation.
*/
public static class ValidationResult {
private final List<ConstraintViolation> violations = new ArrayList<>();
public void addViolation(ConstraintViolation violation) {
violations.add(violation);
}
public List<ConstraintViolation> getViolations() {
return Collections.unmodifiableList(violations);
}
public boolean isValid() {
return violations.isEmpty();
}
public boolean hasHardViolations() {
return violations.stream().anyMatch(ConstraintViolation::isHard);
}
public boolean hasSoftViolations() {
return violations.stream().anyMatch(v -> !v.isHard());
}
public String getFormattedMessage() {
if (violations.isEmpty()) {
return "✓ No constraint violations";
}
StringBuilder sb = new StringBuilder();
for (ConstraintViolation v : violations) {
sb.append(v.isHard() ? "" : "⚠️ ");
sb.append(v.getConstraintName()).append(": ");
sb.append(v.getMessage()).append("\n");
}
return sb.toString().trim();
}
}
/**
* Represents a single constraint violation.
*/
public static class ConstraintViolation {
private final String constraintName;
private final boolean isHard;
private final String message;
private final List<String> affectedStudents;
private final String conflictingCourse;
public ConstraintViolation(String constraintName, boolean isHard, String message,
List<String> affectedStudents, String conflictingCourse) {
this.constraintName = constraintName;
this.isHard = isHard;
this.message = message;
this.affectedStudents = affectedStudents != null ? affectedStudents : Collections.emptyList();
this.conflictingCourse = conflictingCourse;
}
public String getConstraintName() {
return constraintName;
}
public boolean isHard() {
return isHard;
}
public String getMessage() {
return message;
}
public List<String> getAffectedStudents() {
return affectedStudents;
}
public String getConflictingCourse() {
return conflictingCourse;
}
}
}