package de.dhbw.humbuch.util;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import au.com.bytecode.opencsv.CSVReader;
import de.dhbw.humbuch.model.SubjectHandler;
import de.dhbw.humbuch.model.entity.Grade;
import de.dhbw.humbuch.model.entity.Parent;
import de.dhbw.humbuch.model.entity.Student;
import de.dhbw.humbuch.model.entity.Subject;
/**
* Create student objects of data in csv file.
* Checks cells for validity.
*
* @author Benjamin Räthlein
* @author David Vitt
*
*/
public final class CSVHandler {
/**
* Reads a csv file and creates student objects of it's records.
*
* @param path
* a path to the csv which contains information about students
* @return an ArrayList that contains student objects
* @exception throws an UnsupportedOperationException if an error occurred
* @see ArrayList
*/
public static ArrayList<Student> createStudentObjectsFromCSV(CSVReader csvReaderParam) throws UnsupportedOperationException {
ArrayList<Student> studentArrayList = new ArrayList<Student>();
try {
//csvReader - separator is ';';
CSVReader csvReader = csvReaderParam;
Properties csvHeaderProperties = readCSVConfigurationFile();
Map<String, String> csvHeaderPropertyStrings = new LinkedHashMap<>();
for (Object property : csvHeaderProperties.keySet()) {
csvHeaderPropertyStrings.put(((String) property).replaceAll("\\p{C}", ""), csvHeaderProperties.getProperty((String) property));
}
List<String[]> allRecords = csvReader.readAll();
Iterator<String[]> allRecordsIterator = allRecords.iterator();
HashMap<String, Integer> headerIndexMap = new HashMap<String, Integer>();
if (allRecordsIterator.hasNext()) {
String[] headerRecord = allRecordsIterator.next();
for (int i = 0; i < headerRecord.length; i++) {
// removes all non-printable characters
headerIndexMap.put(headerRecord[i].replaceAll("\\p{C}", ""), i);
}
}
int index = 0;
while (allRecordsIterator.hasNext()) {
String[] record = allRecordsIterator.next();
index++;
Student student = createStudentObject(record, csvHeaderPropertyStrings, headerIndexMap, index);
if (student != null) {
studentArrayList.add(student);
}
else {
throw new UnsupportedOperationException("Mindestens ein Studenten-Datensatz war korrumpiert");
}
}
csvReader.close();
}
catch (IOException e) {
throw new UnsupportedOperationException("Die Studentendaten konnten nicht eingelesen werden");
}
return studentArrayList;
}
/**
* Read a properties file that contains information about the names of the header fields in the csv.
* If the name of a header in the csv changes, the new name can be mapped in the properties file. As a result,
* changes of csv headers can be adopted easily without the need of touching the code.
* Also, the properties file enables the program to handle csv files even in case the order of the header fields
* changed.
*
* @return properties that fit the csv header line
*/
private static Properties readCSVConfigurationFile() {
Properties csvHeaderProperties = new Properties();
try {
csvHeaderProperties.load(new InputStreamReader(new ResourceLoader("csvConfiguration.properties").getStream()));
}
catch (FileNotFoundException e) {
e.printStackTrace();
}
catch (IOException e) {
e.printStackTrace();
}
return csvHeaderProperties;
}
/**
* Creates a student object with the information in the record.
*
* @param record
* is one line of the loaded csv-file
* @param dataSetIndex
* @return Student
*/
private static Student createStudentObject(String[] record, Map<String, String> properties, HashMap<String, Integer> index, int dataSetIndex) throws UnsupportedOperationException {
String foreignLanguage1, foreignLanguage2, foreignLanguage3, gradeString, firstName, lastName, gender, birthDay, religion;
String parentTitle, parentLastName, parentFirstName, parentStreet, parentPlace;
int id;
int parentPostalcode;
try {
foreignLanguage1 = record[getAttributeNameToHeaderIndex(properties, index, "foreignLanguage1")];
foreignLanguage2 = record[getAttributeNameToHeaderIndex(properties, index, "foreignLanguage2")];
foreignLanguage3 = record[getAttributeNameToHeaderIndex(properties, index, "foreignLanguage3")];
gradeString = record[getAttributeNameToHeaderIndex(properties, index, "grade")];
firstName = record[getAttributeNameToHeaderIndex(properties, index, "firstName")];
lastName = record[getAttributeNameToHeaderIndex(properties, index, "lastName")];
gender = record[getAttributeNameToHeaderIndex(properties, index, "gender")];
birthDay = record[getAttributeNameToHeaderIndex(properties, index, "birthDay")];
id = Integer.parseInt(record[getAttributeNameToHeaderIndex(properties, index, "id")]);
religion = record[getAttributeNameToHeaderIndex(properties, index, "religion")];
}
catch (ArrayIndexOutOfBoundsException a) {
throw new UnsupportedOperationException("Ein Wert im Studentendatensatz konnte nicht gelesen werden. Fehler in Datensatz: " + dataSetIndex);
}
catch (NumberFormatException e) {
throw new UnsupportedOperationException("Mindestens eine Postleitzahl ist keine gültige Nummer. Fehler in Datensatz: " + dataSetIndex);
}
Parent parent = null;
try {
parentTitle = record[getAttributeNameToHeaderIndex(properties, index, "parentTitle")];
parentLastName = record[getAttributeNameToHeaderIndex(properties, index, "parentLastName")];
parentFirstName = record[getAttributeNameToHeaderIndex(properties, index, "parentFirstName")];
parentStreet = record[getAttributeNameToHeaderIndex(properties, index, "parentStreet")];
parentPostalcode = Integer.parseInt(record[getAttributeNameToHeaderIndex(properties, index, "parentPostalcode")]);
parentPlace = record[getAttributeNameToHeaderIndex(properties, index, "parentPlace")];
parent = new Parent.Builder(parentFirstName, parentLastName).title(parentTitle)
.street(parentStreet).postcode(parentPostalcode).city(parentPlace).build();
}
catch (NullPointerException e) {
throw new UnsupportedOperationException("Die Elterndaten enthalten an mindestens einer Stelle einen Fehler. Fehler in Datensatz: " + dataSetIndex);
}
catch (ArrayIndexOutOfBoundsException e) {
throw new UnsupportedOperationException("Mindestens ein Datensatz enthält keine Eltern-Informationen. Fehler in Datensatz: " + dataSetIndex);
}
catch (NumberFormatException e) {
throw new UnsupportedOperationException("Mindestens eine Postleitzahl ist keine gültige Nummer.Fehler in Datensatz: " + dataSetIndex);
}
Map<String, Boolean> checkValidityMap = new LinkedHashMap<>();
checkValidityMap.put(foreignLanguage1, true);
checkValidityMap.put(foreignLanguage2, true);
checkValidityMap.put(foreignLanguage3, true);
checkValidityMap.put(gradeString, false);
checkValidityMap.put(firstName, false);
checkValidityMap.put(lastName, false);
checkValidityMap.put(gender, false);
checkValidityMap.put(birthDay, false);
checkValidityMap.put("" + id, false);
checkValidityMap.put(religion, false);
checkValidityMap.put(parentTitle, false);
checkValidityMap.put(parentLastName, false);
checkValidityMap.put(parentFirstName, false);
checkValidityMap.put(parentStreet, false);
checkValidityMap.put("" + parentPostalcode, false);
checkValidityMap.put(parentPlace, false);
String check = checkForValidityOfAttributes(checkValidityMap);
if (!check.equals("okay")) {
if(check.equals("error")){
throw new UnsupportedOperationException("Mindestens ein Studenten-Datensatz war korrumpiert. Fehler in Datensatz: " + dataSetIndex);
}
else if(check.equals("empty")){
throw new UnsupportedOperationException("Ein Datensatz ist nicht vollständig gefüllt. Fehler in Datensatz: " + dataSetIndex);
}
}
Date date = null;
try {
date = new SimpleDateFormat("dd.mm.yyyy", Locale.GERMAN).parse(birthDay);
}
catch (ParseException e) {
System.err.println("Could not format date " + e.getStackTrace());
return null;
}
Grade grade = new Grade.Builder(gradeString).build();
String[] foreignLanguage = new String[3];
foreignLanguage[0] = foreignLanguage1;
foreignLanguage[1] = foreignLanguage2;
foreignLanguage[2] = foreignLanguage3;
Set<Subject> subjectSet = SubjectHandler.createProfile(foreignLanguage, religion);
return new Student.Builder(id, firstName, lastName, date, grade).profile(subjectSet).gender(gender).parent(parent).leavingSchool(false).build();
}
private static int getAttributeNameToHeaderIndex(Map<String, String> properties, HashMap<String, Integer> indexMap, String attributeName) throws UnsupportedOperationException {
String headerValue = (String) properties.get(attributeName);
if (headerValue != null) {
int indexHeader = -1;
if (indexMap.get(headerValue) != null) {
indexHeader = indexMap.get(headerValue);
}
else {
throw new UnsupportedOperationException("Ein CSV-Spaltenname konnte nicht zugeordnet werden. "
+ "Bitte die Einstellungsdatei mit der CSV-Datei abgleichen. Spaltenname: " + headerValue);
}
return indexHeader;
}
return -1;
}
/**
* If one string of the map is -1 an error occurred previously and the
* method will return false. If one string is empty that is not to allowed to
* be empty (indicated by 'false' in the map) the method will return false.
* If this method returns false, an exception will be thrown that the CSV lacks important data.
* If this method returns true, the data set of the CSV is correct.
*
* @param attributes
* @return boolean that indicates whether the data set of the CSV is correct or not.
*/
private static String checkForValidityOfAttributes(Map<String, Boolean> attributes) {
for (String str : attributes.keySet()) {
if (str.equals("-1")) {
return "error";
}
if (!attributes.get(str)) {
if (str.equals("")) {
return "empty";
}
}
}
return "okay";
}
}