Initial commit adding source and pom file.

This commit is contained in:
Andrew Lalis 2018-07-01 07:44:07 +02:00
commit c3b1534695
7 changed files with 460 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
.idea/*
target/*
*.iml

42
pom.xml Normal file
View File

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>andrewlalis</groupId>
<artifactId>GithubInitializer</artifactId>
<version>1.0-SNAPSHOT</version>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
</plugins>
</build>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-csv</artifactId>
<version>1.5</version>
</dependency>
<dependency>
<groupId>commons-cli</groupId>
<artifactId>commons-cli</artifactId>
<version>RELEASE</version>
</dependency>
<dependency>
<groupId>commons-email</groupId>
<artifactId>commons-email</artifactId>
<version>RELEASE</version>
</dependency>
</dependencies>
</project>

6
sampleAOOP.csv Normal file
View File

@ -0,0 +1,6 @@
"Timestamp","Username","Name","Student Number","Github Username","I have chosen a partner.","Your Partner's Student Number"
"2018/06/29 7:33:23 PM GMT+2","andrewlalisofficial@gmail.com","Andrew Lalis","3050831","andrewlalis","Yes","3522647"
"2018/06/29 7:50:02 PM GMT+2","klaus@student.rug.nl","Klaus Lalis","3522647","klauslalis","Yes","3050831"
"2018/06/29 7:50:53 PM GMT+2","henry@student.rug.nl","Henry Smith","3123456","henrysmith","Yes","3654321"
"2018/06/29 7:51:42 PM GMT+2","taylor@student.rug.nl","Taylor Jones","3654321","taylorjones","Yes","3123456"
"2018/06/29 9:17:10 PM GMT+2","lonely@gmail.com","Lonely Joe","3222222","lonelyjoe","No",""
1 Timestamp Username Name Student Number Github Username I have chosen a partner. Your Partner's Student Number
2 2018/06/29 7:33:23 PM GMT+2 andrewlalisofficial@gmail.com Andrew Lalis 3050831 andrewlalis Yes 3522647
3 2018/06/29 7:50:02 PM GMT+2 klaus@student.rug.nl Klaus Lalis 3522647 klauslalis Yes 3050831
4 2018/06/29 7:50:53 PM GMT+2 henry@student.rug.nl Henry Smith 3123456 henrysmith Yes 3654321
5 2018/06/29 7:51:42 PM GMT+2 taylor@student.rug.nl Taylor Jones 3654321 taylorjones Yes 3123456
6 2018/06/29 9:17:10 PM GMT+2 lonely@gmail.com Lonely Joe 3222222 lonelyjoe No

View File

@ -0,0 +1,93 @@
package nl.andrewlalis;
import nl.andrewlalis.model.Team;
import nl.andrewlalis.util.TeamGenerator;
import org.apache.commons.cli.*;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Main program entry point.
*/
public class Main {
public static void main(String[] args) throws IOException {
System.out.println("Initializer for Github Repositories in Educational Organizations.");
Map<String, String> userOptions = parseArgs(args);
List<Team> teams = TeamGenerator.generateFromCSV(
userOptions.get("input"),
Integer.parseInt(userOptions.get("teamsize"))
);
System.out.println(teams);
}
/**
* Parses the command line arguments and gets all the needed values.
* @param args The command line arguments as they are given to main.
* @return A map of keys and values for each option that the user must give.
*/
private static Map<String, String> parseArgs(String[] args) {
CommandLineParser parser = new DefaultParser();
HelpFormatter formatter = new HelpFormatter();
Options options = setupCommandOptions();
Map<String, String> userOptions = new HashMap<>();
try {
CommandLine cmd = parser.parse(options, args);
userOptions.put("token", cmd.getOptionValue("token"));
userOptions.put("input", cmd.getOptionValue("input"));
userOptions.put("organization", cmd.getOptionValue("organization"));
// The optional teamsize argument must be handled.
String teamSizeInput = cmd.getOptionValue("teamsize");
System.out.println(teamSizeInput);
if (teamSizeInput == null ) {
userOptions.put("teamsize", "2");
} else {
userOptions.put("teamsize", teamSizeInput);
}
} catch (ParseException e) {
formatter.printHelp("java -jar GithubInitializer.jar", options);
System.exit(1);
}
return userOptions;
}
/**
* Sets up the command line interface options using Apache Commons CLI library.
* @return The Options object used when parsing the arguments.
*/
private static Options setupCommandOptions() {
Options options = new Options();
// Authentication token for github.
Option tokenInput = new Option("t", "token", true, "The authentication token, with which you are authenticated on Github. See the Github OAuth information section for more information.");
tokenInput.setRequired(true);
options.addOption(tokenInput);
// CSV file of responses to form.
Option fileInput = new Option("i", "input", true, "The input file. Should be in CSV format with the following columns:\n" +
"\t Two Student numbers, two Github usernames, two email addresses, and \n" +
"\t whether the first student has a partner.");
fileInput.setRequired(true);
options.addOption(fileInput);
// The github organization to add the repositories to.
Option organizationInput = new Option("o", "organization", true, "The name of the organization for which this program is being run.");
organizationInput.setRequired(true);
options.addOption(organizationInput);
// The maximum team size.
Option teamSizeInput = new Option("s", "teamsize", true, "The maximum size of teams to generate.");
teamSizeInput.setRequired(false);
options.addOption(teamSizeInput);
return options;
}
}

View File

@ -0,0 +1,90 @@
package nl.andrewlalis.model;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* Represents one student's github information.
*/
public class Student {
/**
* The student's S-number.
*/
private int number;
/**
* The student's name.
*/
private String name;
/**
* The student's email.
*/
private String emailAddress;
/**
* The student's github username.
*/
private String githubUsername;
/**
* A list of partners that the student has said that they would like to be partners with.
*/
private List<Integer> preferredPartners;
public Student(int number, String name, String emailAddress, String githubUsername, List<Integer> preferredPartners) {
this.number = number;
this.name = name;
this.emailAddress = emailAddress;
this.githubUsername = githubUsername;
this.preferredPartners = preferredPartners;
}
@Override
public String toString() {
return this.number + " - " + this.name + " - " + this.emailAddress + " - " + this.githubUsername;
}
public int getNumber() {
return number;
}
public String getEmailAddress() {
return emailAddress;
}
public String getGithubUsername() {
return githubUsername;
}
public List<Integer> getPreferredPartners() {
return preferredPartners;
}
/**
* Using a given map of all students, returns a student's preferred team.
* @param studentMap A mapping from student number to student for all students who have signed up.
* @return A team with unknown id, comprised of this student's preferred partners.
*/
public Team getPreferredTeam(Map<Integer, Student> studentMap) {
Team t = new Team();
for (int partnerNumber : this.getPreferredPartners()) {
t.addStudent(studentMap.get(partnerNumber));
}
t.addStudent(this);
return t;
}
@Override
public boolean equals(Object s) {
if (!(s instanceof Student)) {
return false;
}
Student student = (Student) s;
return student.getNumber() == this.getNumber()
|| student.getEmailAddress().equals(this.getEmailAddress())
|| student.getGithubUsername().equals(this.getGithubUsername());
}
}

View File

@ -0,0 +1,147 @@
package nl.andrewlalis.model;
import java.util.ArrayList;
import java.util.List;
/**
* Represents one or more students' collective information.
*/
public class Team {
/**
* The list of students in this team.
*/
private List<Student> students;
/**
* The team identification number.
*/
private int id;
public Team() {
this.students = new ArrayList<>();
this.id = -1;
}
/**
* Determines if a student is already included in this team.
* @param student A student.
* @return True if the student is in this team, false otherwise.
*/
public boolean hasStudent(Student student) {
for (Student s : this.students) {
if (s.equals(student)) {
return true;
}
}
return false;
}
public int getStudentCount() {
return this.students.size();
}
public int getId() {
return this.id;
}
public void setId(int id) {
this.id = id;
}
public void setStudents(List<Student> students) {
this.students = students;
}
public List<Student> getStudents() {
return this.students;
}
/**
* Adds a student to this team.
* @param student The student to add.
* @return True if the student could be added, false otherwise.
*/
public boolean addStudent(Student student) {
if (!this.hasStudent(student)) {
this.students.add(student);
return true;
} else {
return false;
}
}
/**
* Determines if a team is valid, and ready to be added to the Github organization.
* A team is valid if and only if:
* - The student count is equal to the team size.
* - Each student is unique.
* - Each student's preferred partners match all the others.
* @param teamSize The preferred size of teams.
* @return True if the team is valid, and false otherwise.
*/
public boolean isValid(int teamSize) {
if (this.getStudentCount() == teamSize) {
List<Integer> encounteredIds = new ArrayList<>();
for (Student studentA : this.students) {
for (Student studentB : this.students) {
if (!studentA.equals(studentB) && !studentA.getPreferredPartners().contains(studentB.getNumber())) {
return false;
}
}
}
return true;
} else {
return false;
}
}
/**
* Generates a unique name which is intended to be used for the repository name of this team.
* @param prefix A prefix to further reduce the chances of duplicate names.
* It is suggested to use something like "2018_OOP"
* @return A string comprised of the prefix, team id, and student number of each team member.
*/
public String generateUniqueName(String prefix) {
StringBuilder sb = new StringBuilder(prefix);
sb.append("_team_").append(this.id);
for (Student s : this.students) {
sb.append('_').append(s.getNumber());
}
return sb.toString();
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder("Team: ");
sb.append(this.id).append('\n');
for (Student s : this.students) {
sb.append('\t').append(s.toString()).append('\n');
}
return sb.toString();
}
/**
* Determines if one team is equivalent to another. This is determined by if the two teams are comprised of the same
* students, in any order.
* @param o The object to compare to this team.
* @return True if the teams contain the same students, false otherwise.
*/
@Override
public boolean equals(Object o) {
if (o instanceof Team) {
Team t = (Team) o;
if (t.getStudentCount() != this.getStudentCount()) {
return false;
}
for (Student s : this.students) {
if (!t.hasStudent(s)) {
return false;
}
}
return true;
} else {
return false;
}
}
}

View File

@ -0,0 +1,79 @@
package nl.andrewlalis.util;
import nl.andrewlalis.model.Student;
import nl.andrewlalis.model.Team;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVRecord;
import java.io.FileReader;
import java.io.IOException;
import java.util.*;
public class TeamGenerator {
public static List<Team> generateFromCSV(String filename, int teamSize) throws IOException {
System.out.println("Generating teams of size " + teamSize);
Iterable<CSVRecord> records = CSVFormat.DEFAULT.withFirstRecordAsHeader().parse(new FileReader(filename));
Map<Integer, Student> studentMap = readAllStudents(records, teamSize);
return generateAllValidTeams(studentMap, teamSize);
}
private static List<Team> generateAllValidTeams(Map<Integer, Student> studentMap, int teamSize) {
List<Student> singleStudents = new ArrayList<>(studentMap.values());
List<Team> teams = new ArrayList<>();
int teamCount = 1;
// For each student, try to make a team from its preferred partners.
for (Map.Entry<Integer, Student> e : studentMap.entrySet()) {
Team t = e.getValue().getPreferredTeam(studentMap);
// Check if the team is of a valid size, and is not a duplicate.
// Note that at this stage, singles are treated as teams of 1, and thus not valid for any teamSize > 1.
if (t.isValid(teamSize) && !teams.contains(t)) {
t.setId(teamCount++);
// Once we know this team is completely valid, we remove all the students in it from the list of singles.
singleStudents.removeAll(t.getStudents());
teams.add(t);
}
}
teams.addAll(mergeSingleStudents(singleStudents, teamSize, teamCount));
return teams;
}
private static List<Team> mergeSingleStudents(List<Student> singleStudents, int teamSize, int teamIndex) {
List<Team> teams = new ArrayList<>();
while (!singleStudents.isEmpty()) {
Team t = new Team();
t.setId(teamIndex++);
while (t.getStudentCount() < teamSize && !singleStudents.isEmpty()) {
t.addStudent(singleStudents.remove(0));
}
teams.add(t);
}
return teams;
}
/**
* Reads all the rows from the CSV file, and returns a map of students, using student number as the index.
* @param records The records in the CSV file.
* @param teamSize The preferred size of teams, or rather, the expected number of partners.
* @return A map of all students in the file.
*/
private static Map<Integer, Student> readAllStudents(Iterable<CSVRecord> records, int teamSize) {
Map<Integer, Student> studentMap = new HashMap<>();
for (CSVRecord record : records) {
List<Integer> preferredIds = new ArrayList<>();
if (record.get(5).equals("Yes")) {
int columnOffset = 6;
for (int i = 0; i < teamSize-1; i++) {
preferredIds.add(Integer.parseInt(record.get(columnOffset + i)));
}
}
Student s = new Student(Integer.parseInt(record.get(3)), record.get(2), record.get(1), record.get(4), preferredIds);
studentMap.put(s.getNumber(), s);
}
return studentMap;
}
}