src/main/java/edu/tamu/di/SAFCreator/model/VerifierTableModel.java
public class VerifierTableModel extends AbstractTableModel {
    private static final long serialVersionUID = 1L;

    public static final String COLUMN_VERIFIER = "Verifier";
    public static final String COLUMN_GENERATES_ERRORS = "Generates Errors";
    public static final String COLUMN_ACTIVATED = "Activated";

    public static final String VERIFIER_ACTIVE = "Active";
    public static final String VERIFIER_INACTIVE = "Inactive";

    private Vector<String> columnNames;
    private Vector<Vector<Object>> rowData;

    private Vector<String> verifierNames;
    private Vector<VerifierProperty> verifierProperties;


    public VerifierTableModel(List<VerifierProperty> verifiers) {
        columnNames = new Vector<String>();
        rowData = new Vector<Vector<Object>>();

        verifierNames = new Vector<String>();
        verifierProperties = new Vector<VerifierProperty>();

        columnNames.add(COLUMN_VERIFIER);
        columnNames.add(COLUMN_GENERATES_ERRORS);
        columnNames.add(COLUMN_ACTIVATED);

        for (VerifierProperty verifier : verifiers) {
            Vector<Object> row = new Vector<Object>();

            row.add(verifier.prettyName());
            row.add(verifier.generatesError());
            row.add(verifier.getActivated() ? VERIFIER_ACTIVE : VERIFIER_INACTIVE);

            verifierNames.add(verifier.getClass().getName());
            verifierProperties.add(verifier);

            rowData.add(row);
        }
    }

    @Override
    public int getColumnCount() {
        return columnNames.size();
    }

    @Override
    public String getColumnName(int col) {
        return columnNames.get(col);
    }

    @Override
    public int getRowCount() {
        return rowData.size();
    }

    @Override
    public Object getValueAt(int row, int col) {
        Object object = null;
        if (row >= 0 && row < rowData.size()) {
            object = rowData.get(row).get(col);
        }
        return object;
    }

    public VerifierProperty getVerifierPropertyAt(int row) {
        VerifierProperty verifierProperty = null;
        if (row >= 0 && row < verifierProperties.size()) {
            verifierProperty = verifierProperties.get(row);
        }
        return verifierProperty;
    }

    public VerifierProperty getVerifierWithName(String name) {
        VerifierProperty verifierProperty = null;
        int row = 0;
        String verifierName = null;

        for (; row < verifierProperties.size(); row++) {
            verifierName = verifierNames.get(row);
            if (verifierName.equals(name)) {
                break;
            }
        }

        if (row < rowData.size()) {
            verifierProperty = verifierProperties.get(row);
        }

        return verifierProperty;
    }

    @Override
    public void setValueAt(Object value, int row, int column) {
        rowData.get(row).set(column, value);
        if (column == 2) {
            getVerifierPropertyAt(row).setActivated(value.equals(VERIFIER_ACTIVE));
        }
        fireTableCellUpdated(row, column);
    }

}
src/main/java/edu/tamu/di/SAFCreator/model/importData/ImportDataProcessor.java
public interface ImportDataProcessor {
    public Batch loadBatch(String metadataInputFileName, String sourceDirectoryName, String outputDirectoryName, JTextArea console);

    public void writeBatchSAF(Batch batch, JTextArea console, FlagPanel flags);
}
src/main/java/edu/tamu/di/SAFCreator/model/Batch.java
public class Batch {
    private String name;
    private BatchStatus status;
    private File inputFilesDir;
    private File outputSAFDir;
    private List<Item> items = new ArrayList<Item>();
    private License license;
    private List<ColumnLabel> labels = new ArrayList<ColumnLabel>();
    private Boolean ignoreFiles = false;
    private Boolean processUri = false;
    private Boolean flattenDirectories = true;
    private Boolean allowSelfSigned = true;
    private Boolean remoteBitstreamErrorContinue = false;
    private int itemProcessDelay = 0;
    private int remoteFileTimeout = 10000;
    private String userAgent = null;
    private List<Integer> ignoreRows = new ArrayList<Integer>();
    private List<Integer> failedRows = new ArrayList<Integer>();
    private String action = "";

    /**
     * Add a new item to the list of items contained within this batch.
     *
     */
    public void addItem(Item item) {
        items.add(item);
    }

    /**
     * Removes all failed row values.
     */
    public void clearFailedRows() {
        failedRows.clear();
    }

    /**
     * Removes all assigned ignore row values.
     */
    public void clearIgnoredRows() {
        ignoreRows.clear();
    }

    /**
     * Flag a specific batch row as failed.
     *
     * @param row the number of the row to ignore.
     */
    public void failedRow(Integer row) {
        failedRows.add(row);
    }

    /**
     * @return The action associated with this batch.
     */
    public String getAction() {
        return action;
    }

    /**
     * @return Whether or not self-signed certificates are always allowed.
     */
    public Boolean getAllowSelfSigned() {
        return allowSelfSigned;
    }

    /**
     * @return Whether or not to ignore files.
     */
    public Boolean getIgnoreFiles() {
        return ignoreFiles;
    }

    /**
     * @return The base directory from where to begin looking for all associated file content for this batch.
     */
    public File getinputFilesDir() {
        return inputFilesDir;
    }

    /**
     * @return The item process delay time (in milliseconds).
     */
    public int getItemProcessDelay() {
        return itemProcessDelay;
    }

    /**
     * @return A list of all items associated with this batch.
     */
    public List<Item> getItems() {
        return items;
    }

    public List<ColumnLabel> getLabels() {
        return labels;
    }

    public License getLicense() {
        return license;
    }

    /**
     * @return The user supplied name of this batch
     */
    public String getName() {
        return name;
    }

    public File getOutputSAFDir() {
        return outputSAFDir;
    }

    public Boolean getProcessUri() {
        return processUri;
    }

    /**
     * @return The remote bitstream error continue status. When true to enable ignoring errors. When false to stop on errors.
     */
    public Boolean getRemoteBitstreamErrorContinue() {
        return remoteBitstreamErrorContinue;
    }

    /**
     * @return The remote file timeout time (in milliseconds).
     */
    public int getRemoteFileTimeout() {
        return remoteFileTimeout;
    }

    /**
     * @return The current evaluation status of the batch.
     */
    public BatchStatus getStatus() {
        return status;
    }

    /**
     * @return The item process delay time (in milliseconds).
     */
    public String getUserAgent() {
        return userAgent;
    }

    /**
     * Reports whether or not any rows are flagged as failed.
     *
     * @return true if any row is assigned to be ignored, false otherwise.
     */
    public boolean hasFailedRows() {
        return !failedRows.isEmpty();
    }

    /**
     * Reports whether or not any rows are assigned to be ignored.
     *
     * @return true if any row is assigned to be ignored, false otherwise. When remoteBitstreamErrorContinue is false, this always returns false.
     */
    public boolean hasIgnoredRows() {
        if (!remoteBitstreamErrorContinue) {
            return false;
        }
        return !ignoreRows.isEmpty();
    }

    /**
     * Ignore a specific batch row.
     *
     * A particular row number for the items list can be ignored. This is intended to be used when remoteBitstreamErrorContinue is true. If any single column is invalid, the entire row is to be ignored.
     *
     * @param row the number of the row to ignore.
     */
    public void ignoreRow(Integer row) {
        ignoreRows.add(row);
    }

    /**
     * Check to see if a row is flagged as failed.
     *
     * @param row the number of the row that has failed.
     *
     * @return true if failed, false otherwise.
     */
    public boolean isFailedRow(Integer row) {
        return failedRows.contains(row);
    }

    /**
     * Check to see if a row is flagged to be ignored.
     *
     * @param row the number of the row that may be ignored.
     *
     * @return true if ignored, false otherwise. When remoteBitstreamErrorContinue is false, this always returns false.
     */
    public boolean isIgnoredRow(Integer row) {
        if (!remoteBitstreamErrorContinue) {
            return false;
        }
        return ignoreRows.contains(row);
    }

    public void restrictItemsToGroup(String groupName) {
        for (Item item : items) {
            for (Bundle bundle : item.getBundles()) {
                for (Bitstream bitstream : bundle.getBitstreams()) {
                    bitstream.setReadPolicyGroupName(groupName);
                }
            }
        }

    }

    /**
     * Set the action status associated with this batch.
     *
     * @param action The new action name.
     */
    public void setAction(String action) {
        this.action = action;
    }

    /**
     * Set the whether or not to always allow self-signed SSL certificates.
     *
     * @param allowSelfSigned
     */
    public void setAllowSelfSigned(Boolean allowSelfSigned) {
        this.allowSelfSigned = allowSelfSigned;
    }

    /**
     * Set the whether or not to ignore files.
     *
     * @param ignoreFiles
     */
    public void setIgnoreFiles(Boolean ignoreFiles) {
        this.ignoreFiles = ignoreFiles;
    }

    /**
     * Set the base directory from where to begin searching for any associated files.
     *
     * @param directory The new base directory.
     */
    public void setinputFilesDir(File directory) {
        inputFilesDir = directory;
    }

    public void setinputFilesDir(String directoryName) {
        inputFilesDir = new File(directoryName);
    }

    /**
     * Set the item process delay time (in milliseconds).
     *
     * @param itemProcessDelay The process delay time.
     */
    public void setItemProcessDelay(int itemProcessDelay) {
        this.itemProcessDelay = itemProcessDelay;
    }

    /**
     * Set the item process delay time (in milliseconds).
     *
     * @param itemProcessDelay The process delay time.
     */
    public void setItemProcessDelay(String itemProcessDelay) {
        this.itemProcessDelay = Integer.parseInt(itemProcessDelay);
    }

    public void setLabels(List<ColumnLabel> labels) {
        this.labels = labels;
    }

    public void setLicense(String filename, String bundleName, String licenseText) {
        license = new License();
        license.setFilename(filename);
        license.setBundleName(bundleName);
        license.setLicenseText(licenseText);
    }

    /**
     * Set the user supplied name of this batch.
     *
     * @param name The new name.
     */
    public void setName(String name) {
        this.name = name;
    }

    public void setOutputSAFDir(File outputSAFDir) {
        this.outputSAFDir = outputSAFDir;
    }

    public void setOutputSAFDir(String outputSAFDirName) {
        outputSAFDir = new File(outputSAFDirName);
    }

    public void setProcessUri(Boolean processUri) {
        this.processUri = processUri;
    }

    /**
     * Set the remote bitstream error continue status.
     *
     * @param remoteBitstreamErrorContinue Set to true to enable ignoring errors, false to stop on errors.
     */
    public void setRemoteBitstreamErrorContinue(Boolean remoteBitstreamErrorContinue) {
        this.remoteBitstreamErrorContinue = remoteBitstreamErrorContinue;
    }

    /**
     * Set the remote file timeout time (in milliseconds).
     *
     * @param itemProcessDelay The process delay time.
     */
    public void setRemoteFileTimeout(int remoteFileTimeout) {
        this.remoteFileTimeout = remoteFileTimeout;
    }

    /**
     * Set the remote file timeout time (in milliseconds).
     *
     * @param itemProcessDelay The process delay time.
     */
    public void setRemoteFileTimeout(String remoteFileTimeout) {
        this.remoteFileTimeout = Integer.parseInt(remoteFileTimeout);
    }

    /**
     * Set the evaluation status of the batch.
     */
    public void setStatus(BatchStatus status) {
        this.status = status;
    }

    /**
     * Set the user agent string.
     *
     * @param itemProcessDelay The process delay time.
     */
    public void setUserAgent(String userAgent) {
        this.userAgent = userAgent;
    }

    public void unsetLicense() {
        license = null;
    }

    public void setFlattenDirectories(boolean b) {
        flattenDirectories = b;

    }

    public Boolean getFlattenDirectories() {
        return flattenDirectories;
    }

    public void setFlattenDirectories(Boolean flattenDirectories) {
        this.flattenDirectories = flattenDirectories;
    }
}
src/main/java/edu/tamu/di/SAFCreator/model/Flag.java
public class Flag {
    public static String[] ColumnNames = { "Flag", "Description", "Authority", "URL", "Column", "Row", "Action" };

    public static String ACCESS_DENIED = "Access Denied";
    public static String BAD_SCHEMA_NAME = "Bad Schema Name";
    public static String FILE_ERROR = "File Error";
    public static String HTTP_FAILURE = "HTTP Failure";
    public static String INVALID_MIME = "Invalid File Type";
    public static String INVALID_FORMAT = "Invalid Format";
    public static String IO_FAILURE = "I/O Failure";
    public static String NOT_FOUND = "Not Found";
    public static String NO_UNIQUE_ID = "No Unique ID";
    public static String REDIRECT_FAILURE = "Redirect Failure";
    public static String REDIRECT_LIMIT = "Redirect Limit";
    public static String REDIRECT_LOOP = "Redirect Loop";
    public static String SERVICE_ERROR = "Server Error";
    public static String SERVICE_REJECTED = "Service Rejected";
    public static String SERVICE_TIMEOUT = "Service Timeout";
    public static String SERVICE_UNAVAILABLE = "Service Unavailable";
    public static String SSL_FAILURE = "SSL Failure";
    public static String SOCKET_ERROR = "Socket Error";
    public static String DELETE_FAILURE = "Delete Failure";

    private ArrayList<String> rowData;


    /**
     * Initialize row with empty cells.
     */
    public Flag() {
        rowData = new ArrayList<String>();
        rowData.add("");
        rowData.add("");
        rowData.add("");
        rowData.add("");
        rowData.add("");
        rowData.add("");
        rowData.add("");
    }

    /**
     * Initialize row with pre-populated cells.
     *
     * @param flagCode
     *            the flag code.
     * @param flagName
     *            the flag name.
     * @param action
     *            the action status associated with the batch.
     */
    public Flag(String flagCode, String flagName, String action) {
        rowData = new ArrayList<String>();
        rowData.add(flagCode);
        rowData.add(flagName);
        rowData.add("");
        rowData.add("");
        rowData.add("");
        rowData.add("");
        rowData.add(action);
    }

    /**
     * Initialize row with pre-populated cells.
     *
     * @param flagCode
     *            the flag code.
     * @param flagName
     *            the flag name.
     * @param action
     *            the action status associated with the batch.
     * @param bitstream
     *            the bitstream to extract the information from.
     */
    public Flag(String flagCode, String flagName, String action, Bitstream bitstream) {
        rowData = new ArrayList<String>();
        rowData.add(flagCode);
        rowData.add(flagName);
        rowData.add(bitstream.getSource().getAuthority());
        rowData.add(bitstream.getSource().toString());
        rowData.add(bitstream.getColumnLabel());
        rowData.add("" + bitstream.getRow());
        rowData.add(action);
    }

    /**
     * Initialize row with pre-populated cells.
     *
     * @param flagCode
     *            the flag code.
     * @param flagName
     *            the flag name.
     * @param authority
     *            the authority/hostname..
     * @param url
     *            the entire URL (including authority/hostname).
     * @param column
     *            column number/letter (of imported CSV file).
     * @param row
     *            row number (of imported CSV file).
     * @param action
     *            the action status associated with the batch.
     */
    public Flag(String flagCode, String flagName, String authority, String url, String column, String row,
            String action) {
        rowData = new ArrayList<String>();
        rowData.add(flagCode);
        rowData.add(flagName);
        rowData.add(authority);
        rowData.add(url);
        rowData.add(column);
        rowData.add(row);
        rowData.add(action);
    }

    /**
     * Resets all cells to empty strings.
     */
    public void clear() {
        rowData.set(FlagColumns.FLAG.ordinal(), "");
        rowData.set(FlagColumns.DESCRIPTION.ordinal(), "");
        rowData.set(FlagColumns.AUTHORITY.ordinal(), "");
        rowData.set(FlagColumns.URL.ordinal(), "");
        rowData.set(FlagColumns.COLUMN.ordinal(), "");
        rowData.set(FlagColumns.ROW.ordinal(), "");
        rowData.set(FlagColumns.ACTION.ordinal(), "");
    }

    /**
     * Retrieve data from a cell.
     *
     * @param column
     *            the Row.Columns column id.
     *
     * @return String of data for the specified column.
     */
    public String getCell(FlagColumns column) {
        return rowData.get(column.ordinal());
    }

    /**
     * Retrieve a copy of the row data.
     *
     * @return An array list of strings containing the row information.
     */
    public ArrayList<String> getRow() {
        ArrayList<String> returnData = new ArrayList<String>();
        returnData.set(FlagColumns.FLAG.ordinal(), rowData.get(FlagColumns.FLAG.ordinal()));
        returnData.set(FlagColumns.DESCRIPTION.ordinal(), rowData.get(FlagColumns.DESCRIPTION.ordinal()));
        returnData.set(FlagColumns.AUTHORITY.ordinal(), rowData.get(FlagColumns.AUTHORITY.ordinal()));
        returnData.set(FlagColumns.URL.ordinal(), rowData.get(FlagColumns.URL.ordinal()));
        returnData.set(FlagColumns.COLUMN.ordinal(), rowData.get(FlagColumns.COLUMN.ordinal()));
        returnData.set(FlagColumns.ROW.ordinal(), rowData.get(FlagColumns.ROW.ordinal()));
        returnData.set(FlagColumns.ACTION.ordinal(), rowData.get(FlagColumns.ACTION.ordinal()));

        return returnData;
    }

    /**
     * Assign data to a cell.
     *
     * @param column
     *            the Row.Columns column id.
     * @param data
     *            the data to assign.
     */
    public void setCell(FlagColumns column, String data) {
        rowData.set(column.ordinal(), data);
    }

    /**
     * Assign data for the entire row.
     *
     * @param flagCode
     *            the flag code.
     * @param flagName
     *            the flag name.
     * @param authority
     *            the authority/hostname.
     * @param url
     *            the entire URL (including authority/hostname).
     * @param column
     *            column number/letter (of imported CSV file).
     * @param row
     *            row number (of imported CSV file).
     * @param action
     *            the action status associated with the batch.
     */
    public void setRow(String flagCode, String flagName, String authority, String url, String column, String row,
            String action) {
        rowData.set(FlagColumns.FLAG.ordinal(), flagCode);
        rowData.set(FlagColumns.DESCRIPTION.ordinal(), flagName);
        rowData.set(FlagColumns.AUTHORITY.ordinal(), authority);
        rowData.set(FlagColumns.URL.ordinal(), url);
        rowData.set(FlagColumns.COLUMN.ordinal(), column);
        rowData.set(FlagColumns.ROW.ordinal(), row);
        rowData.set(FlagColumns.ACTION.ordinal(), action);
    }

    /**
     * Get the row as an object array.
     *
     * @return an object array of the row cells.
     */
    public Object[] toObject() {
        return new Object[] { rowData.get(FlagColumns.FLAG.ordinal()), rowData.get(FlagColumns.DESCRIPTION.ordinal()),
                rowData.get(FlagColumns.AUTHORITY.ordinal()), rowData.get(FlagColumns.URL.ordinal()),
                rowData.get(FlagColumns.COLUMN.ordinal()), rowData.get(FlagColumns.ROW.ordinal()),
                rowData.get(FlagColumns.ACTION.ordinal()), };
    }
}
src/main/java/edu/tamu/di/SAFCreator/model/verify/impl/RemoteFilesExistVerifierImpl.java
public class RemoteFilesExistVerifierImpl extends VerifierBackground {
    private static int TimeoutRead = 20000;
    private static int MaxRedirects = 20;

    private FTPClient ftpConnection;
    private HttpHead httpHead;
    private HttpGet httpGet;
    private RequestConfig requestConfig;
    private CloseableHttpClient httpClient;
    private CloseableHttpResponse httpResponse;
    private int remoteFileTimeout;
    private int responseCode;


    public RemoteFilesExistVerifierImpl() {
        super();
        remoteFileTimeout = TimeoutRead;
        responseCode = 0;
    }

    public RemoteFilesExistVerifierImpl(VerifierProperty settings) {
        super(settings);
        remoteFileTimeout = TimeoutRead;
        responseCode = 0;
    }

    private void abortConnections() {
        if (ftpConnection != null && ftpConnection.isConnected()) {
            try {
                ftpConnection.abort();
            } catch (IOException e) {
                // error status of abort is not relevant here because this is generally a cancel operation or an exit operation.
            }
            ftpConnection = null;
        }

        if (httpResponse != null) {
            try {
                httpResponse.close();
            } catch (IOException e) {
             // error status of close is not relevant here because this is generally a cancel operation or an exit operation.
            }
            httpResponse = null;
        }

        if (httpHead != null) {
            httpHead.abort();
            httpHead = null;
        }

        if (httpGet != null) {
            httpGet.abort();
            httpGet = null;
        }

        if (httpClient != null) {
            try {
                httpClient.close();
            } catch (IOException e) {
                // error status of close is not relevant here because this is generally a cancel operation or an exit operation.
            }
            httpClient = null;
        }
    }

    @Override
    public void doCancel() {
        abortConnections();
    }

    @Override
    protected List<Problem> doInBackground() {
        return new ArrayList<Problem>();
    }

    @Override
    public boolean generatesError() {
        return true;
    }

    @Override
    public boolean isSwingWorker() {
        return true;
    }

    @Override
    public String prettyName() {
        return "Remote Content Files Exist Verifier";
    }

    @Override
    public List<Problem> verify(Batch batch) {
        return verify(batch, null, null);
    }

    @Override
    public List<Problem> verify(Batch batch, JTextArea console, FlagPanel flagPanel) {
        List<Problem> missingFiles = new ArrayList<Problem>();

        if (!batch.getIgnoreFiles()) {
            int totalItems = batch.getItems().size();
            int itemCount = 0;

            remoteFileTimeout = batch.getRemoteFileTimeout();
            requestConfig = RequestConfig.custom().setSocketTimeout(remoteFileTimeout).setConnectTimeout(remoteFileTimeout).build();

            for (Item item : batch.getItems()) {
                if (isCancelled()) {
                    abortConnections();
                    if (console != null) {
                        console.append("Cancelled " + prettyName() + ".\n");
                    }
                    return missingFiles;
                }

                for (Bundle bundle : item.getBundles()) {
                    if (isCancelled()) {
                        abortConnections();
                        if (console != null) {
                            console.append("Cancelled " + prettyName() + ".\n");
                        }
                        return missingFiles;
                    }

                    for (Bitstream bitstream : bundle.getBitstreams()) {
                        if (isCancelled()) {
                            abortConnections();
                            if (console != null) {
                                console.append("Cancelled " + prettyName() + ".\n");
                            }
                            return missingFiles;
                        }

                        URI source = bitstream.getSource();
                        if (!source.isAbsolute() || source.getScheme().toString().equalsIgnoreCase("file")) {
                            // ignore local files.
                            continue;
                        }

                        if (source.getScheme().toString().equalsIgnoreCase("ftp")) {
                            ftpConnection = new FTPClient();

                            try {
                                int itemProcessDelay = batch.getItemProcessDelay();
                                if (itemProcessDelay > 0) {
                                    TimeUnit.MILLISECONDS.sleep(itemProcessDelay);
                                }

                                ftpConnection.setConnectTimeout(remoteFileTimeout);
                                ftpConnection.setDataTimeout(TimeoutRead);
                                ftpConnection.connect(source.toURL().getHost());
                                ftpConnection.enterLocalPassiveMode();
                                ftpConnection.login("anonymous", "");

                                String decodedUrl = URLDecoder.decode(source.toURL().getPath(), "ASCII");
                                FTPFile[] files = ftpConnection.listFiles(decodedUrl);

                                if (files.length == 0) {
                                    Flag flag = new Flag(Flag.NOT_FOUND, "FTP file URL was not found.", batch.getAction().toString(), bitstream);
                                    batch.ignoreRow(bitstream.getRow());
                                    batch.failedRow(bitstream.getRow());
                                    Problem missingFile = new Problem(bitstream.getRow(), bitstream.getColumnLabel(), generatesError(), "FTP file URL was not found.", flag);
                                    missingFiles.add(missingFile);
                                    if (console != null) {
                                        console.append("\t" + missingFile.toString() + "\n");
                                    }
                                    if (flagPanel != null) {
                                        flagPanel.appendRow(flag);
                                    }
                                }
                            } catch (IOException e) {
                                Flag flag = new Flag(Flag.IO_FAILURE, "FTP file URL had a connection problem, message: " + e.getMessage(), batch.getAction().toString(), bitstream);
                                batch.ignoreRow(bitstream.getRow());
                                batch.failedRow(bitstream.getRow());
                                Problem missingFile = new Problem(bitstream.getRow(), bitstream.getColumnLabel(), generatesError(), "FTP file URL had a connection problem.", flag);
                                missingFiles.add(missingFile);
                                if (console != null) {
                                    console.append("\t" + missingFile.toString() + "\n");
                                }
                                if (flagPanel != null) {
                                    flagPanel.appendRow(flag);
                                }
                            } catch (InterruptedException e) {
                                if (isCancelled()) {
                                    abortConnections();
                                    if (console != null) {
                                        console.append("Cancelled " + prettyName() + ".\n");
                                    }
                                    return missingFiles;
                                }
                            }

                            try {
                                if (ftpConnection.isConnected()) {
                                    ftpConnection.disconnect();
                                }
                            } catch (IOException e) {
                                Problem warning = new Problem(bitstream.getRow(), bitstream.getColumnLabel(), false, "Error when closing FTP connection, reason: " + e.getMessage() + ".");
                                missingFiles.add(warning);
                                if (console != null) {
                                    console.append("\t" + warning.toString() + "\n");
                                }
                            }

                            ftpConnection = null;
                        } else {
                            if (isCancelled()) {
                                abortConnections();
                                if (console != null) {
                                    console.append("Cancelled " + prettyName() + ".\n");
                                }
                                return missingFiles;
                            }

                            int itemProcessDelay = batch.getItemProcessDelay();
                            if (itemProcessDelay > 0) {
                                try {
                                    TimeUnit.MILLISECONDS.sleep(itemProcessDelay);
                                } catch (InterruptedException e) {
                                    if (isCancelled()) {
                                        abortConnections();
                                        if (console != null) {
                                            console.append("Cancelled " + prettyName() + ".\n");
                                        }
                                        return missingFiles;
                                    } else {
                                        Problem warning = new Problem(bitstream.getRow(), bitstream.getColumnLabel(),
                                                false, "Failed to sleep for " + itemProcessDelay
                                                        + " milliseconds, reason: " + e.getMessage() + ".");
                                        missingFiles.add(warning);
                                        if (console != null) {
                                            console.append("\t" + warning.toString() + "\n");
                                        }
                                    }
                                }
                            }

                            abortConnections();

                            String userAgent = batch.getUserAgent();
                            httpClient = createHttpClient(batch.getAllowSelfSigned());
                            httpHead = null;
                            httpGet = null;

                            try {
                                httpHead = new HttpHead(source.toURL().toString());
                                httpHead.setConfig(requestConfig);
                                if (userAgent != null) {
                                    httpHead.addHeader("User-Agent", userAgent);
                                }
                                httpResponse = httpClient.execute(httpHead);
                                processHttpResponseStatus(httpResponse);

                                Header redirectTo = httpResponse.getFirstHeader("Location");
                                httpHead.releaseConnection();
                                httpHead = null;

                                if (isCancelled()) {
                                    abortConnections();
                                    if (console != null) {
                                        console.append("Cancelled " + prettyName() + ".\n");
                                    }
                                    return missingFiles;
                                }

                                // some servers do no support HEAD requests, so attempt a GET request.
                                if (responseCode == HttpURLConnection.HTTP_BAD_METHOD) {
                                    httpGet = new HttpGet(source.toURL().toString());
                                    httpGet.setConfig(requestConfig);
                                    if (userAgent != null) {
                                        httpGet.addHeader("User-Agent", userAgent);
                                    }
                                    httpResponse = httpClient.execute(httpGet);
                                    processHttpResponseStatus(httpResponse);
                                    redirectTo = httpResponse.getFirstHeader("Location");
                                    httpGet.releaseConnection();
                                    httpGet = null;
                                }

                                if (responseCode == HttpURLConnection.HTTP_SEE_OTHER || responseCode == HttpURLConnection.HTTP_MOVED_PERM || responseCode == HttpURLConnection.HTTP_MOVED_TEMP) {
                                    int totalRedirects = 0;
                                    HashSet<String> previousUrls = new HashSet<String>();
                                    previousUrls.add(source.toString());
                                    URL previousUrl = source.toURL();

                                    do {
                                        if (isCancelled()) {
                                            abortConnections();
                                            if (console != null) {
                                                console.append("Cancelled " + prettyName() + ".\n");
                                            }
                                            return missingFiles;
                                        }

                                        if (totalRedirects++ > MaxRedirects) {
                                            Flag flag = new Flag(Flag.REDIRECT_LIMIT, "HTTP URL redirected too many times, final redirect URL: " + previousUrl, batch.getAction().toString(), bitstream);
                                            batch.ignoreRow(bitstream.getRow());
                                            batch.failedRow(bitstream.getRow());
                                            Problem missingFile = new Problem(bitstream.getRow(), bitstream.getColumnLabel(), true, "HTTP URL redirected too many times.", flag);
                                            missingFiles.add(missingFile);
                                            if (console != null) {
                                                console.append("\t" + missingFile.toString() + "\n");
                                            }
                                            if (flagPanel != null) {
                                                flagPanel.appendRow(flag);
                                            }
                                            break;
                                        }

                                        if (redirectTo == null) {
                                            Flag flag = new Flag(Flag.REDIRECT_FAILURE, "HTTP URL redirected without a valid destination URL.", batch.getAction().toString(), bitstream);
                                            batch.ignoreRow(bitstream.getRow());
                                            batch.failedRow(bitstream.getRow());
                                            Problem missingFile = new Problem(bitstream.getRow(), bitstream.getColumnLabel(), true, "HTTP URL redirected without a valid destination URL.", flag);
                                            missingFiles.add(missingFile);
                                            if (console != null) {
                                                console.append("\t" + missingFile.toString() + "\n");
                                            }
                                            if (flagPanel != null) {
                                                flagPanel.appendRow(flag);
                                            }
                                            break;
                                        }

                                        String redirectToLocation = redirectTo.getValue();
                                        URI redirectToUri = null;
                                        try {
                                            redirectToUri = new URI(redirectToLocation);
                                        } catch (URISyntaxException e) {
                                            // attempt to correct an invalid URL, focus on ASCII space.
                                            redirectToLocation = redirectToLocation.replace(" ", "%20");
                                            try {
                                                redirectToUri = new URI(redirectToLocation);
                                            } catch (URISyntaxException e1) {
                                                Flag flag = new Flag(Flag.REDIRECT_FAILURE, "HTTP URL redirected to an invalid URL, reason: " + e.getMessage() + ".", batch.getAction().toString(), bitstream);
                                                batch.ignoreRow(bitstream.getRow());
                                                batch.failedRow(bitstream.getRow());
                                                Problem missingFile = new Problem(bitstream.getRow(), bitstream.getColumnLabel(), true, "HTTP URL redirected to an invalid URL.", flag);
                                                missingFiles.add(missingFile);
                                                if (console != null) {
                                                    console.append("\t" + missingFile.toString() + "\n");
                                                }
                                                if (flagPanel != null) {
                                                    flagPanel.appendRow(flag);
                                                }
                                                break;
                                            }
                                        }

                                        String authority = redirectToUri.getAuthority();
                                        String scheme = redirectToUri.getScheme();
                                        if (authority == null || authority.isEmpty()) {
                                            if (!redirectToLocation.startsWith("/")) {
                                                redirectToLocation = "/" + redirectToLocation;
                                            }
                                            redirectToLocation = previousUrl.getAuthority() + redirectToLocation;
                                            if (scheme == null || scheme.isEmpty()) {
                                                if (redirectToLocation.startsWith("//")) {
                                                    redirectToLocation = "http:" + redirectToLocation;
                                                } else {
                                                    redirectToLocation = "http://" + redirectToLocation;
                                                }
                                            }
                                            try {
                                                redirectToUri = new URI(redirectToLocation);
                                            } catch (URISyntaxException e) {
                                                // attempt to correct an invalid URL, focus on ASCII space.
                                                redirectToLocation = redirectToLocation.replace(" ", "%20");
                                                try {
                                                    redirectToUri = new URI(redirectToLocation);
                                                } catch (URISyntaxException e1) {
                                                    Flag flag = new Flag(Flag.REDIRECT_FAILURE, "HTTP URL redirected to an invalid URL, reason: " + e.getMessage() + ".", batch.getAction().toString(), bitstream);
                                                    batch.ignoreRow(bitstream.getRow());
                                                    batch.failedRow(bitstream.getRow());
                                                    Problem missingFile = new Problem(bitstream.getRow(), bitstream.getColumnLabel(), true, "HTTP URL redirected to an invalid URL.", flag);
                                                    missingFiles.add(missingFile);
                                                    if (console != null) {
                                                        console.append("\t" + missingFile.toString() + "\n");
                                                    }
                                                    if (flagPanel != null) {
                                                        flagPanel.appendRow(flag);
                                                    }
                                                    break;
                                                }
                                            }
                                        }

                                        if (previousUrls.contains(redirectToLocation)) {
                                            Flag flag = new Flag(Flag.REDIRECT_LOOP, "HTTP URL has circular redirects, final redirect URL: " + redirectToLocation + ".", batch.getAction().toString(), bitstream);
                                            batch.ignoreRow(bitstream.getRow());
                                            batch.failedRow(bitstream.getRow());
                                            Problem missingFile = new Problem(bitstream.getRow(), bitstream.getColumnLabel(), true, "HTTP URL has circular redirects.", flag);
                                            missingFiles.add(missingFile);
                                            if (console != null) {
                                                console.append("\t" + missingFile.toString() + "\n");
                                            }
                                            if (flagPanel != null) {
                                                flagPanel.appendRow(flag);
                                            }
                                            break;
                                        }

                                        if (isCancelled()) {
                                            abortConnections();
                                            if (console != null) {
                                                console.append("Cancelled " + prettyName() + ".\n");
                                            }
                                            return missingFiles;
                                        }

                                        httpHead = new HttpHead(redirectToLocation);
                                        httpHead.setConfig(requestConfig);
                                        if (userAgent != null) {
                                            httpHead.addHeader("User-Agent", userAgent);
                                        }
                                        httpResponse = httpClient.execute(httpHead);
                                        processHttpResponseStatus(httpResponse);
                                        httpHead.releaseConnection();
                                        httpHead = null;
                                        previousUrl = redirectToUri.toURL();

                                        // some servers do no support HEAD requests, so attempt a GET request.
                                        if (responseCode == HttpURLConnection.HTTP_BAD_METHOD) {
                                            if (isCancelled()) {
                                                abortConnections();
                                                if (console != null) {
                                                    console.append("Cancelled " + prettyName() + ".\n");
                                                }
                                                return missingFiles;
                                            }

                                            httpGet = new HttpGet(redirectToUri.toURL().toString());
                                            httpGet.setConfig(requestConfig);
                                            if (userAgent != null) {
                                                httpGet.addHeader("User-Agent", userAgent);
                                            }
                                            httpResponse = httpClient.execute(httpGet);
                                            processHttpResponseStatus(httpResponse);
                                            redirectTo = httpResponse.getFirstHeader("Location");
                                            httpGet.releaseConnection();
                                            httpGet = null;
                                        } else {
                                            redirectTo = httpResponse.getFirstHeader("Location");
                                        }
                                    } while (responseCode == HttpURLConnection.HTTP_SEE_OTHER || responseCode == HttpURLConnection.HTTP_MOVED_PERM || responseCode == HttpURLConnection.HTTP_MOVED_TEMP);
                                }

                                if (responseCode != HttpURLConnection.HTTP_OK && responseCode != HttpURLConnection.HTTP_SEE_OTHER && responseCode != HttpURLConnection.HTTP_MOVED_PERM && responseCode != HttpURLConnection.HTTP_MOVED_TEMP) {
                                    if (responseCode == 304 || responseCode == 509) {
                                        Flag flag = new Flag(Flag.SERVICE_REJECTED, "HTTP service was denied (may have a download/bandwidth limit), HTTP response code: " + responseCode + ".", batch.getAction().toString(), bitstream);
                                        batch.ignoreRow(bitstream.getRow());
                                        batch.failedRow(bitstream.getRow());
                                        Problem missingFile = new Problem(bitstream.getRow(), bitstream.getColumnLabel(), generatesError(), "HTTP service was denied, HTTP response code: " + responseCode + ".", flag);
                                        missingFiles.add(missingFile);
                                        if (console != null) {
                                            console.append("\t" + missingFile.toString() + "\n");
                                        }
                                        if (flagPanel != null) {
                                            flagPanel.appendRow(flag);
                                        }
                                    } else if (responseCode == 404) {
                                        Flag flag = new Flag(Flag.NOT_FOUND, "HTTP file was not found, HTTP response code: " + responseCode + ".", batch.getAction().toString(), bitstream);
                                        batch.ignoreRow(bitstream.getRow());
                                        batch.failedRow(bitstream.getRow());
                                        Problem missingFile = new Problem(bitstream.getRow(), bitstream.getColumnLabel(), generatesError(), "HTTP file was not found, HTTP response code: " + responseCode + ".", flag);
                                        missingFiles.add(missingFile);
                                        if (console != null) {
                                            console.append("\t" + missingFile.toString() + "\n");
                                        }
                                        if (flagPanel != null) {
                                            flagPanel.appendRow(flag);
                                        }
                                    } else if (responseCode == 403) {
                                        Flag flag = new Flag(Flag.ACCESS_DENIED, "HTTP file access was denied, HTTP response code: " + responseCode + ".", batch.getAction().toString(), bitstream);
                                        batch.ignoreRow(bitstream.getRow());
                                        batch.failedRow(bitstream.getRow());
                                        Problem missingFile = new Problem(bitstream.getRow(), bitstream.getColumnLabel(), generatesError(), "HTTP file access was denied, HTTP response code: " + responseCode + ".", flag);
                                        missingFiles.add(missingFile);
                                        if (console != null) {
                                            console.append("\t" + missingFile.toString() + "\n");
                                        }
                                        if (flagPanel != null) {
                                            flagPanel.appendRow(flag);
                                        }
                                    } else if (responseCode == 500) {
                                        Flag flag = new Flag(Flag.SERVICE_ERROR, "HTTP server had an internal error, HTTP response code: " + responseCode + ".", batch.getAction().toString(), bitstream);
                                        batch.ignoreRow(bitstream.getRow());
                                        batch.failedRow(bitstream.getRow());
                                        Problem missingFile = new Problem(bitstream.getRow(), bitstream.getColumnLabel(), generatesError(), "HTTP server had an internal error, HTTP response code: " + responseCode + ".", flag);
                                        missingFiles.add(missingFile);
                                        if (console != null) {
                                            console.append("\t" + missingFile.toString() + "\n");
                                        }
                                        if (flagPanel != null) {
                                            flagPanel.appendRow(flag);
                                        }
                                    } else {
                                        Flag flag = new Flag(Flag.HTTP_FAILURE, "HTTP failure, HTTP response code: " + responseCode + ".", batch.getAction().toString(), bitstream);
                                        batch.ignoreRow(bitstream.getRow());
                                        batch.failedRow(bitstream.getRow());
                                        Problem missingFile = new Problem(bitstream.getRow(), bitstream.getColumnLabel(), generatesError(), "HTTP failure, HTTP response code: " + responseCode + ".", flag);
                                        missingFiles.add(missingFile);
                                        if (console != null) {
                                            console.append("\t" + missingFile.toString() + "\n");
                                        }
                                        if (flagPanel != null) {
                                            flagPanel.appendRow(flag);
                                        }
                                    }
                                }
                            } catch (MalformedURLException e) {
                                String responseString = (responseCode > 0 ? ", HTTP response code: " + responseCode : "");
                                Flag flag = new Flag(Flag.INVALID_FORMAT, "HTTP URL is invalid" + responseString + ", reason: " + e.getMessage() + ".", batch.getAction().toString(), bitstream);
                                batch.ignoreRow(bitstream.getRow());
                                batch.failedRow(bitstream.getRow());
                                Problem missingFile = new Problem(bitstream.getRow(), bitstream.getColumnLabel(), generatesError(), "HTTP URL is invalid" + responseString + ".", flag);
                                missingFiles.add(missingFile);
                                if (console != null) {
                                    console.append("\t" + missingFile.toString() + "\n");
                                }
                                if (flagPanel != null) {
                                    flagPanel.appendRow(flag);
                                }
                            } catch (SSLProtocolException e) {
                                String responseString = (responseCode > 0 ? ", HTTP response code: " + responseCode : "");
                                Flag flag = new Flag(Flag.SSL_FAILURE, "HTTP URL had an SSL failure" + responseString + ", reason: " + e.getMessage() + ".", batch.getAction().toString(), bitstream);
                                batch.ignoreRow(bitstream.getRow());
                                batch.failedRow(bitstream.getRow());
                                Problem missingFile = new Problem(bitstream.getRow(), bitstream.getColumnLabel(), generatesError(), "HTTP URL had an SSL failure" + responseString + ".", flag);
                                missingFiles.add(missingFile);
                                if (console != null) {
                                    console.append("\t" + missingFile.toString() + "\n");
                                }
                                if (flagPanel != null) {
                                    flagPanel.appendRow(flag);
                                }
                            } catch (IOException e) {
                                String responseString = (responseCode > 0 ? ", HTTP response code: " + responseCode : "");
                                Flag flag = new Flag(Flag.IO_FAILURE, "HTTP URL had a connection error, reason: " + e.getMessage() + ".", batch.getAction().toString(), bitstream);
                                batch.ignoreRow(bitstream.getRow());
                                batch.failedRow(bitstream.getRow());
                                Problem missingFile = new Problem(bitstream.getRow(), bitstream.getColumnLabel(), generatesError(), "HTTP URL had a connection error" + responseString + ".", flag);
                                missingFiles.add(missingFile);
                                if (console != null) {
                                    console.append("\t" + missingFile.toString() + "\n");
                                }
                                if (flagPanel != null) {
                                    flagPanel.appendRow(flag);
                                }
                            } finally {
                                if (ftpConnection != null && ftpConnection.isConnected()) {
                                    try {
                                        ftpConnection.disconnect();
                                    } catch (IOException e) {
                                    }
                                    ftpConnection = null;
                                }

                                if (httpResponse != null) {
                                    try {
                                        httpResponse.close();
                                    } catch (IOException e) {
                                    }
                                    httpResponse = null;
                                }

                                if (httpHead != null) {
                                    httpHead.releaseConnection();
                                    httpHead = null;
                                }

                                if (httpGet != null) {
                                    httpGet.releaseConnection();
                                    httpGet = null;
                                }

                                if (httpClient != null) {
                                    try {
                                        httpClient.close();
                                    } catch (IOException e) {
                                    }
                                    httpClient = null;
                                }
                            }
                        }
                    }
                }

                if (isCancelled()) {
                    abortConnections();
                    if (console != null) {
                        console.append("Cancelled " + prettyName() + ".\n");
                    }
                    return missingFiles;
                }

                itemCount++;
                publish(new VerifierBackground.VerifierUpdates(itemCount, totalItems));
            }
        }

        if (isCancelled()) {
            abortConnections();
            if (console != null) {
                console.append("Cancelled " + prettyName() + ".\n");
            }
            return missingFiles;
        }

        return missingFiles;
    }

    private void processHttpResponseStatus(CloseableHttpResponse httpResponse) {
        if (httpResponse != null) {
            StatusLine statusLine = httpResponse.getStatusLine();
            if (statusLine != null) {
                responseCode = statusLine.getStatusCode();
            }
        }
    }

    private CloseableHttpClient createHttpClient(boolean allowSelfSigned) {
        CloseableHttpClient httpClient = null;

        if (allowSelfSigned) {
            try {
                SSLContext sslContext = SSLContexts.custom().loadTrustMaterial(null, new TrustSelfSignedStrategy()).build();
                SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext, new NoopHostnameVerifier());
                httpClient = HttpClients.custom().setSSLSocketFactory(sslConnectionSocketFactory).build();
            } catch (KeyManagementException | NoSuchAlgorithmException | KeyStoreException e) {
                e.printStackTrace();
            }
        }

        if (httpClient == null) {
            httpClient = HttpClients.createDefault();
        }

        return httpClient;
    }
}
src/main/java/edu/tamu/di/SAFCreator/model/verify/VerifierProperty.java
public interface VerifierProperty {
    public boolean generatesError();

    public boolean getActivated();

    public boolean isSwingWorker();

    public String prettyName();

    public void setActivated(boolean activated);
}
src/main/java/edu/tamu/di/SAFCreator/model/verify/impl/LocalFilesExistVerifierImpl.java
public class LocalFilesExistVerifierImpl extends VerifierBackground {

    public LocalFilesExistVerifierImpl() {
        super();
    }

    public LocalFilesExistVerifierImpl(VerifierProperty settings) {
        super(settings);
    }

    @Override
    public void doCancel() {
    }

    @Override
    protected List<Problem> doInBackground() {
        return new ArrayList<Problem>();
    }

    @Override
    public boolean generatesError() {
        return true;
    }

    @Override
    public boolean isSwingWorker() {
        return true;
    }

    @Override
    public String prettyName() {
        return "Local Content Files Exist Verifier";
    }

    @Override
    public List<Problem> verify(Batch batch) {
        return verify(batch, null, null);
    }

    @Override
    public List<Problem> verify(Batch batch, JTextArea console, FlagPanel flagPanel) {
        List<Problem> missingFiles = new ArrayList<Problem>();

        if (!batch.getIgnoreFiles()) {
            int totalItems = batch.getItems().size();
            int itemCount = 0;

            for (Item item : batch.getItems()) {
                for (Bundle bundle : item.getBundles()) {
                    for (Bitstream bitstream : bundle.getBitstreams()) {
                        if (isCancelled()) {
                            if (console != null) {
                                console.append("Cancelled " + prettyName() + ".\n");
                            }
                            return missingFiles;
                        }

                        URI source = bitstream.getSource();
                        if (!source.isAbsolute() || source.getScheme().toString().equalsIgnoreCase("file")) {
                            File file = new File(bitstream.getSource().getPath());

                            if (!file.exists()) {
                                Flag flag = new Flag(Flag.NOT_FOUND, "source file path was not found.", "local",
                                        file.getAbsolutePath(), bitstream.getColumnLabel(), "" + bitstream.getRow(),
                                        batch.getAction().toString());
                                Problem missingFile = new Problem(bitstream.getRow(), bitstream.getColumnLabel(),
                                        generatesError(), "Source file path not found.", flag);
                                missingFiles.add(missingFile);
                                if (console != null) {
                                    console.append("\t" + missingFile.toString() + "\n");
                                }
                                if (flagPanel != null) {
                                    flagPanel.appendRow(flag);
                                }
                            }
                        }
                    }
                }

                if (isCancelled()) {
                    if (console != null) {
                        console.append("Cancelled " + prettyName() + ".\n");
                    }
                    return missingFiles;
                }

                itemCount++;
                publish(new VerifierBackground.VerifierUpdates(itemCount, totalItems));
            }
        }

        return missingFiles;
    }
}
src/main/java/edu/tamu/di/SAFCreator/model/importData/ImportDataCleaner.java
public class ImportDataCleaner extends SwingWorker<Boolean, ImportDataOperator.Updates> implements ImportDataOperator {
    private Batch batch = null;
    private JTextArea console = null;
    private FlagPanel flags = null;

    @Override
    protected Boolean doInBackground() {
        boolean noErrors = true;
        int itemCount = 1;
        int totalItems = batch.getItems().size();

        for (Item item : batch.getItems()) {
            if (isCancelled()) {
                break;
            }

            itemCount++;

            if (batch.isFailedRow(itemCount)) {
                File directory = new File(item.getSAFDirectory());

                if (directory.isDirectory()) {
                    try {
                        FileUtils.deleteDirectory(directory);
                        console.append("\tDeleted directory for (row " + itemCount + "), because of write failure.\n");
                    } catch (IOException e) {
                        noErrors = false;
                        Flag flag = new Flag(Flag.DELETE_FAILURE, "Failed to delete directory.", "", "", "", "" + itemCount, batch.getAction());
                        flags.appendRow(flag);
                        console.append("\tFailed to delete directory for (row " + itemCount + "), due to an error.\n");
                    }
                }
            }

            publish(new ImportDataOperator.Updates(itemCount - 1, totalItems));
        }

        if (isCancelled()) {
            console.append("Cancelled cleaning SAF.\n");
            return null;
        }

        console.append("Done cleaning SAF data.\n");
        return noErrors;
    }

    @Override
    public Batch getBatch() {
        return batch;
    }

    @Override
    public JTextArea getConsole() {
        return console;
    }

    @Override
    public FlagPanel getFlags() {
        return flags;
    }

    @Override
    public void setBatch(Batch batch) {
        this.batch = batch;
    }

    @Override
    public void setConsole(JTextArea console) {
        this.console = console;
    }

    @Override
    public void setFlags(FlagPanel flags) {
        this.flags = flags;
    }
}
src/main/java/edu/tamu/di/SAFCreator/model/verify/impl/ValidSchemaNameVerifierImpl.java
public class ValidSchemaNameVerifierImpl extends VerifierBackground {

    public ValidSchemaNameVerifierImpl() {
        super();
    }

    public ValidSchemaNameVerifierImpl(VerifierProperty settings) {
        super(settings);
    }

    @Override
    public void doCancel() {
    }

    @Override
    public boolean generatesError() {
        return true;
    }

    @Override
    public String prettyName() {
        return "Syntactically Valid Schema Names Verifier";
    }

    @Override
    public List<Problem> verify(Batch batch) {
        return verify(batch, null, null);
    }

    @Override
    public List<Problem> verify(Batch batch, JTextArea console, FlagPanel flagPanel) {
        List<Problem> badSchemata = new ArrayList<Problem>();

        int totalLabels = batch.getLabels().size();
        int labelCount = 0;
        for (ColumnLabel label : batch.getLabels()) {
            if (label.isField()) {
                FieldLabel fieldLabel = (FieldLabel) label;
                if (Util.regexMatchCounter(".*\\W+.*", fieldLabel.getSchema()) > 0) {
                    Flag flag = new Flag(Flag.BAD_SCHEMA_NAME, "Bad schema name for row " + labelCount + ".", "", "",
                            label.getColumnLabel(), "" + label.getRow(), batch.getAction());
                    Problem badSchema = new Problem(label.getRow(), label.getColumnLabel(), generatesError(),
                            "Bad schema name " + fieldLabel.getSchema());
                    badSchema.setFlag(flag);
                    batch.failedRow(label.getRow());
                    badSchemata.add(badSchema);
                    if (console != null) {
                        console.append("\t" + badSchemata.toString() + "\n");
                    }
                    if (flagPanel != null) {
                        flagPanel.appendRow(flag);
                    }
                } else {
                    // System.out.println("looks fine: " + fieldLabel.getSchema());
                }
            }

            if (isCancelled()) {
                return badSchemata;
            }

            labelCount++;
            publish(new VerifierBackground.VerifierUpdates(labelCount, totalLabels));
        }

        return badSchemata;
    }
}
src/main/java/edu/tamu/di/SAFCreator/model/Problem.java
public class Problem {
    private Integer rownumber;
    private String columnLabel;
    private boolean error;
    private String note;
    private Flag flag;


    public Problem(boolean error, String note) {
        this.error = error;
        this.note = note;
    }

    public Problem(int rownumber, String columnLabel, boolean error, String note) {
        this.rownumber = rownumber;
        this.columnLabel = columnLabel;
        this.error = error;
        this.note = note;
    }

    public Problem(int rownumber, String columnLabel, boolean error, String note, Flag flag) {
        this.rownumber = rownumber;
        this.columnLabel = columnLabel;
        this.error = error;
        this.note = note;
        this.flag = flag;
    }

    public Flag getFlag() {
        return flag;
    }

    public boolean isError() {
        return error;
    }

    public boolean isFlagged() {
        return flag != null;
    }

    public void setFlag(Flag flag) {
        this.flag = flag;
    }

    @Override
    public String toString() {
        String flagged = "";

        if (flag != null) {
            flagged = "Flagged ";
        }

        if (rownumber == null) {
            return flagged + (error ? "ERROR" : "WARNING") + ": " + note;
        }

        return flagged + (error ? "ERROR at " : "WARNING at ") + "column " + columnLabel + " row " + rownumber + ":\n\t" + note;
    }
}
src/main/java/edu/tamu/di/SAFCreator/enums/FieldChangeStatus.java
public enum FieldChangeStatus {
    NO_CHANGES,
    CHANGES
}
src/main/java/edu/tamu/di/SAFCreator/model/verify/VerifierBackground.java
public abstract class VerifierBackground extends SwingWorker<List<Problem>, VerifierBackground.VerifierUpdates> implements Verifier {

    public class VerifierUpdates {
        private int processed;
        private int total;

        public VerifierUpdates() {
            processed = 0;
            total = 0;
        }

        public VerifierUpdates(int processed, int total) {
            this.processed = processed;
            this.total = total;
        }

        public int getProcessed() {
            return processed;
        }

        public int getTotal() {
            return total;
        }

        public void setProcessed(int processed) {
            this.processed = processed;
        }

        public void setTotal(int total) {
            this.total = total;
        }
    }

    private boolean enabled;

    public VerifierBackground() {
        enabled = true;
    }

    public VerifierBackground(VerifierProperty settings) {
        this();
        enabled = settings.getActivated();
    }

    /**
     * Provide a way to close active connections or similar situations on cancel.
     *
     * This must be provided in addition to cancel() because that method as provided by SwingWorker is a final method.
     */
    public abstract void doCancel();

    @Override
    protected List<Problem> doInBackground() {
        return null;
    }

    @Override
    public boolean getActivated() {
        return enabled;
    }

    @Override
    public boolean isSwingWorker() {
        return true;
    }

    @Override
    public void setActivated(boolean enabled) {
        this.enabled = enabled;
    }
}
src/main/java/edu/tamu/di/SAFCreator/view/UserInterfaceView.java
public final class UserInterfaceView extends JFrame {

    private static final long serialVersionUID = 1L;

    // defaults
    public static final String DEFAULT_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; WOW64; rv:62.0) Gecko/20100101 Firefox/62.0";
    public static final int DEFAULT_PROCESS_DELAY = 400;
    public static final int DEFAULT_REMOTE_FILE_TIMEOUT = 10000;
    public static final String DEFAULT_CSV_OUTPUT_NAME = "SAF-Flags.csv";

    // tabbed views
    private final JPanel mainTab = new JPanel();
    private final JPanel licenseTab = new JPanel();
    private final JPanel validationTab = new JPanel();
    private final JPanel advancedSettingsTab = new JPanel();
    private final JPanel flagTableTab = new JPanel();

    private final JTabbedPane tabs = new JTabbedPane();

    // Components of the Batch Detail tab
    private final JButton chooseInputFileBtn = new JButton("Select metadata CSV file");
    private final JButton chooseSourceDirectoryBtn = new JButton("Select source files directory");
    private final JButton chooseOutputDirectoryBtn = new JButton("Select SAF output directory");

    private final JButton loadBatchBtn = new JButton("Load specified batch now!");
    private final JButton writeSAFBtn = new JButton("No batch loaded");
    private final JButton writeCancelBtn = new JButton("Cancel");

    private final JFileChooser inputFileChooser = new JFileChooser(".");
    private final JFileChooser sourceDirectoryChooser = new JFileChooser(".");
    private final JFileChooser outputDirectoryChooser = new JFileChooser(".");

    private final JTextField inputFileNameField = new JTextField("", 42);
    private final JTextField sourceDirectoryNameField = new JTextField("", 40);
    private final JTextField outputDirectoryNameField = new JTextField("", 40);
    private final JTextField actionStatusField = new JTextField("Please load a batch for processing.");

    // Components of the License tab
    private final JCheckBox addLicenseCheckbox = new JCheckBox("Add a license:");
    private final JCheckBox restrictToGroupCheckbox = new JCheckBox("Restrict read access to a group - Group name:");

    private final JLabel licenseFilenameFieldLabel = new JLabel("License bitstream filename:");
    private final JLabel licenseBundleNameFieldLabel = new JLabel("License bundle name:");

    private final JPanel addLicenseFilePanel = new JPanel();

    private final JTextArea licenseTextField = new JTextArea("This item is hereby licensed.", 6, 0);

    private final JTextField licenseFilenameField = new JTextField("license.txt", 39);
    private final JTextField licenseBundleNameField = new JTextField("LICENSE", 42);
    private final JTextField restrictToGroupField = new JTextField("member", 36);

    private final JScrollPane licenseTextScrollPane = new JScrollPane(licenseTextField);

    // Components of the Verify Batch tab
    private final JButton verifyBatchBtn = new JButton("Verify Batch");
    private final JButton verifyCancelBtn = new JButton("Cancel");

    private final JTable verifierTbl = new JTable();
    private final VerifierTableModel verifierTableModel;
    private List<VerifierProperty> verifierProperties = new ArrayList<VerifierProperty>();

    // Components of the Advanced Settings tab
    private final JCheckBox ignoreFilesBox = new JCheckBox("Omit bitstreams (content files) from generated SAF.");
    private final JCheckBox flattenDirectoryStructureBox = new JCheckBox("Flatten the generated SAF so item directories don't have subdirectories.");
    private final JCheckBox continueOnRemoteErrorBox = new JCheckBox("Allow writing even if remote bitstream verification flags an error.");
    private final JCheckBox allowSelfSignedBox = new JCheckBox("Allow Self-Signed SSL Certificates.");

    private final JLabel itemProcessDelayLabel = new JLabel("Item Processing Delay (in milliseconds):");
    private final JLabel remoteFileTimeoutLabel = new JLabel("Remote File Timeout (in milliseconds):");
    private final JLabel userAgentLabel = new JLabel("User agent:");

    private final JTextField itemProcessDelayField = new JTextField("" + DEFAULT_PROCESS_DELAY, 10);
    private final JTextField remoteFileTimeoutField = new JTextField("" + DEFAULT_REMOTE_FILE_TIMEOUT, 10);
    private final JTextField userAgentField = new JTextField(DEFAULT_USER_AGENT, 48);

    // Components of the Flag List tab
    private final JButton flagsDownloadCsvBtn = new JButton("Generate CSV");
    private final JButton flagsReportSelectedBtn = new JButton("Display Selected Row");

    private final FlagPanel flagPanel = new FlagPanel();

    // Components shown under any tab
    private final JPanel statusPanel = new JPanel();

    private final JTextArea statusIndicator = new JTextArea("No batch loaded", 2, 10);
    private final JTextArea console = new JTextArea(20, 50);

    private final JScrollPane scrollPane = new JScrollPane(console);

    public UserInterfaceView() {
        VerifierProperty validSchemaVerifier = new ValidSchemaNameVerifierImpl();
        VerifierProperty localFileExistsVerifier = new LocalFilesExistVerifierImpl();
        VerifierProperty remoteFileExistsVerifier = new RemoteFilesExistVerifierImpl();

        verifierProperties.add(validSchemaVerifier);
        verifierProperties.add(localFileExistsVerifier);
        verifierProperties.add(remoteFileExistsVerifier);

        verifierTableModel = new VerifierTableModel(verifierProperties);

        createBatchDetailsTab();

        createLicenseTab();

        createVerificationTab();

        createAdvancedSettingsTab();

        createFlagTableTab();

        setLayout(new BoxLayout(getContentPane(), BoxLayout.Y_AXIS));

        // add the tabbed views
        getContentPane().add(tabs);

        // add the status info area present under all tabs
        console.setEditable(false);
        console.setLineWrap(true);
        scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);

        statusIndicator.setBackground(Color.blue);
        statusIndicator.setForeground(Color.white);
        statusIndicator.setEditable(false);
        statusIndicator.setBorder(BorderFactory.createEtchedBorder(EtchedBorder.RAISED));
        scrollPane.setBorder(BorderFactory.createEtchedBorder(EtchedBorder.RAISED));

        statusPanel.add(statusIndicator);
        statusPanel.add(scrollPane);
        getContentPane().add(statusPanel);

        initializeState();

        pack();
        setLocationRelativeTo(null);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setTitle("DSpace Simple Archive Format Creator");
    }

    public JTextField getActionStatusField() {
        return actionStatusField;
    }

    public JCheckBox getAddLicenseCheckbox() {
        return addLicenseCheckbox;
    }

    public JPanel getAddLicenseFilePanel() {
        return addLicenseFilePanel;
    }

    public JPanel getAdvancedSettingsTab() {
        return advancedSettingsTab;
    }

    public JCheckBox getAllowSelfSignedBox() {
        return allowSelfSignedBox;
    }

    public JButton getChooseInputFileBtn() {
        return chooseInputFileBtn;
    }

    public JButton getChooseOutputDirectoryBtn() {
        return chooseOutputDirectoryBtn;
    }

    public JButton getChooseSourceDirectoryBtn() {
        return chooseSourceDirectoryBtn;
    }

    public JTextArea getConsole() {
        return console;
    }

    public JCheckBox getContinueOnRemoteErrorBox() {
        return continueOnRemoteErrorBox;
    }

    public FlagPanel getFlagPanel() {
        return flagPanel;
    }

    public JButton getFlagsDownloadCsvBtn() {
        return flagsDownloadCsvBtn;
    }

    public JButton getFlagsReportSelectedBtn() {
        return flagsReportSelectedBtn;
    }

    public JPanel getFlagTableTab() {
        return flagTableTab;
    }

    public JCheckBox getIgnoreFilesBox() {
        return ignoreFilesBox;
    }

    public JCheckBox getFlattenDirectoryStructureBox() {
        return flattenDirectoryStructureBox;
    }

    public JFileChooser getInputFileChooser() {
        return inputFileChooser;
    }

    public JTextField getInputFileNameField() {
        return inputFileNameField;
    }

    public JTextField getItemProcessDelayField() {
        return itemProcessDelayField;
    }

    public JLabel getItemProcessDelayLabel() {
        return itemProcessDelayLabel;
    }

    public JTextField getLicenseBundleNameField() {
        return licenseBundleNameField;
    }

    public JLabel getLicenseBundleNameFieldLabel() {
        return licenseBundleNameFieldLabel;
    }

    public JTextField getLicenseFilenameField() {
        return licenseFilenameField;
    }

    public JLabel getLicenseFilenameFieldLabel() {
        return licenseFilenameFieldLabel;
    }

    public JPanel getLicenseTab() {
        return licenseTab;
    }

    public JTextArea getLicenseTextField() {
        return licenseTextField;
    }

    public JScrollPane getLicenseTextScrollPane() {
        return licenseTextScrollPane;
    }

    public JButton getLoadBatchBtn() {
        return loadBatchBtn;
    }

    public JPanel getMainTab() {
        return mainTab;
    }

    public JFileChooser getOutputDirectoryChooser() {
        return outputDirectoryChooser;
    }

    public JTextField getOutputDirectoryNameField() {
        return outputDirectoryNameField;
    }

    public JTextField getRemoteFileTimeoutField() {
        return remoteFileTimeoutField;
    }

    public JLabel getRemoteFileTimeoutLabel() {
        return remoteFileTimeoutLabel;
    }

    public JCheckBox getRestrictToGroupCheckbox() {
        return restrictToGroupCheckbox;
    }

    public JTextField getRestrictToGroupField() {
        return restrictToGroupField;
    }

    public JScrollPane getScrollPane() {
        return scrollPane;
    }

    public JFileChooser getSourceDirectoryChooser() {
        return sourceDirectoryChooser;
    }

    public JTextField getSourceDirectoryNameField() {
        return sourceDirectoryNameField;
    }

    public JTextArea getStatusIndicator() {
        return statusIndicator;
    }

    public JPanel getStatusPanel() {
        return statusPanel;
    }

    public JTabbedPane getTabs() {
        return tabs;
    }

    public VerifierTableModel getTableModel() {
        return verifierTableModel;
    }

    public JTextField getUserAgentField() {
        return userAgentField;
    }

    public JLabel getUserAgentLabel() {
        return userAgentLabel;
    }

    public JPanel getValidationTab() {
        return validationTab;
    }

    public JTable getVerifierTbl() {
        return verifierTbl;
    }

    public JButton getVerifyBatchBtn() {
        return verifyBatchBtn;
    }

    public JButton getVerifyCancelBtn() {
        return verifyCancelBtn;
    }

    public JButton getWriteCancelBtn() {
        return writeCancelBtn;
    }

    public JButton getWriteSAFBtn() {
        return writeSAFBtn;
    }

    private void createAdvancedSettingsTab() {
        advancedSettingsTab.setLayout(new BoxLayout(advancedSettingsTab, BoxLayout.Y_AXIS));
        advancedSettingsTab.add(ignoreFilesBox);
        advancedSettingsTab.add(flattenDirectoryStructureBox);
        advancedSettingsTab.add(continueOnRemoteErrorBox);
        advancedSettingsTab.add(allowSelfSignedBox);

        // default to enabled.
        flattenDirectoryStructureBox.setSelected(true);
        allowSelfSignedBox.setSelected(true);

        tabs.addTab("Advanced Settings", advancedSettingsTab);

        JPanel itemProcessDelayPanel = new JPanel();
        itemProcessDelayPanel.add(itemProcessDelayLabel);
        itemProcessDelayPanel.add(itemProcessDelayField);
        advancedSettingsTab.add(itemProcessDelayPanel);

        JPanel remoteFileTimeoutPanel = new JPanel();
        remoteFileTimeoutPanel.add(remoteFileTimeoutLabel);
        remoteFileTimeoutPanel.add(remoteFileTimeoutField);
        advancedSettingsTab.add(remoteFileTimeoutPanel);

        JPanel userAgentPanel = new JPanel();
        userAgentPanel.add(userAgentLabel);
        userAgentPanel.add(userAgentField);
        advancedSettingsTab.add(userAgentPanel);

        // initialize as disabled so that it will only be enable once a batch is assigned.
        ignoreFilesBox.setEnabled(false);
        flattenDirectoryStructureBox.setEnabled(false);
        continueOnRemoteErrorBox.setEnabled(false);
        allowSelfSignedBox.setEnabled(false);
        itemProcessDelayField.setEnabled(false);
        remoteFileTimeoutField.setEnabled(false);
        userAgentField.setEnabled(false);
    }

    private void createBatchDetailsTab() {
        sourceDirectoryChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
        outputDirectoryChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);

        inputFileNameField.setEditable(false);
        sourceDirectoryNameField.setEditable(false);
        outputDirectoryNameField.setEditable(false);

        mainTab.setLayout(new BoxLayout(mainTab, BoxLayout.Y_AXIS));

        JPanel inputCSVPanel = new JPanel();
        inputCSVPanel.add(chooseInputFileBtn);
        inputCSVPanel.add(inputFileNameField);
        mainTab.add(inputCSVPanel);

        JPanel sourceFilesDirPanel = new JPanel();
        sourceFilesDirPanel.add(chooseSourceDirectoryBtn);
        sourceFilesDirPanel.add(sourceDirectoryNameField);
        mainTab.add(sourceFilesDirPanel);

        JPanel outputSAFDirPanel = new JPanel();
        outputSAFDirPanel.add(chooseOutputDirectoryBtn);
        outputSAFDirPanel.add(outputDirectoryNameField);
        mainTab.add(outputSAFDirPanel);

        JPanel writeButtonPanel = new JPanel();
        actionStatusField.setEditable(false);

        writeCancelBtn.setEnabled(false);
        writeSAFBtn.setEnabled(false);
        writeButtonPanel.add(loadBatchBtn);
        writeButtonPanel.add(actionStatusField);
        writeButtonPanel.add(writeSAFBtn);
        writeButtonPanel.add(writeCancelBtn);
        mainTab.add(writeButtonPanel);

        tabs.addTab("Batch Details", mainTab);
    }

    private void createFlagTableTab() {
        JPanel buttonsContainer = new JPanel();
        buttonsContainer.add(flagsDownloadCsvBtn);
        buttonsContainer.add(flagsReportSelectedBtn);
        buttonsContainer.setMaximumSize(new Dimension(400, 200));

        flagTableTab.setLayout(new BoxLayout(flagTableTab, BoxLayout.PAGE_AXIS));
        flagTableTab.add(flagPanel);
        flagTableTab.add(buttonsContainer);

        tabs.addTab("Flags", flagTableTab);

        flagsDownloadCsvBtn.setEnabled(false);
        flagsReportSelectedBtn.setEnabled(false);
    }

    private void createLicenseTab() {
        licenseTab.setLayout(new BoxLayout(licenseTab, BoxLayout.Y_AXIS));

        addLicenseFilePanel.setLayout(new BoxLayout(addLicenseFilePanel, BoxLayout.Y_AXIS));
        addLicenseFilePanel.add(addLicenseCheckbox);

        JPanel licenseFilenameLine = new JPanel();
        licenseFilenameLine.add(licenseFilenameFieldLabel);
        licenseFilenameLine.add(licenseFilenameField);
        addLicenseFilePanel.add(licenseFilenameLine);

        JPanel licenseBundleNameLine = new JPanel();
        licenseBundleNameLine.add(licenseBundleNameFieldLabel);
        licenseBundleNameLine.add(licenseBundleNameField);
        addLicenseFilePanel.add(licenseBundleNameLine);

        licenseTextScrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
        addLicenseFilePanel.add(licenseTextScrollPane);

        JPanel restrictToGroupPanel = new JPanel();
        restrictToGroupPanel.add(restrictToGroupCheckbox);
        restrictToGroupPanel.add(restrictToGroupField);

        // TODO:
        // radio button complex for picking CC license
        // JPanel addCCLicensePanel = new JPanel();

        licenseTab.add(addLicenseFilePanel);
        licenseTab.add(restrictToGroupPanel);

        tabs.addTab("License Settings", licenseTab);
    }

    private void createVerificationTab() {
        verifierTbl.setModel(verifierTableModel);
        verifierTbl.setPreferredScrollableViewportSize(new Dimension(400, 50));
        verifierTbl.setEnabled(false);

        JScrollPane verifierTblScrollPane = new JScrollPane(verifierTbl);
        verifierTbl.setFillsViewportHeight(true);

        validationTab.setLayout(new BoxLayout(validationTab, BoxLayout.Y_AXIS));

        validationTab.add(verifierTblScrollPane);

        verifyCancelBtn.setEnabled(false);
        JPanel verifyBatchBtnPanel = new JPanel();
        verifyBatchBtnPanel.add(verifyBatchBtn);
        verifyBatchBtnPanel.add(verifyCancelBtn);
        validationTab.add(verifyBatchBtnPanel);

        tabs.addTab("Batch Verification", validationTab);
    }

    private void initializeState() {
        actionStatusField.setForeground(Color.white);
        actionStatusField.setBackground(Color.blue);

        loadBatchBtn.setText("Load specified batch now!");
        verifyBatchBtn.setEnabled(false);
        writeSAFBtn.setText("No batch loaded");

        statusIndicator.setForeground(Color.white);
        statusIndicator.setBackground(Color.blue);
        statusIndicator.setText("No batch loaded");
    }
}
src/main/java/edu/tamu/di/SAFCreator/enums/ActionStatus.java
public enum ActionStatus {
    NONE_LOADED,
    LOADED,
    FAILED_VERIFICATION,
    VERIFIED,
    WRITTEN,
    CLEANED
}
src/main/java/edu/tamu/di/SAFCreator/enums/FlagColumns.java
public enum FlagColumns {
    FLAG,
    DESCRIPTION,
    AUTHORITY,
    URL,
    COLUMN,
    ROW,
    ACTION
}
src/main/java/edu/tamu/di/SAFCreator/model/importData/ImportDataWriter.java
public class ImportDataWriter extends SwingWorker<Boolean, ImportDataOperator.Updates> implements ImportDataOperator {
    private Batch batch = null;
    private JTextArea console = null;
    private FlagPanel flags = null;

    @Override
    protected Boolean doInBackground() {
        boolean noErrors = true;
        int itemCount = 1;
        int totalItems = batch.getItems().size();
        String cancelledMessage = "Cancelled writing SAF.\n";

        for (Item item : batch.getItems()) {
            if (isCancelled()) {
                console.append(cancelledMessage);
                return null;
            }

            if (batch.isIgnoredRow(++itemCount)) {
                File directory = new File(batch.getOutputSAFDir().getAbsolutePath() + File.separator + itemCount);
                directory.delete();

                console.append("\tSkipped item (row " + itemCount + "), because of verification failure.\n");
                publish(new ImportDataOperator.Updates(itemCount - 1, totalItems));
                continue;
            }

            if (isCancelled()) {
                console.append(cancelledMessage);
                return null;
            }

            boolean hasError = false;
            Method method;
            List<Problem> problems = null;
            try {
                method = this.getClass().getMethod("isCancelled");
                problems = item.writeItemSAF(this, method);

                for (Problem problem : problems) {
                    if (isCancelled()) {
                        console.append(cancelledMessage);
                        return null;
                    }

                    console.append("\t" + problem.toString() + "\n");
                    if (problem.isError()) {
                        hasError = true;
                        noErrors = false;
                    }
                    if (problem.isFlagged()) {
                        flags.appendRow(problem.getFlag());
                    }
                }
            } catch (NoSuchMethodException | SecurityException e) {
                e.printStackTrace();
            }

            if (isCancelled()) {
                console.append(cancelledMessage);
                return null;
            }

            if (hasError) {
                batch.failedRow(itemCount);
                console.append("\tFailed to write item (row " + itemCount + ") " + item.getSAFDirectory() + ".\n");
            } else {
                console.append("\tWrote item (row " + itemCount + ") " + item.getSAFDirectory() + ".\n");
            }

            publish(new ImportDataOperator.Updates(itemCount - 1, totalItems));
        }

        if (isCancelled()) {
            console.append(cancelledMessage);
            return null;
        }

        console.append("Done writing SAF data.\n");
        return noErrors;
    }

    @Override
    public Batch getBatch() {
        return batch;
    }

    @Override
    public JTextArea getConsole() {
        return console;
    }

    @Override
    public FlagPanel getFlags() {
        return flags;
    }

    @Override
    public void setBatch(Batch batch) {
        this.batch = batch;
    }

    @Override
    public void setConsole(JTextArea console) {
        this.console = console;
    }

    @Override
    public void setFlags(FlagPanel flags) {
        this.flags = flags;
    }
}


Question: package edu.tamu.di.SAFCreator.controller;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ExecutionException;
import javax.swing.JFileChooser;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import edu.tamu.di.SAFCreator.enums.ActionStatus;
import edu.tamu.di.SAFCreator.enums.FieldChangeStatus;
import edu.tamu.di.SAFCreator.enums.FlagColumns;
import edu.tamu.di.SAFCreator.model.Batch;
import edu.tamu.di.SAFCreator.model.Flag;
import edu.tamu.di.SAFCreator.model.Problem;
import edu.tamu.di.SAFCreator.model.VerifierTableModel;
import edu.tamu.di.SAFCreator.model.importData.ImportDataCleaner;
import edu.tamu.di.SAFCreator.model.importData.ImportDataProcessor;
import edu.tamu.di.SAFCreator.model.importData.ImportDataWriter;
import edu.tamu.di.SAFCreator.model.verify.VerifierBackground;
import edu.tamu.di.SAFCreator.model.verify.VerifierProperty;
import edu.tamu.di.SAFCreator.model.verify.impl.LocalFilesExistVerifierImpl;
import edu.tamu.di.SAFCreator.model.verify.impl.RemoteFilesExistVerifierImpl;
import edu.tamu.di.SAFCreator.model.verify.impl.ValidSchemaNameVerifierImpl;
import edu.tamu.di.SAFCreator.view.UserInterfaceView;




public class UserInterfaceController {
    private final UserInterfaceView userInterface;

    private final ImportDataProcessor processor;

    private ActionStatus actionStatus = ActionStatus.NONE_LOADED;

    private FieldChangeStatus itemProcessDelayFieldChangeStatus = FieldChangeStatus.NO_CHANGES;
    private FieldChangeStatus remoteFileTimeoutFieldChangeStatus = FieldChangeStatus.NO_CHANGES;
    private FieldChangeStatus userAgentFieldChangeStatus = FieldChangeStatus.NO_CHANGES;

    // file and directory names
    private String csvOutputFileName = "SAF-Flags.csv";
    private String metadataInputFileName;
    private String sourceDirectoryName;
    private String outputDirectoryName;

    // batch
    private Boolean batchVerified;
    private Batch batch;
    private boolean batchContinue;
    private final Map<String, VerifierBackground> verifiers;

    // swing background process handling
    private ImportDataWriter currentWriter;
    private ImportDataCleaner currentCleaner;
    private final List<VerifierBackground> currentVerifiers;
    private int currentVerifier = -1;

    public UserInterfaceController(final ImportDataProcessor processor, final UserInterfaceView userInterface) {
        this.processor = processor;
        this.userInterface = userInterface;

        batch = new Batch();
        currentVerifiers = new ArrayList<VerifierBackground>();
        batchContinue = false;
        verifiers = new HashMap<String, VerifierBackground>();

        createBatchListeners();
        createFlagListeners();
        createLicenseListeners();
        createSettingsListeners();
        createVerificationListeners();
    }

    public void cancelVerifyCleanup() {
        userInterface.getStatusIndicator().setText("Batch Status:\n Unverified");

        unlockVerifyButtons();
    }

    public void cancelWriteCleanup() {
        userInterface.getWriteSAFBtn().setText("Write SAF data now!");

        userInterface.getStatusIndicator().setText("Batch Status:\n Unverified");
        userInterface.getStatusIndicator().setForeground(Color.white);
        userInterface.getStatusIndicator().setBackground(Color.blue);

        userInterface.getActionStatusField().setText("Batch write cancelled.");
        userInterface.getActionStatusField().setForeground(Color.black);
        userInterface.getActionStatusField().setBackground(Color.orange);
    }

    public void createVerifiers() {
Answer:         VerifierProperty settings = null;