0% found this document useful (0 votes)
14 views23 pages

Take Exam

The TakeExamPanel class is a Java Swing component that facilitates an exam-taking interface for students, managing multiple exams and their respective questions. It includes features such as a timer, navigation buttons, and the ability to request retakes, while also handling database interactions to fetch exam and student data. The panel initializes with a student's information, displays exam questions, and tracks answers across multiple exams in a session.

Uploaded by

panditpuspa000
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
14 views23 pages

Take Exam

The TakeExamPanel class is a Java Swing component that facilitates an exam-taking interface for students, managing multiple exams and their respective questions. It includes features such as a timer, navigation buttons, and the ability to request retakes, while also handling database interactions to fetch exam and student data. The panel initializes with a student's information, displays exam questions, and tracks answers across multiple exams in a session.

Uploaded by

panditpuspa000
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd

package student;

import [Link];

import [Link].*;
import [Link].*;
import [Link];
import [Link];
import [Link].*;
import [Link].*;
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];

public class TakeExamPanel extends JPanel {


private int studentId;
private String studentName;
private StudentDashboard parentDashboard;

// UI Components for the current exam


private JLabel examTitleLabel;
private JLabel questionLabel;
private JRadioButton[] optionButtons;
private ButtonGroup optionsGroup;
private JButton nextButton, prevButton, finishCurrentExamButton;
private JButton submitAllExamsButton;

// New: Request Retake Button


private JButton requestRetakeButton;
private JLabel retakeRequestStatusLabel; // To show status like "Pending",
"Approved", "Denied"

// Session Management for Multiple Exams


private List<Integer> examIdsInSession;
private int currentExamIndexInSession = 0;

// Data for the current exam being displayed


private ExamSessionData currentExamSessionData;
private int currentQuestionIndexInExam = 0;

// To store all answers across all exams taken in this session


private Map<Integer, Map<Integer, String>> allSelectedAnswers = new
HashMap<>();

// Timer related variables


private JLabel timerLabel;
private Timer timer;
private int secondsRemaining;
private boolean timerRunning;

public TakeExamPanel(int studentId, List<Integer> examIdsInSession,


StudentDashboard parentDashboard) {
[Link] = studentId;
[Link] = examIdsInSession;
[Link] = parentDashboard;

[Link] = getStudentName(studentId);
if ([Link] == null) {
[Link] = "Unknown Student";
[Link](this, "Could not retrieve student name.
Please contact support.", "Error", JOptionPane.ERROR_MESSAGE);
}

setLayout(new BorderLayout(15, 15));


setBorder([Link](20, 20, 20, 20));

// --- TOP PANEL: Heading, Exam Title, Timer ---


JPanel topPanel = new JPanel(new BorderLayout());
JLabel sessionHeading = new JLabel("✨ Exam Session", [Link]);
[Link](new Font("Arial", [Link], 24));
[Link](new Color(40, 90, 170));
[Link](sessionHeading, [Link]);

JPanel examInfoPanel = new JPanel(new BorderLayout(10, 5));


[Link]([Link](10, 0, 0, 0));
examTitleLabel = new JLabel("Loading Exam...", [Link]);
[Link](new Font("Arial", [Link], 18));
[Link](examTitleLabel, [Link]);

timerLabel = new JLabel("Time: --:--", [Link]);


[Link](new Font("Monospaced", [Link], 16));
[Link]([Link]);
[Link](timerLabel, [Link]);

[Link](examInfoPanel, [Link]);
add(topPanel, [Link]);

// --- CENTER PANEL: Question Display ---


JPanel questionDisplayPanel = new JPanel(new BorderLayout(10, 10));
questionLabel = new JLabel("Select an exam to begin.",
[Link]);
[Link](new Font("Arial", [Link], 16));
[Link]([Link](0, 10, 10, 10));
[Link](new JScrollPane(questionLabel),
[Link]);

JPanel optionsPanel = new JPanel(new GridLayout(4, 1, 5, 5));


[Link]([Link](0, 10, 0, 10));
optionButtons = new JRadioButton[4];
optionsGroup = new ButtonGroup();
for (int i = 0; i < 4; i++) {
optionButtons[i] = new JRadioButton();
optionButtons[i].setFont(new Font("Arial", [Link], 14));
optionButtons[i].setEnabled(false);
[Link](optionButtons[i]);
[Link](optionButtons[i]);
}
[Link](optionsPanel, [Link]);
add(questionDisplayPanel, [Link]);
// --- BOTTOM PANEL: Navigation and Action Buttons ---
JPanel bottomPanel = new JPanel(new FlowLayout([Link], 15, 10));
prevButton = new JButton("◀ Previous");
nextButton = new JButton("Next ▶");
finishCurrentExamButton = new JButton("✔ Finish This Exam");
submitAllExamsButton = new JButton("🏁 Submit All Exams");

// New: Request Retake button and status label


requestRetakeButton = new JButton("✉️ Request Retake");
retakeRequestStatusLabel = new JLabel("", [Link]);
[Link](new Font("Segoe UI", [Link], 12));
[Link]([Link]);

// Set button styles


styleButton(prevButton, new Color(70, 130, 180));
styleButton(nextButton, new Color(70, 130, 180));
styleButton(finishCurrentExamButton, new Color(255, 140, 0));
styleButton(submitAllExamsButton, new Color(34, 139, 34));
styleButton(requestRetakeButton, new Color(100, 149, 237)); // Cornflower
Blue

// Initial states
disableAllExamControls();
[Link](false);
[Link](false);
[Link](false); // Hide until an exam is finished or
determined it's a retake candidate

[Link](prevButton);
[Link](nextButton);
[Link](finishCurrentExamButton);
[Link](submitAllExamsButton);

// Panel for retake request button and status


JPanel retakePanel = new JPanel(new BorderLayout());
[Link](false); // Make it transparent
[Link](requestRetakeButton, [Link]);
[Link](retakeRequestStatusLabel, [Link]);
[Link](retakePanel); // Add retakePanel to the main bottomPanel

add(bottomPanel, [Link]);

// --- Action Listeners ---


[Link](e -> showPreviousQuestion());
[Link](e -> showNextQuestion());
[Link](e -> finishCurrentExam());
[Link](e -> submitAllRemainingExams());
[Link](e -> requestRetake());

// Start the exam session


initializeExamSession();
}

private void styleButton(JButton button, Color bgColor) {


[Link](new Font("Segoe UI", [Link], 14));
[Link](bgColor);
[Link]([Link]);
[Link](false);
[Link]([Link](
[Link]([Link](), 1),
[Link](8, 15, 8, 15)
));
[Link](new Cursor(Cursor.HAND_CURSOR));
}

private String getStudentName(int studentId) {


String name = null;
String query = "SELECT name FROM students WHERE student_id = ?";
Connection con = null; // Declare con outside try-with-resources
try {
con = [Link]();
PreparedStatement pst = [Link](query);
[Link](1, studentId);
ResultSet rs = [Link]();
if ([Link]()) {
name = [Link]("name");
}
} catch (SQLException e) {
[Link]();
[Link]("Error fetching student name: " + [Link]());
} finally {
if (con != null) {
try {
[Link]();
} catch (SQLException e) {
[Link]();
}
}
}
return name;
}

/**
* Initializes the exam session by loading details for all available exams
* and then displaying the first one.
*/
private void initializeExamSession() {
if (examIdsInSession == null || [Link]()) {
[Link](this, "No exams provided for this
session. Please contact support.", "Session Error", JOptionPane.ERROR_MESSAGE);
[Link]("No exams to take.");
disableAllExamControls();
redirectToDashboard(); // Go back to dashboard if no exams
return;
}

// Initialize answer maps for all exams in the session


for (int examId : examIdsInSession) {
[Link](examId, new HashMap<>());
}

loadAndDisplayExamAtIndex(0); // Start with the first exam


[Link](true); // Always enable submit all if there
are exams
}

/**
* Loads and displays the exam at the specified index in the session.
* Manages transitioning between exams or ending the session.
*/
private void loadAndDisplayExamAtIndex(int index) {
stopExamTimer(); // Stop timer for previous exam/transition

// Check if all exams in the session have been processed


if (index < 0 || index >= [Link]()) {
[Link](this, "You have completed all exams in
this session.", "Session Complete", JOptionPane.INFORMATION_MESSAGE);
[Link]("Session Complete!");
resetQuestionDisplay(); // Clear UI
disableAllExamControls(); // Disable all controls
redirectToDashboard(); // Return to dashboard
return;
}

currentExamIndexInSession = index;
int examId = [Link](currentExamIndexInSession);

// If the current exam is already completed, skip it


if (isExamCompletedInDB(studentId, examId)) {
[Link](this, "Exam '" + getExamTitleById(examId)
+ "' has already been completed. Moving to the next exam.", "Exam Already Done",
JOptionPane.INFORMATION_MESSAGE);
loadAndDisplayExamAtIndex(currentExamIndexInSession + 1); // Skip to
next
return;
}

currentExamSessionData = new ExamSessionData(examId);

// Load basic exam details


loadExamDetailsForCurrentSessionData(currentExamSessionData);

// Handle cases where exam details couldn't be loaded or access denied


if (![Link]()) {
[Link](this, "You are not allowed to access exam
'" + [Link]() + "'. Skipping this exam.", "Access Denied",
JOptionPane.WARNING_MESSAGE);
disableExamControlsForCurrentExam(); // Disable controls specific to
this problematic exam
loadAndDisplayExamAtIndex(currentExamIndexInSession + 1); // Try next
exam
return;
}

[Link]([Link]("Exam %d of %d: %s",


currentExamIndexInSession + 1, [Link](),
[Link]()));

// Get or create an attempt record for this specific exam


[Link](getOrCreateAttemptId(studentId,
examId));

if ([Link]() == -1) {
[Link](this, "Failed to start/resume attempt for
exam '" + [Link]() + "'. Please try again or contact
support.", "Error", JOptionPane.ERROR_MESSAGE);
disableAllExamControls();
redirectToDashboard(); // Critical error, go back to dashboard
return;
}

// Load questions for the current exam


loadQuestionsForCurrentExam(currentExamSessionData);

if ([Link]().isEmpty()) {
[Link](this, "No questions available for '" +
[Link]() + "'. Skipping this exam and moving to next.",
"No Questions", JOptionPane.WARNING_MESSAGE);
disableExamControlsForCurrentExam();
loadAndDisplayExamAtIndex(currentExamIndexInSession + 1); // Skip to
next
return;
}

currentQuestionIndexInExam = 0; // Reset question index for new exam

startExamTimer([Link]()); // Start timer for


current exam

showQuestion(currentQuestionIndexInExam); // Display first question


enableExamControls(); // Enable navigation buttons

// Update retake request button visibility/status for the new exam


updateRetakeRequestButtonStatus();
}

/**
* Helper to get exam title by ID, primarily for messages when details might
not be fully loaded.
*/
private String getExamTitleById(int examId) {
String title = "Unknown Exam";
Connection con = null;
try {
con = [Link]();
String query = "SELECT title FROM exams WHERE exam_id = ?";
PreparedStatement pst = [Link](query);
[Link](1, examId);
ResultSet rs = [Link]();
if ([Link]()) {
title = [Link]("title");
}
} catch (SQLException e) {
[Link]("Error getting exam title by ID: " +
[Link]());
} finally {
if (con != null) {
try {
[Link]();
} catch (SQLException e) {
[Link]();
}
}
}
return title;
}
/**
* Loads exam details (title, duration, question limit, shuffle, is_allowed)
for the given ExamSessionData object.
*/
private void loadExamDetailsForCurrentSessionData(ExamSessionData sessionData)
{
Connection con = null;
try {
con = [Link]();
String query = """
SELECT [Link], [Link], COALESCE(sea.question_limit, 0) AS
question_limit, COALESCE(sea.shuffle_questions, 0) AS shuffle_questions,
COALESCE(sea.is_allowed, FALSE) AS is_allowed
FROM exams e
LEFT JOIN student_exam_access sea ON e.exam_id = sea.exam_id AND
sea.student_id = ?
WHERE e.exam_id = ?
"""; // Changed JOIN to LEFT JOIN to handle cases where
student_exam_access entry might not exist yet
PreparedStatement pst = [Link](query);
[Link](1, studentId);
[Link](2, [Link]());
ResultSet rs = [Link]();

if ([Link]()) {
[Link]([Link]("title"));
[Link]([Link]("duration"));
[Link]([Link]("question_limit"));

[Link]([Link]("shuffle_questions"));
[Link]([Link]("is_allowed"));
} else {
[Link]("Unknown Exam (ID: " + [Link]()
+ ")");
[Link](false); // Default to not allowed if no
access record
[Link](this, "Could not retrieve access
details for exam ID: " + [Link](), "Error",
JOptionPane.ERROR_MESSAGE);
}
} catch (SQLException e) {
[Link]();
[Link](this, "Database error loading exam
details: " + [Link](), "DB Error", JOptionPane.ERROR_MESSAGE);
[Link](false);
} finally {
if (con != null) {
try {
[Link]();
} catch (SQLException e) {
[Link]();
}
}
}
}
/**
* Retrieves an existing attempt ID or creates a new one for the current
student and exam.
* Marks the attempt as started.
*/
private int getOrCreateAttemptId(int studentId, int examId) {
int attemptId = -1;
Connection con = null;
try {
con = [Link]();
// Check for existing uncompleted attempt
String checkAttemptQuery = "SELECT ex_id FROM exam_attempts WHERE
student_id = ? AND exam_id = ? AND is_completed = 0 ORDER BY start_time DESC LIMIT
1";
PreparedStatement checkPst = [Link](checkAttemptQuery);
[Link](1, studentId);
[Link](2, examId);
ResultSet rs = [Link]();

if ([Link]()) {
attemptId = [Link]("ex_id");
// Update start_time to now (resuming)
String updateStartTimeQuery = "UPDATE exam_attempts SET start_time
= NOW(), last_activity = NOW() WHERE ex_id = ?";
PreparedStatement updatePst =
[Link](updateStartTimeQuery);
[Link](1, attemptId);
[Link]();
[Link]("Resuming attempt: " + attemptId + " for exam: "
+ examId);
} else {
// No uncompleted attempt found, create a new one
String insertAttemptQuery = "INSERT INTO exam_attempts (student_id,
exam_id, start_time, last_activity, is_completed, score) VALUES (?, ?, NOW(),
NOW(), 0, 0)";
PreparedStatement insertPst =
[Link](insertAttemptQuery, Statement.RETURN_GENERATED_KEYS);
[Link](1, studentId);
[Link](2, examId);
[Link]();

ResultSet generatedKeys = [Link]();


if ([Link]()) {
attemptId = [Link](1);
[Link]("New attempt created: " + attemptId + " for
exam: " + examId);
} else {
throw new SQLException("Creating attempt failed, no ID
obtained.");
}
}
} catch (SQLException e) {
[Link]();
[Link](this, "Database error getting/creating
attempt ID: " + [Link](), "DB Error", JOptionPane.ERROR_MESSAGE);
} finally {
if (con != null) {
try {
[Link]();
} catch (SQLException e) {
[Link]();
}
}
}
return attemptId;
}

/**
* Loads questions for the current exam. Shuffles if specified.
*/
private void loadQuestionsForCurrentExam(ExamSessionData sessionData) {
List<Question> questions = new ArrayList<>();
String query = "SELECT question_id, question_text, option_a, option_b,
option_c, option_d, correct_answer, marks " +
"FROM questions " +
"WHERE exam_id = ?";
Connection con = null;
try {
con = [Link]();
PreparedStatement pst = [Link](query);
[Link](1, [Link]());
ResultSet rs = [Link]();

while ([Link]()) {
[Link](new Question(
[Link]("question_id"),
[Link]("question_text"),
[Link]("option_a"),
[Link]("option_b"),
[Link]("option_c"),
[Link]("option_d"),
[Link]("correct_answer"),
[Link]("marks")
));
}

if ([Link]()) {
[Link](questions);
}

if ([Link]() > 0 && [Link]() >


[Link]()) {
questions = [Link](0, [Link]());
}
[Link](questions);

// Load any previously saved answers for this attempt


Map<Integer, String> savedAnswers =
[Link]([Link](), new HashMap<>());
loadSavedAnswersForAttempt([Link](), savedAnswers);
[Link]([Link](), savedAnswers);

} catch (SQLException e) {
[Link]();
[Link](this, "Database error loading questions:
" + [Link](), "DB Error", JOptionPane.ERROR_MESSAGE);
} finally {
if (con != null) {
try {
[Link]();
} catch (SQLException e) {
[Link]();
}
}
}
}

/**
* Loads previously saved answers for a given attempt ID and populates the map.
*/
private void loadSavedAnswersForAttempt(int attemptId, Map<Integer, String>
savedAnswers) {
String query = "SELECT question_id, selected_option FROM student_answers
WHERE attempt_id = ?";
Connection con = null;
try {
con = [Link]();
PreparedStatement pst = [Link](query);
[Link](1, attemptId);
ResultSet rs = [Link]();
while ([Link]()) {
[Link]([Link]("question_id"),
[Link]("selected_option"));
}
} catch (SQLException e) {
[Link]();
[Link]("Error loading saved answers: " + [Link]());
} finally {
if (con != null) {
try {
[Link]();
} catch (SQLException e) {
[Link]();
}
}
}
}

/**
* Displays the question at the specified index.
*/
private void showQuestion(int index) {
if (currentExamSessionData == null ||
[Link]().isEmpty() || index < 0 || index >=
[Link]().size()) {
resetQuestionDisplay();
return;
}

currentQuestionIndexInExam = index;
Question currentQuestion =
[Link]().get(currentQuestionIndexInExam);

[Link]("<html>Q" + (currentQuestionIndexInExam + 1) + ". " +


[Link]() + "</html>");
[Link]();
String[] options = {
[Link](),
[Link](),
[Link](),
[Link]()
};

for (int i = 0; i < 4; i++) {


optionButtons[i].setText("<html>" + (char)('A' + i) + ". " + options[i]
+ "</html>");
optionButtons[i].setActionCommand([Link]((char)('A' + i)));
optionButtons[i].setEnabled(true);

// Remove all existing action listeners to prevent duplicates


for (ActionListener al : optionButtons[i].getActionListeners()) {
optionButtons[i].removeActionListener(al);
}
// Add new listener to save answer
optionButtons[i].addActionListener(e ->
saveAnswer([Link](), [Link]()));
}

// Select the previously saved answer if exists


String savedAnswer =
[Link]([Link]())
.get([Link]());
if (savedAnswer != null) {
for (JRadioButton button : optionButtons) {
if ([Link]().equals(savedAnswer)) {
[Link](true);
break;
}
}
}

updateNavigationButtons(); // Update button states


}

/**
* Saves the selected answer to the in-memory map and also to the database.
* Updates `last_activity` in `exam_attempts` to keep the session alive.
*/
private void saveAnswer(int questionId, String selectedOption) {
// Save to in-memory map
Map<Integer, String> examAnswers =
[Link]([Link](), new
HashMap<>());
[Link](questionId, selectedOption);
[Link]([Link](), examAnswers);

// Save to database
Connection con = null;
try {
con = [Link]();
String query = "INSERT INTO student_answers (attempt_id, question_id,
selected_option) VALUES (?, ?, ?) " +
"ON DUPLICATE KEY UPDATE selected_option = ?"; // UPSERT:
Insert if not exists, update if exists
PreparedStatement pst = [Link](query);
[Link](1, [Link]());
[Link](2, questionId);
[Link](3, selectedOption);
[Link](4, selectedOption); // For ON DUPLICATE KEY UPDATE
[Link]();

// Update last_activity for the current attempt to keep it active


String updateLastActivityQuery = "UPDATE exam_attempts SET
last_activity = NOW() WHERE ex_id = ?";
PreparedStatement updateActivityPst =
[Link](updateLastActivityQuery);
[Link](1, [Link]());
[Link]();

} catch (SQLException e) {
[Link]();
[Link]("Error saving answer to database: " +
[Link]());
} finally {
if (con != null) {
try {
[Link]();
} catch (SQLException e) {
[Link]();
}
}
}
}

private void showNextQuestion() {


if (currentExamSessionData != null && currentQuestionIndexInExam <
[Link]().size() - 1) {
saveCurrentQuestionAnswer(); // Save current question's answer before
moving
showQuestion(currentQuestionIndexInExam + 1);
} else {
// Only show this message if it's genuinely the last question of the
current exam
if (currentExamSessionData != null && currentQuestionIndexInExam ==
[Link]().size() - 1) {
[Link](this, "You have reached the last
question of this exam. Please click 'Finish This Exam' or 'Submit All Exams'.",
"End of Exam", JOptionPane.INFORMATION_MESSAGE);
}
}
}

private void showPreviousQuestion() {


if (currentExamSessionData != null && currentQuestionIndexInExam > 0) {
saveCurrentQuestionAnswer(); // Save current question's answer before
moving
showQuestion(currentQuestionIndexInExam - 1);
}
}

/**
* Saves the currently selected answer for the displayed question.
* Called before moving to next/previous question or finishing exam.
*/
private void saveCurrentQuestionAnswer() {
ButtonModel selected = [Link]();
if (selected != null && currentExamSessionData != null && !
[Link]().isEmpty()) {
int currentQuestionId =
[Link]().get(currentQuestionIndexInExam).getQuestionId
();
saveAnswer(currentQuestionId, [Link]());
}
}

/**
* Updates the enabled state of navigation buttons.
*/
private void updateNavigationButtons() {
if (currentExamSessionData != null && !
[Link]().isEmpty()) {
[Link](currentQuestionIndexInExam > 0);
[Link](currentQuestionIndexInExam <
[Link]().size() - 1);
[Link](!
[Link]()); // Only enable if not already finished
} else {
// Disable all if no exam or no questions
[Link](false);
[Link](false);
[Link](false);
}
}

/**
* Disables all exam-related controls and resets the display.
*/
private void disableAllExamControls() {
[Link]("Exam Not Available / Session Ended.");
[Link]();
for (JRadioButton button : optionButtons) {
[Link]("");
[Link](false);
}
[Link](false);
[Link](false);
[Link](false);
[Link](false); // Also disable "Submit All"
stopExamTimer();
[Link]("Time: --:--");

[Link](false); // Hide retake button


[Link](false);
[Link]("");
}

/**
* Disables controls for the current exam, but doesn't necessarily end the
session.
* Useful when an exam has no questions or access issues.
*/
private void disableExamControlsForCurrentExam() {
[Link]("No questions or exam not accessible.");
[Link]();
for (JRadioButton button : optionButtons) {
[Link]("");
[Link](false);
}
[Link](false);
[Link](false);
[Link](false);
stopExamTimer();
[Link]("Time: --:--");
[Link](false); // Disable for this specific exam
[Link]("");
}

/**
* Enables exam controls (radio buttons, navigation).
*/
private void enableExamControls() {
for (JRadioButton button : optionButtons) {
[Link](true);
}
updateNavigationButtons();
}

/**
* Resets the question display area.
*/
private void resetQuestionDisplay() {
[Link]("No exam questions to display.");
[Link]();
for (JRadioButton button : optionButtons) {
[Link]("");
[Link](false);
}
}

/**
* Manages finishing the currently active exam.
*/
private void finishCurrentExam() {
if (currentExamSessionData == null || [Link]())
{
[Link](this, "This exam has already been
finished or is not active.", "Info", JOptionPane.INFORMATION_MESSAGE);
return;
}

int confirm = [Link](this, // Corrected method name


"Are you sure you want to finish '" +
[Link]() + "'?\n" +
"You cannot change answers for this exam after finishing.",
"Confirm Finish Exam", JOptionPane.YES_NO_OPTION,
JOptionPane.QUESTION_MESSAGE);

if (confirm == JOptionPane.YES_OPTION) {
saveCurrentQuestionAnswer(); // Save the last answer
stopExamTimer(); // Stop the timer for this exam
calculateAndSaveAttemptScore(currentExamSessionData);
[Link](); // Mark as completed in memory

[Link](this, "Exam '" +


[Link]() + "' finished successfully!\n" +
"Your score: " + [Link]() + " out of "
+ [Link](), "Exam Finished",
JOptionPane.INFORMATION_MESSAGE);

// After finishing an exam, the student might want to request a retake


for *this* exam.
// So, make the retake button visible and update its status
[Link](true);
updateRetakeRequestButtonStatus(); // Check status for THIS exam

// Move to the next exam in the session, or end session and redirect
// loadAndDisplayExamAtIndex will handle the final redirect to
dashboard if no more exams
loadAndDisplayExamAtIndex(currentExamIndexInSession + 1);
}
}

/**
* Calculates the score for a given exam attempt and saves it to the database.
* Also updates `is_completed` and `end_time` in `exam_attempts`.
* Increments `attempts_taken` in `student_exam_access` (or updates it based on
count).
*/
private void calculateAndSaveAttemptScore(ExamSessionData sessionData) {
int score = 0;
int totalPossibleMarks = 0;
Map<Integer, String> studentAnswers =
[Link]([Link](), new HashMap<>());

Connection con = null; // Declare con here


try {
con = [Link]();
// Disable auto-commit for batch operations
[Link](false);

String insertResultQuery = "INSERT INTO results (student_id, exam_id,


exam_title, question_id, selected_answer, correct_answer, marks_awarded,
attempt_time, attempt_id) VALUES (?, ?, ?, ?, ?, ?, ?, NOW(), ?) " +
"ON DUPLICATE KEY UPDATE selected_answer =
VALUES(selected_answer), correct_answer = VALUES(correct_answer), marks_awarded =
VALUES(marks_awarded), attempt_time = NOW()";
PreparedStatement resultPst = [Link](insertResultQuery);

for (Question q : [Link]()) {


String studentSelectedOption =
[Link]([Link]());
int marksAwarded = 0;

if (studentSelectedOption != null &&


[Link]([Link]())) {
marksAwarded = [Link]();
score += marksAwarded;
}
totalPossibleMarks += [Link]();

[Link](1, studentId);
[Link](2, [Link]());
[Link](3, [Link]());
[Link](4, [Link]());
[Link](5, studentSelectedOption != null ?
studentSelectedOption : "N/A");
[Link](6, [Link]());
[Link](7, marksAwarded);
[Link](8, [Link]());
[Link](); // Add to batch for efficiency
}

[Link](); // Execute all inserts/updates for results

// Update exam_attempts record


String updateAttemptQuery = "UPDATE exam_attempts SET score = ?,
total_possible_marks = ?, is_completed = 1, end_time = NOW() WHERE ex_id = ?";
PreparedStatement updateAttemptPst =
[Link](updateAttemptQuery);
[Link](1, score);
[Link](2, totalPossibleMarks);
[Link](3, [Link]());
[Link]();

// Update attempts_taken in student_exam_access


// This query counts all completed attempts for this student and exam
and updates
String updateStudentExamAccessAttempts = "UPDATE student_exam_access
SET attempts_taken = (SELECT COUNT(*) FROM exam_attempts WHERE student_id = ? AND
exam_id = ? AND is_completed = 1) WHERE student_id = ? AND exam_id = ?";
PreparedStatement updateAccessPst =
[Link](updateStudentExamAccessAttempts);
[Link](1, studentId);
[Link](2, [Link]());
[Link](3, studentId);
[Link](4, [Link]());
[Link]();

[Link](); // Commit the transaction

} catch (SQLException e) {
[Link]();
try {
if (con != null) [Link](); // Rollback on error
} catch (SQLException ex) {
[Link]("Rollback failed: " + [Link]());
}
[Link](this, "Database error saving score or
updating attempt: " + [Link](), "DB Error", JOptionPane.ERROR_MESSAGE);
} finally {
if (con != null) {
try {
[Link](true); // Re-enable auto-commit
[Link]();
} catch (SQLException e) {
[Link]();
}
}
}
}

/**
* Checks if an exam is already marked as completed in the database for the
given student.
*/
private boolean isExamCompletedInDB(int studentId, int examId) {
String query = "SELECT COUNT(*) FROM exam_attempts WHERE student_id = ? AND
exam_id = ? AND is_completed = 1";
Connection con = null;
try {
con = [Link]();
PreparedStatement pst = [Link](query);
[Link](1, studentId);
[Link](2, examId);
ResultSet rs = [Link]();
if ([Link]()) {
return [Link](1) > 0;
}
} catch (SQLException e) {
[Link]();
[Link]("Error checking exam completion status in DB: " +
[Link]());
} finally {
if (con != null) {
try {
[Link]();
} catch (SQLException e) {
[Link]();
}
}
}
return false;
}

/**
* Starts the exam timer.
* @param durationMinutes The duration of the exam in minutes.
*/
private void startExamTimer(int durationMinutes) {
stopExamTimer(); // Ensure any previous timer is stopped

secondsRemaining = durationMinutes * 60;


timerRunning = true;
timer = new Timer();
[Link](new TimerTask() {
@Override
public void run() {
[Link](() -> { // Update UI on EDT
if (secondsRemaining > 0) {
secondsRemaining--;
int minutes = secondsRemaining / 60;
int seconds = secondsRemaining % 60;
[Link]([Link]("Time: %02d:%02d",
minutes, seconds));
} else {
timerExpired();
}
});
}
}, 0, 1000); // Delay 0, repeat every 1 second
}

/**
* Stops the exam timer.
*/
private void stopExamTimer() {
if (timer != null) {
[Link]();
[Link]();
timer = null;
}
timerRunning = false;
}

/**
* Handles what happens when the timer runs out.
*/
private void timerExpired() {
stopExamTimer();
[Link](this, "Time's up for '" +
[Link]() + "'! Your current exam will be submitted.",
"Time Expired", JOptionPane.WARNING_MESSAGE);
finishCurrentExam(); // Automatically finish the current exam
}

/**
* Submits all remaining exams in the session that haven't been completed yet.
*/
private void submitAllRemainingExams() {
int confirm = [Link](this,
"Are you sure you want to submit all remaining exams in this
session?\n" +
"Any unattempted or unfinished exams will be marked as
submitted with current progress.",
"Confirm Submit All Exams", JOptionPane.YES_NO_OPTION,
JOptionPane.QUESTION_MESSAGE);

if (confirm == JOptionPane.YES_OPTION) {
// First, finish the currently active exam if any
if (currentExamSessionData != null && !
[Link]()) {
saveCurrentQuestionAnswer(); // Save the last answer
stopExamTimer();
calculateAndSaveAttemptScore(currentExamSessionData);
[Link]();
}

// Iterate through the remaining exams in the session and mark them as
completed
// This is crucial for exams that were skipped or not fully attempted
for (int i = 0; i < [Link](); i++) {
int examIdToFinish = [Link](i);
// Only process if it's NOT the current exam (which was handled
above)
// AND it's not already completed in the DB.
if (examIdToFinish != [Link]() ||
[Link]()) {
if (!isExamCompletedInDB(studentId, examIdToFinish)) {
// Create a dummy session data for uncompleted exams to
save their state
ExamSessionData dummySessionData = new
ExamSessionData(examIdToFinish);
loadExamDetailsForCurrentSessionData(dummySessionData); //
Load details to get title/duration

[Link](getOrCreateAttemptId(studentId, examIdToFinish)); //
Get or create attempt
// No need to load questions if no answers were selected,
as score will be 0.
// However, if you want totalPossibleMarks to be accurate
for unattempted exams,
// you would need to load questions here. For now, it's
fine for score 0.
if (![Link]().isEmpty()) { // Ensure
questions are loaded for total marks
loadQuestionsForCurrentExam(dummySessionData);
}
calculateAndSaveAttemptScore(dummySessionData); // Mark as
completed with current (likely 0) score
[Link]();
[Link]("Force-submitted exam: " +
[Link]() + " due to 'Submit All'.");
}
}
}
[Link](this, "All exams in this session have
been submitted.", "Session Concluded", JOptionPane.INFORMATION_MESSAGE);
redirectToDashboard(); // Always redirect to dashboard after submitting
all
}
}

/**
* Handles requesting a retake for the current exam.
* This method is only visible if the exam is already completed.
*/
private void requestRetake() {
if (currentExamSessionData == null) {
[Link](this, "No active exam to request retake
for.", "Error", JOptionPane.WARNING_MESSAGE);
return;
}

int examId = [Link]();


String examTitle = [Link]();

int confirm = [Link](this,


"<html>Are you sure you want to request a retake for <b>" +
examTitle + "</b>?<br>" +
"An examiner will review your request.</html>",
"Confirm Retake Request", JOptionPane.YES_NO_OPTION,
JOptionPane.QUESTION_MESSAGE);

if (confirm == JOptionPane.YES_OPTION) {
Connection con = null;
try {
con = [Link]();
// Check for existing pending request
String checkPendingQuery = "SELECT COUNT(*) FROM
exam_retake_requests WHERE student_id = ? AND exam_id = ? AND status = 'Pending'";
PreparedStatement checkPst =
[Link](checkPendingQuery);
[Link](1, studentId);
[Link](2, examId);
ResultSet rs = [Link]();
if ([Link]() && [Link](1) > 0) {
[Link](this, "You already have a pending
retake request for '" + examTitle + "'. Please wait for examiner's decision.",
"Request Already Pending", JOptionPane.INFORMATION_MESSAGE);
return;
}

// Insert new retake request


String insertRequestQuery = "INSERT INTO exam_retake_requests
(student_id, exam_id, request_date, status) VALUES (?, ?, NOW(), 'Pending')";
PreparedStatement pst = [Link](insertRequestQuery);
[Link](1, studentId);
[Link](2, examId);
[Link]();

[Link](this, "Your retake request for '" +


examTitle + "' has been submitted to the examiner. Please await approval.",
"Request Submitted", JOptionPane.INFORMATION_MESSAGE);
updateRetakeRequestButtonStatus(); // Update UI to reflect
"Pending" status
} catch (SQLException e) {
[Link]();
[Link](this, "Error submitting retake
request: " + [Link](), "Database Error", JOptionPane.ERROR_MESSAGE);
} finally {
if (con != null) {
try {
[Link]();
} catch (SQLException e) {
[Link]();
}
}
}
}
}

/**
* Updates the visibility and text of the retake request button and status
label.
*/
private void updateRetakeRequestButtonStatus() {
if (currentExamSessionData == null) {
[Link](false);
[Link](false);
[Link]("");
return;
}
int examId = [Link]();
boolean examCompleted = isExamCompletedInDB(studentId, examId);

if (examCompleted) {
[Link](true);
Connection con = null;
try {
con = [Link]();
String query = "SELECT status FROM exam_retake_requests WHERE
student_id = ? AND exam_id = ? ORDER BY request_date DESC LIMIT 1";
PreparedStatement pst = [Link](query);
[Link](1, studentId);
[Link](2, examId);
ResultSet rs = [Link]();

if ([Link]()) {
String status = [Link]("status");
[Link]("Retake Status: " + status);
switch (status) {
case "Pending":
[Link](false); // Can't request
if already pending
break;
case "Granted":
[Link](false); // Already
granted, student should be able to take it from dashboard (auto-start)
[Link]("Retake Status:
Approved!"); // More user-friendly
break;
case "Denied":
[Link](true); // Can request
again
break;
default:
[Link](true); // Fallback to
allow request
break;
}
} else {
[Link](""); // No previous request
[Link](true); // Allow requesting
retake
}
} catch (SQLException e) {
[Link]();
[Link]("Error checking retake status.");
[Link](false);
} finally {
if (con != null) {
try {
[Link]();
} catch (SQLException e) {
[Link]();
}
}
}
} else {
// Exam not completed, retake not applicable
[Link](false);
[Link](false);
[Link]("");
}
}

/**
* Redirects the student back to the main dashboard.
*/
private void redirectToDashboard() {
stopExamTimer(); // Ensure timer is stopped
disableAllExamControls(); // Clean up UI state
if (parentDashboard != null) {
// Restore default close operation.
// [Link](JFrame.EXIT_ON_CLOSE); //
This is handled by showSelectExamPanel

// Important: Call the new method in StudentDashboard to handle the


switch
[Link]();
}
}

// --- Inner Classes for Data Modeling ---

/**
* Represents a single question in an exam.
*/
private static class Question {
private int questionId;
private String questionText;
private String optionA, optionB, optionC, optionD;
private String correctAnswer;
private int marks;

public Question(int questionId, String questionText, String optionA, String


optionB, String optionC, String optionD, String correctAnswer, int marks) {
[Link] = questionId;
[Link] = questionText;
[Link] = optionA;
[Link] = optionB;
[Link] = optionC;
[Link] = optionD;
[Link] = correctAnswer;
[Link] = marks;
}

// Getters
public int getQuestionId() { return questionId; }
public String getQuestionText() { return questionText; }
public String getOptionA() { return optionA; }
public String getOptionB() { return optionB; }
public String getOptionC() { return optionC; }
public String getOptionD() { return optionD; }
public String getCorrectAnswer() { return correctAnswer; }
public int getMarks() { return marks; }
}
/**
* Holds all data pertinent to the current exam being taken in the session.
*/
private static class ExamSessionData {
private int examId;
private String title;
private int duration; // in minutes
private int questionLimit;
private boolean shuffleQuestions;
private boolean isAllowed; // From student_exam_access
private List<Question> questions;
private int attemptId;
private boolean completed;
private int score;
private int totalPossibleMarks;

public ExamSessionData(int examId) {


[Link] = examId;
[Link] = new ArrayList<>();
[Link] = false; // Default to not completed
}

// Getters and Setters


public int getExamId() { return examId; }
public String getTitle() { return title; }
public void setTitle(String title) { [Link] = title; }
public int getDuration() { return duration; }
public void setDuration(int duration) { [Link] = duration; }
public int getQuestionLimit() { return questionLimit; }
public void setQuestionLimit(int questionLimit) { [Link] =
questionLimit; }
public boolean isShuffleQuestions() { return shuffleQuestions; }
public void setShuffleQuestions(boolean shuffleQuestions)
{ [Link] = shuffleQuestions; }
public boolean isAllowed() { return isAllowed; }
public void setAllowed(boolean allowed) { isAllowed = allowed; }
public List<Question> getQuestions() { return questions; }
public void setQuestions(List<Question> questions) { [Link] =
questions; }
public int getAttemptId() { return attemptId; }
public void setAttemptId(int attemptId) { [Link] = attemptId; }
public boolean isCompleted() { return completed; }
public void markCompleted() { [Link] = true; } // Method to set
completion status
public int getScore() { return score; }
public void setScore(int score) { [Link] = score; }
public int getTotalPossibleMarks() { return totalPossibleMarks; }
public void setTotalPossibleMarks(int totalPossibleMarks)
{ [Link] = totalPossibleMarks; }
}
}

You might also like