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