Initial commit adding source and pom file.
This commit is contained in:
commit
c3b1534695
|
@ -0,0 +1,3 @@
|
||||||
|
.idea/*
|
||||||
|
target/*
|
||||||
|
*.iml
|
|
@ -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>
|
|
@ -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",""
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue