com.moorestudio.seniorimageprocessing.SeniorSorter.java Source code

Java tutorial

Introduction

Here is the source code for com.moorestudio.seniorimageprocessing.SeniorSorter.java

Source

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 *//*
    * To change this license header, choose License Headers in Project Properties.
    * To change this template file, choose Tools | Templates
    * and open the template in the editor.
    */

package com.moorestudio.seniorimageprocessing;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FilenameFilter;
import java.io.IOException;
import static java.lang.Character.isDigit;
import static java.lang.Integer.parseInt;
import static java.lang.String.valueOf;
import static java.nio.file.Files.copy;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import static java.util.Arrays.asList;
import static java.util.Collections.sort;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import org.apache.commons.imaging.ImageReadException;
import static org.apache.commons.imaging.Imaging.getMetadata;
import org.apache.commons.imaging.common.IImageMetadata;
import org.apache.commons.imaging.formats.jpeg.JpegImageMetadata;
import org.apache.commons.imaging.formats.tiff.constants.TiffTagConstants;

/**
 *
 * @author nrwebb
 */
public class SeniorSorter implements Runnable {
    private UserInterface parent;
    private File cameraDirectory;
    private File imageDirectory;
    private File dataFile;
    private HashMap<File, Long> imageData;
    private HashMap<String, Long> timestampData; // An hashMap of a students id and the timestamp that it was scanned
    private int id;
    private long cameraTimeOffset;

    public SeniorSorter(File directory, UserInterface prnt, int threadId) throws IOException {
        try {
            id = threadId;
            parent = prnt;
            cameraDirectory = directory;
            imageData = new HashMap<>();
            timestampData = new HashMap<>();
            cameraTimeOffset = 0;

            // set the image directory
            imageDirectory = new File(cameraDirectory.getAbsolutePath() + "/Images");

            // set the data file
            dataFile = new File(cameraDirectory.getAbsolutePath() + "/Data").listFiles()[0]; // get the first file out of the data directory, needs to have ONLY 1 FILE!!!

            String errorMessage = "";
            if (!imageDirectory.exists()) {
                errorMessage += "Image directory: " + imageDirectory.getAbsolutePath() + " ";
            }
            if (!dataFile.exists()) {
                errorMessage += "Data file: " + dataFile.getAbsolutePath() + " ";
            }
            if (!imageDirectory.exists() || !dataFile.exists()) {
                errorMessage += "do(es) not exist!";
                throw new IOException(errorMessage);
            }
        } catch (NullPointerException e) {
            throw new IOException(
                    "Missing data file under the directory: " + cameraDirectory.getAbsolutePath() + "/Data/");
        }
    }

    //get all of the image timestamps
    public void getTimestampData() throws ImageReadException, IOException, ParseException {
        int numImages = imageData.size();
        for (File image : imageData.keySet()) {
            IImageMetadata metadata = getMetadata(image);
            if (metadata instanceof JpegImageMetadata) {
                JpegImageMetadata md = (JpegImageMetadata) metadata;
                String dateTime = md.findEXIFValue(TiffTagConstants.TIFF_TAG_DATE_TIME).getStringValue();

                //if the dateTime is null, then throw an error
                if (dateTime == null || dateTime.isEmpty()) {
                    throw new ImageReadException("Images do not contain the EXIF date information.");
                } else {
                    //convert from EXIF format to a date
                    SimpleDateFormat dateParser = new SimpleDateFormat("yyyy:MM:dd HH:mm:ss");
                    imageData.put(image, dateParser.parse(dateTime).getTime());
                }

                //update the gui
                parent.addProgress((.125 / parent.numThreads) / numImages);
            } else {
                throw new ImageReadException("Images do not contain the EXIF date information.");
            }
        }
    }

    //TODO: get the line count for the updating of the GUI.
    public int getLineCount(File file) throws FileNotFoundException, IOException {
        BufferedReader reader = new BufferedReader(new FileReader(file));

        int count = 0;
        while (reader.readLine() != null) {
            count++;
        }

        return count;
    }

    public void getStudentData() throws FileNotFoundException, Exception {
        try {
            Scanner scanner = new Scanner(dataFile);

            //get the line count for the GUI
            int lineCount = getLineCount(dataFile);

            //the csv file does not contain headers
            while (scanner.hasNextLine()) {
                //Create the new student map
                String studentId;
                Long timestamp;
                String line = scanner.nextLine();
                //check to make sure there arent just empty rows
                if (!line.replace("\"", "").replace(",", "").isEmpty()) {
                    String[] lineInformation = line.replace("\"", "").split(",");
                    SimpleDateFormat dateParser = new SimpleDateFormat("M/dd/yy HH:mm:ss");
                    studentId = lineInformation[0];
                    timestamp = dateParser.parse(lineInformation[3] + " " + lineInformation[2]).getTime();
                    timestampData.put(studentId, timestamp);
                }

                //update the GUI
                parent.addProgress((.125 / parent.numThreads) / lineCount);
            }
        } catch (FileNotFoundException e) {
            throw new FileNotFoundException("Data file at: " + dataFile.getAbsolutePath() + " does not exist!");
        } catch (ParseException ex) {
            throw new Exception(
                    "The date in the data file at: " + dataFile.getAbsolutePath() + " is not formatted correctly!");
        }
    }

    public void getImageFiles() {
        FilenameFilter imageFilter = (File dir, String name) -> {
            String extension = name.substring(name.lastIndexOf('.')).toLowerCase();
            List<String> acceptableExtensions = asList(parent.imageTypes);
            return acceptableExtensions.contains(extension);
        };

        File[] imageFiles = imageDirectory.listFiles(imageFilter);
        int imageCount = imageFiles.length;
        for (File image : imageFiles) {
            imageData.put(image, (long) 0);

            //update the GUI
            parent.addProgress((.125 / parent.numThreads) / imageCount);

        }
    }

    //sort the images with the timestamps
    public void sortImages() {
        LinkedList<Map.Entry<String, Long>> timestampList = new LinkedList<>(timestampData.entrySet());
        sort(timestampList, (x, y) -> x.getValue() > y.getValue() ? -1 : x.getValue().equals(y.getValue()) ? 0 : 1);
        // Sort in reverse so that the most recent timestamps are first.e so that the most recent timestamps are first.

        LinkedList<Map.Entry<File, Long>> imageDataList = new LinkedList<>(imageData.entrySet());
        sort(imageDataList, (x, y) -> x.getValue() > y.getValue() ? -1 : x.getValue().equals(y.getValue()) ? 0 : 1); // Sort in reverse so that the most recent timestamps are first.

        // For the gui update
        int idCount = imageDataList.size();

        //Take the first image and the first timestamp scan taken, which is last in the list, 
        //and sync the camera time to the timestamp time.Both are throwaways.
        if (!timestampList.isEmpty() && !imageDataList.isEmpty() && parent.syncTime) {
            Map.Entry<File, Long> iData = imageDataList.pollLast();
            Map.Entry<String, Long> tsData = timestampList.pollLast();

            //Make the offset
            cameraTimeOffset = tsData.getValue() - iData.getValue();
        }

        //add the file to the top timestamp student until it is no longer more than it
        while (!timestampList.isEmpty() && !imageDataList.isEmpty()) {
            Map.Entry<File, Long> iData = imageDataList.peekFirst();
            Map.Entry<String, Long> tsData = timestampList.pollFirst();
            ArrayList<File> studentImages = new ArrayList<>();
            while (!imageDataList.isEmpty() && iData.getValue() + cameraTimeOffset > tsData.getValue()) {
                iData = imageDataList.pollFirst();
                studentImages.add(iData.getKey());
                iData = imageDataList.peekFirst();
                //update the GUI
                parent.addProgress((.125 / parent.numThreads) / idCount);
            }
            if (!studentImages.isEmpty()) {
                parent.addImagesToStudent(tsData.getKey(), studentImages);
            }
        }

        //add the unsorted images to the parent's unsorted queue
        for (Map.Entry<File, Long> entry : imageDataList) {
            parent.unsortedFiles.add(entry.getKey());
            //update the GUI
            parent.addProgress((.125 / parent.numThreads) / idCount);
        }
    }

    private File makeStudentFolder(HashMap<String, String> studentInfo) {
        //Get the student last name and first name and School name
        String firstName = studentInfo.get("FIRST");
        String lastName = studentInfo.get("LAST");
        String schoolName = studentInfo.get("SCHOOL");

        String folderName = lastName.toUpperCase() + "_" + firstName.toUpperCase();

        //see if the folder exists, if it doesn't then make it.
        File studentFolder = new File(parent.studentSchoolDirectories.get(schoolName), folderName);
        if (!studentFolder.exists()) {
            studentFolder.mkdir();
        }

        return studentFolder;
    }

    private void saveImages(HashMap<String, String> studentInfo, File activeStudentFolder,
            ArrayList<File> studentImages) throws IOException {
        // Get the student information
        String schoolName = studentInfo.get("SCHOOL").replaceAll(" ", "_"); // If the filenames have spaces then an error will be thrown!
        String year = studentInfo.get("YEAR").replaceAll(" ", "");
        String lastName = studentInfo.get("LAST").replaceAll(" ", "");
        String firstName = studentInfo.get("FIRST").replaceAll(" ", "");

        //Write out the images
        for (File image : studentImages) {
            String oldImageName = image.getName();
            //Get the image extension
            String ext = oldImageName.substring(oldImageName.lastIndexOf("."));

            //Make the index string
            StringBuilder indexString = new StringBuilder(valueOf(parent.pollNextImageIndex()));

            //If image index is less than the desired number of digits, then padd it.
            while (indexString.length() < parent.getNumImageIndexDigits()) {
                indexString.insert(0, '0');
            }
            //Make the new file
            File newFile = new File(activeStudentFolder,
                    schoolName + year + lastName + indexString.toString() + ext);

            //Copy and rename the old file to the new file
            copy(image.toPath(), newFile.toPath());
            parent.addProgress(((.5 / parent.studentImages.size()) / studentImages.size()));
        }

    }

    @Override
    public void run() {

        try {
            getImageFiles();

            getTimestampData();

            getStudentData();

            sortImages();

            //tell the parent thread that we are finished and ready to write
            parent.acknowledgeReadyToWrite();

            String activeStudentId = null;
            while ((activeStudentId = parent.availableStudents.poll()) != null) {
                HashMap<String, String> studentInfo = parent.studentInformation.get(activeStudentId);
                File studentFolder = makeStudentFolder(studentInfo);
                saveImages(studentInfo, studentFolder, parent.studentImages.get(activeStudentId));
            }

            parent.acknowledgeFinished();
        } catch (Exception e) {
            parent.throwException(e);
        }

    }
}