All Features are implemented

- Validate classroom capacity limits
  - Check no double-booking of classrooms
  - Ensure no consecutive exams for students
  - Enforce max 2 exams per day per student
  - Return detailed validation results with error messages
This commit is contained in:
sabazadam
2025-12-14 00:18:30 +03:00
parent 445ee9aae7
commit 9ad4cdc69b

View File

@@ -0,0 +1,291 @@
package org.example.se302.service;
import org.example.se302.model.*;
import java.util.*;
/**
* Validates scheduling constraints for exam assignments.
* Ensures that all hard constraints are satisfied.
* Works with the day/timeSlotIndex based ExamAssignment architecture.
*/
public class ConstraintValidator {
private final DataManager dataManager;
public ConstraintValidator() {
this.dataManager = DataManager.getInstance();
}
/**
* Validate all constraints for a complete schedule.
*/
public ValidationResult validateSchedule(ScheduleState scheduleState) {
ValidationResult result = new ValidationResult();
// Check all assignments
for (ExamAssignment assignment : scheduleState.getAssignments().values()) {
if (assignment.isAssigned()) {
ValidationResult assignmentResult = validateAssignment(assignment, scheduleState);
result.merge(assignmentResult);
}
}
return result;
}
/**
* Validate a single assignment against the current schedule state.
*/
public ValidationResult validateAssignment(ExamAssignment assignment, ScheduleState scheduleState) {
ValidationResult result = new ValidationResult();
if (!assignment.isAssigned()) {
result.addError("Assignment is not complete (missing time slot or classroom)");
return result;
}
// Check classroom capacity
ValidationResult capacityResult = checkClassroomCapacity(assignment);
result.merge(capacityResult);
// Check no double-booking
ValidationResult doubleBookingResult = checkNoDoubleBooking(assignment, scheduleState);
result.merge(doubleBookingResult);
// Check student constraints
Course course = dataManager.getCourse(assignment.getCourseCode());
if (course != null) {
for (String studentId : course.getEnrolledStudents()) {
// Check no consecutive exams
ValidationResult consecutiveResult = checkNoConsecutiveExams(
studentId, assignment, scheduleState);
result.merge(consecutiveResult);
// Check max 2 exams per day
ValidationResult maxPerDayResult = checkMaxTwoExamsPerDay(
studentId, assignment, scheduleState);
result.merge(maxPerDayResult);
}
}
return result;
}
/**
* Check that classroom capacity is not exceeded.
*/
public ValidationResult checkClassroomCapacity(ExamAssignment assignment) {
ValidationResult result = new ValidationResult();
Classroom classroom = dataManager.getClassroom(assignment.getClassroomId());
Course course = dataManager.getCourse(assignment.getCourseCode());
if (classroom == null) {
result.addError("Classroom not found: " + assignment.getClassroomId());
return result;
}
if (course == null) {
result.addError("Course not found: " + assignment.getCourseCode());
return result;
}
int enrolledCount = course.getEnrolledStudentsCount();
int capacity = classroom.getCapacity();
if (enrolledCount > capacity) {
result.addError(String.format(
"Classroom capacity exceeded: %s has %d students but %s capacity is %d",
course.getCourseCode(), enrolledCount, classroom.getClassroomId(), capacity));
}
return result;
}
/**
* Check that no classroom is double-booked at the same time.
*/
public ValidationResult checkNoDoubleBooking(ExamAssignment assignment, ScheduleState scheduleState) {
ValidationResult result = new ValidationResult();
// Check if any other assignment uses the same classroom at the same time
for (ExamAssignment existing : scheduleState.getAssignments().values()) {
if (existing.isAssigned() &&
!existing.getCourseCode().equals(assignment.getCourseCode()) &&
existing.getClassroomId().equals(assignment.getClassroomId()) &&
existing.getDay() == assignment.getDay() &&
existing.getTimeSlotIndex() == assignment.getTimeSlotIndex()) {
result.addError(String.format(
"Classroom double-booking: %s already has %s at Day %d, Slot %d",
assignment.getClassroomId(),
existing.getCourseCode(),
assignment.getDay() + 1,
assignment.getTimeSlotIndex() + 1));
break;
}
}
return result;
}
/**
* Check that a student has no consecutive exams.
* Consecutive means exams in adjacent time slots on the same day (back-to-back).
*/
public ValidationResult checkNoConsecutiveExams(String studentId,
ExamAssignment newAssignment,
ScheduleState scheduleState) {
ValidationResult result = new ValidationResult();
// Get all courses this student is enrolled in
Student student = dataManager.getStudent(studentId);
if (student == null) {
return result; // Student not found, skip
}
List<String> studentCourses = student.getEnrolledCourses();
int newDay = newAssignment.getDay();
int newSlot = newAssignment.getTimeSlotIndex();
// Check each existing assignment for this student
for (ExamAssignment existing : scheduleState.getAssignments().values()) {
if (!existing.isAssigned() || existing.getCourseCode().equals(newAssignment.getCourseCode())) {
continue;
}
// Check if this assignment is for a course the student is enrolled in
if (studentCourses.contains(existing.getCourseCode())) {
int existingDay = existing.getDay();
int existingSlot = existing.getTimeSlotIndex();
// Check if consecutive (adjacent slots on the same day)
boolean isConsecutive = (existingDay == newDay) &&
(Math.abs(existingSlot - newSlot) == 1);
if (isConsecutive) {
result.addError(String.format(
"Consecutive exams for student %s: %s (Day %d, Slot %d) and %s (Day %d, Slot %d) are back-to-back",
studentId,
existing.getCourseCode(), existingDay + 1, existingSlot + 1,
newAssignment.getCourseCode(), newDay + 1, newSlot + 1));
}
}
}
return result;
}
/**
* Check that a student has at most 2 exams per day.
*/
public ValidationResult checkMaxTwoExamsPerDay(String studentId,
ExamAssignment newAssignment,
ScheduleState scheduleState) {
ValidationResult result = new ValidationResult();
// Get all courses this student is enrolled in
Student student = dataManager.getStudent(studentId);
if (student == null) {
return result;
}
List<String> studentCourses = student.getEnrolledCourses();
int newDay = newAssignment.getDay();
// Count exams on the same day as new assignment
long examsOnSameDay = scheduleState.getAssignments().values().stream()
.filter(a -> a.isAssigned())
.filter(a -> !a.getCourseCode().equals(newAssignment.getCourseCode()))
.filter(a -> studentCourses.contains(a.getCourseCode()))
.filter(a -> a.getDay() == newDay)
.count();
// Including the new assignment, would be examsOnSameDay + 1
if (examsOnSameDay + 1 > 2) {
result.addError(String.format(
"Too many exams for student %s on Day %d: would have %d exams (max 2)",
studentId,
newDay + 1,
examsOnSameDay + 1));
}
return result;
}
/**
* Get all students affected by an assignment.
*/
public List<String> getAffectedStudents(ExamAssignment assignment) {
Course course = dataManager.getCourse(assignment.getCourseCode());
return course != null ? course.getEnrolledStudents() : new ArrayList<>();
}
/**
* Check if a specific assignment would create conflicts.
* Returns list of conflict descriptions.
*/
public List<String> getConflictsForAssignment(ExamAssignment assignment, ScheduleState scheduleState) {
ValidationResult result = validateAssignment(assignment, scheduleState);
return result.getErrors();
}
/**
* Result of constraint validation.
*/
public static class ValidationResult {
private final List<String> errors;
private final List<String> warnings;
public ValidationResult() {
this.errors = new ArrayList<>();
this.warnings = new ArrayList<>();
}
public void addError(String error) {
if (!errors.contains(error)) { // Avoid duplicates
errors.add(error);
}
}
public void addWarning(String warning) {
if (!warnings.contains(warning)) {
warnings.add(warning);
}
}
public void merge(ValidationResult other) {
this.errors.addAll(other.errors);
this.warnings.addAll(other.warnings);
}
public boolean isValid() {
return errors.isEmpty();
}
public List<String> getErrors() {
return new ArrayList<>(errors);
}
public List<String> getWarnings() {
return new ArrayList<>(warnings);
}
public int getErrorCount() {
return errors.size();
}
public String getFormattedErrors() {
if (errors.isEmpty()) {
return "No errors";
}
return String.join("\n", errors);
}
@Override
public String toString() {
return String.format("ValidationResult{errors=%d, warnings=%d}",
errors.size(), warnings.size());
}
}
}