net.lldp.checksims.submission.Submission.java Source code

Java tutorial

Introduction

Here is the source code for net.lldp.checksims.submission.Submission.java

Source

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * See LICENSE.txt included in this distribution for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at LICENSE.txt.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 * Copyright (c) 2014-2015 Nicholas DeMarinis, Matthew Heon, and Dolan Murvihill
 */

package net.lldp.checksims.submission;

import com.google.common.collect.Ordering;

import net.lldp.checksims.parse.Percentable;
import net.lldp.checksims.util.data.Real;

import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;

/**
 * Interface for Submissions.
 *
 * Submissions are considered Comparable so they can be ordered for output. Generally, we only expect that their names,
 * and not their contents, will be compared.
 *
 * Also contains factory methods for submissions
 */
public interface Submission extends Comparable<Submission> {

    /**
     * Increase the cumulative score for this submission.
     * The Score is the sum of all comparison values between
     * this and every other submission
     * @param i the score to increase by
     * @param o the original score calculation
     */
    void increaseScore(double i, double o);

    /**
     * get the total copy score for this submission.
     * see {@link #increaseScore(Real)}
     * @return the total copy score for this submission
     */
    double getTotalCopyScore();

    /**
     * get the maximum copy score for this submission.
     * see {@link #increaseScore(Real)}
     * @return the maximum score for this submission
     */
    Double getMaximumCopyScore();

    /**
     * set an arbitrary flag on a submission
     * @param flagName the name of the flag to set
     */
    void setFlag(String flagName);

    /**
     * unset a flag on a submission
     * @param flagName the flag name to unset
     */
    void unsetFlag(String flagName);

    /**
     * test whether a flag is set
     * @param flagName
     * @return whether flagName is set
     */
    boolean testFlag(String flagName);

    /**
     * @return String consisting of the body of the submission
     */
    String getContentAsString();

    /**
     * get the total number of lines of code in this submission.
     * @return
     */
    int getLinesOfCode();

    /**
     * 
     * @param clazz the class type of the percentable
     * @param percentable the percentable
     */
    <T extends Percentable> void addType(Class<T> clazz, T percentable);

    /**
     * 
     * @param clazz the class type of the percentable
     * @return whether this Submission has a percentable of the given type
     */
    boolean contains(Class<? extends Percentable> clazz);

    /**
     * Invalidate the Percentable cache
     */
    void invalidateCache();

    /**
     * 
     * @param clazz the class to get a percentable for
     * @return a percentable of type T, or null
     */
    <T extends Percentable> T get(Class<T> clazz);

    /**
     * @return Name of this submission
     */
    String getName();

    /**
     * Generate a list of all student submissions from a directory.
     *
     * The directory is assumed to hold a number of subdirectories, each containing one student or group's submission
     * The student/group directories may contain subdirectories with files
     *
     * @param directory Directory containing student submission directories
     * @param glob Match pattern used to identify files to include in submission
     * @param splitter Tokenizes files to produce Token Lists for a submission
     * @return Set of submissions including all unique nonempty submissions in the given directory
     * @throws java.io.IOException Thrown on error interacting with file or filesystem
     */
    static Set<Submission> submissionListFromDir(File directory, String glob, boolean recursive)
            throws IOException {
        checkNotNull(directory);
        checkNotNull(glob);
        checkArgument(!glob.isEmpty(), "Glob pattern cannot be empty!");

        Set<Submission> submissions = new HashSet<>();
        Logger local = LoggerFactory.getLogger(Submission.class);

        if (!directory.exists()) {
            throw new NoSuchFileException("Does not exist: " + directory.getAbsolutePath());
        } else if (!directory.isDirectory()) {
            throw new NotDirectoryException("Not a directory: " + directory.getAbsolutePath());
        }

        // List all the subdirectories we find
        File[] contents = directory.listFiles(File::isDirectory);

        for (File f : contents) {
            try {
                Submission s = submissionFromDir(f, glob, recursive);
                submissions.add(s);
                if (s.getContentAsString().isEmpty()) {
                    local.warn("Warning: Submission " + s.getName() + " is empty!");
                } else {
                    local.debug("Created submission with name " + s.getName());
                }
            } catch (NoMatchingFilesException e) {
                local.warn("Could not create submission from directory " + f.getName()
                        + " - no files matching pattern found!");
            }
        }

        return submissions;
    }

    /**
     * Get a single submission from a directory.
     *
     * @param directory Directory containing the student's submission
     * @param glob Match pattern used to identify files to include in submission
     * @param splitter Tokenizes files to produce Token List in this submission
     * @return Single submission from all files matching the glob in given directory
     * @throws IOException Thrown on error interacting with file
     */
    static Submission submissionFromDir(File directory, String glob, boolean recursive)
            throws IOException, NoMatchingFilesException {
        checkNotNull(directory);
        checkNotNull(glob);
        checkArgument(!glob.isEmpty(), "Glob pattern cannot be empty!");

        if (!directory.exists()) {
            throw new NoSuchFileException("Does not exist: " + directory.getAbsolutePath());
        } else if (!directory.isDirectory()) {
            throw new NotDirectoryException("Not a directory: " + directory.getAbsolutePath());
        }

        // TODO consider verbose logging of which files we're adding to the submission?

        Set<File> files = getAllMatchingFiles(directory, glob, recursive);

        return submissionFromFiles(directory.getName(), files);
    }

    /**
     * Recursively find all files matching in a directory.
     *
     * @param directory Directory to search in
     * @param glob Match pattern used to identify files to include
     * @return List of all matching files in this directory and subdirectories
     */
    static Set<File> getAllMatchingFiles(File directory, String glob, boolean recursive)
            throws NoSuchFileException, NotDirectoryException {
        checkNotNull(directory);
        checkNotNull(glob);
        checkArgument(!glob.isEmpty(), "Glob pattern cannot be empty");

        if (!directory.exists()) {
            throw new NoSuchFileException("Does not exist: " + directory.getAbsolutePath());
        } else if (!directory.isDirectory()) {
            throw new NotDirectoryException("Not a directory: " + directory.getAbsolutePath());
        }

        PathMatcher matcher = FileSystems.getDefault().getPathMatcher("glob:" + glob);

        Set<File> allFiles = new HashSet<>();
        Logger logs = LoggerFactory.getLogger(Submission.class);

        if (recursive) {
            logs.trace("Recursively traversing directory " + directory.getName());
        }

        // Get files in this directory
        File[] contents = directory
                .listFiles((f) -> matcher.matches(Paths.get(f.getAbsolutePath()).getFileName()) && f.isFile());

        // TODO consider mapping to absolute paths?

        // Add this directory
        Collections.addAll(allFiles, contents);

        // Get subdirectories
        File[] subdirs = directory.listFiles(File::isDirectory);

        // Recursively call on all subdirectories if specified
        if (recursive) {
            for (File subdir : subdirs) {
                allFiles.addAll(getAllMatchingFiles(subdir, glob, true));
            }
        }

        return allFiles;
    }

    /**
     * Turn a list of files and a name into a Submission.
     *
     * The contents of a submission are built deterministically by reading in files in alphabetical order and appending
     * their contents.
     *
     * @param name Name of the new submission
     * @param files List of files to include in submission
     * @param splitter Tokenizer for files in the submission
     * @return A new submission formed from the contents of all given files, appended and tokenized
     * @throws IOException Thrown on error reading from file
     * @throws NoMatchingFilesException Thrown if no files are given
     */
    static Submission submissionFromFiles(String name, Set<File> files)
            throws IOException, NoMatchingFilesException {
        checkNotNull(name);
        checkArgument(!name.isEmpty(), "Submission name cannot be empty");
        checkNotNull(files);

        Logger logs = LoggerFactory.getLogger(Submission.class);

        if (files.size() == 0) {
            throw new NoMatchingFilesException(
                    "No matching files found, cannot create submission named \"" + name + "\"");
        }

        // To ensure submission generation is deterministic, sort files by name, and read them in that order
        List<File> orderedFiles = Ordering
                .from((File file1, File file2) -> file1.getName().compareTo(file2.getName()))
                .immutableSortedCopy(files);

        StringBuilder fileContent = new StringBuilder();

        // Could do this with a .stream().forEach(...) but we'd have to handle the IOException inside
        for (File f : orderedFiles) {
            String content = FileUtils.readFileToString(f, StandardCharsets.UTF_8);

            fileContent.append(content);

            if (!content.endsWith("\n") && !content.isEmpty()) {
                fileContent.append("\n");
            }
        }

        String contentString = fileContent.toString();

        if (contentString.length() > 7500 * 4) { // large number of tokens * average token length
            logs.warn(
                    "Warning: Submission " + name + " has very large source size (" + contentString.length() + ")");
        }

        return new ConcreteSubmission(name, contentString);
    }
}