main.java.vasolsim.common.file.__NONUSEDREFERENCE_VaSOLSimExam.java Source code

Java tutorial

Introduction

Here is the source code for main.java.vasolsim.common.file.__NONUSEDREFERENCE_VaSOLSimExam.java

Source

/*
 * Copyright (c) 2015.
 *
 *     This file is part of VaSOLSim.
 *
 *     VaSOLSim is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 *
 *     VaSOLSim is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU General Public License for more details.
 *
 *     You should have received a copy of the GNU General Public License
 *     along with VaSOLSim.  If not, see <http://www.gnu.org/licenses/>.
 */

package main.java.vasolsim.common.file;

import main.java.vasolsim.common.*;

import org.apache.commons.lang3.exception.ExceptionUtils;

import org.w3c.dom.Document;
import org.w3c.dom.Element;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.regex.Pattern;

/**
 * @author guyfleeman
 * @date 6/25/14 <p></p>
 */
public class __NONUSEDREFERENCE_VaSOLSimExam {
    public static int initialQuestionArraySize;

    //root depth
    public static final String xmlRootElementName = "vssroot";

    //root+ depth
    public static final String xmlInfoElementName = "information";

    //root++ depth (information+)
    public static final String xmlTestNameElementName = "testName";
    public static final String xmlAuthorElementName = "author";
    public static final String xmlSchoolNameElementName = "school";
    public static final String xmlClassPeriodElementName = "class";
    public static final String xmlDateElementName = "date";

    //root+ depth
    public static final String xmlSecurityElementName = "sec";

    //root++ depth (sec+)
    public static final String xmlHashAlgorithmElementName = "hashAlgorithm";
    public static final String xmlValidationHashElementName = "encryptedValidationHash";
    public static final String xmlValidationPIVElementName = "parametricIV";
    public static final String xmlEncryptingQuestionsElementName = "encryptingQuestions";
    public static final String xmlReportingStatisticsElementName = "statisticsReporting";
    public static final String xmlStandaloneSMTPStatisticsReportingElementName = "statisticsStandaloneParadigm";
    public static final String xmlEncryptingStatisticsElementName = "encryptingStatistics";
    public static final String xmlEncryptedStatisticsReportingEmailAddressElementName = "encryptedStatisticsReportingEmail";
    public static final String xmlEncryptedStatisticsReportingEmailPasswordElementName = "encryptedStatisticsReportingEmailPassword";
    public static final String xmlReportingNotificationsElementName = "notificationReporting";
    public static final String xmlStandaloneSMTPNotificationReportingElementName = "notificationStandaloneParadigm";
    public static final String xmlEncryptedNotificationReportingEmailAddressElementName = "encryptedNotificationReportingEmail";
    public static final String xmlEncryptedNotificationReportingEmailPasswordElementName = "encryptedNotificationReportingEmailPassword";

    //root+ depth
    public static final String xmlQuestionSetElementName = "questionSet";

    //root++ depth (questionSet+)
    public static final String xmlQuestionSetIDElementName = "setID";
    public static final String xmlQuestionSetUsingResourceElementName = "usingResource";
    public static final String xmlQuestionSetResourceParadigmElementName = "resourceParadigm";
    public static final String xmlQuestionSetResourceDataElementName = "resourceData";
    public static final String xmlQuestionGroupingElementName = "questionGrouping";

    //root+++ depth (questionSet++, questionGrouping+)
    public static final String xmlQuestionGroupingIDElementName = "questionID";
    public static final String xmlQuestionGroupingQuestionElementName = "QandA";
    public static final String xmlQuestionGroupingEncryptedAnswerHashElementName = "encryptedAnswerHash";
    public static final String xmlQuestionGroupingAnswerChoiceElementName = "answerChoice";
    public static final String xmlQuestionGroupingIsScramblingAnswers = "scramblingAnswers";
    public static final String xmlQuestionGroupingDoesAnswerOrderMater = "orderMatters";

    //root++++ depth (questionSet+++, questionGrouping++, answer+)
    public static final String xmlAnswerID = "answerID";
    public static final String xmlAnswerChoiceID = "answerChoiceID";
    public static final String xmlAnswerChoice = "answer";

    protected static byte[] IV = new byte[16];
    protected static short algorithmExpectedKeyLengthInBytes = 16;
    protected static String serviceProviderInterface = "AES/CBC/PKCS5Padding";
    protected static String serviceProvider = "SunJCE";
    protected static String algorithm = "AES";
    protected static String charsetEncoding = "UTF-8";
    protected static String validEmailRegex = "^[_A-Za-z0-9-\\+]+(\\.[_A-Za-z0-9-]+)*@"
            + "[A-Za-z0-9-]+(\\.[A-Za-z0-9]+)*(\\.[A-Za-z]{2,})$";
    protected static String indentationKey = "{http://xml.apache.org/xslt}indent-amount";
    protected static SecureRandom secureRandom;
    protected static Pattern validEmailPattern;

    private static final String ERROR_MESSAGE_FILE_ALREADY_EXISTS = "File already exists and overwrite not permitted.";
    private static final String ERROR_MESSAGE_FILE_NOT_FOUND_AFTER_INTERNAL_CHECK = "File not found after internal check. Try running as admin. Is this a bug or a file permissions error?";
    private static final String ERROR_MESSAGE_COULD_NOT_CREATE_DIRS = "Could not create file directory. Do you have permission to create a directory on your machine? "
            + "(try running the jar as admin)";
    private static final String ERROR_MESSAGE_COULD_NOT_CREATE_FILE = "Could not create file. Do you have permission to create a file on your machine? "
            + "(try running the jar as admin)";
    private static final String ERROR_MESSAGE_CREATE_FILE_EXCEPTION = "File creation internal exception. Do you have permission to create a file on your machine? Could this "
            + "be" + " " + "a bug? (try running the jar as admin)";
    private static final String ERROR_MESSAGE_GENERIC_CRYPTO = "This message being shown for debugging purposes. If the problem persists, please paste the following "
            + "information into an email for the project manager.";
    private static final String ERROR_MESSAGE_INTERNAL_TRANSFORMER_CONFIGURATION = "This message being "
            + "shown for "
            + "debugging purposes. If the problem persists, please paste the following information into an email for "
            + "the " + "project manager. COULD NOT WRITE XML FILE. INTERNAL CONFIGURATION ERROR.";
    private static final String ERROR_MESSAGE_INTERNAL_TRANSFORMER_EXCEPTION = "This message being " + "shown for "
            + "debugging "
            + "purposes. If the problem persists, please paste the following information into an email for the project"
            + " " + "manager. COULD NOT WRITE XML FILE. INTERNAL TRANSFORMER EXCEPTION";
    private static final String ERROR_MESSAGE_VALIDATION_KEY_NOT_PROVIDED = "VALIDATION KEY NOT "
            + "PROVIDED AND IS " + "REQUIRED";
    private static final String ERROR_MESSAGE_STANDALONE_STATS_PASSWORD_NOT_PROVIDED = "Statistics requested "
            + "with " + "standalone paradigm and no password provided.";
    private static final String ERROR_MESSAGE_STANDALONE_NOTIFICATION_PASSWORD_NOT_PROVIDED = "Notification "
            + "requested" + " " + "with standalone paradigm and no password provided.";
    private static final String ERROR_MESSAGE_INTERNAL_XML_PARSER_INITIALIZATION_EXCEPTION = "This message being "
            + "shown "
            + "for debugging purposes. If the problem persists, please paste the following information into an email "
            + "for " + "the project manager.";

    static {
        secureRandom = new SecureRandom();
        secureRandom.nextBytes(IV);

        validEmailPattern = Pattern.compile(validEmailRegex);
    }

    public String getTestName() {
        return testName;
    }

    public void setTestName(String testName) {
        this.testName = testName;
    }

    public static enum HashType {
        SHA256, SHA512
    }

    private String testName = "undefined name";
    private String testPublisher = "undefined author";
    private String schoolName = "undefined school";
    private String className = "undefined class";
    private String publishDate = "undefined date";

    private boolean isEncryptingFullQuestions = false;
    private byte[] validationKey = new byte[] {};
    private byte[] encryptedValidationHash = new byte[] {};
    private byte[] decryptedValidationHash = new byte[] {};
    private HashType hashAlgorithm = HashType.SHA256;

    private boolean isReportingStatistics = false;
    private boolean isReportingStatisticsUsingStandaloneEmailParadigm = false;
    private boolean isReportingStatisticsEncrypted = true;
    private boolean isNotifyingCompletion = false;
    private boolean isNotifyingCompletionUsingStandaloneEmailParadigm = false;
    private byte[] encryptedStatisticsEmail = new byte[] {};
    private byte[] encryptedStatisticsEmailPassword = new byte[] {};
    private byte[] decryptedStatisticsEmailPassword = new byte[] {};
    private byte[] encryptedNotificationEmail = new byte[] {};
    private byte[] encryptedNotificationEmailPassword = new byte[] {};
    private byte[] decryptedNotificationEmailPassword = new byte[] {};
    private String decryptedStatisticsEmail = "";
    private String decryptedNotificationEmail = "";

    private Cipher encryptionCipher;
    private Cipher decryptionCipher;

    private ArrayList<QuestionSet> questions = new ArrayList<QuestionSet>(initialQuestionArraySize);

    @SuppressWarnings("unused")
    private final byte __BEGIN_ENCAPSULATED_FIELDS = 0x00;

    /**
     * Returns the publisher or author of the test.
     *
     * @return publisher
     */
    public String getTestPublisher() {
        return testPublisher;
    }

    /**
     * Sets the publisher or author of the test.
     *
     * @param testPublisher publisher
     */
    public void setTestPublisher(String testPublisher) {
        this.testPublisher = testPublisher;
    }

    /**
     * Gets the name of the school form which the test was made
     *
     * @return school name
     */
    public String getSchoolName() {
        return schoolName;
    }

    /**
     * Sets the school form which the test will be made
     *
     * @param schoolName school name
     */
    public void setSchoolName(String schoolName) {
        this.schoolName = schoolName;
    }

    /**
     * Gets the name of the class for which the test was made
     *
     * @return class name
     */
    public String getClassName() {
        return className;
    }

    /**
     * Sets the name of the class for which the test will be made
     *
     * @param className class name
     */
    public void setClassName(String className) {
        this.className = className;
    }

    /**
     * Get the date of publication
     *
     * @return publication date
     */
    public String getPublishDate() {
        return publishDate;
    }

    /**
     * Set the date of publication
     *
     * @param publishDate publication date
     */
    public void setPublishDate(String publishDate) {
        this.publishDate = publishDate;
    }

    /**
     * Directs the writer to encrypt the entire QandA, not just the correct answer, when the test is exported.
     */
    public void fullyEncryptQuestions() {
        isEncryptingFullQuestions = true;
    }

    /**
     * Directs the writer to encrypt only the answers, saving time, when the test is exported.
     */
    public void partiallyEncryptQuestions() {
        isEncryptingFullQuestions = false;
    }

    /**
     * @return if the writer is encrypting the entire QandA
     */
    public boolean isEncryptingFullQuestions() {
        return isEncryptingFullQuestions;
    }

    /**
     * Sets the key that will be used to lock and protect the file. Then generates and updates the key hash.
     *
     * @param key key
     */
    public void setValidationKey(String key) {
        setValidationKey(key.getBytes());
    }

    /**
     * Sets the key that will be used to lock and protect the file. Then generates and updates the key hash.
     *
     * @param key key
     */
    public void setValidationKey(byte[] key) {
        this.validationKey = key;
        decryptedValidationHash = generateHash(validationKey, HashType.SHA256);
    }

    /**
     * Returns the key that will be used to lock and protect the file. Will not return the key from a read file.
     *
     * @return key
     */
    public byte[] getValidationKey() {
        return validationKey;
    }

    /**
     * Returns the hash generated from the key with the parameters specified by the static fields.
     *
     * @return hash
     */
    public byte[] getDecryptedValidationHash() {
        return decryptedValidationHash;
    }

    /**
     * Returns the hash of the key encrypted by the encryption DEFAULT_ENCRYPTION_ALGORITHM initialized by the hash.
     * This one way process locks file's sensitive data from the students who might try to poke around. This value will
     * not be initialized until the file is written, or the crypto is forced active and called prior to writing;
     *
     * @return encrypted validation hash.
     */
    public byte[] getEncryptedValidationHash() {
        return encryptedValidationHash;
    }

    /**
     * Returns the DEFAULT_ENCRYPTION_ALGORITHM used to hash the key
     *
     * @return hash DEFAULT_ENCRYPTION_ALGORITHM
     */
    public HashType getHashAlgorithm() {
        return hashAlgorithm;
    }

    /**
     * Returns if the test will report statistics when the student completes it.
     *
     * @return will statistics be reported
     */
    public boolean isReportingStatistics() {
        return isReportingStatistics;
    }

    /**
     * Sets if the test will report statistics when the student completes it. If this is set to true,
     * a statistics email
     * address must be provided. After the test finishes, the data regarding time taken, date taken, questions
     * answered,
     * and correct answers will be built into an email body. An email will then be sent to the given email address so
     * the teacher/admin side client can read all of the emails and assemble reporting statistics for the student body
     * that took the test. This feature is included because I know it can be difficult for a teacher to get access to a
     * server to host test data. I believe this is an easy way to compile stats without requiring teachers to have
     * access to a reliable server. If this is set to true the student will provide his/her email at the beginning of
     * the test and the program will automatically and securely log into the students email and send the statistics. It
     * is strongly recommended that you not disable the encryption of the statistics when they are sent. This protects
     * the student's email, as well as the integrity of the collected information.
     *
     * @param isReportingStatistics
     */
    public void setReportingStatistics(boolean isReportingStatistics) {
        this.isReportingStatistics = isReportingStatistics;
    }

    /**
     * Returns if the test will be using a standalone email paradigm for reporting test statistics.
     *
     * @return if the test will be using a standalone email paradigm
     */
    public boolean isReportingStatisticsUsingStandaloneEmailParadigm() {
        return isReportingStatisticsUsingStandaloneEmailParadigm;
    }

    /**
     * Sets if the test will be using a standalone email paradigm for reporting test statistics. A standalone paradigm
     * means that the student will not have to provide any email information for the email to be sent. Instead, the
     * password for the receiving email account must be provided. Then, then the student completes the test, the
     * receiving email account will send an email to itself. This means however that the email's password must be sent
     * in the file since the student is no longer providing this information.
     * <p/>
     * NOTE: THE STANDALONE EMAIL PARADIGM IS NOT SECURE IF THE TEST WILL BE PUBLISHED ONLINE PUBLICLY IN COMBINATION
     * WITH THE TEST'S PASSWORD. SECURITY ISSUES ARE PREVENTED BY USING A SERVER, BUT THIS PROGRAM IS DESIGNED TO ACT
     * INDEPENDENTLY OF SUCH. IF YOU ARE PUBLISHING THE TEST'S XML FILE ONLINE, DO NOT PUBLISH THE PASSWORD ALONGSIDE
     * IT. The password is encrypted so it cannot be read, but a hacker who knows the tests password and can program in
     * Java could reverse engineer the encryption process to obtain the email address and password. It is expected that
     * this skill will be outside of the scope of high school students.
     *
     * @param isReportingStatisticsUsingStandaloneEmailParadigm if the test will be using a standalone email paradigm
     *                                                          for reporting test statistics
     */
    public void setReportingStatisticsUsingStandaloneEmailParadigm(
            boolean isReportingStatisticsUsingStandaloneEmailParadigm) {
        this.isReportingStatisticsUsingStandaloneEmailParadigm = isReportingStatisticsUsingStandaloneEmailParadigm;
    }

    /**
     * Returns if the statistics being reported will be encrypted. True by default.
     *
     * @return if the statistics being reported will be encrypted
     */
    public boolean isReportingStatisticsEncrypted() {
        return isReportingStatisticsEncrypted;
    }

    /**
     * Sets if the statistics being reported will be encrypted. True by default.
     *
     * @param isReportingStatisticsEncrypted if the statistics being reported will be encrypted
     */
    public void setReportingStatisticsEncrypted(boolean isReportingStatisticsEncrypted) {
        this.isReportingStatisticsEncrypted = isReportingStatisticsEncrypted;
    }

    /**
     * Returns if notification emails will be sent.
     *
     * @return if notification emails will be sent.
     */
    public boolean isNotifyingCompletion() {
        return isNotifyingCompletion;
    }

    /**
     * Sets if notification emails will be sent when a student completes a test. Notifications emails are sent to the
     * notification address, and notify the holder of that address that x student, completed x test. If this is enabled
     * the student must provide email credentials when he/she starts the test. The email will be automatically sent
     * form
     * his/her account. This is not the same as reporting statistics. I assume teachers don't want a bunch of
     * statistics
     * bulking up their email, but they might want a log of who has/hasn't completed the test. Also,
     * this can serve as a
     * log if the teacher doesn't want statistics at all.
     *
     * @param isNotifyingCompletion if notification emails will be sent
     */
    public void setNotifyingCompletion(boolean isNotifyingCompletion) {
        this.isNotifyingCompletion = isNotifyingCompletion;
    }

    /**
     * Returns if the test will be using a standalone email paradigm for reporting test completion notification.
     *
     * @return if the test will be using a standalone email paradigm
     */
    public boolean isNotifyingCompletionUsingStandaloneEmailParadigm() {
        return isNotifyingCompletionUsingStandaloneEmailParadigm;
    }

    /**
     * Sets if the test will be using a standalone email paradigm for reporting test completion. A standalone paradigm
     * means that the student will not have to provide any email information for the email to be sent. Instead, the
     * password for the receiving email account must be provided. Then, then the student completes the test, the
     * receiving email account will send an email to itself. This means however that the email's password must be sent
     * in the file since the student is no longer providing this information.
     * <p/>
     * NOTE: THE STANDALONE EMAIL PARADIGM IS NOT SECURE IF THE TEST WILL BE PUBLISHED ONLINE PUBLICLY IN COMBINATION
     * WITH THE TEST'S PASSWORD. SECURITY ISSUES ARE PREVENTED BY USING A SERVER, BUT THIS PROGRAM IS DESIGNED TO ACT
     * INDEPENDENTLY OF SUCH. IF YOU ARE PUBLISHING THE TEST'S XML FILE ONLINE, DO NOT PUBLISH THE PASSWORD ALONGSIDE
     * IT. The password is encrypted so it cannot be read, but a hacker who knows the tests password and can program in
     * Java could reverse engineer the encryption process to obtain the email address and password. It is expected that
     * this skill will be outside of the scope of high school students.
     *
     * @param isNotifyingCompletionUsingStandaloneEmailParadigm if the test will be using a standalone email paradigm
     *                                                          for reporting test statistics
     */
    public void setNotifyingCompletionUsingStandaloneEmailParadigm(
            boolean isNotifyingCompletionUsingStandaloneEmailParadigm) {
        this.isNotifyingCompletionUsingStandaloneEmailParadigm = isNotifyingCompletionUsingStandaloneEmailParadigm;
    }

    /**
     * Gets decrypted statistics email password
     *
     * @return
     */
    public byte[] getDecryptedStatisticsEmailPassword() {
        return decryptedStatisticsEmailPassword;
    }

    /**
     * Sets decrypted statistics email password
     *
     * @param decryptedStatisticsEmailPassword
     */
    public void setDecryptedStatisticsEmailPassword(byte[] decryptedStatisticsEmailPassword) {
        this.decryptedStatisticsEmailPassword = decryptedStatisticsEmailPassword;
    }

    /**
     * Gets decrypted notification email password
     *
     * @return
     */
    public byte[] getDecryptedNotificationEmailPassword() {
        return decryptedNotificationEmailPassword;
    }

    /**
     * Sets decrypted notification email password
     *
     * @param decryptedNotificationEmailPassword
     */
    public void setDecryptedNotificationEmailPassword(byte[] decryptedNotificationEmailPassword) {
        this.decryptedNotificationEmailPassword = decryptedNotificationEmailPassword;
    }

    /**
     * Gets decrypted statistics email address
     *
     * @return
     */
    public String getDecryptedStatisticsEmail() {
        return decryptedStatisticsEmail;
    }

    /**
     * Sets decrypted statistics email address
     *
     * @param decryptedStatisticsEmail
     */
    public void setDecryptedStatisticsEmail(String decryptedStatisticsEmail) throws VaSolSimException {
        if (!isValidEmail(decryptedStatisticsEmail))
            throw new VaSolSimException("Bad statistics email");

        this.decryptedStatisticsEmail = decryptedStatisticsEmail;
    }

    /**
     * Gets decrypted statistics email address
     *
     * @return
     */
    public String getDecryptedNotificationEmail() {
        return decryptedNotificationEmail;
    }

    /**
     * Sets decrypted notification email address
     *
     * @param decryptedNotificationEmail
     */
    public void setDecryptedNotificationEmail(String decryptedNotificationEmail) throws VaSolSimException {
        if (!isValidEmail(decryptedNotificationEmail))
            throw new VaSolSimException("Bad notification email");

        this.decryptedNotificationEmail = decryptedNotificationEmail;
    }

    @SuppressWarnings("unused")
    private final byte __END_ENCAPSULATED_FIELDS = 0x00;

    /**
     * This function will initialize the internal ciphers that protect the data in the file. This process is done
     * automatically when the write function is called. If access is needed to the encrypted versions of data for
     * whatever reason prior to the write process, OR IF THE KEY IS UPDATED prior to the write process, this function
     * must be called
     *
     * @throws VaSolSimException if the ciphers cannot be initialized, an exception will be thrown
     */
    public void updateCryptoEngine() throws VaSolSimException {
        decryptionCipher = getDecryptionCipher(decryptedValidationHash);
        encryptionCipher = getEncryptionCipher(decryptedValidationHash);
    }

    /**
     * This function uses the initialized ciphers to encrypt properties of the file. If any properties or questions are
     * updated, this function must be called in order to have access to the encrypted data prior to the write process.
     * This function is called by default at the start of the write function. NOTE: if the cryptographic key is
     * updated,
     * updateCryptoEngine() must be called first or this function will encrypt the data based off of the old key.
     * Regardless, the write function will force everything to be updated prior to actually writing to avoid write
     * complications.
     */
    public void updateCryptoProperties() throws VaSolSimException {
        try {
            if (decryptedValidationHash.length > 0)
                encryptedValidationHash = encryptionCipher.doFinal(decryptedValidationHash);

            if (getDecryptedNotificationEmail() != null)
                encryptedNotificationEmail = encryptionCipher.doFinal(getDecryptedNotificationEmail().getBytes());

            if (getDecryptedNotificationEmailPassword().length > 0)
                encryptedNotificationEmailPassword = encryptionCipher
                        .doFinal(getDecryptedNotificationEmailPassword());

            if (getDecryptedStatisticsEmail() != null)
                encryptedStatisticsEmail = encryptionCipher.doFinal(getDecryptedStatisticsEmail().getBytes());

            if (getDecryptedStatisticsEmailPassword().length > 0)
                encryptedStatisticsEmailPassword = encryptionCipher.doFinal(getDecryptedStatisticsEmailPassword());
        } catch (IllegalBlockSizeException e) {
            throw new VaSolSimException(ERROR_MESSAGE_GENERIC_CRYPTO + "\n\nILLEGAL BLOCK SIZE\n" + e.toString()
                    + "\n" + e.getCause() + "\n" + ExceptionUtils.getStackTrace(e), e);
        } catch (BadPaddingException e) {
            throw new VaSolSimException(ERROR_MESSAGE_GENERIC_CRYPTO + "\n\nBAD PADDING\n" + e.toString() + "\n"
                    + e.getCause() + "\n" + ExceptionUtils.getStackTrace(e), e);
        }

    }

    /**
     * Writes a digitized version of the VaSOLSim test, represented by an thisInstance of this class,
     * to a given file in
     * XML format. Will not overwrite by default. Prior to writing, this function will (re)initialize teh Ciphers used
     * to protect the file, and update all protected information accordingly.
     *
     * @param simFile the file on the disk to be written
     *
     * @return if the write operation was successful
     *
     * @throws VaSolSimException thrown if insufficient information to write the file is contained in VaSolSimTest
     *                           object. Please ensure a password is provided to protect answer data and potentially
     *                           email data if test statistics and notifications are reported.
     */
    public boolean write(File simFile) throws VaSolSimException {
        return write(simFile, false);
    }

    /**
     * Writes a digitized version of the VaSOLSim test, represented by an thisInstance of this class,
     * to a given file in
     * XML format. Prior to writing, this function will (re)initialize teh Ciphers used to protect the file, and update
     * all protected information accordingly.
     *
     * @param simFile          the file on the disk to be written
     * @param canOverwriteFile can this method call write over an existing file with content
     *
     * @return if the write operation was successful
     *
     * @throws VaSolSimException thrown if insufficient information to write the file is contained in VaSolSimTest
     *                           object. Please ensure a password is provided to protect answer data and potentially
     *                           email data if test statistics and notifications are reported.
     */
    public boolean write(File simFile, boolean canOverwriteFile) throws VaSolSimException {
        if (simFile.isFile()) {
            if (!canOverwriteFile) {
                throw new VaSolSimException(ERROR_MESSAGE_FILE_ALREADY_EXISTS);
            } else {
                PrintWriter printWriter;
                try {
                    printWriter = new PrintWriter(simFile);
                } catch (FileNotFoundException e) {
                    throw new VaSolSimException(ERROR_MESSAGE_FILE_NOT_FOUND_AFTER_INTERNAL_CHECK);
                }

                printWriter.print("");
                printWriter.close();
            }
        } else {
            if (!simFile.getParentFile().isDirectory() && !simFile.getParentFile().mkdirs()) {
                throw new VaSolSimException(ERROR_MESSAGE_COULD_NOT_CREATE_DIRS);
            }

            try {
                if (!simFile.createNewFile()) {
                    throw new VaSolSimException(ERROR_MESSAGE_COULD_NOT_CREATE_FILE);
                }
            } catch (IOException e) {
                throw new VaSolSimException(ERROR_MESSAGE_CREATE_FILE_EXCEPTION);
            }
        }

        //update crypto stuff
        updateCryptoEngine();
        updateCryptoProperties();

        /*
           * Check for any missing encrypted data that is required to read the test as specified
         */
        if (encryptedValidationHash.length < 1)
            throw new VaSolSimException(ERROR_MESSAGE_VALIDATION_KEY_NOT_PROVIDED);

        /*
         * Leave out for client side information gathering
         */
        /*
        if (isNotifyingCompletion && encryptedNotificationEmail.length < 1)
           throw new VaSolSimException("Notification requested with no email provided!");
           */

        if (isNotifyingCompletion && isNotifyingCompletionUsingStandaloneEmailParadigm
                && encryptedNotificationEmailPassword.length < 1)
            throw new VaSolSimException(ERROR_MESSAGE_STANDALONE_NOTIFICATION_PASSWORD_NOT_PROVIDED);

        /*
        if (isReportingStatistics && encryptedStatisticsEmail.length < 1)
           throw new VaSolSimException("Statistics requested with no email provided!");
           */

        if (isReportingStatistics && isReportingStatisticsUsingStandaloneEmailParadigm
                && encryptedStatisticsEmailPassword.length < 1)
            throw new VaSolSimException(ERROR_MESSAGE_STANDALONE_STATS_PASSWORD_NOT_PROVIDED);

        Document solExam;
        try {
            solExam = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
        } catch (ParserConfigurationException e) {
            throw new VaSolSimException(ERROR_MESSAGE_INTERNAL_XML_PARSER_INITIALIZATION_EXCEPTION, e);
        }

        //create document root
        Element root = solExam.createElement(xmlRootElementName);
        solExam.appendChild(root);

        //append the information sub element
        Element information = solExam.createElement(xmlInfoElementName);
        root.appendChild(information);

        Element security = solExam.createElement(xmlSecurityElementName);
        root.appendChild(security);

        /*
         * Build information element tree
         */
        createInformationElements(information, solExam);

        /*
         * Build security element tree
         */
        createSecurityElements(security, solExam);

        try {
            Transformer transformer = TransformerFactory.newInstance().newTransformer();
            transformer.setOutputProperty(OutputKeys.INDENT, "yes");
            transformer.setOutputProperty(OutputKeys.METHOD, "xml");
            transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
            transformer.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, "roles.dtd");
            transformer.setOutputProperty(indentationKey, "4");

            transformer.transform(new DOMSource(solExam), new StreamResult(new FileOutputStream(simFile)));
        } catch (FileNotFoundException e) {
            throw new VaSolSimException(ERROR_MESSAGE_FILE_NOT_FOUND_AFTER_INTERNAL_CHECK, e);
        } catch (TransformerConfigurationException e) {
            throw new VaSolSimException(ERROR_MESSAGE_INTERNAL_TRANSFORMER_CONFIGURATION, e);
        } catch (TransformerException e) {
            throw new VaSolSimException(ERROR_MESSAGE_INTERNAL_TRANSFORMER_EXCEPTION, e);
        }

        return true;
    }

    /**
     * Builds the elements for the information node of the xml file. If further information is needed, please see the
     * file called vssXMLSchema.xml
     *
     * @param informationParentElement the parent element for the attached information
     * @param test                     the primary document
     */
    protected void createInformationElements(Element informationParentElement, Document test) {
        appendSubNode(xmlTestNameElementName, testName, informationParentElement, test);
        appendSubNode(xmlAuthorElementName, testPublisher, informationParentElement, test);
        appendSubNode(xmlSchoolNameElementName, testPublisher, informationParentElement, test);
        appendSubNode(xmlClassPeriodElementName, className, informationParentElement, test);
        appendSubNode(xmlDateElementName, publishDate, informationParentElement, test);
    }

    /**
     * Builds the elements for the security node of the xml file. If further information is needed, please see the file
     * called vssXMLSchema.xml.
     * <p/>
     * I wrote this after two tequila sunrises and a malibu sunset and it worked first try. Get on my level.
     *
     * @param securityParentElement the parent element for the attached security information
     * @param test                  the primary document
     *
     * @throws VaSolSimException thrown if insufficient information for the crypto is given
     */
    protected void createSecurityElements(Element securityParentElement, Document test) throws VaSolSimException {
        /*
         * Internal security
         */
        appendSubNode(xmlHashAlgorithmElementName, hashAlgorithm.toString(), securityParentElement, test);
        appendSubNode(xmlValidationHashElementName,
                //convertHashToString(encryptionCipher.doFinal(validationKey)),
                convertHashToString(encryptedValidationHash), securityParentElement, test);
        appendSubNode(xmlValidationPIVElementName, new String(IV), securityParentElement, test);
        appendSubNode(xmlEncryptingQuestionsElementName, Boolean.toString(isEncryptingFullQuestions),
                securityParentElement, test);

        /*
         * Statistics reporting
         */
        appendSubNode(xmlReportingStatisticsElementName, Boolean.toString(isReportingStatistics),
                securityParentElement, test);
        appendSubNode(xmlStandaloneSMTPStatisticsReportingElementName,
                Boolean.toString(isReportingStatisticsUsingStandaloneEmailParadigm), securityParentElement, test);
        appendSubNode(xmlEncryptingQuestionsElementName, Boolean.toString(isReportingStatisticsEncrypted),
                securityParentElement, test);

        if (isReportingStatistics)
            appendSubNode(xmlEncryptedStatisticsReportingEmailAddressElementName,
                    new String(encryptedStatisticsEmail), securityParentElement, test);
        else
            appendSubNode(xmlEncryptedStatisticsReportingEmailAddressElementName, "!!statisticsemailnotprovided!!",
                    securityParentElement, test);

        if (isReportingStatisticsUsingStandaloneEmailParadigm)
            appendSubNode(xmlEncryptedStatisticsReportingEmailPasswordElementName,
                    new String(encryptedStatisticsEmailPassword), securityParentElement, test);
        else
            appendSubNode(xmlEncryptedStatisticsReportingEmailPasswordElementName, "!!notusingstandaloneparadigm",
                    securityParentElement, test);

        /*
         * Notification reporting
         */
        appendSubNode(xmlReportingNotificationsElementName, Boolean.toString(isNotifyingCompletion),
                securityParentElement, test);
        appendSubNode(xmlStandaloneSMTPNotificationReportingElementName,
                Boolean.toString(isNotifyingCompletionUsingStandaloneEmailParadigm), securityParentElement, test);

        if (isNotifyingCompletion)
            appendSubNode(xmlEncryptedNotificationReportingEmailAddressElementName,
                    new String(encryptedNotificationEmail), securityParentElement, test);
        else
            appendSubNode(xmlEncryptedNotificationReportingEmailAddressElementName,
                    "!!notificationemailnotprovided!!", securityParentElement, test);

        if (isNotifyingCompletionUsingStandaloneEmailParadigm)
            appendSubNode(xmlEncryptedNotificationReportingEmailPasswordElementName,
                    new String(encryptedNotificationEmailPassword), securityParentElement, test);
        else
            appendSubNode(xmlEncryptedNotificationReportingEmailPasswordElementName,
                    "!!notusingstandaloneparadigm!!", securityParentElement, test);
    }

    protected void createQuestionElements() {

    }

    protected static void appendSubNode(String elementName, String nodeData, Element parentElement, Document doc) {
        Element subElement = doc.createElement(elementName);
        subElement.appendChild(doc.createTextNode(nodeData));
        parentElement.appendChild(subElement);
    }

    /**
     * Returns if a given string is a valid email regex
     *
     * @param email email address
     *
     * @return if the email address is a valid email regex
     */
    public static boolean isValidEmail(String email) {
        return validEmailPattern.matcher(email).matches();
    }

    /**
     * Returns the bytes currently being used as the parametric initialization vector used to encrypted elements of the
     * file
     *
     * @return parametricIV
     */
    public static byte[] getEncryptionParametricIV() {
        return IV;
    }

    /**
     * Sets the bytes currently being used as the parametric initialization vector used to encrypted elements of the
     * file. It is recommended the function generateNewEncryptionParametricIV() be used for the highest level of
     * security
     *
     * @param encryptionParametricIV teh new parametricIV
     */
    public static void setEncryptionParametricIV(byte[] encryptionParametricIV) {
        IV = encryptionParametricIV;
    }

    /**
     * Sets the bytes currently being used as the parametric initialization vector used to encrypted elements of the
     * file. This function uses a SecureRandom object internal to the class.
     */
    public static void generateNewEncryptionParametricIV() {
        secureRandom.nextBytes(IV);
    }

    /**
     * Reads a file from a given directory, validates it with the given key, and loads the data into a VaSOLSimExam
     * object.
     *
     * @param file the file to be read
     * @param key  the key to open the exam
     *
     * @return the loaded and verified exam
     *
     * @throws IOException           thrown if the function hits any IO issues independent of the VaSOMSim program
     * @throws VaSolSimException     thrown if any problems occur with validating the file or any other data in the
     *                               file
     * @throws MalformedXMLException thrown if the XML file does not follow the schema established for VaSOLSim files.
     *                               If you are unsure as to what this is, please see the file
     *                               /xmlschema/cssXMLSchema.xml and consult the documentation located there and on w3.
     */
    public static __NONUSEDREFERENCE_VaSOLSimExam read(File file, byte[] key)
            throws IOException, VaSolSimException {
        __NONUSEDREFERENCE_VaSOLSimExam exam = null;

        return exam;
    }

    /**
     * Generates a hash from a password with a given key length using a Secure Hash Algorithm (SHA)
     *
     * @param password the password form which the hash will be generated
     * @param length   the length in bytes of the hash, defaults to SHA-256. Valid values are SHA-1, SHA-256, SHA-512.
     *
     * @return hash
     */
    protected static byte[] generateHash(byte[] password, HashType length) {
        String hashAlgorithm;
        switch (length) {
        case SHA256:
            hashAlgorithm = "SHA-256";
            break;
        case SHA512:
            hashAlgorithm = "SHA-512";
            break;
        default:
            hashAlgorithm = "SHA-256";
        }

        try {
            MessageDigest hash = MessageDigest.getInstance(hashAlgorithm);
            hash.update(password);
            return hash.digest();
        } catch (NoSuchAlgorithmException e) {
            return new byte[] { -1 };
        }
    }

    /**
     * Converts a byte array hash to a character representation for storage and comparison
     *
     * @param hash the hash to convert to plain text
     *
     * @return plain text representation of the hash value
     */
    public static String convertHashToString(byte[] hash) {
        StringBuilder hashString = new StringBuilder();
        for (int index = 0; index < hash.length; index++)
            hashString.append(Integer.toString((hash[index] & 0xff) + 0x0100, 16).substring(1));

        return hashString.toString();
    }

    /**
     * Creates a cipher around a 128bit AES encryption initialized to decryption mode.
     *
     * @param key the key for the cipher
     *
     * @return initialized cipher, null if there is an error. The popup manager is used to notify errors.
     */
    protected static Cipher getDecryptionCipher(byte[] key) throws VaSolSimException {
        byte[] validatedKey = new byte[algorithmExpectedKeyLengthInBytes];

        /*
         * Holy crap yes I know this is terribly insecure but if you give this function an encryption key smaller
         * than 128 bits or of a value than the one you have set in teh static fields we have bigger problems to
         * deal with. Also, its for school, who is actually going to read this but me haha. If you do, email me with
         * the subject line LAWL YOUR CRYPTO IS REALLY BAD and we can share a laugh;
         *
         * Anyways, if the key is too short, repeat the byte sequence (copy it) until the byte array key is long
         * enough.
         */
        if (key.length < algorithmExpectedKeyLengthInBytes) {
            int invalidatedKeyIndex = 0;
            for (int index = 0; index < algorithmExpectedKeyLengthInBytes; index++) {
                validatedKey[index] = key[invalidatedKeyIndex++];

                if (index >= key.length)
                    invalidatedKeyIndex = 0;
            }
        } else if (key.length > algorithmExpectedKeyLengthInBytes) {
            if (!(key.length == 32 || key.length == 64))
                throw new VaSolSimException(
                        ERROR_MESSAGE_GENERIC_CRYPTO + "\n\nBAD KEY: HIGHER KEY NOT POT (256, 512)");

            if (key.length == 32) {
                byte[] xorKey = new byte[16];
                byte[] lowerHalf = new byte[16];
                byte[] higherHalf = new byte[16];

                System.arraycopy(key, 0, lowerHalf, 0, 16);
                System.arraycopy(key, 16, higherHalf, 0, 16);

                for (int index = 0; index < 16; index++) {
                    int xorBytes = (int) lowerHalf[index] ^ (int) higherHalf[index];
                    xorKey[index] = (byte) (0xff & xorBytes);
                }

                validatedKey = xorKey;
            } else {
                byte[] lowerQuarterOne = new byte[16];
                byte[] lowerQuarterTwo = new byte[16];
                byte[] higherQuarterOne = new byte[16];
                byte[] higherQuarterTwo = new byte[16];
                byte[] lowerHalf = new byte[16];
                byte[] higherHalf = new byte[16];
                byte[] xorKey = new byte[16];

                System.arraycopy(key, 0, lowerQuarterOne, 0, 16);
                System.arraycopy(key, 16, lowerQuarterTwo, 0, 16);
                System.arraycopy(key, 32, higherQuarterOne, 0, 16);
                System.arraycopy(key, 48, higherQuarterTwo, 0, 16);

                for (int index = 0; index < 16; index++) {
                    int xorBytes = (int) lowerQuarterOne[index] ^ (int) lowerQuarterTwo[index];
                    lowerHalf[index] = (byte) (0xff & xorBytes);
                }

                for (int index = 0; index < 16; index++) {
                    int xorBytes = (int) higherQuarterOne[index] ^ (int) higherQuarterTwo[index];
                    higherHalf[index] = (byte) (0xff & xorBytes);
                }

                for (int index = 0; index < 16; index++) {
                    int xorBytes = (int) lowerHalf[index] ^ (int) higherHalf[index];
                    xorKey[index] = (byte) (0xff & xorBytes);
                }

                validatedKey = xorKey;
            }

        } else if (key.length == algorithmExpectedKeyLengthInBytes)
            validatedKey = key;

        Cipher cipher = null;
        try {
            System.out.println(validatedKey.length);

            cipher = Cipher.getInstance(serviceProviderInterface, serviceProvider);
            cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(validatedKey, algorithm), new IvParameterSpec(IV));
        } catch (NoSuchAlgorithmException e) {
            throw new VaSolSimException(ERROR_MESSAGE_GENERIC_CRYPTO + "\n\nBAD ALGORITHM\n" + e.toString() + "\n"
                    + e.getCause() + "\n" + ExceptionUtils.getStackTrace(e), e);
        } catch (NoSuchProviderException e) {
            throw new VaSolSimException(ERROR_MESSAGE_GENERIC_CRYPTO + "\n\nBAD PROVIDER\n" + e.toString() + "\n"
                    + e.getCause() + "\n" + ExceptionUtils.getStackTrace(e), e);
        } catch (NoSuchPaddingException e) {
            throw new VaSolSimException(ERROR_MESSAGE_GENERIC_CRYPTO + "\n\nNO SUCH PADDING\n" + e.toString() + "\n"
                    + e.getCause() + "\n" + ExceptionUtils.getStackTrace(e), e);
        } catch (InvalidKeyException e) {
            throw new VaSolSimException(ERROR_MESSAGE_GENERIC_CRYPTO + "\n\nBAD KEY\n" + e.toString() + "\n"
                    + e.getCause() + "\n" + ExceptionUtils.getStackTrace(e), e);
        } catch (InvalidAlgorithmParameterException e) {
            throw new VaSolSimException(ERROR_MESSAGE_GENERIC_CRYPTO + "\n\nBAD ALGORITHM PARAMS\n" + e.toString()
                    + "\n" + e.getCause() + "\n" + ExceptionUtils.getStackTrace(e), e);
        }

        return cipher;
    }

    /**
     * Creates a cipher around a statically defined DEFAULT_ENCRYPTION_ALGORITHM, AES 128bit by default, initialized to
     * encryption mode.
     *
     * @param key secure hash of some sort of the key
     *
     * @return initialized cipher, null if internal exception
     *
     * @throws VaSolSimException any internal cryptographic related exceptions throw this for debugging to the user
     */
    protected static Cipher getEncryptionCipher(byte[] key) throws VaSolSimException {
        byte[] validatedKey = new byte[algorithmExpectedKeyLengthInBytes];

        /*
         * Holy crap yes I know this is terribly insecure but if you give this function an encryption key smaller
         * than 128 bits or of a value than the one you have set in teh static fields we have bigger problems to
         * deal with. Also, its for school, who is actually going to read this but me haha. If you do, email me with
         * the subject line LAWL YOUR CRYPTO IS REALLY BAD and we can share a laugh;
         *
         * Anyways, if the key is too short, repeat the byte sequence (copy it) until the byte array key is long
         * enough.
         */
        if (key.length < algorithmExpectedKeyLengthInBytes) {

            int invalidatedKeyIndex = 0;
            for (int index = 0; index < algorithmExpectedKeyLengthInBytes; index++) {
                validatedKey[index] = key[invalidatedKeyIndex++];

                if (index >= key.length)
                    invalidatedKeyIndex = 0;
            }
        } else if (key.length > algorithmExpectedKeyLengthInBytes) {
            if (!(key.length == 32 || key.length == 64))
                throw new VaSolSimException(
                        ERROR_MESSAGE_GENERIC_CRYPTO + "\n\nBAD KEY: HIGHER KEY NOT POT (256, 512)");

            if (key.length == 32) {
                byte[] xorKey = new byte[16];
                byte[] lowerHalf = new byte[16];
                byte[] higherHalf = new byte[16];

                System.arraycopy(key, 0, lowerHalf, 0, 16);
                System.arraycopy(key, 16, higherHalf, 0, 16);

                for (int index = 0; index < 16; index++) {
                    int xorBytes = (int) lowerHalf[index] ^ (int) higherHalf[index];
                    xorKey[index] = (byte) (0xff & xorBytes);
                }

                validatedKey = xorKey;
            } else {
                byte[] lowerQuarterOne = new byte[16];
                byte[] lowerQuarterTwo = new byte[16];
                byte[] higherQuarterOne = new byte[16];
                byte[] higherQuarterTwo = new byte[16];
                byte[] lowerHalf = new byte[16];
                byte[] higherHalf = new byte[16];
                byte[] xorKey = new byte[16];

                System.arraycopy(key, 0, lowerQuarterOne, 0, 16);
                System.arraycopy(key, 16, lowerQuarterTwo, 0, 16);
                System.arraycopy(key, 32, higherQuarterOne, 0, 16);
                System.arraycopy(key, 48, higherQuarterTwo, 0, 16);

                for (int index = 0; index < 16; index++) {
                    int xorBytes = (int) lowerQuarterOne[index] ^ (int) lowerQuarterTwo[index];
                    lowerHalf[index] = (byte) (0xff & xorBytes);
                }

                for (int index = 0; index < 16; index++) {
                    int xorBytes = (int) higherQuarterOne[index] ^ (int) higherQuarterTwo[index];
                    higherHalf[index] = (byte) (0xff & xorBytes);
                }

                for (int index = 0; index < 16; index++) {
                    int xorBytes = (int) lowerHalf[index] ^ (int) higherHalf[index];
                    xorKey[index] = (byte) (0xff & xorBytes);
                }

                validatedKey = xorKey;
            }
        } else if (key.length == algorithmExpectedKeyLengthInBytes)
            validatedKey = key;

        /*
         * Initialize the Cipher
         */
        Cipher cipher = null;
        try {
            cipher = Cipher.getInstance(serviceProviderInterface, serviceProvider);
            cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(validatedKey, algorithm), new IvParameterSpec(IV));
        } catch (NoSuchAlgorithmException e) {
            throw new VaSolSimException(ERROR_MESSAGE_GENERIC_CRYPTO + "\n\nBAD ALGORITHM\n" + e.toString() + "\n"
                    + e.getCause() + "\n" + ExceptionUtils.getStackTrace(e), e);
        } catch (NoSuchProviderException e) {
            throw new VaSolSimException(ERROR_MESSAGE_GENERIC_CRYPTO + "\n\nBAD PROVIDER\n" + e.toString() + "\n"
                    + e.getCause() + "\n" + ExceptionUtils.getStackTrace(e), e);
        } catch (NoSuchPaddingException e) {
            throw new VaSolSimException(ERROR_MESSAGE_GENERIC_CRYPTO + "\n\nNO SUCH PADDING\n" + e.toString() + "\n"
                    + e.getCause() + "\n" + ExceptionUtils.getStackTrace(e), e);
        } catch (InvalidKeyException e) {
            throw new VaSolSimException(ERROR_MESSAGE_GENERIC_CRYPTO + "\n\nBAD KEY\n" + e.toString() + "\n"
                    + e.getCause() + "\n" + ExceptionUtils.getStackTrace(e), e);
        } catch (InvalidAlgorithmParameterException e) {
            throw new VaSolSimException(ERROR_MESSAGE_GENERIC_CRYPTO + "\n\nBAD ALGORITHM PARAMS\n" + e.toString()
                    + "\n" + e.getCause() + "\n" + ExceptionUtils.getStackTrace(e), e);
        }

        return cipher;
    }
}