StudentCsvFormat.java

package edu.ucsb.cs156.happiercows.helpers;

import edu.ucsb.cs156.happiercows.entities.Student;
import org.apache.commons.csv.CSVRecord;

import java.util.List;
import java.util.function.BiFunction;

/**
 * Different schools export their class rosters with different CSV column
 * layouts. Each format below knows how to recognize its own header row and
 * how to map a data row to a {@link Student}.
 *
 * <p>{@code UCSB_EGRADES} matches the header row exported by UCSB's
 * registrar (eGrades); see {@code docs/examples/egrades.csv}.
 * {@code CHICO_STATE_ROSTER} matches the Canvas roster export used at Chico
 * State (see {@code docs/examples/chico_canvas.csv}) and, along with
 * {@code GENERIC}, is included as a worked example of how to add support for
 * another school's roster format later (see issue #251); {@code GENERIC} is
 * also the format used for courses associated with the "Other" school.
 */
public enum StudentCsvFormat {
    UCSB_EGRADES(
            List.of(
                    "Enrl Cd", "Perm #", "Grade", "Final Units", "Student Last",
                    "Student First Middle", "Quarter", "Course ID", "Section",
                    "Meeting Time(s) / Location(s)", "Email", "ClassLevel", "Major1",
                    "Major2", "Date/Time", "Pronoun"),
            (row, courseId) -> Student.builder()
                    .perm(row.get(1).trim())
                    .lastName(row.get(4).trim())
                    .firstMiddleName(row.get(5).trim())
                    .email(row.get(10).trim())
                    .courseId(courseId)
                    .build()),

    CHICO_STATE_ROSTER(
            List.of("Student Name", "Student ID", "Student SIS ID", "Email", "Section Name"),
            (row, courseId) -> {
                String fullName = row.get(0).trim();
                return Student.builder()
                        .perm(row.get(2).trim())
                        .lastName(lastNameFromFullName(fullName))
                        .firstMiddleName(firstNameFromFullName(fullName))
                        .email(row.get(3).trim())
                        .courseId(courseId)
                        .build();
            }),

    GENERIC(
            List.of("lastName", "firstMiddleName", "email", "perm"),
            (row, courseId) -> Student.builder()
                    .lastName(row.get(0).trim())
                    .firstMiddleName(row.get(1).trim())
                    .email(row.get(2).trim())
                    .perm(row.get(3).trim())
                    .courseId(courseId)
                    .build());

    private final List<String> headers;
    private final BiFunction<CSVRecord, Long, Student> rowMapper;

    StudentCsvFormat(List<String> headers, BiFunction<CSVRecord, Long, Student> rowMapper) {
        this.headers = headers;
        this.rowMapper = rowMapper;
    }

    public List<String> getHeaders() {
        return headers;
    }

    public boolean matchesHeaders(List<String> candidateHeaders) {
        if (candidateHeaders.size() != headers.size()) {
            return false;
        }
        for (int i = 0; i < headers.size(); i++) {
            if (!headers.get(i).equalsIgnoreCase(candidateHeaders.get(i).trim())) {
                return false;
            }
        }
        return true;
    }

    public Student toStudent(CSVRecord row, Long courseId) {
        return rowMapper.apply(row, courseId);
    }

    /**
     * @param candidateHeaders the header row read from an uploaded CSV file
     * @return the matching format, or null if no known format matches
     */
    public static StudentCsvFormat detect(List<String> candidateHeaders) {
        for (StudentCsvFormat format : values()) {
            if (format.matchesHeaders(candidateHeaders)) {
                return format;
            }
        }
        return null;
    }

    /**
     * Get everything after the last space in the full name. If the string
     * contains no spaces, return the entire input string as the result.
     *
     * @param fullName the full name, e.g. "Marge Simpson"
     * @return best estimate of last name, e.g. "Simpson"
     */
    static String lastNameFromFullName(String fullName) {
        int lastSpaceIndex = fullName.lastIndexOf(" ");
        if (lastSpaceIndex == -1) {
            return fullName;
        }
        return fullName.substring(lastSpaceIndex + 1).trim();
    }

    /**
     * Get everything up to and not including the last space in the full name.
     * If the string contains no spaces, return an empty string.
     *
     * @param fullName the full name, e.g. "Marge Simpson"
     * @return best estimate of first (and middle) name, e.g. "Marge"
     */
    static String firstNameFromFullName(String fullName) {
        int lastSpaceIndex = fullName.lastIndexOf(" ");
        if (lastSpaceIndex == -1) {
            return "";
        }
        return fullName.substring(0, lastSpaceIndex).trim();
    }
}