Random Exams

import java.util.*;

interface ScorableWork
{
    Integer getScore();
    int getStudentId();
}

class StudentComparator implements Comparator<ScorableWork> {
    public int compare(final ScorableWork sw1, final ScorableWork sw2) {
        final int score1 = sw1.getScore();
        final int score2 = sw2.getScore();
        if (score1 > score2) return -1;
        else if (score1 < score2) return 1;
        else {
            final int id1 = sw1.getStudentId();
            final int id2 = sw2.getStudentId();
            if (id1 < id2) return -1;
            else if (id1 > id2) return 1;
            else return 0;
        }
    }
}

class StudentAnswers implements ScorableWork
{
    private final int studentId;
    private final int[] answerArray;
    private Integer score = null;
    public StudentAnswers(final int studentId, final int[] answerArray) {
        this.studentId = studentId;
        this.answerArray = answerArray;
    }
    public StudentAnswers gradeUsing(AnswerKey key) {
        score = key.countNumberCorrect(answerArray);
        return this;
    }
    public int getStudentId() {
        return studentId;
    }
    public Integer getScore() {
        return score;
    }
    public Integer getPercentCorrect() {
        if (score == null) return null;
        return (score * 100) / answerArray.length;
    }
    public String toString() {
        final StringBuilder sb = new StringBuilder("Student #" + studentId + ":");
        for (int answer : answerArray) {
            sb.append(" " + answer);
        }
        sb.append(" " + getPercentCorrect() + "%");
        return sb.toString() + "\n";
    }
}

class AnswerKey
{
    private final int[] answerArray;
    public AnswerKey(final int[] answerArray) {
        this.answerArray = answerArray;
    }
    public int countNumberCorrect(final int[] answerArray) {
        if (answerArray == null || this.answerArray == null) return 0;
        final int n = Math.min(answerArray.length, this.answerArray.length);
        int score = 0;
        for (int i = 0; i < n; i++) {
            if (this.answerArray[i] == answerArray[i]) score++;
        }
        return score;
    }
    public String toString() {
        final StringBuilder sb = new StringBuilder("Answer Key:");
        for (int answer : answerArray) {
            sb.append(" " + answer);
        }
        return sb.toString() + "\n";
    }
}

class Exam
{
    private final static StudentComparator comparator = new StudentComparator();
    private final int examId;
    private final AnswerKey answerKey;
    private final List<StudentAnswers> studentAnswersList;
    private final int numQuestions;
    private final int numStudents;
    
    public Exam(final int examId, final int[][] answerGrid) {
        this.examId = examId;
        numQuestions = answerGrid[0].length;
        numStudents = answerGrid.length - 1;
        answerKey = new AnswerKey(answerGrid[0]);
        studentAnswersList = new ArrayList<StudentAnswers>();
        for (int i = 0; i < numStudents; i++) {
            final int studentId = i + 1;
            final StudentAnswers studentAnswers = new StudentAnswers(studentId, answerGrid[studentId]);
            studentAnswersList.add(studentAnswers.gradeUsing(answerKey));
        }
        Collections.sort(studentAnswersList, comparator);
    }
    public String toString() {
        final StringBuilder sb = new StringBuilder(answerKey.toString());
        for (StudentAnswers studentAnswers: studentAnswersList) {
            sb.append(studentAnswers.toString());
        }
        return sb.toString();
    }
}

abstract class ExamAnswerCube
{
    protected final int
        numExams,
        numChoices,
        numQuestions,
        numStudents;

    private final int[][][] answerCube;
    private final List<Exam> exams = new ArrayList<Exam>();

    public ExamAnswerCube(final int numChoices, final int numQuestions, final int numStudents, final int numExams) {
        this.numChoices = numChoices;
        this.numQuestions = numQuestions;
        this.numStudents = numStudents;
        this.numExams = numExams;
        answerCube = getAllAnswerSetsForAllExams();
        int examId = 0;
        for (int[][] answerGrid : answerCube)
            exams.add(new Exam(++examId, answerGrid));
    }

    /** Returns a 3-dimensional array of answers.
     * 
     *  First dimension:
     *  answerSets[0] is the set of answer sets for the first exam.
     *  answerSets[1] is the set of answer sets for the second exam.
     *  etc.
     * 
     *  Second dimension:
     *  answerSets[i][0] is the answer key for an exam.
     *  answerSets[i][j], j > 0, is the jth student's set of answers.
     * 
     *  Third dimension:
     *  answerSets[i][j][0] is an answer to the first question of an exam.
     *  answerSets[i][j][1] is an answer to the second question of an exam.
     *  etc.
     */
    private int[][][] getAllAnswerSetsForAllExams() {
        int[][][] answerSets = new int[numExams][][];
        for (int i = 0; i < numExams; i = i + 1) {
            answerSets[i] = getAllAnswerSetsForOneExam();
        }
        return answerSets;
    }
    private int[][] getAllAnswerSetsForOneExam() {
        int[][] answerSets = new int[numStudents + 1][];
        answerSets[0] = getAnswerKey();
        for (int i = 1; i <= numStudents; i = i + 1) {
            answerSets[i] = getStudentAnswers();
        }
        return answerSets;
    }
    protected abstract int[] getAnswerKey();
    protected abstract int[] getStudentAnswers();
    public String toString() {
        StringBuilder sb = new StringBuilder();
        for (Exam exam : exams) sb.append(exam.toString() + "\n");
        return sb.toString();
    }
}

final class RandomExamAnswerCube extends ExamAnswerCube
{
    public RandomExamAnswerCube(final int numChoices, final int numQuestions, final int numStudents, final int numExams) {
        super(numChoices, numQuestions, numStudents, numExams);
    }
    protected int[] getAnswerKey() {
        return getRandomAnswers();
    }
    protected int[] getStudentAnswers() {
        return getRandomAnswers();
    }
    private int[] getRandomAnswers() {
        // An answer is an integer in the range [0, numChoices).
        // answerSet[0] is the answer for the first question.
        // answerSet[1] is the answer for the second question.
        // etc.
        int[] answerSet = new int[numQuestions];
        for (int i = 0; i < numQuestions; i = i + 1) {
            answerSet[i] = (int)(Math.random() * numChoices);
        }
        return answerSet;
    }
}

public class MyClass
{
    public static void main(final String args[]) {

        final int
            n = args.length,
            numChoices = n > 0 ? Integer.parseInt(args[0]) : 5,
            numQuestions = n > 1 ? Integer.parseInt(args[1]) : 10, 
            numStudents = n > 2 ? Integer.parseInt(args[2]) : 25,
            numExams = n > 3 ? Integer.parseInt(args[3]) : 1;

        ExamAnswerCube cube = new RandomExamAnswerCube(numChoices, numQuestions, numStudents, numExams);
        System.out.println(cube);
    }
}