From f220399f9a728d6ccb6a2f4de9ed804620f5256b Mon Sep 17 00:00:00 2001 From: andrewlalis Date: Sun, 9 Sep 2018 09:19:35 +0200 Subject: [PATCH 01/12] Removed Jackson dependencies from pom file. --- pom.xml | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/pom.xml b/pom.xml index 7137563..7d11d05 100644 --- a/pom.xml +++ b/pom.xml @@ -21,10 +21,6 @@ jar - - 2.9.6 - - org.apache.commons @@ -52,21 +48,6 @@ httpclient RELEASE - - com.fasterxml.jackson.core - jackson-core - ${jackson.version} - - - com.fasterxml.jackson.core - jackson-databind - ${jackson.version} - - - com.fasterxml.jackson.core - jackson-annotations - ${jackson.version} - org.kohsuke From 003e84d960c9ff10d5a41f689dd6caaf195bfbc6 Mon Sep 17 00:00:00 2001 From: andrewlalis Date: Wed, 12 Sep 2018 15:19:17 +0200 Subject: [PATCH 02/12] Improved adding collaborators. --- .../nl/andrewlalis/git_api/GithubManager.java | 67 +++++++++++++------ .../executables/SetupStudentRepos.java | 9 ++- 2 files changed, 52 insertions(+), 24 deletions(-) diff --git a/src/main/java/nl/andrewlalis/git_api/GithubManager.java b/src/main/java/nl/andrewlalis/git_api/GithubManager.java index c5b6450..4832a3a 100644 --- a/src/main/java/nl/andrewlalis/git_api/GithubManager.java +++ b/src/main/java/nl/andrewlalis/git_api/GithubManager.java @@ -25,11 +25,6 @@ import java.util.logging.Logger; */ public class GithubManager { - /** - * The assignments repository where students will get assignments from. - */ - private GHRepository assignmentsRepo; - /** * Github object for API interactions. */ @@ -56,6 +51,22 @@ public class GithubManager { } } + /** + * Gets a repository by name. + * @param name The name of the repository. + * @return The repository with the given name, or null if none exists. + */ + public GHRepository getRepository(String name) { + System.out.println(name); + try { + return this.organization.getRepository(name); + } catch (IOException e) { + logger.severe("No repository with name: " + name + " exists."); + e.printStackTrace(); + return null; + } + } + /** * Gets a list of teams in the organization. * @return A List of all TA teams in the organization. @@ -112,17 +123,17 @@ public class GithubManager { logger.fine("Deleted pre-existing assignments repository."); } - this.assignmentsRepo = this.createRepository(assignmentsRepoName, team, description, false, true, true); + GHRepository assignmentsRepo = this.createRepository(assignmentsRepoName, team, description, false, true, true); - if (this.assignmentsRepo == null) { + if (assignmentsRepo == null) { logger.severe("Could not create assignments repository."); return; } - this.protectMasterBranch(this.assignmentsRepo, team); + this.protectMasterBranch(assignmentsRepo, team); // Grant all teaching assistants write access. - team.add(this.assignmentsRepo, GHOrganization.Permission.ADMIN); + team.add(assignmentsRepo, GHOrganization.Permission.ADMIN); logger.fine("Gave admin rights to all teaching assistants in team: " + team.getName()); } @@ -132,10 +143,11 @@ public class GithubManager { * @param team The student team to set up. * @param taTeam The team of teaching assistants that is responsible for these students. * @param prefix The prefix to append to the front of the repo name. + * @param assignmentsRepo The assignments repository. */ - public void setupStudentRepo(StudentTeam team, TATeam taTeam, String prefix) { + public void setupStudentRepo(StudentTeam team, TATeam taTeam, String prefix, GHRepository assignmentsRepo) { // First check that the assignments repo exists, otherwise no invitations can be sent. - if (this.assignmentsRepo == null) { + if (assignmentsRepo == null) { logger.warning("Assignments repository must be created before student repositories."); return; } @@ -147,13 +159,13 @@ public class GithubManager { return; } + team.setRepository(repo); + team.setTaTeam(taTeam); + this.protectMasterBranch(repo, taTeam.getGithubTeam()); this.createDevelopmentBranch(repo); this.addTATeamAsAdmin(repo, taTeam.getGithubTeam()); - this.inviteStudentsToRepos(team, repo); - - team.setRepository(repo); - team.setTaTeam(taTeam); + this.inviteStudentsToRepos(team, assignmentsRepo); } /** @@ -216,25 +228,36 @@ public class GithubManager { /** * Invites students in a team to their repository, and the assignments repository. * @param team The team of students to invite as collaborators. - * @param repo The repository created for the students. + * @param assignmentsRepo The repository that contains assignments for the class. */ - private void inviteStudentsToRepos(StudentTeam team, GHRepository repo) { + private void inviteStudentsToRepos(StudentTeam team, GHRepository assignmentsRepo) { try { logger.finest("Adding students from team: " + team.getId() + " as collaborators."); - List users = new ArrayList<>(); for (Student student : team.getStudents()) { GHUser user = this.github.getUser(student.getGithubUsername()); - users.add(user); - } - repo.addCollaborators(users); - this.assignmentsRepo.addCollaborators(users); + this.addCollaboratorToRepo(user, assignmentsRepo); + this.addCollaboratorToRepo(user, team.getRepository()); + } } catch (IOException e) { logger.severe("Could not add students as collaborators to assignments or their repo.\n" + team); e.printStackTrace(); } } + /** + * Adds a user to a repository, or if a failure occurs, log the failure. + * @param user The user to add as a collaborator. + * @param repository The repository to add the user to. + */ + private void addCollaboratorToRepo(GHUser user, GHRepository repository) { + try { + repository.addCollaborators(user); + } catch (IOException e) { + logger.severe("Could not add user " + user.getLogin() + " to repository " + repository.getName()); + } + } + /** * Adds a teaching assistant team as admins to a particular student repository. * @param studentRepo The student repository. diff --git a/src/main/java/nl/andrewlalis/ui/control/command/executables/SetupStudentRepos.java b/src/main/java/nl/andrewlalis/ui/control/command/executables/SetupStudentRepos.java index 411f255..cdc4380 100644 --- a/src/main/java/nl/andrewlalis/ui/control/command/executables/SetupStudentRepos.java +++ b/src/main/java/nl/andrewlalis/ui/control/command/executables/SetupStudentRepos.java @@ -4,6 +4,7 @@ import nl.andrewlalis.git_api.GithubManager; import nl.andrewlalis.model.StudentTeam; import nl.andrewlalis.model.TATeam; import nl.andrewlalis.ui.view.InitializerApp; +import org.kohsuke.github.GHRepository; import java.util.List; @@ -23,13 +24,17 @@ public class SetupStudentRepos extends GithubExecutable { @Override protected boolean executeWithManager(GithubManager manager, String[] args) { - if (args.length < 1) { + if (args.length < 2) { return false; } List taTeams = this.app.getOrganization().getTaTeams(); for (TATeam team : taTeams) { for (StudentTeam studentTeam : team.getStudentTeams()) { - manager.setupStudentRepo(studentTeam, team, args[0]); + GHRepository assignmentsRepo = manager.getRepository(args[1]); + if (assignmentsRepo == null) { + return false; + } + manager.setupStudentRepo(studentTeam, team, args[0], assignmentsRepo); } } return true; From 83fd710e5ca2d3e2da896d89e3518d3666decb43 Mon Sep 17 00:00:00 2001 From: andrewlalis Date: Thu, 13 Sep 2018 13:50:39 +0200 Subject: [PATCH 03/12] Added a recording mechanism for all commands executed. --- .../ui/control/command/CommandExecutor.java | 32 ++++++++++++++--- .../executables/ExecutableContext.java | 34 +++++++++++++++++++ 2 files changed, 62 insertions(+), 4 deletions(-) create mode 100644 src/main/java/nl/andrewlalis/ui/control/command/executables/ExecutableContext.java diff --git a/src/main/java/nl/andrewlalis/ui/control/command/CommandExecutor.java b/src/main/java/nl/andrewlalis/ui/control/command/CommandExecutor.java index d66bb01..4d3eaa0 100644 --- a/src/main/java/nl/andrewlalis/ui/control/command/CommandExecutor.java +++ b/src/main/java/nl/andrewlalis/ui/control/command/CommandExecutor.java @@ -1,8 +1,8 @@ package nl.andrewlalis.ui.control.command; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; +import nl.andrewlalis.ui.control.command.executables.ExecutableContext; + +import java.util.*; import java.util.logging.Logger; /** @@ -23,8 +23,20 @@ public class CommandExecutor { */ private Map commands; + /** + * A list of all the executables which have failed to execute. + */ + private List failedExecutables; + + /** + * A list of all executables which have been run successfully. + */ + private List successfulExecutables; + public CommandExecutor() { this.commands = new HashMap<>(); + this.failedExecutables = new ArrayList<>(); + this.successfulExecutables = new ArrayList<>(); } /** @@ -64,12 +76,24 @@ public class CommandExecutor { public void executeCommand(String commandName, String[] args) { if (this.commands.containsKey(commandName)) { logger.info("Command executed: " + commandName + ' ' + Arrays.toString(args)); - if (!this.commands.get(commandName).execute(args)) { + Executable executable = this.commands.get(commandName); + ExecutableContext context = new ExecutableContext(executable, args); + if (!executable.execute(args)) { logger.warning("Command did not execute successfully."); + this.failedExecutables.add(context); + } else { + this.successfulExecutables.add(context); } } else { logger.warning(commandName + " is not a valid command."); } } + /** + * Retries all failed executables, and if successful, removes them from the queue. + */ + public void rerunFailedExecutables() { + this.failedExecutables.removeIf(ExecutableContext::runAgain); + } + } diff --git a/src/main/java/nl/andrewlalis/ui/control/command/executables/ExecutableContext.java b/src/main/java/nl/andrewlalis/ui/control/command/executables/ExecutableContext.java new file mode 100644 index 0000000..fc69f2c --- /dev/null +++ b/src/main/java/nl/andrewlalis/ui/control/command/executables/ExecutableContext.java @@ -0,0 +1,34 @@ +package nl.andrewlalis.ui.control.command.executables; + +import nl.andrewlalis.ui.control.command.Executable; + +/** + * An object to record a specific executable's execution context (args given), with the ability to re-run the executable + * if a failure occurs. + */ +public class ExecutableContext { + + /** + * The executable object, without any contextual information. + */ + private Executable executable; + + /** + * A list of arguments given to the executable when it was called. + */ + private String[] args; + + public ExecutableContext(Executable executable, String[] args) { + this.executable = executable; + this.args = args; + } + + /** + * Runs the stored executable again with the same arguments it was originally given. + * @return True if the execution was successful, or false otherwise. + */ + public boolean runAgain() { + return this.executable.execute(this.args); + } + +} From 4a4628e6e687bd7fa8d89ea65388de7075c2b331 Mon Sep 17 00:00:00 2001 From: andrewlalis Date: Mon, 24 Sep 2018 12:14:33 +0200 Subject: [PATCH 04/12] Added a function to list all repositories. --- src/main/java/nl/andrewlalis/Main.java | 1 + .../nl/andrewlalis/git_api/GithubManager.java | 22 +++++++++ .../command/executables/ListRepos.java | 48 +++++++++++++++++++ 3 files changed, 71 insertions(+) create mode 100644 src/main/java/nl/andrewlalis/ui/control/command/executables/ListRepos.java diff --git a/src/main/java/nl/andrewlalis/Main.java b/src/main/java/nl/andrewlalis/Main.java index ed72ebc..f0be3c2 100644 --- a/src/main/java/nl/andrewlalis/Main.java +++ b/src/main/java/nl/andrewlalis/Main.java @@ -41,6 +41,7 @@ public class Main { executor.registerCommand("delete_repos", new DeleteRepos()); executor.registerCommand("delegate_student_teams", new DelegateStudentTeams(app)); executor.registerCommand("setup_student_repos", new SetupStudentRepos(app)); + executor.registerCommand("list_repos", new ListRepos()); logger.info("GithubManager for Github Repositories in Educational Organizations.\n" + "© Andrew Lalis (2018), All rights reserved.\n" + diff --git a/src/main/java/nl/andrewlalis/git_api/GithubManager.java b/src/main/java/nl/andrewlalis/git_api/GithubManager.java index 4832a3a..4f8b148 100644 --- a/src/main/java/nl/andrewlalis/git_api/GithubManager.java +++ b/src/main/java/nl/andrewlalis/git_api/GithubManager.java @@ -51,6 +51,28 @@ public class GithubManager { } } + /** + * Returns a list of repositories with the given substring. + * @param substring A string which all repositories should contain. + * @return A List of repositories whose names contain the given substring. + */ + public List listReposWithPrefix(String substring) { + List repos = new ArrayList<>(); + try { + List allRepos = this.organization.listRepositories().asList(); + for (GHRepository repo : allRepos) { + if (repo.getName().contains(substring)) { + repos.add(repo); + } + } + } catch (Exception e) { + logger.severe("IOException while listing repositories in the organization."); + e.printStackTrace(); + } + + return repos; + } + /** * Gets a repository by name. * @param name The name of the repository. diff --git a/src/main/java/nl/andrewlalis/ui/control/command/executables/ListRepos.java b/src/main/java/nl/andrewlalis/ui/control/command/executables/ListRepos.java new file mode 100644 index 0000000..34e5874 --- /dev/null +++ b/src/main/java/nl/andrewlalis/ui/control/command/executables/ListRepos.java @@ -0,0 +1,48 @@ +package nl.andrewlalis.ui.control.command.executables; + +import nl.andrewlalis.git_api.GithubManager; +import org.kohsuke.github.GHRepository; + +import java.util.List; +import java.util.logging.Logger; + +/** + * This executable shows a list of repositories with a given substring. + */ +public class ListRepos extends GithubExecutable { + + + /** + * The logger for outputting debug info. + */ + private static final Logger logger = Logger.getLogger(ListRepos.class.getName()); + static { + logger.setParent(Logger.getGlobal()); + } + + @Override + protected boolean executeWithManager(GithubManager manager, String[] args) { + if (args.length < 1) { + return false; + } + + List repos = manager.listReposWithPrefix(args[0]); + logger.info(outputRepoList(repos)); + + return true; + } + + /** + * Prints a nicely formatted list of repositories to a string. + * @param repos The list of repositories. + * @return A string representation of the list of repos. + */ + private static String outputRepoList(List repos) { + StringBuilder sb = new StringBuilder(); + sb.append("List of ").append(repos.size()).append(" repositories:\n"); + for (GHRepository repo : repos) { + sb.append('\t').append(repo.getName()).append('\n'); + } + return sb.toString(); + } +} From 6e9ebcad74c50cf8f77a333b6289a1f095040f7f Mon Sep 17 00:00:00 2001 From: andrewlalis Date: Sun, 7 Oct 2018 01:21:01 +0200 Subject: [PATCH 05/12] Added hibernate support on the most basic level. Not yet implemented in production code. --- pom.xml | 19 +- src/main/java/nl/andrewlalis/Main.java | 11 + .../nl/andrewlalis/git_api/GithubManager.java | 4 +- .../java/nl/andrewlalis/model/Person.java | 40 ++- .../java/nl/andrewlalis/model/Student.java | 43 ++- .../nl/andrewlalis/model/StudentTeam.java | 16 +- .../java/nl/andrewlalis/model/TATeam.java | 11 +- .../andrewlalis/model/TeachingAssistant.java | 5 + src/main/java/nl/andrewlalis/model/Team.java | 42 ++- .../model/database/BaseEntity.java | 28 ++ .../andrewlalis/model/database/Database.java | 266 ------------------ .../nl/andrewlalis/model/database/DbUtil.java | 51 ++++ .../nl/andrewlalis/model/database/Utils.java | 52 ---- .../nl/andrewlalis/util/TeamGenerator.java | 64 +++-- src/main/resources/hibernate.cfg.xml | 30 ++ 15 files changed, 303 insertions(+), 379 deletions(-) create mode 100644 src/main/java/nl/andrewlalis/model/database/BaseEntity.java delete mode 100644 src/main/java/nl/andrewlalis/model/database/Database.java create mode 100644 src/main/java/nl/andrewlalis/model/database/DbUtil.java delete mode 100644 src/main/java/nl/andrewlalis/model/database/Utils.java create mode 100644 src/main/resources/hibernate.cfg.xml diff --git a/pom.xml b/pom.xml index 7d11d05..18af176 100644 --- a/pom.xml +++ b/pom.xml @@ -54,11 +54,22 @@ github-api 1.93 - + - org.xerial - sqlite-jdbc - 3.23.1 + mysql + mysql-connector-java + 8.0.12 + + + com.h2database + h2 + 1.4.197 + + + + org.hibernate + hibernate-core + 5.3.6.Final diff --git a/src/main/java/nl/andrewlalis/Main.java b/src/main/java/nl/andrewlalis/Main.java index f0be3c2..a7f8af3 100644 --- a/src/main/java/nl/andrewlalis/Main.java +++ b/src/main/java/nl/andrewlalis/Main.java @@ -1,10 +1,14 @@ package nl.andrewlalis; +import nl.andrewlalis.model.Student; +import nl.andrewlalis.model.database.DbUtil; import nl.andrewlalis.ui.control.command.CommandExecutor; import nl.andrewlalis.ui.control.command.executables.*; import nl.andrewlalis.ui.view.InitializerApp; import nl.andrewlalis.util.CommandLine; import nl.andrewlalis.util.Logging; +import org.hibernate.Session; +import org.hibernate.SessionFactory; import java.util.Map; import java.util.logging.Logger; @@ -46,6 +50,13 @@ public class Main { logger.info("GithubManager for Github Repositories in Educational Organizations.\n" + "© Andrew Lalis (2018), All rights reserved.\n" + "Program initialized."); + + SessionFactory factory = DbUtil.getSessionFactory(); + Session session = factory.openSession(); + session.beginTransaction(); + System.out.println(session.save(new Student(1, "a", "a@e.com", "git", null))); + session.getTransaction().commit(); + session.close(); } } diff --git a/src/main/java/nl/andrewlalis/git_api/GithubManager.java b/src/main/java/nl/andrewlalis/git_api/GithubManager.java index 4f8b148..9748239 100644 --- a/src/main/java/nl/andrewlalis/git_api/GithubManager.java +++ b/src/main/java/nl/andrewlalis/git_api/GithubManager.java @@ -177,7 +177,7 @@ public class GithubManager { GHRepository repo = this.createRepository(team.generateUniqueName(prefix), taTeam.getGithubTeam(), team.generateRepoDescription(), false, true, true); if (repo == null) { - logger.severe("Repository for student team " + team.getId() + " could not be created."); + logger.severe("Repository for student team " + team.getNumber() + " could not be created."); return; } @@ -254,7 +254,7 @@ public class GithubManager { */ private void inviteStudentsToRepos(StudentTeam team, GHRepository assignmentsRepo) { try { - logger.finest("Adding students from team: " + team.getId() + " as collaborators."); + logger.finest("Adding students from team: " + team.getNumber() + " as collaborators."); for (Student student : team.getStudents()) { GHUser user = this.github.getUser(student.getGithubUsername()); diff --git a/src/main/java/nl/andrewlalis/model/Person.java b/src/main/java/nl/andrewlalis/model/Person.java index 50b420a..62639b9 100644 --- a/src/main/java/nl/andrewlalis/model/Person.java +++ b/src/main/java/nl/andrewlalis/model/Person.java @@ -1,31 +1,53 @@ package nl.andrewlalis.model; +import nl.andrewlalis.model.database.BaseEntity; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + /** * A generic object that students, teaching assistants, and professors can extend from. This covers all the basic * functionality that applies to anyone in the system. */ -public abstract class Person { +@Entity(name = "Person") +@Table(name = "persons") +public abstract class Person extends BaseEntity { /** * The unique identification number for this person. (P- or S-Number) */ + @Column(name="number") protected int number; /** * The person's first and last name. */ + @Column(name="name") protected String name; /** * The person's email address. */ + @Column(name="email_address") protected String emailAddress; /** * The person's github username. */ + @Column(name="github_username") protected String githubUsername; + /** + * Constructs an empty default Person. + */ + public Person() { + this.number = -1; + this.name = null; + this.emailAddress = null; + this.githubUsername = null; + } + /** * Constructs a Person from only a github username, which is, in some cases, enough to perform a lot of actions. * @param githubUsername The person's github username. @@ -58,18 +80,34 @@ public abstract class Person { return this.number; } + public void setNumber(int number) { + this.number = number; + } + public String getName(){ return this.name; } + public void setName(String name) { + this.name = name; + } + public String getEmailAddress(){ return this.emailAddress; } + public void setEmailAddress(String emailAddress) { + this.emailAddress = emailAddress; + } + public String getGithubUsername(){ return this.githubUsername; } + public void setGithubUsername(String githubUsername) { + this.githubUsername = githubUsername; + } + /** * Determines if two persons are the same. This is defined as: * Two persons are equal if at least one of their personal data points is identical. Because each of the data points diff --git a/src/main/java/nl/andrewlalis/model/Student.java b/src/main/java/nl/andrewlalis/model/Student.java index e347b58..f68df82 100644 --- a/src/main/java/nl/andrewlalis/model/Student.java +++ b/src/main/java/nl/andrewlalis/model/Student.java @@ -1,12 +1,15 @@ package nl.andrewlalis.model; +import javax.persistence.*; +import java.util.ArrayList; import java.util.List; -import java.util.Map; import java.util.logging.Logger; /** * Represents one student's github information. */ +@Entity(name = "Student") +@Table(name="students") public class Student extends Person { private static final Logger logger = Logger.getLogger(Student.class.getName()); @@ -17,7 +20,20 @@ public class Student extends Person { /** * A list of partners that the student has said that they would like to be partners with. */ - private List preferredPartners; + @ManyToMany + @JoinTable( + name = "student_preferred_partners", + joinColumns = { @JoinColumn(name = "student_id")}, + inverseJoinColumns = {@JoinColumn(name = "preferred_partner_id")} + ) + private List preferredPartners; + + /** + * Constructs an empty student object. + */ + public Student() { + this.preferredPartners = new ArrayList<>(); + } /** * Constructs a student similarly to a Person, but with an extra preferredPartners list. @@ -28,24 +44,31 @@ public class Student extends Person { * @param preferredPartners A list of this student's preferred partners, as a list of integers representing the * other students' numbers. */ - public Student(int number, String name, String emailAddress, String githubUsername, List preferredPartners) { + public Student(int number, String name, String emailAddress, String githubUsername, List preferredPartners) { super(number, name, emailAddress, githubUsername); this.preferredPartners = preferredPartners; } - public List getPreferredPartners() { + public List getPreferredPartners() { return this.preferredPartners; } + public void setPreferredPartners(List preferredPartners) { + this.preferredPartners = preferredPartners; + } + + public void addPreferredPartner(Student student) { + this.preferredPartners.add(student); + } + /** - * 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. + * Returns a student's preferred team, including himself. + * @return A team with unknown number, comprised of this student's preferred partners. */ - public StudentTeam getPreferredTeam(Map studentMap) { + public StudentTeam getPreferredTeam() { StudentTeam t = new StudentTeam(); - for (int partnerNumber : this.getPreferredPartners()) { - t.addMember(studentMap.get(partnerNumber)); + for (Student partner : this.getPreferredPartners()) { + t.addMember(partner); } t.addMember(this); return t; diff --git a/src/main/java/nl/andrewlalis/model/StudentTeam.java b/src/main/java/nl/andrewlalis/model/StudentTeam.java index 7da57ff..6d987e1 100644 --- a/src/main/java/nl/andrewlalis/model/StudentTeam.java +++ b/src/main/java/nl/andrewlalis/model/StudentTeam.java @@ -2,21 +2,29 @@ package nl.andrewlalis.model; import org.kohsuke.github.GHRepository; +import javax.persistence.Entity; +import javax.persistence.ManyToOne; +import javax.persistence.Table; +import javax.persistence.Transient; import java.util.Arrays; /** * Represents one or more students' collective information. */ -public class StudentTeam extends Team{ +@Entity(name = "StudentTeam") +@Table(name = "student_teams") +public class StudentTeam extends Team { /** * The repository belonging to this team. */ + @Transient private GHRepository repository; /** * The TATeam responsible for this student team. */ + @ManyToOne private TATeam taTeam; public StudentTeam() { @@ -62,11 +70,11 @@ public class StudentTeam extends Team{ * 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. + * @return A string comprised of the prefix, team number, and student number of each team member. */ public String generateUniqueName(String prefix) { StringBuilder sb = new StringBuilder(prefix); - sb.append("_team_").append(this.id); + sb.append("_team_").append(this.number); for (Student s : this.getStudents()) { sb.append('_').append(s.getNumber()); } @@ -79,7 +87,7 @@ public class StudentTeam extends Team{ */ public String generateRepoDescription() { StringBuilder sb = new StringBuilder(); - sb.append("Group ").append(this.id).append(": "); + sb.append("Group ").append(this.number).append(": "); for (int i = 0; i < this.memberCount(); i++) { sb.append(this.getStudents()[i].getName()); if (i != this.memberCount()-1) { diff --git a/src/main/java/nl/andrewlalis/model/TATeam.java b/src/main/java/nl/andrewlalis/model/TATeam.java index 99dc211..cc26560 100644 --- a/src/main/java/nl/andrewlalis/model/TATeam.java +++ b/src/main/java/nl/andrewlalis/model/TATeam.java @@ -2,14 +2,19 @@ package nl.andrewlalis.model; import org.kohsuke.github.GHTeam; +import javax.persistence.Entity; +import javax.persistence.OneToMany; +import javax.persistence.Table; +import javax.persistence.Transient; import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** - * Represents a teaching assistant team, which is itself a 'team' in the organization. This class is used for parsing - * json from requests to github to get a list of all teams in the organization. + * Represents a teaching assistant team, which is itself a 'team' in the organization. */ +@Entity(name = "TATeam") +@Table(name = "ta_teams") public class TATeam extends Team { /** @@ -20,11 +25,13 @@ public class TATeam extends Team { /** * The Github team associated with this team. */ + @Transient private GHTeam githubTeam; /** * A list of all student teams for which this TA team is responsible. */ + @OneToMany private List studentTeams; /** diff --git a/src/main/java/nl/andrewlalis/model/TeachingAssistant.java b/src/main/java/nl/andrewlalis/model/TeachingAssistant.java index 7362c48..1c2787b 100644 --- a/src/main/java/nl/andrewlalis/model/TeachingAssistant.java +++ b/src/main/java/nl/andrewlalis/model/TeachingAssistant.java @@ -1,8 +1,13 @@ package nl.andrewlalis.model; +import javax.persistence.Entity; +import javax.persistence.Table; + /** * Represents an administrator in the organization, who manages student teams. */ +@Entity(name = "TeachingAssistant") +@Table(name = "teaching_assistants") public class TeachingAssistant extends Person { /** diff --git a/src/main/java/nl/andrewlalis/model/Team.java b/src/main/java/nl/andrewlalis/model/Team.java index f1b23db..eee12cb 100644 --- a/src/main/java/nl/andrewlalis/model/Team.java +++ b/src/main/java/nl/andrewlalis/model/Team.java @@ -1,5 +1,8 @@ package nl.andrewlalis.model; +import nl.andrewlalis.model.database.BaseEntity; + +import javax.persistence.*; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -8,39 +11,48 @@ import java.util.List; * An abstract Team object from which both Teaching Assistant and Student teams can be built. A Team consists of a list * of members, and a unique identification number. */ -public abstract class Team { +@Entity(name = "Team") +@Table(name = "teams") +public abstract class Team extends BaseEntity { /** * An identification number unique to this team alone. */ - protected int id; + @Column(name = "number") + protected int number; /** * A list of members of this team. */ + @OneToMany + @JoinTable( + name = "team_members", + joinColumns = {@JoinColumn(name = "team_id")}, + inverseJoinColumns = {@JoinColumn(name = "person_id")} + ) private List members; /** - * Constructs this team with the given id. - * @param id The id to assign to this team. + * Constructs this team with the given number. + * @param number The number to assign to this team. */ - public Team(int id) { - this.id = id; + public Team(int number) { + this.number = number; this.members = new ArrayList<>(); } /** - * @param newId The new id number to assign to this team. + * @param newId The new number number to assign to this team. */ - public void setId(int newId) { - this.id = newId; + public void setNumber(int newId) { + this.number = newId; } /** - * @return This team's id number. + * @return This team's number number. */ - public int getId() { - return this.id; + public int getNumber() { + return this.number; } /** @@ -123,7 +135,7 @@ public abstract class Team { /** * Checks if an object is equal to this team. First checks if the other object is a Team, and then if it has the - * same id and team size. If both of those conditions are met, then it will check that all team members are the + * same number and team size. If both of those conditions are met, then it will check that all team members are the * same. * @param obj The object to check for equality. * @return True if the two objects represent the same team, or false otherwise. @@ -132,7 +144,7 @@ public abstract class Team { public boolean equals(Object obj) { if (obj instanceof Team) { Team team = (Team) obj; - return team.getId() == this.getId() && this.hasSameMembers(team); + return team.getNumber() == this.getNumber() && this.hasSameMembers(team); } return false; } @@ -143,7 +155,7 @@ public abstract class Team { @Override public String toString() { StringBuilder sb = new StringBuilder(); - sb.append("Team of ").append(this.memberCount()).append(" members:\tID: ").append(this.id).append('\n'); + sb.append("Team of ").append(this.memberCount()).append(" members:\tID: ").append(this.number).append('\n'); for (Person person : this.members) { sb.append(person.toString()).append('\n'); } diff --git a/src/main/java/nl/andrewlalis/model/database/BaseEntity.java b/src/main/java/nl/andrewlalis/model/database/BaseEntity.java new file mode 100644 index 0000000..a31e294 --- /dev/null +++ b/src/main/java/nl/andrewlalis/model/database/BaseEntity.java @@ -0,0 +1,28 @@ +package nl.andrewlalis.model.database; + +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.MappedSuperclass; + +/** + * Defines a base entity which all others in the database extend from. + */ +@MappedSuperclass +public abstract class BaseEntity { + + /** + * The number for this entity. + */ + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } +} diff --git a/src/main/java/nl/andrewlalis/model/database/Database.java b/src/main/java/nl/andrewlalis/model/database/Database.java deleted file mode 100644 index a62149d..0000000 --- a/src/main/java/nl/andrewlalis/model/database/Database.java +++ /dev/null @@ -1,266 +0,0 @@ -package nl.andrewlalis.model.database; - -import nl.andrewlalis.model.*; - -import java.sql.*; -import java.util.ArrayList; -import java.util.List; -import java.util.logging.Logger; - -/** - * This class abstracts many of the functions needed for interaction with the application's SQLite database. - */ -public class Database { - - private static final int PERSON_TYPE_STUDENT = 0; - private static final int PERSON_TYPE_TA = 1; - - private static final int TEAM_TYPE_STUDENT = 0; - private static final int TEAM_TYPE_TA = 1; - private static final int TEAM_TYPE_TA_ALL = 2; - - private static final int TEAM_NONE = 1000000; - private static final int TEAM_TA_ALL = 1000001; - - private static final int ERROR_TYPE_TEAM = 0; - private static final int ERROR_TYPE_PERSON = 1; - private static final int ERROR_TYPE_SYSTEM = 2; - - /** - * The connection needed for all queries. - */ - private Connection connection; - - /** - * The logger for outputting debug info. - */ - private static final Logger logger = Logger.getLogger(Database.class.getName()); - static { - logger.setParent(Logger.getGlobal()); - } - - public Database(String databaseFilename) { - try { - this.connection = DriverManager.getConnection("jdbc:sqlite:" + databaseFilename); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - /** - * Initializes the database from the table_init.sql script, which defines the table schema. - * Then, inserts some constant starter data from /sql/insert/types.sql. - * @return True if successful, false if not. - */ - public boolean initialize() { - List tableStatements = Utils.prepareStatementsFromFile("/sql/table_init.sql", this.connection); - for (PreparedStatement statement : tableStatements) { - try { - statement.execute(); - } catch (SQLException e) { - logger.severe("SQLException while executing prepared statement:\n" + statement.toString() + "\nCode: " + e.getErrorCode()); - return false; - } - } - logger.fine("Database tables initialized."); - List insertStatements = Utils.prepareStatementsFromFile("/sql/insert/types.sql", this.connection); - for (PreparedStatement statement : insertStatements) { - try { - statement.execute(); - } catch (SQLException e) { - logger.severe("SQLException while inserting into table:\n" + statement.toString() + "\nCode: " + e.getErrorCode()); - return false; - } - } - logger.fine("Initial types inserted."); - return true; - } - - /** - * Stores a person in the database. - * @param person The person object to store. - * @param personType The type of person to store, using a constant defined above. - * @return True if successful, false otherwise. - */ - public boolean insertPerson(Person person, int personType) { - try { - logger.finest("Storing person: " + person); - String sql = "INSERT INTO persons (id, name, email_address, github_username, person_type_id) VALUES (?, ?, ?, ?, ?);"; - PreparedStatement stmt = this.connection.prepareStatement(sql); - stmt.setInt(1, person.getNumber()); - stmt.setString(2, person.getName()); - stmt.setString(3, person.getEmailAddress()); - stmt.setString(4, person.getGithubUsername()); - stmt.setInt(5, personType); - return stmt.execute(); - } catch (SQLException e) { - logger.severe("SQLException while inserting Person: " + person + '\n' + e.getMessage()); - return false; - } - } - - /** - * Stores a student in the database. - * @param student The student to store. - * @return True if the operation was successful, false otherwise. - */ - public boolean insertStudent(Student student) { - logger.finest("Storing student: " + student); - if (!insertPerson(student, PERSON_TYPE_STUDENT)) { - return false; - } - try { - String sql = "INSERT INTO students (person_id, chose_partner) VALUES (?, ?);"; - PreparedStatement stmt = this.connection.prepareStatement(sql); - stmt.setInt(1, student.getNumber()); - stmt.setInt(2, student.getPreferredPartners().size() > 0 ? 1 : 0); - if (!stmt.execute()) { - return false; - } - // Storing partners. - String sqlPartner = "INSERT INTO student_preferred_partners (student_id, partner_id) VALUES (?, ?);"; - PreparedStatement stmtPartner = this.connection.prepareStatement(sqlPartner); - for (int partnerId : student.getPreferredPartners()) { - stmtPartner.setInt(1, student.getNumber()); - stmtPartner.setInt(2, partnerId); - stmtPartner.execute(); - } - return true; - } catch (SQLException e) { - logger.severe("SQL Exception while inserting Student into database.\n" + e.getMessage()); - e.printStackTrace(); - return false; - } - } - - /** - * Stores a teaching assistant in the database. - * @param ta The teaching assistant to store. - * @return True if successful, false otherwise. - */ - public boolean insertTeachingAssistant(TeachingAssistant ta) { - if (!insertPerson(ta, PERSON_TYPE_TA)) { - return false; - } - try { - String sql = "INSERT INTO teaching_assistants (person_id) VALUES (?);"; - PreparedStatement stmt = this.connection.prepareStatement(sql); - stmt.setInt(1, ta.getNumber()); - stmt.execute(); - return true; - } catch (SQLException e) { - logger.severe("SQL Exception while inserting TeachingAssistant.\n" + e.getMessage()); - e.printStackTrace(); - return false; - } - } - - /** - * Stores a team in the database, and any persons who do not yet exist. - * @param team The team to store. - * @param type The type of team that this is. - * @param personType The type of people that this team is made of. - * @return True if successful, false otherwise. - */ - public boolean insertTeam(Team team, int type, int personType) { - try { - String sql = "INSERT INTO teams (id, team_type_id) VALUES (?, ?);"; - PreparedStatement stmt = this.connection.prepareStatement(sql); - stmt.setInt(1, team.getId()); - stmt.setInt(2, type); - if (stmt.execute()) { - for (Person p : team.getMembers()) { - this.insertPerson(p, personType); - String sqlMembers = "INSERT INTO team_members (team_id, person_id) VALUES (?, ?);"; - PreparedStatement stmtMembers = this.connection.prepareStatement(sqlMembers); - stmtMembers.setInt(1, team.getId()); - stmtMembers.setInt(2, p.getNumber()); - stmtMembers.execute(); - } - } - return true; - } catch (SQLException e) { - logger.severe("SQLException while inserting team: " + team + '\n' + e.getMessage()); - return false; - } - } - - /** - * Stores a student team in the database. - * @param team The team to store. - * @return True if successful, false otherwise. - */ - public boolean insertStudentTeam(StudentTeam team) { - if (!this.insertTeam(team, TEAM_TYPE_STUDENT, PERSON_TYPE_STUDENT)) { - return false; - } - try { - String sql = "INSERT INTO student_teams (team_id, repository_name, teaching_assistant_team_id) VALUES (?, ?, ?);"; - PreparedStatement stmt = this.connection.prepareStatement(sql); - stmt.setInt(1, team.getId()); - stmt.setString(2, (team.getRepository() == null) ? null : team.getRepository().getName()); - stmt.setInt(3, (team.getTaTeam() == null) ? TEAM_NONE : team.getTaTeam().getId()); - return stmt.execute(); - } catch (SQLException e) { - logger.severe("SQLException while inserting student team: " + team + '\n' + e.getMessage()); - e.printStackTrace(); - return false; - } - } - - /** - * Stores a list of student teams in the database. - * @param teams The list of teams to store. - * @return True if successful, or false if an error occurred. - */ - public boolean storeStudentTeams(List teams) { - for (StudentTeam team : teams) { - this.insertStudentTeam(team); - } - return true; - } - - /** - * Retrieves a list of preferred partners that each student has set. - * @param studentId The student id to search by. - * @return A list of student id's for all students that the given student wishes to be their partner. - */ - private List retrievePreferredPartners(int studentId) { - try { - logger.finest("Retrieving preferred partners of student: " + studentId); - String sql = "SELECT partner_id FROM student_preferred_partners WHERE student_id=?;"; - PreparedStatement stmt = this.connection.prepareStatement(sql); - stmt.setInt(1, studentId); - ResultSet results = stmt.executeQuery(); - List partners = new ArrayList<>(); - while (results.next()) { - partners.add(results.getInt(1)); - } - return partners; - } catch (SQLException e) { - logger.severe("SQL Exception while retrieving preferred partners of student: " + studentId + '\n' + e.getMessage()); - e.printStackTrace(); - return new ArrayList<>(); - } - } - - /** - * Retrieves a student by their id. - * @param id The id of the student (student number) - * @return The student corresponding to this number, or null if it could not be found. - */ - public Student retrieveStudent(int id) { - try { - String sql = "SELECT * FROM persons WHERE id=?"; - PreparedStatement stmt = this.connection.prepareStatement(sql); - stmt.setInt(1, id); - ResultSet result = stmt.executeQuery(); - return new Student(id, result.getString("name"), result.getString("email_address"), result.getString("github_username"), this.retrievePreferredPartners(id)); - } catch (SQLException e) { - logger.severe("SQL Exception while retrieving Student.\n" + e.getMessage()); - e.printStackTrace(); - return null; - } - } - -} diff --git a/src/main/java/nl/andrewlalis/model/database/DbUtil.java b/src/main/java/nl/andrewlalis/model/database/DbUtil.java new file mode 100644 index 0000000..d58cee0 --- /dev/null +++ b/src/main/java/nl/andrewlalis/model/database/DbUtil.java @@ -0,0 +1,51 @@ +package nl.andrewlalis.model.database; + +import org.hibernate.SessionFactory; +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.registry.StandardServiceRegistry; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; + +/** + * A utility class for easier interaction with the Hibernate database. + */ +public class DbUtil { + + private static SessionFactory sessionFactory; + + /** + * Set up the session factory based on hibernate.cfg.xml. + */ + private static void setUp() { + final StandardServiceRegistry registry = new StandardServiceRegistryBuilder() + .configure() + .build(); + try { + sessionFactory = new MetadataSources(registry) + .buildMetadata() + .buildSessionFactory(); + } catch (Exception e) { + e.printStackTrace(); + StandardServiceRegistryBuilder.destroy(registry); + } + } + + /** + * Close the session factory when it's no longer needed. + */ + public static void tearDown() { + if (sessionFactory != null) { + sessionFactory.close(); + } + } + + /** + * Gets the session factory so that sessions can be made. + * @return The session factory. + */ + public static SessionFactory getSessionFactory() { + if (sessionFactory == null) { + setUp(); + } + return sessionFactory; + } +} diff --git a/src/main/java/nl/andrewlalis/model/database/Utils.java b/src/main/java/nl/andrewlalis/model/database/Utils.java deleted file mode 100644 index e908f1d..0000000 --- a/src/main/java/nl/andrewlalis/model/database/Utils.java +++ /dev/null @@ -1,52 +0,0 @@ -package nl.andrewlalis.model.database; - -import nl.andrewlalis.util.FileUtils; - -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.List; -import java.util.logging.Logger; - -/** - * Contains some methods which make database actions much easier. - */ -public class Utils { - - /** - * The logger for outputting debug info. - */ - private static final Logger logger = Logger.getLogger(Utils.class.getName()); - static { - logger.setParent(Logger.getGlobal()); - } - - /** - * Gets an ordered list of prepared statements from a file which contains multiple statements separated by a - * semicolon. This method separates those statements into their own strings, and prepares them individually to be - * executed later. - * @param filename The name of the file which contains the statements. - * @param connection The connection to a database; used to prepare statements. - * @return An ordered list of prepared statements which are based on the contents of the file provided. - */ - public static List prepareStatementsFromFile(String filename, Connection connection) { - String string = FileUtils.readStringFromFile(filename); - if (string == null || string.isEmpty()) { - return new ArrayList<>(); - } - String[] splits = string.split(";"); - List statements = new ArrayList<>(); - for (String split : splits) { - if (split.trim().length() > 1) { - try { - statements.add(connection.prepareStatement(split)); - } catch (SQLException e) { - logger.severe("SQLException while preparing a statement:\n" + split + "\nError Code: " + e.getErrorCode() + '\n' + e.getMessage()); - } - } - } - return statements; - } - -} diff --git a/src/main/java/nl/andrewlalis/util/TeamGenerator.java b/src/main/java/nl/andrewlalis/util/TeamGenerator.java index 89b8b35..aa7b426 100644 --- a/src/main/java/nl/andrewlalis/util/TeamGenerator.java +++ b/src/main/java/nl/andrewlalis/util/TeamGenerator.java @@ -2,9 +2,6 @@ package nl.andrewlalis.util; import nl.andrewlalis.model.Student; import nl.andrewlalis.model.StudentTeam; -import nl.andrewlalis.model.error.Error; -import nl.andrewlalis.model.error.Severity; -import nl.andrewlalis.ui.view.InitializerApp; import org.apache.commons.csv.CSVFormat; import org.apache.commons.csv.CSVRecord; @@ -45,9 +42,9 @@ public class TeamGenerator { Iterable records = CSVFormat.DEFAULT.withFirstRecordAsHeader().parse(new FileReader(filename)); logger.fine("Reading all records into map."); - Map studentMap; + List students; try { - studentMap = readAllStudents(records, teamSize); + students = readAllStudents(records, teamSize); } catch (ArrayIndexOutOfBoundsException e) { logger.severe("StudentTeam size does not match column count in records."); throw new IllegalArgumentException("StudentTeam size does not match column count in records."); @@ -55,7 +52,7 @@ public class TeamGenerator { logger.fine("Generating all valid teams from student map."); - return generateAllValidTeams(studentMap, teamSize); + return generateAllValidTeams(students, teamSize); } /** @@ -71,21 +68,23 @@ public class TeamGenerator { * After all students with preferred partners are placed in teams, the single students are merged, and their teams * are added afterwards. * - * @param studentMap A mapping for each student to their student number. + * @param students A list of students, each with a list of preferred partners. * @param teamSize The preferred maximum size for a team. * @return A list of teams, most of which are of teamSize size. */ - private static List generateAllValidTeams(Map studentMap, int teamSize) { - List singleStudents = new ArrayList<>(studentMap.values()); + private static List generateAllValidTeams(List students, int teamSize) { + List singleStudents = new ArrayList<>(students); List studentTeams = new ArrayList<>(); + // An integer which increments for each valid team. Used to create identifiers for each team. int teamCount = 1; + // For each student, try to make a team from its preferred partners. - for (Map.Entry e : studentMap.entrySet()) { - StudentTeam newTeam = e.getValue().getPreferredTeam(studentMap); + for (Student student : students) { + StudentTeam newTeam = student.getPreferredTeam(); logger.finest("Checking if student's preferred team is valid:\n" + newTeam); // Check if the team is of a valid size, and is not a duplicate. - // Note that at this stage, singles are treated as studentTeams of 1, and thus not valid for any teamSize > 1. + // Note that at this stage, singles are treated as student teams of 1, and thus not valid for any team size > 1. if (newTeam.isValid(teamSize)) { // We know that the team is valid on its own, so now we check if it has members identical to any team already created. boolean matchFound = false; @@ -97,7 +96,7 @@ public class TeamGenerator { } if (!matchFound) { // Once we know this team is completely valid, we remove all the students in it from the list of singles. - newTeam.setId(teamCount++); + newTeam.setNumber(teamCount++); singleStudents.removeAll(Arrays.asList(newTeam.getStudents())); studentTeams.add(newTeam); logger.fine("Created team:\n" + newTeam); @@ -114,14 +113,14 @@ public class TeamGenerator { * size as possible. * @param singleStudents A list of students who have no preferred partners. * @param teamSize The preferred team size. - * @param teamIndex The current number used in assigning an id to the team. + * @param teamIndex The current number used in assigning an number to the team. * @return A list of teams comprising of single students. */ private static List mergeSingleStudents(List singleStudents, int teamSize, int teamIndex) { List studentTeams = new ArrayList<>(); while (!singleStudents.isEmpty()) { StudentTeam t = new StudentTeam(); - t.setId(teamIndex++); + t.setNumber(teamIndex++); logger.fine("Creating new team of single students:\n" + t); while (t.memberCount() < teamSize && !singleStudents.isEmpty()) { Student s = singleStudents.remove(0); @@ -141,8 +140,10 @@ public class TeamGenerator { * @return A map of all students in the file. * @throws ArrayIndexOutOfBoundsException if the teamSize does not work with the columns in the record. */ - private static Map readAllStudents(Iterable records, int teamSize) throws ArrayIndexOutOfBoundsException { + private static List readAllStudents(Iterable records, int teamSize) throws ArrayIndexOutOfBoundsException { Map studentMap = new HashMap<>(); + Map> studentPreferredIds = new HashMap<>(); + // Perform the initial read of the students. for (CSVRecord record : records) { logger.finest("Read record: " + record); List preferredIds = new ArrayList<>(); @@ -152,21 +153,38 @@ public class TeamGenerator { 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); + Student s = new Student(); + s.setNumber(Integer.parseInt(record.get(3))); + s.setName(record.get(2)); + s.setEmailAddress(record.get(1)); + s.setGithubUsername(record.get(4)); if (studentMap.containsValue(s)) { logger.warning("Duplicate entry found for student: " + s + "\nOverwriting previous value."); } studentMap.put(s.getNumber(), s); + studentPreferredIds.put(s, preferredIds); } - // Perform a safety check to ensure all preferred partners are valid students. - for (Map.Entry entry : studentMap.entrySet()) { - // Remove any ids that don't exist in the whole list of students. - entry.getValue().getPreferredPartners().removeIf(partnerId -> !studentMap.containsKey(partnerId)); + // The final list of students, with preferred partners set. + List students = new ArrayList<>(); + + // Assign students to their preferred students. + for (Map.Entry> entry : studentPreferredIds.entrySet()) { + Student s = entry.getKey(); + for (int partnerNumber : entry.getValue()) { + // Check that this preferred partner number exists. + if (studentMap.containsKey(partnerNumber)) { + s.addPreferredPartner(studentMap.get(partnerNumber)); + } else { + logger.warning("Student " + s + " has invalid preferred partner."); + } + } + students.add(s); } + // At this point, all students are valid, and all preferred partners are valid. - logger.fine("Read " + studentMap.size() + " students from records."); - return studentMap; + logger.fine("Read " + students.size() + " students from records."); + return students; } } diff --git a/src/main/resources/hibernate.cfg.xml b/src/main/resources/hibernate.cfg.xml new file mode 100644 index 0000000..de5ea3a --- /dev/null +++ b/src/main/resources/hibernate.cfg.xml @@ -0,0 +1,30 @@ + + + + + + org.h2.Driver + jdbc:h2:./initializer.h2 + org.hibernate.dialect.H2Dialect + root + root + + + 1 + + + false + + + create-drop + + + + + + + + + \ No newline at end of file From b939094b9839abc834c4f744f2dc0bf5323edc7f Mon Sep 17 00:00:00 2001 From: andrewlalis Date: Mon, 15 Oct 2018 13:57:35 +0200 Subject: [PATCH 06/12] Added beginning of new view framework, and first three views for new workflow. Also added design for new workflow, and made many changes to existing Github Manager. --- design/SystemDiagram.odg | Bin 0 -> 15806 bytes design/SystemDiagram.pdf | Bin 0 -> 18820 bytes src/main/java/nl/andrewlalis/Main.java | 25 +-- .../control => }/command/CommandExecutor.java | 4 +- .../{ui/control => }/command/Executable.java | 2 +- .../command/executables/ArchiveRepos.java | 2 +- .../command/executables/DefineTaTeams.java | 4 +- .../executables/DelegateStudentTeams.java | 2 +- .../command/executables/DeleteRepos.java | 2 +- .../executables/ExecutableContext.java | 4 +- .../executables/GenerateAssignmentsRepo.java | 2 +- .../command/executables/GithubExecutable.java | 4 +- .../command/executables/ListErrors.java | 4 +- .../command/executables/ListRepos.java | 2 +- .../command/executables/ReadStudentsFile.java | 4 +- .../executables/SetupStudentRepos.java | 2 +- .../nl/andrewlalis/git_api/GithubManager.java | 163 +++++++++++++----- .../nl/andrewlalis/model/DatabaseObject.java | 9 - .../control/listeners/ArchiveAllListener.java | 2 +- .../listeners/CommandFieldKeyListener.java | 2 +- .../listeners/DefineTaTeamsListener.java | 2 +- .../DelegateStudentTeamsListener.java | 2 +- .../listeners/DeleteReposListener.java | 2 +- .../control/listeners/ExecutableListener.java | 2 +- .../GenerateAssignmentsRepoListener.java | 2 +- .../listeners/ReadStudentsFileListener.java | 2 +- .../control/listeners/ViewChangeListener.java | 68 ++++++++ .../create_assignments_view/NextListener.java | 59 +++++++ .../CreateAssignmentsRepoListener.java | 27 +++ .../nl/andrewlalis/ui/view/AbstractView.java | 72 ++++++++ .../ui/view/CreateAssignmentsView.java | 45 +++++ .../andrewlalis/ui/view/InitializerApp.java | 7 +- .../ui/view/InputStudentsFileView.java | 33 ++++ .../nl/andrewlalis/ui/view/StartView.java | 63 +++++++ src/main/resources/sql/insert/types.sql | 19 -- src/main/resources/sql/table_init.sql | 153 ---------------- 36 files changed, 527 insertions(+), 270 deletions(-) create mode 100644 design/SystemDiagram.odg create mode 100644 design/SystemDiagram.pdf rename src/main/java/nl/andrewlalis/{ui/control => }/command/CommandExecutor.java (96%) rename src/main/java/nl/andrewlalis/{ui/control => }/command/Executable.java (90%) rename src/main/java/nl/andrewlalis/{ui/control => }/command/executables/ArchiveRepos.java (90%) rename src/main/java/nl/andrewlalis/{ui/control => }/command/executables/DefineTaTeams.java (94%) rename src/main/java/nl/andrewlalis/{ui/control => }/command/executables/DelegateStudentTeams.java (98%) rename src/main/java/nl/andrewlalis/{ui/control => }/command/executables/DeleteRepos.java (87%) rename src/main/java/nl/andrewlalis/{ui/control => }/command/executables/ExecutableContext.java (88%) rename src/main/java/nl/andrewlalis/{ui/control => }/command/executables/GenerateAssignmentsRepo.java (93%) rename src/main/java/nl/andrewlalis/{ui/control => }/command/executables/GithubExecutable.java (92%) rename src/main/java/nl/andrewlalis/{ui/control => }/command/executables/ListErrors.java (91%) rename src/main/java/nl/andrewlalis/{ui/control => }/command/executables/ListRepos.java (96%) rename src/main/java/nl/andrewlalis/{ui/control => }/command/executables/ReadStudentsFile.java (91%) rename src/main/java/nl/andrewlalis/{ui/control => }/command/executables/SetupStudentRepos.java (95%) delete mode 100644 src/main/java/nl/andrewlalis/model/DatabaseObject.java create mode 100644 src/main/java/nl/andrewlalis/ui/control/listeners/ViewChangeListener.java create mode 100644 src/main/java/nl/andrewlalis/ui/control/listeners/create_assignments_view/NextListener.java create mode 100644 src/main/java/nl/andrewlalis/ui/control/listeners/start_view/CreateAssignmentsRepoListener.java create mode 100644 src/main/java/nl/andrewlalis/ui/view/AbstractView.java create mode 100644 src/main/java/nl/andrewlalis/ui/view/CreateAssignmentsView.java create mode 100644 src/main/java/nl/andrewlalis/ui/view/InputStudentsFileView.java create mode 100644 src/main/java/nl/andrewlalis/ui/view/StartView.java delete mode 100644 src/main/resources/sql/insert/types.sql delete mode 100644 src/main/resources/sql/table_init.sql diff --git a/design/SystemDiagram.odg b/design/SystemDiagram.odg new file mode 100644 index 0000000000000000000000000000000000000000..f857babc6ea08fd8c66288e2572874850fb78319 GIT binary patch literal 15806 zcmb801$-MzlJI4=V`j(9%*-(}Gcz+YvmG-tW6T({V`gTim?36{&)K_o@9o~({oPli zky=t$O;?YmRNen+IY|&u6aWAc05Ii~mCga7?V|z!0KfN-j{w%@*2YfmcEWEwsyugMz)5|*2XqYw5AUFc4p>=j&lEG5C*YWXafTP_`QET z07_=g)&@5E=2nh$PJa|>?QBfLN6s6CvYLW@Q)W=Fp+zHUUT?0n|`2L;(P0e1Pd^ zfGak@f)Zds4{#v{crp^H189r^)Q$i~4*(^f2AiNXi>NxUoEDX!5v!Cjv!oNJf*Gf> ziyAAyloKG1qoPTyDal~sKqBix>#QgX(9i=oN&$@I80>5Tii(P2BIe>^?t-co0-7#z z63(g`CMpWPvijCy1|I4LW(uZmMp|mNCK@)@mYf=a-1?Ec#z}5WFlZ;vWfgU zL6VAT3bOtxN)f7R398yW*P5t|{U+`BKg$(r&eCdIcJWjmn-y za^CfZnh|D(X;$vR`re85rYX)oVKx?J*3OeQUfY0R96%@`Acz@|LIOzU0F+PwN;m=E zX#j2XETI6-WB_+Q0FcfP_$Cb5EDC5=M61+Bs!_ozHzF>z1@zDWMwkFoa)2d0z>yZ< z)mSJUAY29z{|=CA0C@QWG+T|uhXB$u0EHfa+6qAH5TJ8}rgIB0egm+iePyjp<1Wn? zPS27_`X-xQujyZ=7TBPY-mFuYVib^R9A0J^`Q0k6+B&JhC9A|WrP(VlMLV)bCw9mv zs^2zx&MaxrH(=F1d%`Vf-!pG4ipe97!#zq75N`!2QU^3x1N>aA$J_vM9MNC7OG>#T zQuu2F+#757Q))$OGu##$q()m6_X;c*J1lp5gj0qjYo`<^XVeZCjn9tNrw_D`-;Cd4 zVgc#JfRYqIXCzN;9Uvjx*RXp!f=h6FtM5_`H~KWyf)M3VL;~u zPu+mjLLi{874Vh{c+W+7`$p4W;{4Vu``)ELF$LJ%0qiURUS9$4eO8GH@ll!8p=D8i zg$WtCMY#!K)!)+V>gvLS=TlN%1Bxah%BSP2XEMGo7Iv&u_N=#zZqL^Fk5(Q`4R^lJ z#J{hXzwb1Uj*gBmAFk}4znx4U9v*HTUY=as-JEW}-5tNZy$xu-i~s zS@3GerrCo>&XS&Fi-q%dh=IL@o}Qr|a!ks*UF0sF^RS?}--Rh;YyD$^mF=s64VF#3 zSF(A|aMZ#%(Cx615SjIcx7WdmiLLIk&(3ExH3F(gg*+{V1N>wg!TXecWr+j(OTEdy z-EOn}YG=AO@ssU*?C5ge9Oj~9h{>Ne*e^Z!%jR1;mkcSnA-5?o?SWDmm8#@Bigk=g z-D4Hxa4bbiEz>IpBRCta97wa&OH3bt&M(9Op63R|l;svj-Yqvrb}VmOD(d|hRl$5& zOXk3i@D;g@lqNZ8Kx`=tM^lR?+?Z(_25!utWU!7y4OS_tF0Nq-267}QH_}bS+O2WG zLQNA$=k%KfI~i=1KZhurL27CWq&`BQ;h-tw?Dnn{4C=KWWN&V!8>o7uk=4)x$DYQs zP{${kB{cZ@oS+k=rK2%JY;a2=IkfG*I@TZ5Hq=<#JxFjaD2WyR-L!ob{#ylDOil7t zw`TY(p&b@fwZRk>Q3d=l1O~O%Sknx|D6E)H@K9K{QkBy6DlsNe%bn@NOklRFqaA zxOwU$vX@I8&QurC<~Q1ENQxWYL8&T#yQv*~i6)q#e{lZM1d$?!Ip-oKF0L-F&Y^bk z(~Lx@&Ki66w6X80VD^MAKo?71Pbg#+DpIVEv6uc?9OKwXMtH6RPFO_aw{ zc3SaUyDl!{Db8*80q@g8LEwTD!fAlg%i2T?ugCijQK4b}_XfDS(01LH>)xH6k*r^+ zs-4$fd;SMhK>I!L8|rE;jt5*x(C8|(FtCl`FFofGPmenXl3}-FioxeQ1>Y}0h%C%p2xcWTG*c&tCzBOZ% zzQ6hJ-1BdD8#q{$)lKYCVR3(ST1l%cKMQ{Rr}k6}SCHfNgFnK*(TFqqEO zCc}OzmAG=2jc&lU`~|F~ES!NWr7Qu47YfxIxeMP|KrEsxyWYw>_es2?N+wxZ0@|w) ze@bt8GvE2|)|)HNPk)UhjDzIMUij8sr7s>cv@}b_S*iYQk$ULE_1 za{5>Zgs@U9;Z);L1hEs@6piap=JfI3lt`uZ=3Hq*4 zglB0Q3q~kzqjAC8`_=n(+}rdytR1Qp2D`Mn<^5DWHcTMDkHA1wr`^U1&Hx5#v~hZ} z97b6KLcp8BN<+~u393U^(OwwuvJB_yi!PW^7iq)WE?yU8A2Go(n}VUjDkB>&vD#Do zFVVaOg^|t_D75TErDKFhhJs?W5Z8v}VKNPe>VZQ2drLeKIq{Oij;oW)>FL_(+8<|= zfr@PfO~DTxch1HBDQBkYt|HbaONVY|1Fae3hWq^2du@o^0=_dakfyZoxICbAT2V?C z0dY<+JT89aH9 zw&$r*Xho53S?XO6c-o!VnCH}ZG`_B=rbyz5IXm}tj$U6JXr1eRTug~|qimN>6G`Rz zxh5s6*txdI+1I&qR@nRSI$eVt!A+FLIlt41E|I|q+&S~YT3-j(yA!_}@*;4( z?h!b4k`8Gshu?ch-SQZuX;EWv`lt_C?E1>Ap^mXLmUJvb6n!9RX8+QZ+or*_6=}7s z41P&Radt%FQ2>BThO#qfJ8RbC{vx130gK{xf$-bL95AQ095MA6GH?z+M~9O7Cp4&@ zD*MUjGQrtPM(#P!*Kh*Qtkxdc)%=s=z|;(O&A4zzKHA0i0%1aAa z%z1sHkXN*WbzRA!2xiK{F z!B@#@=AVNx$J4>%@v;q6DPOJ(!X_^os|$f5C>5uG1rqMHeI4spO1B;(QdxG679?^K ztMFdkZTif^aod3~fN&aAy`BQ@`Q3D1%T@_K9$LIB?#iYcKDD)b^fa{iHc3x0CVI)* z$;l~5EiCKvXAuQf1Fq%tA0E&0HJnTvQIYja3uR-h3$u(&j^^8WE_M8yGBF4ij!Fum zMGO1F6!Ky`pRbjeJ;X-Xrp3Iy8GA~LO3_L0_KBUI_GkNZ6<6_wP9+VLvIasVk7t?T*~ zFGZ5Xhv}gQ2k0t;b({T^U$i5DLe4?Mo3xixiXSKqbkk&jX-9WMg$O7Y$G!g4wtz4a zY3zt$Wx^e?M=he`zXWr07(?#`UQ^+7MZiOUNhH~? z*!__Z>GV)ONjUcz8c$U@a|-BlZt??E>N}ZEI;b&Z+Yv2keT^(k37W`X4brp{A&}xj za_)cFR%Vrc4HC$kBD$r^Y-GI$Ic5mp#4R((jm(VVW<4-7K;wrcGg4t7UDMZ>H49_-Ca01IG`k}R9Z&SEY-d;JU_DX!FSBxVTLg_YA0QI z!9@xcUf*Cam&IHRHX)%t?!p?{_#+}bF#G6WYs-ls&pf&_1M$Sr<$5lxaEFWXTGj*j zXBcaxB1e;yBz1u52<=QvNEIeU7+gr%B;)({kSaw_n{pWX)GaZ^*sTzAv+1@b(Wee( zJ93*+?Yla04wcB>ENB4YYJCv6gdbz@u!5AC$Kb-bXkGU7UuxBixcsJ|DGVc}rW$!) z37(&eUp$OnG)5ZvHDS_zbZ|lk6076C=(7N7KM6B2*e9uJDZ=jHcv_PBK3>_4WF3+%0Cb$g)S!R%5p#j;0L5+e& zgbfe3ll-&^@9XezL6(CJ%uyo??;j}JU0d&e&C3I$!yA<2_^_E-#b^S38BBD6jj=Hz zGP5j(h8~2RItucPq1q!P7t}9lcpXcP8-eZ-yW)K^9K=Rn*e5Z@Pe_smDPqxOYrrFZ zY&UaLv;)U){rRrd+Ay#4mY5_5ZQwEJ$gk& zxLoholy~Sp6UFys+~Ll)Jh5`P99m_5}F;J z7UmZr8qLL|cr)=8Y&XY^m<1ZEv*q)pusj-H8sU&wIL470RSM@Lz?mgIoiICEMeCXC zN{4xfm(7&!59bMAXR>wThDy)p-9^3WD&{!7JWK+8_zGq}GpA>VUB}V|VhIX-YwVi4 z&yVL+(s+Nnp8WDYd*FLDORS$?tam<4?*>`4i3+M~S@&Mg$R#c|VnX`jS*)2?a5qBD z3pW7O8tP{1>QxvGS;?>IPKa5?kL*i$%pG*ty78G=RwG~1GGk|-Yr&moJRu=LUXaH) zVIOLW#wjyrZrichGi@bQFOU(sf`@Y_C-Cz-Xw}{QW)#vfg-iV--t#k_2rA3@GXsNn z3Z5|5Ez$LEl&heuHPqa|qW7227j&0#Pe7P8;=u2yjcA*tWaK&RSkA8jqhPn{mUw>A zGy{z6sThJJ4=EZCz8J)J{X*Bu)Z)DkKG6he54B95-o3G-ML@UmClH-OZa1Qh)T9~X z+I{_w1L;DnbPWE_#F^l=j63=p1~9?Vz2)p$;FQm>fIR> z2Z7WQV2F?LvDV1{G~*nr*x4_n12^RkubtHwLdxmx4k%?|sM>9YAo7K`w6pJE{jTwh zEZB+bWUFf!3XkVd*Bg3=+w@Cab#T#S715$mq$6+R`xiN`Ao=6P)R;L`=uZ+qOmv0a zIeePQzW7ewd?jgf@%M5Cv#+V{iCf89=)H{z*d~CR+iRl)Je-@lpmTTR0@Ej-E-TB& z>eg7g0MM8ymh|3ysU31JfAM|dWtdMJ=QsEKvY3p>B>vg|9 z%I-p-+D`g5ZKVaQPwwRWSbgS}k6C8yG6=Jsn>^o0(2eUVFVyqtpZ&ohA#pdkTCc|z zizk-R<@rXN>+u+%y$5}@xgou?Xb5LnV;ELof%;78*Q>IXY)dn=nG=(O&?(hkx0yWD z;A8%52`k8Ed%hIs$;8cIl+)PUZs6N;fMHPL!@$ONUqWZ{ZuKa+Ty6X1^BhLNe4gm> zkDZ+DFRG?7UUXSj4_1SBr#PdktnIBcDlW*d5-^X|?{IT6D~&oW-i~*g%Q>%3l6TQ( z)vMZtwB!sw1zdCQawZsl?mIkZmu*a9I9Q(@pBVK~*;z1?&k2%&L=#X1E>>6S+?z;@ zL?kj%p;dbBj(rRU7OHi=^ZIL44u1Tnan0%-{m4|gZMqOt`ozV6rRoltR`nXd>H1YTAoSZsD#$10aSk@$LDm9$<)graM9d=A81Ey5(k&CqzWCcXXdbw(>czWKUS6|`H}OFI%Mx_e2zuA=+kShX7uliTP7a*jZWY-P!h^VCO)N`>W< zLQSH|s7(b2({5FGdD3CjvXS#D=n|#env(ztu$!cEKFM!_MaMn8_{~*;rI2VZ)LsVx z5b%#KrQ1OyX1G`IWo~`FSmGrpG&FThcQ(dfD^QgN&U0S&wG=`L%PRNCMx?h|v*+Sg zDy>q6R}_uwqPrBEJhusA0`>{4A95ff(c}4Qg|{w3gPANr5q>M**T1U7FuEJj5VktBWcn7iJcqG~66ouDmk7X%z*V{X)*H`q`rF)x2yW zzp?SQkyctuHh7^=)nvgO>{PI?g0$b1`cmHVtMfTE{GIg=*8RKtn4l+5rxELDkH2ok zuU~X;&kr56*u_4$1wdR_MyN_aFW|qJ2qYxrKOoERZ~l;fKo(;seOfnbt0+Zn+YEXX z@6BpH9o=qLVo>T_1-!dC^Mqsx@2_EJCOX|BO%%&rISd3O*35g;_t}n~y^;=0?BfX3`5kCre^Atu6Q$NC*}yL3z-Ge~Y3Q zTA*R;Q^@C}HT|TXAC8_*I4Cl-i1gCbM1%m>ivTNLcmuS7yK4?1<2WP{=qK}rwIUOsdvJ!8^nk6~Yn5JlM`9>@Qe5-O0vU2Mtd%75PyfT7_ zweZ_n=6+{OsyU6Yr)3)A^YAS{(}I>}#C0Yg+wIE2cE(n(s+~Rf`!CinuAZEM{oDPy z>D5+RqRJ0Na^oG}r=YGVMcAPkK3#fzb%?JRvcT!*n6PgRUj%FKA;%!WE5krcL}&#GFk^Ytmb;8Rx4YGbXaK9wEKvJp2za^OVTJ* z<_r5qJgVllqaqsqOkg$C3xkV`71QcBAUR10$U+)rv`!!ZfD#k{_-79G$NB#K`kR9Z z+S-_yn>suE1F1PO(mC1MS{dj&(EWQY?7z{nbFejaFm`nO&zgUCybomO{Qs|IVr8rE zWc;66iTO9J{Oy~azKyZffAH?_^c?7wS{ovod--S2+;4{qRnSpC;iYi_M?YV1fSVD4nC zZ|C^m%75p}Z}{oxdt8U;q!+lmZ?QM%9vO|w?Uu!ENSbd%(da+GV->R)EN)PIq1TG(Yxo#T8Q*j|%lEi#YD3SPYtT0ByHxVeT}l4tr6E z4UZVhFlx{$x1)fKS5D3B=^Pi0g=Je+G@G~-p?b?12FHCTl2qAMT^Or!A{=(9puYkx zu5zOs`1B=lO*Rr!UH)DeR zPDK}FrX&o6e5$cd4s+oDcxq=GQ)f$}sKj}CDSwxG(af)92z%C91UHU-q`z%gUJaFOr&KTB}%+w7INFPXwm@^L7p1tNM?a@H0^k` zQ`ZMBc7Hb2&LxEFOAc=e3Z3`}LT@rLy$~eKJjs5&zr_r%KMzkj zY1r56Akn|o6MdZ`5h05r zLr=REeYbA)wjV#nQ(!d0ZcA=kAdD%PSZL4^T;?U8x~fL&^|D5(j39nvkX6JCb^TSA zl4K-6FX{&pB@ESee~4PcSIQXWB0-r+*tYIE;MtD6UYg`5DsiYaCw^ZvD#>mtV6BQ* zDPnq_luixjrr15G{%eGCG#+Tm{PK*%i+EPO?jzvbi2ObtT-I`Z(Kfh>N{QoMmM@5! zx^y~3Wk$kW60mzjd>XTocb^ZZ+vPkW6TGzJSYW2L;QQ=)$e}dfPS(5Id9}dBI&nQ^9_6YpvQ>VCAryxny9nviC6*5jmQ0KRn8<}!|6Vxadze2>a_$kn za957-9*BOp4=^=mYdL?69UaIE*$P?KRnAmoVGzO&1e=dVediXE#L} zJZO*_aL7qOl}qpN0RaaUJQ^g0J6UzD33P3l5>VVdsD{gX{EXP@kp!uo4V!mp@J5N~;m8_fQjL`J{h;?CY5%Ca zo`Bi6$lb$UBuK|JJ3GWQ@bKzBsT9XSyB16^cyW3FxB_;50uz>1Sx8%8F0d)!Fgl&G zh-(3b{I3y+?fQ5f9BJK$d$h4NeRe41clQ&Zx#|-s$}%a{Hb0`t z2N}l$sg#`C)=+FqWw*o8KWqw!ZVVG;P!Gh~=TE?j)-S+H2CBz;s@VpiUYRvhu?n+a z4orpoNyp_w)aj-#~YTbvl+`hSucf*^# zLf)3D19vkb@LFAhzR)lh<21d2`zt>UfNPaME+z8?RXV{b8+n4We7!#2JjExua}iw) z?f}6BK4jA650)kFXHwj93`G(b}&DkzZREk9g8l4$aPjDsa`!w6cZaOiEvOIBWd4Gu4_F}Zuk83 ze(kkA5p}wT{8Ah2BvH#;oC|G*`?Nek`+CB964$fxBs~`sd=JT1?fGlJ8H^+YIY54yi2Q zcu4_x+og^GQk8Mk(^Y-GvMAxmdmV%Y+qTA*{M*R|PitB;cBT!m-QFC z-#M5^$|?EcM<(`}^^Y9v@6sXYk95e;*2d|-)3-CN)%cHm$a_+?rXi_4ju7xM_5Pi8 z!+D;xslIV{U1BdvkRNH0xSOzSdgx2s;EQf2G3*7Q9I>!MGB=mRfHEecnP1dw)Oq0O z!~Azy`BLr~W@t4pHo+eBFq!YIsF_j zD!I;?G!ZLp%dAo8W1|69h@Ob!Xf0iOP*LS7(CQup8_J1BkG$CHT9Biaj!%g()?>z4 z)*YdjwrD*LC&tHT1FZXXfNyJF{R6B%BLdF-fLrp*t1YpwCG=rm*2h`hv#Iy5*KJD9 z!Ukq;v0JLR&>I_jd}vavX63|aUO8YXeAQV+G1FD+5XU=epX}Aui`=kyrkt3Wb_DXix|3Tw8^8y`vt#k%h8Z#4n>LzV?OU z+%r#?Xd<6>iVtp_U!W*vZSO?9&wt1RbIV;`8`^Yqqw4+gZM~x_FU}PKE+lv!f5L%| z7e7ImpMB*M{_1Xn4tAsq80~60CxvT>PT#n@1ak6zqrb{-0-zmi5ySe5{1jfly$MOX zWYoBXyi{->VFEw3GzMtho5|9sWH$N;m!lZ+R!}-9HWONgmG3EYbZ#3|>o&G|#yLw< z)e4IanhOOmQxa4RRDC5dZ8d$!-=s1$aacDtr~n^Dj5<{l@llzxO$ zGJ}IIDto|rkc>jJD%z9#lbTO*T>B^OxDv#qj%s^xnBj6etr}35 zylS@WJjreE>1{dTm-&8)%dy+ej1Fl0Whl2KPr49tAn`(^;IfvkjMrgFwYvkNmVCQn z=GOhIXhQdlgtdg@dEN@L__$8(7+Y{T~ThtYkwyhxDS#W7}cmGBDy>vv~+UkC! zDV#3q>s-CC7wbTil-z5b0}KhB3bHK?|Ex``jHdKrS&9{K7o|*lBAs+!pa$~}hx1X< zsQSF^vXC@>?i4e;S+O11$6apep2s0Y-KGOXvB$yPn85wEtJLr+&&zRJM7HO6eGbWq zgxGM-S#?>hdEFi$-R672pNS(-HHG-KQdTeplJVyE+VqCqaqF0O2+0qIB+W7dz;zMR zl@T0V5E-|wH!HS87h`JKu^PzzjAY1mLYPOHSDV$-4Krr-h;-pu7dvp4BC#^UiuYFv zqaq6k45S4IUNqo}PC8|Wt~nE{mQ_Sw7+J6q%xl!&;dKES`Yp41^(DOnxIP6wozPdX ztNYmL{4a!buR@BbXm+mkzZ5VwPMg~?{j0*ol?r^=zN$-!8nEjxP5k0~$vi@4Jx`So zB3kGx`}tPYoraqMJAMQ+?nX&3_O2T-Uc)z_%`nh-7G9&B;QS6}$i#R`8*P*jUi(=) zA^yvx5c65pd_)-mw|!&4#MXKO61pzAsrv};w4lulPF0cCy!gau19-8RBSmdEfsAZe z$efLX#7{A@jd;BNGCjHtygJH#Ic4q|?gOlKZD#^)sPvHaf`|q}JGXDDx(QEg`Q0G< z@7Zja2;&Z%`9MZ{WpMBF3t;6*#+53Q&{zx9SdIGiELKv|yt>x-QQhKvZPME$XajM=qH>i}u=P8& zr3#Nc-`kdb-(IUZO~tdsJR(@RHA6N)=H{S(UU?OnQHso9Ywb2a zkR~+JLLfLGMP#H4L0E{)5E^R55co-YSxXl>88u*jU;z1<4YOn&n$Ri3K0RRrn%}6i zf{#WyMay%;ld7)5O>fluRZhp(>s5jp@IW$TKp|)6_!c?xO5uJlwN^kao5iU2vU69c zvHnfleU&~ZN&9IP=0>hQDrL$=bVtRwLwsQ=@44(7R0q$yChkLjle>L@OY^tG=G)qp zZ5<+tbFj-EvTTny{%;37WK_iyCHzOa;zwZPs$Kg{qd=ecc%eG13f7(}itNK(72u)= zu--(GC7q?!g|40GHo9%!--Ej|oP|~C>u#>f8&k_AWG4-^#oYbY^-yh+mq~eYhdN0BEYtS*AqO%QKLkganI0JIh)~KZCvxvJZi% zDf)ce&+Hm8*<>za3+FlWm8S*f4$^!m9p=rGZeM>ZIG}UiH>DOb*|lCzT{!mGyihxm z4pKbY1o32vHaUdBT!^?sA054CDObI7v`!D_YgdAjPchWKW&S<%HoM&R00zy_#uWcZ8Q{~B$AzaYQ#oiC|lyy zkQ|#{O}GxpajYuIUV5>*?9UI9x6un^rRGtPE~?pf!Ab~a;rk6zp4ro(r#3|GZEbV~{GrH(kvW`^J zr+k<(#Yjo*b%}$jpAIV=Q0qC3{yN({{Y>9&$GmD`?@g{*|RjM>v`^fbu6yg!oh ztef2EK>{4nl?Stcc5b5o}q@K({6kYe_hff@n2- zs9@?Zx2*=6YCsD8;%o;h>+P$Iw0*7e@7a1pnHf=b8-Y#jnwA!5HkUSnjy3HfY|KV% z?U`oDHc@L0J8eWG)!m~SEO5{4$D0(~Ik;(<53`7lI2xjaY8za|r|*sKoNF!JY(+)H zP1+X*sbb&sSNhq&KC^b?=)fjLml97-(igE4fC0%6)rL5`BFTyR_oSkcOO?r?E$jh+ zOAW?ucgo3o<)v(Z4UYTKqdg!tY>+v&}pi+kIBefw?N-6I9EKP;6 zv)p>>e(ZoLR4|PUg5x16^l!CZ>o*1zm|`f3u>1mYusF>0z_r;6OW5mnMx$htnc;)Q zY_pse4{u()U$Pft6tJPcag_UXQL;8qF@!0*)=QUQg;O>^&mS&D!}!~JA9cwXUAX3=$5kA7A@zG4>_ zqhomnZ^>qsRUKLXBCn4ra6%+nKB!_i%aj;J27ZnH>oR`-mHhxqaRFcrWfTEen1b?lr?QC*{|dbLu?sZIC+?B-1Ydf2Ii`*#tOr^I`y5eMhzt@cS`|yL=N~p zmeP=_u@GHU@>Z+OkT=I%CpKOYF;#Zs&xa2N|Di5hg>r}-&G0FODby+H%7 zC++JWQG+mtmS*^3ecwrLq#O4)Yi{-AdFW7aG0KE5t2`|{130-qYPorP$zQ+8qn)aflW8zdfxCRw(y=**aRe z&U2LpZkBc9{n_!YUU0QyGW>0KVQw02hG=3D-tt(Y#s}%<~EW_H?l^cEwMved8&aP>dt& zb(zbjgCrdRpMfrw5gc7X#l3c~_yqB5BV$Iz_hLR$?1HE#%Il%U=kna!D#%+6@mV4x z|ErisgBC3!kG?0}dc>q_ zO77(IV*W~zwG{vwW2{7#f$jMm-y%L1r6VIdlINPXflhUeti>eJ-M_1_k{#WfznMHA z?F2(Kkf+5JrAF~&&GaBK)l;2T;Sf9JiH`!Nn^?1gg-@A$f$yS$Qf`EAICF+g&Dt&Q zA|FCr`!uXZ2`@#1x$= z#Tb1ZA-)(p=#th#jx6&WvFDe4@W~L8b7MkxV}%zdp6sFNMek0=iTxYn#05P)L|vxD zB>yiurFx(*LUVmI$e()=gAg~7dpA^~TO39dt5h`Z@D?Mv+^x#x+ksk-}K&DWm0HtAiI zbX=riOZg-osqan39QiktX~gmzV(|?(KsDE#Tzv7m2YdZT3*&$$tM6s!J{`4kDX;7s zCC^sYI4C%pq{%|(gK(S$7MLInQo(vrDoO)=P+UGh`;IcaLa5OqWTXpr*wM@0xs0q( zt;yV5H9zz-G5MtK%eAm$cSwf7lmnQ;k35nN%Lg}LICK=mHS-$4p^I+i53zSqM(PeW z+i|^$6YT}ImhE^{)l9R^8e-pgx&(y2nBH)$g9`1T^L| z<6}9aoFou13gF+bx%}wdzsMgeF#lESUl&&XsUrVT{CCSN|FH7MeA{1v^*_+)|CllU z{IUA;zi&+VL+g)KqW@FrqwpIp|0(KN|C@jQiMIdQ??1HwfZuTYPl@>(EB}nT|GlMu zZqM-FSo#xv|6i@V|BaPDQTV^N@|z<6DJ&mO{wo{)D;od5+Ohr{JAXyx|7GWoGyMDb zfAH?#u>4Oc`Wrj{2A%(>AHY95`SLeL{|e6ky_3Ju`Jcj!^S?Xme}VLW_Vll_%D++i zpJM-E>`$=%hckcl&|lNO-*e7?iXZ7e%|QRF;$MU1@2v1oL1+5s9PwW@{~8W{2h%?V bll|X>Ryj#9@ZZ%CJ~q~m2<^b}``!No1~71c literal 0 HcmV?d00001 diff --git a/design/SystemDiagram.pdf b/design/SystemDiagram.pdf new file mode 100644 index 0000000000000000000000000000000000000000..f2b342c2718af0fc76488e282bf2227a34535a8b GIT binary patch literal 18820 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 QhZjOnS7Y&bw$qIL0oIVS listReposWithPrefix(String substring) { List repos = new ArrayList<>(); try { - List allRepos = this.organization.listRepositories().asList(); + List allRepos = this.getOrganization().listRepositories().asList(); for (GHRepository repo : allRepos) { if (repo.getName().contains(substring)) { repos.add(repo); @@ -81,7 +150,7 @@ public class GithubManager { public GHRepository getRepository(String name) { System.out.println(name); try { - return this.organization.getRepository(name); + return this.getOrganization().getRepository(name); } catch (IOException e) { logger.severe("No repository with name: " + name + " exists."); e.printStackTrace(); @@ -97,7 +166,7 @@ public class GithubManager { List teams = new ArrayList<>(); try { Random rand = new Random(); - for (Map.Entry entry : this.organization.getTeams().entrySet()) { + for (Map.Entry entry : this.getOrganization().getTeams().entrySet()) { TATeam team = new TATeam(entry.getKey(), entry.getValue().getId()); team.setGithubTeam(entry.getValue()); for (GHUser user : entry.getValue().listMembers().asList()) { @@ -119,7 +188,7 @@ public class GithubManager { public List getMembers() { List teachingAssistants = new ArrayList<>(); try { - for (GHUser member : this.organization.listMembers().asList()) { + for (GHUser member : this.getOrganization().listMembers().asList()) { teachingAssistants.add(new TeachingAssistant(-1, member.getName(), member.getEmail(), member.getLogin())); } } catch (IOException e) { @@ -137,9 +206,9 @@ public class GithubManager { * @throws IOException If an HTTP request failed. */ public void setupAssignmentsRepo(String assignmentsRepoName, String description, String allTeachingAssistants) throws IOException { - GHTeam team = this.organization.getTeamByName(allTeachingAssistants); + GHTeam team = this.getOrganization().getTeamByName(allTeachingAssistants); // Check if the repository already exists. - GHRepository existingRepo = this.organization.getRepository(assignmentsRepoName); + GHRepository existingRepo = this.getOrganization().getRepository(assignmentsRepoName); if (existingRepo != null) { existingRepo.delete(); logger.fine("Deleted pre-existing assignments repository."); @@ -174,9 +243,10 @@ public class GithubManager { return; } - GHRepository repo = this.createRepository(team.generateUniqueName(prefix), taTeam.getGithubTeam(), team.generateRepoDescription(), false, true, true); - - if (repo == null) { + GHRepository repo; + try { + repo = this.createRepository(team.generateUniqueName(prefix), taTeam.getGithubTeam(), team.generateRepoDescription(), false, true, true); + } catch (IOException e) { logger.severe("Repository for student team " + team.getNumber() + " could not be created."); return; } @@ -195,17 +265,22 @@ public class GithubManager { * @param substring The substring which repository names should contain to be deleted. */ public void deleteAllRepositories(String substring) { - List repositories = this.organization.listRepositories().asList(); - for (GHRepository repo : repositories) { - if (repo.getName().contains(substring)) { - try { - repo.delete(); - logger.info("Deleted repository: " + repo.getName()); - } catch (IOException e) { - logger.severe("Could not delete repository: " + repo.getName()); - e.printStackTrace(); + try { + List repositories = this.getOrganization().listRepositories().asList(); + for (GHRepository repo : repositories) { + if (repo.getName().contains(substring)) { + try { + repo.delete(); + logger.info("Deleted repository: " + repo.getName()); + } catch (IOException e) { + logger.severe("Could not delete repository: " + repo.getName()); + e.printStackTrace(); + } } } + } catch (IOException e) { + logger.severe("Could not get Organization for listing repositories."); + e.printStackTrace(); } } @@ -214,11 +289,16 @@ public class GithubManager { * @param sub Any repository containing this substring will be archived. */ public void archiveAllRepositories(String sub) { - List repositories = this.organization.listRepositories().asList(); - for (GHRepository repo : repositories) { - if (repo.getName().contains(sub)) { - archiveRepository(repo); + try { + List repositories = this.getOrganization().listRepositories().asList(); + for (GHRepository repo : repositories) { + if (repo.getName().contains(sub)) { + archiveRepository(repo); + } } + } catch (IOException e) { + logger.severe("Could not get Organization for listing repositories."); + e.printStackTrace(); } } @@ -339,25 +419,20 @@ public class GithubManager { * @param hasWiki Whether the repo has a wiki enabled. * @param hasIssues Whether the repo has issues enabled. * @param isPrivate Whether or not the repository is private. - * @return The repository that was created, or null if it could not be created. + * @return The repository that was created. + * @throws IOException If an exception occurred while sending a request. */ - private GHRepository createRepository(String name, GHTeam taTeam, String description, boolean hasWiki, boolean hasIssues, boolean isPrivate){ - try { - GHCreateRepositoryBuilder builder = this.organization.createRepository(name); - builder.team(taTeam); - builder.wiki(hasWiki); - builder.issues(hasIssues); - builder.description(description); - builder.gitignoreTemplate("Java"); - builder.private_(isPrivate); - GHRepository repo = builder.create(); - logger.fine("Created repository: " + repo.getName()); - return repo; - } catch (IOException e) { - logger.severe("Could not create repository: " + name); - e.printStackTrace(); - return null; - } + private GHRepository createRepository(String name, GHTeam taTeam, String description, boolean hasWiki, boolean hasIssues, boolean isPrivate) throws IOException { + GHCreateRepositoryBuilder builder = this.getOrganization().createRepository(name); + builder.team(taTeam); + builder.wiki(hasWiki); + builder.issues(hasIssues); + builder.description(description); + builder.gitignoreTemplate("Java"); + builder.private_(isPrivate); + GHRepository repo = builder.create(); + logger.fine("Created repository: " + repo.getName()); + return repo; } } diff --git a/src/main/java/nl/andrewlalis/model/DatabaseObject.java b/src/main/java/nl/andrewlalis/model/DatabaseObject.java deleted file mode 100644 index 9928da2..0000000 --- a/src/main/java/nl/andrewlalis/model/DatabaseObject.java +++ /dev/null @@ -1,9 +0,0 @@ -package nl.andrewlalis.model; - -public abstract class DatabaseObject { - - public abstract DatabaseObject retrieve(); - - public abstract boolean store(); - -} diff --git a/src/main/java/nl/andrewlalis/ui/control/listeners/ArchiveAllListener.java b/src/main/java/nl/andrewlalis/ui/control/listeners/ArchiveAllListener.java index 98e4b86..065cc7b 100644 --- a/src/main/java/nl/andrewlalis/ui/control/listeners/ArchiveAllListener.java +++ b/src/main/java/nl/andrewlalis/ui/control/listeners/ArchiveAllListener.java @@ -1,6 +1,6 @@ package nl.andrewlalis.ui.control.listeners; -import nl.andrewlalis.ui.control.command.CommandExecutor; +import nl.andrewlalis.command.CommandExecutor; import nl.andrewlalis.ui.view.InitializerApp; import javax.swing.*; diff --git a/src/main/java/nl/andrewlalis/ui/control/listeners/CommandFieldKeyListener.java b/src/main/java/nl/andrewlalis/ui/control/listeners/CommandFieldKeyListener.java index d68ac1e..2e98919 100644 --- a/src/main/java/nl/andrewlalis/ui/control/listeners/CommandFieldKeyListener.java +++ b/src/main/java/nl/andrewlalis/ui/control/listeners/CommandFieldKeyListener.java @@ -1,6 +1,6 @@ package nl.andrewlalis.ui.control.listeners; -import nl.andrewlalis.ui.control.command.CommandExecutor; +import nl.andrewlalis.command.CommandExecutor; import javax.swing.*; import java.awt.event.KeyEvent; diff --git a/src/main/java/nl/andrewlalis/ui/control/listeners/DefineTaTeamsListener.java b/src/main/java/nl/andrewlalis/ui/control/listeners/DefineTaTeamsListener.java index fec0429..b4f12f3 100644 --- a/src/main/java/nl/andrewlalis/ui/control/listeners/DefineTaTeamsListener.java +++ b/src/main/java/nl/andrewlalis/ui/control/listeners/DefineTaTeamsListener.java @@ -1,6 +1,6 @@ package nl.andrewlalis.ui.control.listeners; -import nl.andrewlalis.ui.control.command.CommandExecutor; +import nl.andrewlalis.command.CommandExecutor; import nl.andrewlalis.ui.view.InitializerApp; import java.awt.event.ActionEvent; diff --git a/src/main/java/nl/andrewlalis/ui/control/listeners/DelegateStudentTeamsListener.java b/src/main/java/nl/andrewlalis/ui/control/listeners/DelegateStudentTeamsListener.java index b9969ea..c5b40d1 100644 --- a/src/main/java/nl/andrewlalis/ui/control/listeners/DelegateStudentTeamsListener.java +++ b/src/main/java/nl/andrewlalis/ui/control/listeners/DelegateStudentTeamsListener.java @@ -1,6 +1,6 @@ package nl.andrewlalis.ui.control.listeners; -import nl.andrewlalis.ui.control.command.CommandExecutor; +import nl.andrewlalis.command.CommandExecutor; import nl.andrewlalis.ui.view.InitializerApp; import java.awt.event.ActionEvent; diff --git a/src/main/java/nl/andrewlalis/ui/control/listeners/DeleteReposListener.java b/src/main/java/nl/andrewlalis/ui/control/listeners/DeleteReposListener.java index d615bde..5e37ea2 100644 --- a/src/main/java/nl/andrewlalis/ui/control/listeners/DeleteReposListener.java +++ b/src/main/java/nl/andrewlalis/ui/control/listeners/DeleteReposListener.java @@ -1,6 +1,6 @@ package nl.andrewlalis.ui.control.listeners; -import nl.andrewlalis.ui.control.command.CommandExecutor; +import nl.andrewlalis.command.CommandExecutor; import nl.andrewlalis.ui.view.InitializerApp; import javax.swing.*; diff --git a/src/main/java/nl/andrewlalis/ui/control/listeners/ExecutableListener.java b/src/main/java/nl/andrewlalis/ui/control/listeners/ExecutableListener.java index 33401c0..4c7f268 100644 --- a/src/main/java/nl/andrewlalis/ui/control/listeners/ExecutableListener.java +++ b/src/main/java/nl/andrewlalis/ui/control/listeners/ExecutableListener.java @@ -1,6 +1,6 @@ package nl.andrewlalis.ui.control.listeners; -import nl.andrewlalis.ui.control.command.CommandExecutor; +import nl.andrewlalis.command.CommandExecutor; import nl.andrewlalis.ui.view.InitializerApp; import java.awt.event.ActionListener; diff --git a/src/main/java/nl/andrewlalis/ui/control/listeners/GenerateAssignmentsRepoListener.java b/src/main/java/nl/andrewlalis/ui/control/listeners/GenerateAssignmentsRepoListener.java index ba74466..f194cb0 100644 --- a/src/main/java/nl/andrewlalis/ui/control/listeners/GenerateAssignmentsRepoListener.java +++ b/src/main/java/nl/andrewlalis/ui/control/listeners/GenerateAssignmentsRepoListener.java @@ -1,6 +1,6 @@ package nl.andrewlalis.ui.control.listeners; -import nl.andrewlalis.ui.control.command.CommandExecutor; +import nl.andrewlalis.command.CommandExecutor; import nl.andrewlalis.ui.view.InitializerApp; import javax.swing.*; diff --git a/src/main/java/nl/andrewlalis/ui/control/listeners/ReadStudentsFileListener.java b/src/main/java/nl/andrewlalis/ui/control/listeners/ReadStudentsFileListener.java index e65e7ba..c0535d2 100644 --- a/src/main/java/nl/andrewlalis/ui/control/listeners/ReadStudentsFileListener.java +++ b/src/main/java/nl/andrewlalis/ui/control/listeners/ReadStudentsFileListener.java @@ -1,6 +1,6 @@ package nl.andrewlalis.ui.control.listeners; -import nl.andrewlalis.ui.control.command.CommandExecutor; +import nl.andrewlalis.command.CommandExecutor; import nl.andrewlalis.ui.view.InitializerApp; import javax.swing.*; diff --git a/src/main/java/nl/andrewlalis/ui/control/listeners/ViewChangeListener.java b/src/main/java/nl/andrewlalis/ui/control/listeners/ViewChangeListener.java new file mode 100644 index 0000000..4d9a519 --- /dev/null +++ b/src/main/java/nl/andrewlalis/ui/control/listeners/ViewChangeListener.java @@ -0,0 +1,68 @@ +package nl.andrewlalis.ui.control.listeners; + +import nl.andrewlalis.ui.view.AbstractView; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; + +/** + * The ViewChangeListener is attached to buttons which should change the view to a new view. With this listener, one + * needs to simply give the previous view, and the next view, and + */ +public class ViewChangeListener implements ActionListener { + + protected AbstractView previousView; + protected AbstractView newView; + + public ViewChangeListener(AbstractView previousView, AbstractView newView) { + this.previousView = previousView; + this.newView = newView; + this.newView.addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent windowEvent) { + back(); + } + }); + } + + /** + * This method is called just before the view is changed. + * @return True if the change should happen, or false if some validation or check prevents the user from moving to + * the next view. + */ + protected boolean beforeChange() { + // Child classes can implement extra behavior here. + return true; + } + + /** + * Defines some default behavior for switching to a new view. + * @param actionEvent The event which triggered this action. + */ + @Override + public void actionPerformed(ActionEvent actionEvent) { + if (this.beforeChange()) { + this.forward(); + } + } + + /** + * Goes to the new view, and hides the previous view. + */ + private void forward() { + this.previousView.setVisible(false); + this.newView.reset(); + this.newView.setVisible(true); + } + + /** + * Goes 'back in time', or rather, hides the current view and moves back to the one which sent us here. + */ + private void back() { + this.previousView.reset(); + this.previousView.setVisible(true); + this.newView.setVisible(false); + } +} diff --git a/src/main/java/nl/andrewlalis/ui/control/listeners/create_assignments_view/NextListener.java b/src/main/java/nl/andrewlalis/ui/control/listeners/create_assignments_view/NextListener.java new file mode 100644 index 0000000..268cdc0 --- /dev/null +++ b/src/main/java/nl/andrewlalis/ui/control/listeners/create_assignments_view/NextListener.java @@ -0,0 +1,59 @@ +package nl.andrewlalis.ui.control.listeners.create_assignments_view; + +import nl.andrewlalis.git_api.GithubManager; +import nl.andrewlalis.ui.control.listeners.ViewChangeListener; +import nl.andrewlalis.ui.view.AbstractView; +import nl.andrewlalis.ui.view.CreateAssignmentsView; + +import javax.swing.*; +import java.io.IOException; + +/** + * Listens for when the user clicks 'next' in the CreateAssignmentsView. This listener is responsible for checking that + * the user enters a correct repository name, or if not, asks if the user wishes to create the repository with that + * name. + */ +public class NextListener extends ViewChangeListener { + + public NextListener(AbstractView previousView, AbstractView newView) { + super(previousView, newView); + } + + /** + * Validate that the repository the user has entered exists in the organization. + * @return True if the repository exists, or if the user creates a repository with that name, or false if an + * Assignments repository was not created. + */ + @Override + protected boolean beforeChange() { + CreateAssignmentsView assignmentsView = (CreateAssignmentsView) this.previousView; + String repoName = assignmentsView.getRepositoryName(); + GithubManager manager = assignmentsView.getGithubManager(); + if (manager.repoExists(repoName)) { + return true; + } else { + int reply = JOptionPane.showConfirmDialog( + assignmentsView, + "The repository you gave does not exist.\nWould you like to create it?", + "Create new repository?", + JOptionPane.YES_NO_OPTION); + if (reply == JOptionPane.YES_OPTION) { + try { + assignmentsView.getGithubManager().setupAssignmentsRepo(repoName, "", this.getTeachingAssistantsTeamName()); + return true; + } catch (IOException e) { + //e.printStackTrace(); + JOptionPane.showMessageDialog(assignmentsView, "Could not create repository:\n" + e.getMessage(), "Error", JOptionPane.ERROR_MESSAGE); + return false; + } + } else { + return false; + } + } + } + + private String getTeachingAssistantsTeamName() { + String name = JOptionPane.showInputDialog(this.previousView, "Please enter (exactly) the name of Github team\nthat contains all teaching assistants.", "Select TA Team", JOptionPane.QUESTION_MESSAGE); + return name; + } +} diff --git a/src/main/java/nl/andrewlalis/ui/control/listeners/start_view/CreateAssignmentsRepoListener.java b/src/main/java/nl/andrewlalis/ui/control/listeners/start_view/CreateAssignmentsRepoListener.java new file mode 100644 index 0000000..7a53c60 --- /dev/null +++ b/src/main/java/nl/andrewlalis/ui/control/listeners/start_view/CreateAssignmentsRepoListener.java @@ -0,0 +1,27 @@ +package nl.andrewlalis.ui.control.listeners.start_view; + +import nl.andrewlalis.ui.control.listeners.ViewChangeListener; +import nl.andrewlalis.ui.view.AbstractView; +import nl.andrewlalis.ui.view.StartView; + +/** + * Listener for when the user intends to create repositories for a new course. + */ +public class CreateAssignmentsRepoListener extends ViewChangeListener { + + public CreateAssignmentsRepoListener(AbstractView previousView, AbstractView newView) { + super(previousView, newView); + } + + /** + * All that needs to be done here is check that the github manager can work with the given info. + * @return True if the github manager accepts the organization name and access token, false otherwise. + */ + @Override + protected boolean beforeChange() { + StartView startView = (StartView) this.previousView; + startView.getGithubManager().setOrganizationName(startView.getOrganizationName()); + startView.getGithubManager().setAccessToken(startView.getAccessToken()); + return startView.getGithubManager().validate(); + } +} diff --git a/src/main/java/nl/andrewlalis/ui/view/AbstractView.java b/src/main/java/nl/andrewlalis/ui/view/AbstractView.java new file mode 100644 index 0000000..01e9cb7 --- /dev/null +++ b/src/main/java/nl/andrewlalis/ui/view/AbstractView.java @@ -0,0 +1,72 @@ +package nl.andrewlalis.ui.view; + +import nl.andrewlalis.git_api.GithubManager; + +import javax.swing.*; +import java.awt.*; + +/** + * All views in the application will extend from this view, as a means of simplifying and organizing how visual + * components are made. + */ +public abstract class AbstractView extends JFrame { + + /** + * A GithubManager object which can be used to interact with github. + */ + private GithubManager githubManager; + + /** + * Initializes the view by packing the content pane as it is defined by any child, and setting some generic swing + * values. + * @param title The window's title. + * @param startVisible Whether or not to start the view as visible. + * @param defaultCloseOperation What to do when the user closes the window. + * @param preferredSize The preferred size of the view. + */ + AbstractView(String title, boolean startVisible, int defaultCloseOperation, Dimension preferredSize, GithubManager githubManager) { + super(title); + this.githubManager = githubManager; + this.setContentPane(this.buildContentPane()); + this.setDefaultCloseOperation(defaultCloseOperation); + if (preferredSize != null) { + this.setSize(preferredSize); + } + this.setLocationRelativeTo(null); + this.pack(); + this.setVisible(startVisible); + } + + /** + * Constructs this view. Child classes will define how the content pane is constructed by returning that content + * pane here. + * @return The content pane containing the view to be rendered. + */ + protected abstract JPanel buildContentPane(); + + /** + * Resets this view and all form components within it. It is the responsibility of child classes to define how to + * reset themselves. + */ + public void reset() { + // Child classes can define custom behavior here. + } + + public GithubManager getGithubManager() { + return githubManager; + } + + /** + * Generates a text input field panel. + * @param labelText The text for the label above the panel. + * @param textField A reference to the text field that is used in the panel. + * @return A JPanel containing the label and text field. + */ + final JPanel generateTextFieldPanel(String labelText, JTextField textField) { + JPanel newPanel = new JPanel(new BorderLayout()); + newPanel.add(new JLabel(labelText), BorderLayout.NORTH); + newPanel.add(textField); + newPanel.setBorder(BorderFactory.createEmptyBorder(5, 2, 5, 2)); + return newPanel; + } +} diff --git a/src/main/java/nl/andrewlalis/ui/view/CreateAssignmentsView.java b/src/main/java/nl/andrewlalis/ui/view/CreateAssignmentsView.java new file mode 100644 index 0000000..7fb9473 --- /dev/null +++ b/src/main/java/nl/andrewlalis/ui/view/CreateAssignmentsView.java @@ -0,0 +1,45 @@ +package nl.andrewlalis.ui.view; + +import nl.andrewlalis.git_api.GithubManager; +import nl.andrewlalis.ui.control.listeners.create_assignments_view.NextListener; + +import javax.swing.*; +import java.awt.*; + +/** + * In this view, the user will enter the name of an assignments repository to use for the course, or allows the user to + * create a new one. + * + * Once the user is here, it is guaranteed that the github manager has been validated. + */ +public class CreateAssignmentsView extends AbstractView { + + private JTextField repositoryNameField; + + public CreateAssignmentsView(GithubManager manager) { + super("Create/Set Assignments Repository", + false, + DISPOSE_ON_CLOSE, + null, + manager); + } + + public String getRepositoryName() { + return this.repositoryNameField.getText(); + } + + @Override + protected JPanel buildContentPane() { + JPanel contentPane = new JPanel(); + + contentPane.setLayout(new BorderLayout()); + this.repositoryNameField = new JTextField(); + contentPane.add(this.generateTextFieldPanel("Repository name:", this.repositoryNameField), BorderLayout.CENTER); + + JButton nextButton = new JButton("Next"); + nextButton.addActionListener(new NextListener(this, new InputStudentsFileView(this.getGithubManager()))); + contentPane.add(nextButton, BorderLayout.SOUTH); + + return contentPane; + } +} diff --git a/src/main/java/nl/andrewlalis/ui/view/InitializerApp.java b/src/main/java/nl/andrewlalis/ui/view/InitializerApp.java index bf5b353..b21c65e 100644 --- a/src/main/java/nl/andrewlalis/ui/view/InitializerApp.java +++ b/src/main/java/nl/andrewlalis/ui/view/InitializerApp.java @@ -1,8 +1,8 @@ package nl.andrewlalis.ui.view; +import nl.andrewlalis.command.CommandExecutor; import nl.andrewlalis.model.Organization; import nl.andrewlalis.ui.control.OutputTextHandler; -import nl.andrewlalis.ui.control.command.CommandExecutor; import nl.andrewlalis.ui.control.listeners.*; import javax.swing.*; @@ -129,11 +129,6 @@ public class InitializerApp extends JFrame { commonActionsPanel.add(this.generateButtonPanel("Delegate Student Teams", new DelegateStudentTeamsListener(this.executor, this))); commonActionsPanel.add(this.generateButtonPanel("Generate Assignments Repo", new GenerateAssignmentsRepoListener(this.executor, this))); - // TODO: Enable this once the define teams dialog is complete. -// JButton defineTaTeamsButton = new JButton("Define TA Teams"); -// defineTaTeamsButton.addActionListener(new DefineTaTeamsListener(this.executor, this)); -// commonActionsPanel.add(f); - commonActionsPanel.add(this.generateButtonPanel("Delete Repos", new DeleteReposListener(this.executor, this))); // Extra panel to push buttons to the top. diff --git a/src/main/java/nl/andrewlalis/ui/view/InputStudentsFileView.java b/src/main/java/nl/andrewlalis/ui/view/InputStudentsFileView.java new file mode 100644 index 0000000..6c48e80 --- /dev/null +++ b/src/main/java/nl/andrewlalis/ui/view/InputStudentsFileView.java @@ -0,0 +1,33 @@ +package nl.andrewlalis.ui.view; + +import nl.andrewlalis.git_api.GithubManager; + +import javax.swing.*; +import java.awt.*; + +/** + * In this view, the user will select a file to read a list of students from, and generates the list of teams from that. + */ +public class InputStudentsFileView extends AbstractView { + + InputStudentsFileView(GithubManager manager) { + super("Input Students CSV", + false, + DISPOSE_ON_CLOSE, + null, + manager); + } + + @Override + protected JPanel buildContentPane() { + JPanel contentPane = new JPanel(new BorderLayout()); + + JButton selectFileButton = new JButton("Select File"); + contentPane.add(selectFileButton, BorderLayout.CENTER); + + JButton doneButton = new JButton("Done"); + contentPane.add(doneButton, BorderLayout.SOUTH); + + return contentPane; + } +} diff --git a/src/main/java/nl/andrewlalis/ui/view/StartView.java b/src/main/java/nl/andrewlalis/ui/view/StartView.java new file mode 100644 index 0000000..65fde1a --- /dev/null +++ b/src/main/java/nl/andrewlalis/ui/view/StartView.java @@ -0,0 +1,63 @@ +package nl.andrewlalis.ui.view; + +import nl.andrewlalis.git_api.GithubManager; +import nl.andrewlalis.ui.control.listeners.start_view.CreateAssignmentsRepoListener; + +import javax.swing.*; +import java.awt.*; + +/** + * At this view, the user is asked to first enter the name of the organization, and the access token they created for + * their authenticated Github account. + * + * Then, the user must choose whether they are starting a new course setup, or managing an existing one. + * + * If they choose to start a new course, they are taken to the AssignmentsRepoView, otherwise if they want to manage + * an existing course, they are taken to the ManagementView. + */ +public class StartView extends AbstractView { + + // Fields which hold information needed by the Github Manager. + private JTextField organizationNameField; + private JTextField accessTokenField; + + public StartView(GithubManager githubManager) { + super("Github Initializer Startup", + true, + DISPOSE_ON_CLOSE, + null, + githubManager); + } + + public String getOrganizationName() { + return this.organizationNameField.getText(); + } + + public String getAccessToken() { + return this.accessTokenField.getText(); + } + + @Override + protected JPanel buildContentPane() { + JPanel contentPane = new JPanel(new BorderLayout()); + + JPanel infoInputPanel = new JPanel(); + infoInputPanel.setLayout(new BoxLayout(infoInputPanel, BoxLayout.PAGE_AXIS)); + this.organizationNameField = new JTextField(); + infoInputPanel.add(this.generateTextFieldPanel("Organization name:", this.organizationNameField)); + this.accessTokenField = new JTextField(); + infoInputPanel.add(this.generateTextFieldPanel("Access token:", this.accessTokenField)); + + JPanel buttonsPanel = new JPanel(); + JButton assignmentsViewButton = new JButton("Start New Course"); + assignmentsViewButton.addActionListener(new CreateAssignmentsRepoListener(this, new CreateAssignmentsView(this.getGithubManager()))); + JButton managementViewButton = new JButton("Manage Existing Course"); + + buttonsPanel.add(assignmentsViewButton); + buttonsPanel.add(managementViewButton); + + contentPane.add(infoInputPanel, BorderLayout.CENTER); + contentPane.add(buttonsPanel, BorderLayout.SOUTH); + return contentPane; + } +} diff --git a/src/main/resources/sql/insert/types.sql b/src/main/resources/sql/insert/types.sql deleted file mode 100644 index 3940146..0000000 --- a/src/main/resources/sql/insert/types.sql +++ /dev/null @@ -1,19 +0,0 @@ -INSERT INTO person_types (id, name) -VALUES (0, 'student'), - (1, 'teaching-assistant'), - (2, 'professor'); - -INSERT INTO team_types (id, name) -VALUES (0, 'student_team'), - (1, 'teaching_assistant_team'), - (2, 'all_teaching_assistants'), - (3, 'none'); - -INSERT INTO teams (id, team_type_id) -VALUES (1000000, 3), -- None team for all students or TA's without a team. - (1000001, 2); -- Team for all teaching assistants. - -INSERT INTO error_types (id, name) -VALUES (0, 'team_error'), - (1, 'person_error'), - (2, 'system_error'); \ No newline at end of file diff --git a/src/main/resources/sql/table_init.sql b/src/main/resources/sql/table_init.sql deleted file mode 100644 index 0bba75d..0000000 --- a/src/main/resources/sql/table_init.sql +++ /dev/null @@ -1,153 +0,0 @@ -PRAGMA foreign_keys = TRUE; -PRAGMA writable_schema = 1; -DELETE FROM sqlite_master WHERE type IN ('table', 'index', 'trigger'); -PRAGMA writable_schema = 0; -VACUUM; - --- Basic schema design. -CREATE TABLE IF NOT EXISTS person_types ( - id INTEGER PRIMARY KEY, - name TEXT NOT NULL UNIQUE -); - -CREATE TABLE IF NOT EXISTS persons ( - id INTEGER PRIMARY KEY, - name TEXT NOT NULL, - email_address TEXT NOT NULL, - github_username TEXT NOT NULL UNIQUE, - person_type_id INTEGER NOT NULL, - team_id INTEGER NULL, - FOREIGN KEY (person_type_id) - REFERENCES person_types(id) - ON DELETE CASCADE - ON UPDATE CASCADE, - FOREIGN KEY (team_id) - REFERENCES teams(id) - ON DELETE CASCADE - ON UPDATE CASCADE -); - --- Team tables for all types of teams. -CREATE TABLE IF NOT EXISTS team_types ( - id INTEGER PRIMARY KEY, - name TEXT NOT NULL UNIQUE -); - -CREATE TABLE IF NOT EXISTS teams ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - team_type_id INTEGER NOT NULL, - FOREIGN KEY (team_type_id) - REFERENCES team_types(id) - ON DELETE CASCADE - ON UPDATE CASCADE -); - -CREATE TABLE IF NOT EXISTS team_members ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - team_id INTEGER NOT NULL, - person_id INTEGER NOT NULL, - FOREIGN KEY (team_id) - REFERENCES teams(id) - ON DELETE CASCADE - ON UPDATE CASCADE, - FOREIGN KEY (person_id) - REFERENCES persons(id) - ON DELETE CASCADE - ON UPDATE CASCADE, - UNIQUE (team_id, person_id) -); - -CREATE TABLE IF NOT EXISTS teaching_assistant_teams ( - team_id INTEGER PRIMARY KEY, - name TEXT NOT NULL UNIQUE, - FOREIGN KEY (team_id) - REFERENCES teams(id) - ON DELETE CASCADE - ON UPDATE CASCADE -); - -CREATE TABLE IF NOT EXISTS student_teams ( - team_id INTEGER PRIMARY KEY, - repository_name TEXT, - teaching_assistant_team_id INTEGER, - FOREIGN KEY (team_id) - REFERENCES teams(id) - ON DELETE CASCADE - ON UPDATE CASCADE, - FOREIGN KEY (teaching_assistant_team_id) - REFERENCES teaching_assistant_teams(team_id) - ON DELETE CASCADE - ON UPDATE CASCADE -); - --- Student-specific tables. -CREATE TABLE IF NOT EXISTS students ( - person_id INTEGER PRIMARY KEY, - chose_partner INTEGER NOT NULL, - FOREIGN KEY (person_id) - REFERENCES persons(id) - ON DELETE CASCADE - ON UPDATE CASCADE -); - -CREATE TABLE IF NOT EXISTS student_preferred_partners ( - student_id INTEGER PRIMARY KEY, - partner_id INTEGER NOT NULL, - FOREIGN KEY (student_id) - REFERENCES students(person_id) - ON DELETE CASCADE - ON UPDATE CASCADE, - UNIQUE (student_id, partner_id) -); - --- TeachingAssistant-specific tables. -CREATE TABLE IF NOT EXISTS teaching_assistants ( - person_id INTEGER PRIMARY KEY, - FOREIGN KEY (person_id) - REFERENCES persons(id) - ON DELETE CASCADE - ON UPDATE CASCADE -); - --- Error queue storage. -CREATE TABLE IF NOT EXISTS error_types ( - id INTEGER PRIMARY KEY, - name TEXT NOT NULL UNIQUE -); - -CREATE TABLE IF NOT EXISTS errors ( - id INTEGER PRIMARY KEY, - timestamp DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - error_type_id INTEGER NOT NULL, - message TEXT NOT NULL, - FOREIGN KEY (error_type_id) - REFERENCES error_types(id) - ON DELETE CASCADE - ON UPDATE CASCADE -); - -CREATE TABLE IF NOT EXISTS team_errors ( - error_id INTEGER PRIMARY KEY, - team_id INTEGER NOT NULL, - FOREIGN KEY (error_id) - REFERENCES errors(id) - ON DELETE CASCADE - ON UPDATE CASCADE, - FOREIGN KEY (team_id) - REFERENCES teams(id) - ON DELETE CASCADE - ON UPDATE CASCADE -); - -CREATE TABLE IF NOT EXISTS person_errors ( - error_id INTEGER PRIMARY KEY, - person_id INTEGER NOT NULL, - FOREIGN KEY (error_id) - REFERENCES errors(id) - ON DELETE CASCADE - ON UPDATE CASCADE, - FOREIGN KEY (person_id) - REFERENCES persons(id) - ON DELETE CASCADE - ON UPDATE CASCADE -); \ No newline at end of file From 44cde211462a1048024d5f9a368de5410ed15eb3 Mon Sep 17 00:00:00 2001 From: andrewlalis Date: Mon, 22 Oct 2018 09:54:30 +0200 Subject: [PATCH 07/12] Polished up some views, made it so that when the start view is disposed, all child views are disposed of. About to begin the management view! Exciting stuff. --- src/main/java/nl/andrewlalis/Main.java | 2 +- .../nl/andrewlalis/git_api/GithubManager.java | 2 +- .../create_assignments_view/NextListener.java | 13 +++- .../FileSelectListener.java | 63 +++++++++++++++++++ .../nl/andrewlalis/ui/view/AbstractView.java | 26 ++++++++ .../ui/view/CreateAssignmentsView.java | 13 +++- .../ui/view/InputStudentsFileView.java | 20 +++++- .../nl/andrewlalis/ui/view/StartView.java | 18 +++++- 8 files changed, 148 insertions(+), 9 deletions(-) create mode 100644 src/main/java/nl/andrewlalis/ui/control/listeners/input_students_file_view/FileSelectListener.java diff --git a/src/main/java/nl/andrewlalis/Main.java b/src/main/java/nl/andrewlalis/Main.java index e7702a7..faac15d 100644 --- a/src/main/java/nl/andrewlalis/Main.java +++ b/src/main/java/nl/andrewlalis/Main.java @@ -50,7 +50,7 @@ public class Main { "Program initialized."); GithubManager manager = new GithubManager(); - StartView startView = new StartView(manager); + StartView startView = new StartView(manager, "InitializerTesting", userOptions.get("token")); // SessionFactory factory = DbUtil.getSessionFactory(); // Session session = factory.openSession(); diff --git a/src/main/java/nl/andrewlalis/git_api/GithubManager.java b/src/main/java/nl/andrewlalis/git_api/GithubManager.java index 3ad9091..1f7c2a3 100644 --- a/src/main/java/nl/andrewlalis/git_api/GithubManager.java +++ b/src/main/java/nl/andrewlalis/git_api/GithubManager.java @@ -429,7 +429,7 @@ public class GithubManager { builder.issues(hasIssues); builder.description(description); builder.gitignoreTemplate("Java"); - builder.private_(isPrivate); + builder.private_(false); // TODO: Testing value of false. Production uses true. GHRepository repo = builder.create(); logger.fine("Created repository: " + repo.getName()); return repo; diff --git a/src/main/java/nl/andrewlalis/ui/control/listeners/create_assignments_view/NextListener.java b/src/main/java/nl/andrewlalis/ui/control/listeners/create_assignments_view/NextListener.java index 268cdc0..2970d7c 100644 --- a/src/main/java/nl/andrewlalis/ui/control/listeners/create_assignments_view/NextListener.java +++ b/src/main/java/nl/andrewlalis/ui/control/listeners/create_assignments_view/NextListener.java @@ -28,10 +28,19 @@ public class NextListener extends ViewChangeListener { protected boolean beforeChange() { CreateAssignmentsView assignmentsView = (CreateAssignmentsView) this.previousView; String repoName = assignmentsView.getRepositoryName(); + + // Check that the repository name is legitimate. + if (repoName.trim().length() == 0) { + JOptionPane.showMessageDialog(this.previousView, "Repository name is empty.", "Error", JOptionPane.WARNING_MESSAGE); + return false; + } + + // Check if the repository already exists. GithubManager manager = assignmentsView.getGithubManager(); if (manager.repoExists(repoName)) { return true; } else { + // If not, we have to create it here. int reply = JOptionPane.showConfirmDialog( assignmentsView, "The repository you gave does not exist.\nWould you like to create it?", @@ -39,7 +48,8 @@ public class NextListener extends ViewChangeListener { JOptionPane.YES_NO_OPTION); if (reply == JOptionPane.YES_OPTION) { try { - assignmentsView.getGithubManager().setupAssignmentsRepo(repoName, "", this.getTeachingAssistantsTeamName()); + String description = JOptionPane.showInputDialog(assignmentsView, "Enter a description for the repository.", "Assignments Repository Description", JOptionPane.QUESTION_MESSAGE); + assignmentsView.getGithubManager().setupAssignmentsRepo(repoName, description, this.getTeachingAssistantsTeamName()); return true; } catch (IOException e) { //e.printStackTrace(); @@ -52,6 +62,7 @@ public class NextListener extends ViewChangeListener { } } + // TODO: Replace this with a selector for an existing team of teaching assistants. Or configure this afterwards. private String getTeachingAssistantsTeamName() { String name = JOptionPane.showInputDialog(this.previousView, "Please enter (exactly) the name of Github team\nthat contains all teaching assistants.", "Select TA Team", JOptionPane.QUESTION_MESSAGE); return name; diff --git a/src/main/java/nl/andrewlalis/ui/control/listeners/input_students_file_view/FileSelectListener.java b/src/main/java/nl/andrewlalis/ui/control/listeners/input_students_file_view/FileSelectListener.java new file mode 100644 index 0000000..664fe44 --- /dev/null +++ b/src/main/java/nl/andrewlalis/ui/control/listeners/input_students_file_view/FileSelectListener.java @@ -0,0 +1,63 @@ +package nl.andrewlalis.ui.control.listeners.input_students_file_view; + +import nl.andrewlalis.model.StudentTeam; +import nl.andrewlalis.ui.view.InputStudentsFileView; +import nl.andrewlalis.util.TeamGenerator; + +import javax.swing.*; +import javax.swing.filechooser.FileFilter; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.File; +import java.io.IOException; +import java.util.List; + +/** + * Listens for when the user selects a CSV file to use to populate the teams list. + */ +public class FileSelectListener implements ActionListener { + + private InputStudentsFileView fileView; + + public FileSelectListener(InputStudentsFileView parent) { + this.fileView = parent; + } + + @Override + public void actionPerformed(ActionEvent actionEvent) { + // First check if the user has entered a valid team size. + if (this.fileView.getStudentsPerTeam() < 1) { + JOptionPane.showMessageDialog(this.fileView, "Invalid or missing team size.", "Error", JOptionPane.WARNING_MESSAGE); + return; + } + + // It is assumed that the team size is valid, so the user can choose a file. + JFileChooser chooser = new JFileChooser(); + chooser.setAcceptAllFileFilterUsed(false); + chooser.addChoosableFileFilter(new FileFilter() { + @Override + public boolean accept(File file) { + if (file.isDirectory()) { + return true; + } + return file.getName().toLowerCase().endsWith(".csv"); + } + + @Override + public String getDescription() { + return "CSV Files (*.csv)"; + } + }); + int fileResponse = chooser.showOpenDialog(this.fileView); + + if (fileResponse == JFileChooser.APPROVE_OPTION) { + int teamSize = this.fileView.getStudentsPerTeam(); + try { + List teams = TeamGenerator.generateFromCSV(chooser.getSelectedFile().getAbsolutePath(), teamSize); + System.out.println(teams); + } catch (IOException e) { + e.printStackTrace(); + } + } + } +} diff --git a/src/main/java/nl/andrewlalis/ui/view/AbstractView.java b/src/main/java/nl/andrewlalis/ui/view/AbstractView.java index 01e9cb7..5480cc1 100644 --- a/src/main/java/nl/andrewlalis/ui/view/AbstractView.java +++ b/src/main/java/nl/andrewlalis/ui/view/AbstractView.java @@ -4,6 +4,8 @@ import nl.andrewlalis.git_api.GithubManager; import javax.swing.*; import java.awt.*; +import java.util.ArrayList; +import java.util.List; /** * All views in the application will extend from this view, as a means of simplifying and organizing how visual @@ -16,6 +18,11 @@ public abstract class AbstractView extends JFrame { */ private GithubManager githubManager; + /** + * A list of views which are linked to this one via buttons in the component pane. + */ + private List childViews; + /** * Initializes the view by packing the content pane as it is defined by any child, and setting some generic swing * values. @@ -27,6 +34,7 @@ public abstract class AbstractView extends JFrame { AbstractView(String title, boolean startVisible, int defaultCloseOperation, Dimension preferredSize, GithubManager githubManager) { super(title); this.githubManager = githubManager; + this.childViews = new ArrayList<>(); this.setContentPane(this.buildContentPane()); this.setDefaultCloseOperation(defaultCloseOperation); if (preferredSize != null) { @@ -52,6 +60,16 @@ public abstract class AbstractView extends JFrame { // Child classes can define custom behavior here. } + /** + * Extends the default expose behaviour by recursively disposing all views which are linked to this one. + */ + public void dispose() { + for (AbstractView view : this.childViews) { + view.dispose(); + } + super.dispose(); + } + public GithubManager getGithubManager() { return githubManager; } @@ -69,4 +87,12 @@ public abstract class AbstractView extends JFrame { newPanel.setBorder(BorderFactory.createEmptyBorder(5, 2, 5, 2)); return newPanel; } + + /** + * Adds a view as linked to this one. That way, this view can be referenced elsewhere, even when hidden. + * @param view The view to link. + */ + protected final void addChildView(AbstractView view) { + this.childViews.add(view); + } } diff --git a/src/main/java/nl/andrewlalis/ui/view/CreateAssignmentsView.java b/src/main/java/nl/andrewlalis/ui/view/CreateAssignmentsView.java index 7fb9473..22d5418 100644 --- a/src/main/java/nl/andrewlalis/ui/view/CreateAssignmentsView.java +++ b/src/main/java/nl/andrewlalis/ui/view/CreateAssignmentsView.java @@ -31,13 +31,20 @@ public class CreateAssignmentsView extends AbstractView { @Override protected JPanel buildContentPane() { JPanel contentPane = new JPanel(); - contentPane.setLayout(new BorderLayout()); + + JPanel fieldPanel = new JPanel(); + fieldPanel.setLayout(new BoxLayout(fieldPanel, BoxLayout.PAGE_AXIS)); + this.repositoryNameField = new JTextField(); - contentPane.add(this.generateTextFieldPanel("Repository name:", this.repositoryNameField), BorderLayout.CENTER); + fieldPanel.add(this.generateTextFieldPanel("Assignments repository name:", this.repositoryNameField)); + contentPane.add(fieldPanel, BorderLayout.CENTER); + JButton nextButton = new JButton("Next"); - nextButton.addActionListener(new NextListener(this, new InputStudentsFileView(this.getGithubManager()))); + InputStudentsFileView inputStudentsFileView = new InputStudentsFileView(this.getGithubManager()); + this.addChildView(inputStudentsFileView); + nextButton.addActionListener(new NextListener(this, inputStudentsFileView)); contentPane.add(nextButton, BorderLayout.SOUTH); return contentPane; diff --git a/src/main/java/nl/andrewlalis/ui/view/InputStudentsFileView.java b/src/main/java/nl/andrewlalis/ui/view/InputStudentsFileView.java index 6c48e80..10a77b6 100644 --- a/src/main/java/nl/andrewlalis/ui/view/InputStudentsFileView.java +++ b/src/main/java/nl/andrewlalis/ui/view/InputStudentsFileView.java @@ -1,6 +1,7 @@ package nl.andrewlalis.ui.view; import nl.andrewlalis.git_api.GithubManager; +import nl.andrewlalis.ui.control.listeners.input_students_file_view.FileSelectListener; import javax.swing.*; import java.awt.*; @@ -10,6 +11,8 @@ import java.awt.*; */ public class InputStudentsFileView extends AbstractView { + private JTextField studentsPerTeamField; + InputStudentsFileView(GithubManager manager) { super("Input Students CSV", false, @@ -18,12 +21,27 @@ public class InputStudentsFileView extends AbstractView { manager); } + public int getStudentsPerTeam() { + return Integer.parseUnsignedInt(this.studentsPerTeamField.getText()); + } + @Override protected JPanel buildContentPane() { JPanel contentPane = new JPanel(new BorderLayout()); + JLabel helpLabel = new JLabel("Please select the CSV file containing student sign-up responses."); + contentPane.add(helpLabel, BorderLayout.NORTH); + + JPanel inputPanel = new JPanel(); + inputPanel.setLayout(new BoxLayout(inputPanel, BoxLayout.PAGE_AXIS)); + JButton selectFileButton = new JButton("Select File"); - contentPane.add(selectFileButton, BorderLayout.CENTER); + this.studentsPerTeamField = new JTextField("2"); + inputPanel.add(this.generateTextFieldPanel("How many students per team?", this.studentsPerTeamField)); + selectFileButton.addActionListener(new FileSelectListener(this)); + inputPanel.add(selectFileButton); + + contentPane.add(inputPanel, BorderLayout.CENTER); JButton doneButton = new JButton("Done"); contentPane.add(doneButton, BorderLayout.SOUTH); diff --git a/src/main/java/nl/andrewlalis/ui/view/StartView.java b/src/main/java/nl/andrewlalis/ui/view/StartView.java index 65fde1a..c53ebc8 100644 --- a/src/main/java/nl/andrewlalis/ui/view/StartView.java +++ b/src/main/java/nl/andrewlalis/ui/view/StartView.java @@ -29,6 +29,18 @@ public class StartView extends AbstractView { githubManager); } + /** + * Constructs the starting view, with pre-defined organization and access tokens. + * @param githubManager A reference to the github manager this application uses. + * @param organizationName The name of the organization. + * @param accessToken The access token from the user. + */ + public StartView(GithubManager githubManager, String organizationName, String accessToken) { + this(githubManager); + this.organizationNameField.setText(organizationName); + this.accessTokenField.setText(accessToken); + } + public String getOrganizationName() { return this.organizationNameField.getText(); } @@ -44,13 +56,15 @@ public class StartView extends AbstractView { JPanel infoInputPanel = new JPanel(); infoInputPanel.setLayout(new BoxLayout(infoInputPanel, BoxLayout.PAGE_AXIS)); this.organizationNameField = new JTextField(); - infoInputPanel.add(this.generateTextFieldPanel("Organization name:", this.organizationNameField)); this.accessTokenField = new JTextField(); + infoInputPanel.add(this.generateTextFieldPanel("Organization name:", this.organizationNameField)); infoInputPanel.add(this.generateTextFieldPanel("Access token:", this.accessTokenField)); JPanel buttonsPanel = new JPanel(); JButton assignmentsViewButton = new JButton("Start New Course"); - assignmentsViewButton.addActionListener(new CreateAssignmentsRepoListener(this, new CreateAssignmentsView(this.getGithubManager()))); + CreateAssignmentsView assignmentsView = new CreateAssignmentsView(this.getGithubManager()); + this.addChildView(assignmentsView); + assignmentsViewButton.addActionListener(new CreateAssignmentsRepoListener(this, assignmentsView)); JButton managementViewButton = new JButton("Manage Existing Course"); buttonsPanel.add(assignmentsViewButton); From e57897a0054a2b9ba7ba9c19e7bb6145c1210ab4 Mon Sep 17 00:00:00 2001 From: andrewlalis Date: Mon, 22 Oct 2018 13:45:42 +0200 Subject: [PATCH 08/12] Fixed window closing, can now display ORM data in table in the management view, but that's about it. About to start work on the detail panel. --- src/main/java/nl/andrewlalis/Main.java | 17 ++- .../java/nl/andrewlalis/model/Student.java | 22 ++++ .../andrewlalis/model/database/DbHelper.java | 26 ++++ .../DoneListener.java | 15 +++ .../FileSelectListener.java | 20 ++- .../nl/andrewlalis/ui/view/AbstractView.java | 36 ++++++ .../ui/view/CreateAssignmentsView.java | 1 + .../ui/view/InputStudentsFileView.java | 7 +- .../andrewlalis/ui/view/ManagementView.java | 114 ++++++++++++++++++ .../nl/andrewlalis/ui/view/StartView.java | 9 ++ .../view/table_models/StudentTableModel.java | 74 ++++++++++++ .../nl/andrewlalis/util/TeamGenerator.java | 14 ++- 12 files changed, 351 insertions(+), 4 deletions(-) create mode 100644 src/main/java/nl/andrewlalis/model/database/DbHelper.java create mode 100644 src/main/java/nl/andrewlalis/ui/control/listeners/input_students_file_view/DoneListener.java create mode 100644 src/main/java/nl/andrewlalis/ui/view/ManagementView.java create mode 100644 src/main/java/nl/andrewlalis/ui/view/table_models/StudentTableModel.java diff --git a/src/main/java/nl/andrewlalis/Main.java b/src/main/java/nl/andrewlalis/Main.java index faac15d..834af58 100644 --- a/src/main/java/nl/andrewlalis/Main.java +++ b/src/main/java/nl/andrewlalis/Main.java @@ -4,6 +4,7 @@ import nl.andrewlalis.command.CommandExecutor; import nl.andrewlalis.command.executables.*; import nl.andrewlalis.git_api.GithubManager; import nl.andrewlalis.ui.view.InitializerApp; +import nl.andrewlalis.ui.view.ManagementView; import nl.andrewlalis.ui.view.StartView; import nl.andrewlalis.util.CommandLine; import nl.andrewlalis.util.Logging; @@ -18,6 +19,11 @@ public class Main { private static final Logger logger = Logger.getGlobal(); + /** + * The main application's view, which should be able to be referenced in many places. + */ + private static ManagementView managementView; + public static void main(String[] args) { // Parsed command line arguments. @@ -50,7 +56,7 @@ public class Main { "Program initialized."); GithubManager manager = new GithubManager(); - StartView startView = new StartView(manager, "InitializerTesting", userOptions.get("token")); + managementView = new ManagementView(manager); // SessionFactory factory = DbUtil.getSessionFactory(); // Session session = factory.openSession(); @@ -58,6 +64,15 @@ public class Main { // System.out.println(session.save(new Student(1, "a", "a@e.com", "git", null))); // session.getTransaction().commit(); // session.close(); + + StartView startView = new StartView(manager, "InitializerTesting", userOptions.get("token")); + } + + /** + * @return The management view used for the application. + */ + public static ManagementView getManagementView() { + return managementView; } } diff --git a/src/main/java/nl/andrewlalis/model/Student.java b/src/main/java/nl/andrewlalis/model/Student.java index f68df82..dbf9360 100644 --- a/src/main/java/nl/andrewlalis/model/Student.java +++ b/src/main/java/nl/andrewlalis/model/Student.java @@ -28,6 +28,13 @@ public class Student extends Person { ) private List preferredPartners; + /** + * The team that this student is assigned to. + */ + @ManyToOne + @JoinColumn(name = "team_id") + private StudentTeam team; + /** * Constructs an empty student object. */ @@ -73,4 +80,19 @@ public class Student extends Person { t.addMember(this); return t; } + + /** + * Assigns this student to the given team, from the student's perspective. + * @param team The team to set as the assigned team. + */ + public void assignToTeam(StudentTeam team) { + this.team = team; + } + + /** + * @return The team that this student is assigned to. May return null if the student is unassigned. + */ + public StudentTeam getAssignedTeam() { + return this.team; + } } diff --git a/src/main/java/nl/andrewlalis/model/database/DbHelper.java b/src/main/java/nl/andrewlalis/model/database/DbHelper.java new file mode 100644 index 0000000..fde57e2 --- /dev/null +++ b/src/main/java/nl/andrewlalis/model/database/DbHelper.java @@ -0,0 +1,26 @@ +package nl.andrewlalis.model.database; + +import nl.andrewlalis.model.Student; +import org.hibernate.Session; +import org.hibernate.SessionFactory; + +import java.util.List; + +/** + * This class will contain some static methods to help in the retrieval of commonly used information. + */ +public class DbHelper { + + /** + * Gets a list of students in the database. + * @return A list of students. + */ + public static List getStudents() { + SessionFactory sessionFactory = DbUtil.getSessionFactory(); + Session session = sessionFactory.openSession(); + List students = (List) session.createQuery("from Student").list(); + session.close(); + return students; + } + +} diff --git a/src/main/java/nl/andrewlalis/ui/control/listeners/input_students_file_view/DoneListener.java b/src/main/java/nl/andrewlalis/ui/control/listeners/input_students_file_view/DoneListener.java new file mode 100644 index 0000000..f98c11f --- /dev/null +++ b/src/main/java/nl/andrewlalis/ui/control/listeners/input_students_file_view/DoneListener.java @@ -0,0 +1,15 @@ +package nl.andrewlalis.ui.control.listeners.input_students_file_view; + +import nl.andrewlalis.ui.control.listeners.ViewChangeListener; +import nl.andrewlalis.ui.view.AbstractView; + +/** + * Listens for when the user clicks 'Done' after selecting a file to input. + */ +public class DoneListener extends ViewChangeListener { + + public DoneListener(AbstractView previousView, AbstractView newView) { + super(previousView, newView); + } + +} diff --git a/src/main/java/nl/andrewlalis/ui/control/listeners/input_students_file_view/FileSelectListener.java b/src/main/java/nl/andrewlalis/ui/control/listeners/input_students_file_view/FileSelectListener.java index 664fe44..afc053b 100644 --- a/src/main/java/nl/andrewlalis/ui/control/listeners/input_students_file_view/FileSelectListener.java +++ b/src/main/java/nl/andrewlalis/ui/control/listeners/input_students_file_view/FileSelectListener.java @@ -1,8 +1,13 @@ package nl.andrewlalis.ui.control.listeners.input_students_file_view; +import nl.andrewlalis.Main; +import nl.andrewlalis.model.Student; import nl.andrewlalis.model.StudentTeam; +import nl.andrewlalis.model.database.DbUtil; import nl.andrewlalis.ui.view.InputStudentsFileView; import nl.andrewlalis.util.TeamGenerator; +import org.hibernate.Session; +import org.hibernate.Transaction; import javax.swing.*; import javax.swing.filechooser.FileFilter; @@ -54,7 +59,20 @@ public class FileSelectListener implements ActionListener { int teamSize = this.fileView.getStudentsPerTeam(); try { List teams = TeamGenerator.generateFromCSV(chooser.getSelectedFile().getAbsolutePath(), teamSize); - System.out.println(teams); + Session session = DbUtil.getSessionFactory().openSession(); + Transaction tx = session.beginTransaction(); + + for (StudentTeam team : teams) { + for (Student s : team.getStudents()) { + session.save(s); + } + session.save(team); + } + + tx.commit(); + session.close(); + + Main.getManagementView().updateModels(); } catch (IOException e) { e.printStackTrace(); } diff --git a/src/main/java/nl/andrewlalis/ui/view/AbstractView.java b/src/main/java/nl/andrewlalis/ui/view/AbstractView.java index 5480cc1..0d29dfe 100644 --- a/src/main/java/nl/andrewlalis/ui/view/AbstractView.java +++ b/src/main/java/nl/andrewlalis/ui/view/AbstractView.java @@ -23,6 +23,11 @@ public abstract class AbstractView extends JFrame { */ private List childViews; + /** + * A list of views which lead to this one. + */ + private List parentViews; + /** * Initializes the view by packing the content pane as it is defined by any child, and setting some generic swing * values. @@ -30,11 +35,13 @@ public abstract class AbstractView extends JFrame { * @param startVisible Whether or not to start the view as visible. * @param defaultCloseOperation What to do when the user closes the window. * @param preferredSize The preferred size of the view. + * @param githubManager The manager used for this view. */ AbstractView(String title, boolean startVisible, int defaultCloseOperation, Dimension preferredSize, GithubManager githubManager) { super(title); this.githubManager = githubManager; this.childViews = new ArrayList<>(); + this.parentViews = new ArrayList<>(); this.setContentPane(this.buildContentPane()); this.setDefaultCloseOperation(defaultCloseOperation); if (preferredSize != null) { @@ -95,4 +102,33 @@ public abstract class AbstractView extends JFrame { protected final void addChildView(AbstractView view) { this.childViews.add(view); } + + /** + * @return The list of children of this view. + */ + protected final List getChildViews() { + return this.childViews; + } + + /** + * Adds a view as linked to this one as a parent. + * @param view The parent view. + */ + protected final void addParentView(AbstractView view) { + this.parentViews.add(view); + } + + /** + * @return The list of parents of this view. + */ + protected final List getParentViews() { + return this.parentViews; + } + + /** + * Removes all parents registered to this view. + */ + protected final void removeParents() { + this.parentViews.clear(); + } } diff --git a/src/main/java/nl/andrewlalis/ui/view/CreateAssignmentsView.java b/src/main/java/nl/andrewlalis/ui/view/CreateAssignmentsView.java index 22d5418..a85880e 100644 --- a/src/main/java/nl/andrewlalis/ui/view/CreateAssignmentsView.java +++ b/src/main/java/nl/andrewlalis/ui/view/CreateAssignmentsView.java @@ -44,6 +44,7 @@ public class CreateAssignmentsView extends AbstractView { JButton nextButton = new JButton("Next"); InputStudentsFileView inputStudentsFileView = new InputStudentsFileView(this.getGithubManager()); this.addChildView(inputStudentsFileView); + inputStudentsFileView.addParentView(this); nextButton.addActionListener(new NextListener(this, inputStudentsFileView)); contentPane.add(nextButton, BorderLayout.SOUTH); diff --git a/src/main/java/nl/andrewlalis/ui/view/InputStudentsFileView.java b/src/main/java/nl/andrewlalis/ui/view/InputStudentsFileView.java index 10a77b6..18324c6 100644 --- a/src/main/java/nl/andrewlalis/ui/view/InputStudentsFileView.java +++ b/src/main/java/nl/andrewlalis/ui/view/InputStudentsFileView.java @@ -1,6 +1,8 @@ package nl.andrewlalis.ui.view; +import nl.andrewlalis.Main; import nl.andrewlalis.git_api.GithubManager; +import nl.andrewlalis.ui.control.listeners.input_students_file_view.DoneListener; import nl.andrewlalis.ui.control.listeners.input_students_file_view.FileSelectListener; import javax.swing.*; @@ -34,7 +36,7 @@ public class InputStudentsFileView extends AbstractView { JPanel inputPanel = new JPanel(); inputPanel.setLayout(new BoxLayout(inputPanel, BoxLayout.PAGE_AXIS)); - + // Button to select a file. JButton selectFileButton = new JButton("Select File"); this.studentsPerTeamField = new JTextField("2"); inputPanel.add(this.generateTextFieldPanel("How many students per team?", this.studentsPerTeamField)); @@ -43,7 +45,10 @@ public class InputStudentsFileView extends AbstractView { contentPane.add(inputPanel, BorderLayout.CENTER); + // Button to confirm and move to the next view. JButton doneButton = new JButton("Done"); + Main.getManagementView().addParentView(this); + doneButton.addActionListener(new DoneListener(this, Main.getManagementView())); contentPane.add(doneButton, BorderLayout.SOUTH); return contentPane; diff --git a/src/main/java/nl/andrewlalis/ui/view/ManagementView.java b/src/main/java/nl/andrewlalis/ui/view/ManagementView.java new file mode 100644 index 0000000..e0462a8 --- /dev/null +++ b/src/main/java/nl/andrewlalis/ui/view/ManagementView.java @@ -0,0 +1,114 @@ +package nl.andrewlalis.ui.view; + +import nl.andrewlalis.git_api.GithubManager; +import nl.andrewlalis.model.database.DbHelper; +import nl.andrewlalis.model.database.DbUtil; +import nl.andrewlalis.ui.view.table_models.StudentTableModel; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; + +/** + * The view in which the user manages a course. + */ +public class ManagementView extends AbstractView { + + /** + * The model for the students table. + */ + private StudentTableModel studentsModel; + + public ManagementView(GithubManager githubManager) { + super( + "Course Management", + false, + DISPOSE_ON_CLOSE, + null, + githubManager + ); + this.setExtendedState(this.getExtendedState() | JFrame.MAXIMIZED_BOTH); + + // Dispose of all parents when this window closes. + this.addWindowListener(new WindowAdapter() { + @Override + public void windowClosed(WindowEvent windowEvent) { + for (AbstractView parent : getParentViews()) { + parent.dispose(); + } + DbUtil.tearDown(); + } + }); + } + + @Override + protected JPanel buildContentPane() { + JPanel contentPane = new JPanel(new BorderLayout()); + + contentPane.add(this.buildCommandPanel(), BorderLayout.WEST); + contentPane.add(this.buildDetailPanel(), BorderLayout.EAST); + contentPane.add(this.buildOverviewPanel(), BorderLayout.CENTER); + + return contentPane; + } + + /** + * @return A JPanel for the command prompt interface. + */ + private JPanel buildCommandPanel() { + JPanel commandPanel = new JPanel(new BorderLayout()); + + commandPanel.add(new JLabel("Commands", SwingConstants.CENTER), BorderLayout.NORTH); + commandPanel.add(new JTextArea("Command prompt area goes here."), BorderLayout.CENTER); + + // Construct the sub-panel for commands at the bottom of the panel. + JPanel inputPanel = new JPanel(new BorderLayout()); + JTextField commandTextField = new JTextField(); + inputPanel.add(commandTextField, BorderLayout.CENTER); + + commandPanel.add(inputPanel, BorderLayout.SOUTH); + + return commandPanel; + } + + /** + * @return A JPanel for the entity details section. + */ + private JPanel buildDetailPanel() { + JPanel detailPanel = new JPanel(new BorderLayout()); + + detailPanel.add(new JLabel("Details", SwingConstants.CENTER), BorderLayout.NORTH); + + return detailPanel; + } + + /** + * @return Builds the overview panel, containing a listing of entities. + */ + private JPanel buildOverviewPanel() { + JPanel overviewPanel = new JPanel(new BorderLayout()); + + overviewPanel.add(new JLabel("Overview"), BorderLayout.NORTH); + + // The real container for all the data views. + JTabbedPane tabbedPane = new JTabbedPane(); + + this.studentsModel = new StudentTableModel(DbHelper.getStudents()); + JTable studentsTable = new JTable(this.studentsModel); + JScrollPane studentsScrollPane = new JScrollPane(studentsTable); + tabbedPane.addTab("Students", studentsScrollPane); + + overviewPanel.add(tabbedPane, BorderLayout.CENTER); + + return overviewPanel; + } + + /** + * Updates all models in the management view in accordance with the database. + */ + public void updateModels() { + this.studentsModel.setStudentsList(DbHelper.getStudents()); + + } +} diff --git a/src/main/java/nl/andrewlalis/ui/view/StartView.java b/src/main/java/nl/andrewlalis/ui/view/StartView.java index c53ebc8..a90efc1 100644 --- a/src/main/java/nl/andrewlalis/ui/view/StartView.java +++ b/src/main/java/nl/andrewlalis/ui/view/StartView.java @@ -1,6 +1,8 @@ package nl.andrewlalis.ui.view; +import nl.andrewlalis.Main; import nl.andrewlalis.git_api.GithubManager; +import nl.andrewlalis.ui.control.listeners.ViewChangeListener; import nl.andrewlalis.ui.control.listeners.start_view.CreateAssignmentsRepoListener; import javax.swing.*; @@ -61,11 +63,18 @@ public class StartView extends AbstractView { infoInputPanel.add(this.generateTextFieldPanel("Access token:", this.accessTokenField)); JPanel buttonsPanel = new JPanel(); + // Create the button for going to the Create assignments repository view. JButton assignmentsViewButton = new JButton("Start New Course"); CreateAssignmentsView assignmentsView = new CreateAssignmentsView(this.getGithubManager()); this.addChildView(assignmentsView); + assignmentsView.addParentView(this); assignmentsViewButton.addActionListener(new CreateAssignmentsRepoListener(this, assignmentsView)); + + // Create the button for going straight to the management view. JButton managementViewButton = new JButton("Manage Existing Course"); + this.addChildView(Main.getManagementView()); + Main.getManagementView().addParentView(this); + managementViewButton.addActionListener(new ViewChangeListener(this, Main.getManagementView())); buttonsPanel.add(assignmentsViewButton); buttonsPanel.add(managementViewButton); diff --git a/src/main/java/nl/andrewlalis/ui/view/table_models/StudentTableModel.java b/src/main/java/nl/andrewlalis/ui/view/table_models/StudentTableModel.java new file mode 100644 index 0000000..03fb2a3 --- /dev/null +++ b/src/main/java/nl/andrewlalis/ui/view/table_models/StudentTableModel.java @@ -0,0 +1,74 @@ +package nl.andrewlalis.ui.view.table_models; + +import nl.andrewlalis.model.Student; + +import javax.swing.table.AbstractTableModel; +import java.util.List; + +/** + * This table model is used for the representation of a list of persons, with their basic information. + */ +public class StudentTableModel extends AbstractTableModel { + + /** + * The list of data that is used in the table. + */ + private List studentsList; + + /** + * A default list of column headers for this table. + */ + private String[] columns = {"Number", "Name", "Email", "Github", "Team"}; + + /** + * Constructs a new model based on the given list of students. + * @param studentsList A list of students to display in the table model. + */ + public StudentTableModel(List studentsList) { + this.studentsList = studentsList; + } + + /** + * Sets a new list of students as the data for this list model. + * @param newList The new list of students to use. + */ + public void setStudentsList(List newList) { + this.studentsList = newList; + this.fireTableDataChanged(); + } + + @Override + public int getRowCount() { + return studentsList.size(); + } + + @Override + public int getColumnCount() { + return this.columns.length; + } + + @Override + public String getColumnName(int i) { + return this.columns[i]; + } + + @Override + public Object getValueAt(int row, int col) { + Student student = studentsList.get(row); + + switch(col) { + case 0: + return student.getNumber(); + case 1: + return student.getName(); + case 2: + return student.getEmailAddress(); + case 3: + return student.getGithubUsername(); + case 4: + return student.getAssignedTeam().getId(); + default: + return null; + } + } +} diff --git a/src/main/java/nl/andrewlalis/util/TeamGenerator.java b/src/main/java/nl/andrewlalis/util/TeamGenerator.java index aa7b426..15f6fe9 100644 --- a/src/main/java/nl/andrewlalis/util/TeamGenerator.java +++ b/src/main/java/nl/andrewlalis/util/TeamGenerator.java @@ -98,7 +98,7 @@ public class TeamGenerator { // Once we know this team is completely valid, we remove all the students in it from the list of singles. newTeam.setNumber(teamCount++); singleStudents.removeAll(Arrays.asList(newTeam.getStudents())); - studentTeams.add(newTeam); + assignStudentsToTeam(newTeam); logger.fine("Created team:\n" + newTeam); } } @@ -128,6 +128,7 @@ public class TeamGenerator { t.addMember(s); } studentTeams.add(t); + assignStudentsToTeam(t); logger.fine("Created team:\n" + t); } return studentTeams; @@ -187,4 +188,15 @@ public class TeamGenerator { return students; } + /** + * Assigns all students in the given team to that team, such that all students then have a reference to the team + * they are in. + * @param team The team to assign students for. + */ + private static void assignStudentsToTeam(StudentTeam team) { + for (Student student : team.getStudents()) { + student.assignToTeam(team); + } + } + } From a2797e54a658f13a7334b5c6fb9c20eee6296f90 Mon Sep 17 00:00:00 2001 From: andrewlalis Date: Mon, 22 Oct 2018 15:47:54 +0200 Subject: [PATCH 09/12] Implemented a basic detail view. --- src/main/java/nl/andrewlalis/Main.java | 63 +++++++----- .../java/nl/andrewlalis/model/Person.java | 26 ++++- .../andrewlalis/model/database/DbHelper.java | 20 ++++ .../FileSelectListener.java | 24 +---- .../andrewlalis/ui/view/ManagementView.java | 65 +++++++++---- .../ui/view/components/DetailPanel.java | 95 +++++++++++++++++++ .../ui/view/components/Detailable.java | 27 ++++++ .../view/table_models/DetailPairsModel.java | 64 +++++++++++++ .../view/table_models/StudentTableModel.java | 11 ++- src/main/java/nl/andrewlalis/util/Pair.java | 25 +++++ 10 files changed, 355 insertions(+), 65 deletions(-) create mode 100644 src/main/java/nl/andrewlalis/ui/view/components/DetailPanel.java create mode 100644 src/main/java/nl/andrewlalis/ui/view/components/Detailable.java create mode 100644 src/main/java/nl/andrewlalis/ui/view/table_models/DetailPairsModel.java create mode 100644 src/main/java/nl/andrewlalis/util/Pair.java diff --git a/src/main/java/nl/andrewlalis/Main.java b/src/main/java/nl/andrewlalis/Main.java index 834af58..0f3658a 100644 --- a/src/main/java/nl/andrewlalis/Main.java +++ b/src/main/java/nl/andrewlalis/Main.java @@ -3,12 +3,17 @@ package nl.andrewlalis; import nl.andrewlalis.command.CommandExecutor; import nl.andrewlalis.command.executables.*; import nl.andrewlalis.git_api.GithubManager; +import nl.andrewlalis.model.StudentTeam; +import nl.andrewlalis.model.database.DbHelper; import nl.andrewlalis.ui.view.InitializerApp; import nl.andrewlalis.ui.view.ManagementView; import nl.andrewlalis.ui.view.StartView; import nl.andrewlalis.util.CommandLine; import nl.andrewlalis.util.Logging; +import nl.andrewlalis.util.TeamGenerator; +import java.io.IOException; +import java.util.List; import java.util.Map; import java.util.logging.Logger; @@ -32,6 +37,41 @@ public class Main { // Initialize logger. Logging.setup(); + //startOldVersion(userOptions); + + logger.info("GithubManager for Github Repositories in Educational Organizations.\n" + + "© Andrew Lalis (2018), All rights reserved.\n" + + "Program initialized."); + + GithubManager manager = new GithubManager(); + managementView = new ManagementView(manager); + + initializeTestingData(); + StartView startView = new StartView(manager, "InitializerTesting", userOptions.get("token")); + } + + /** + * @return The management view used for the application. + */ + public static ManagementView getManagementView() { + return managementView; + } + + private static void initializeTestingData() { + try { + List teams = TeamGenerator.generateFromCSV("/home/andrew/Documents/School/ta/GithubInitializer/sampleAOOP.csv", 2); + DbHelper.saveStudentTeams(teams); + managementView.updateModels(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + /** + * Legacy code to run the old version of the application. + * @param userOptions The options the user has entered in the command line. + */ + public static void startOldVersion(Map userOptions) { // Command executor which will be used by all actions the user can do. CommandExecutor executor = new CommandExecutor(); @@ -50,29 +90,6 @@ public class Main { executor.registerCommand("delegate_student_teams", new DelegateStudentTeams(app)); executor.registerCommand("setup_student_repos", new SetupStudentRepos(app)); executor.registerCommand("list_repos", new ListRepos()); - - logger.info("GithubManager for Github Repositories in Educational Organizations.\n" + - "© Andrew Lalis (2018), All rights reserved.\n" + - "Program initialized."); - - GithubManager manager = new GithubManager(); - managementView = new ManagementView(manager); - -// SessionFactory factory = DbUtil.getSessionFactory(); -// Session session = factory.openSession(); -// session.beginTransaction(); -// System.out.println(session.save(new Student(1, "a", "a@e.com", "git", null))); -// session.getTransaction().commit(); -// session.close(); - - StartView startView = new StartView(manager, "InitializerTesting", userOptions.get("token")); - } - - /** - * @return The management view used for the application. - */ - public static ManagementView getManagementView() { - return managementView; } } diff --git a/src/main/java/nl/andrewlalis/model/Person.java b/src/main/java/nl/andrewlalis/model/Person.java index 62639b9..10b2488 100644 --- a/src/main/java/nl/andrewlalis/model/Person.java +++ b/src/main/java/nl/andrewlalis/model/Person.java @@ -1,10 +1,14 @@ package nl.andrewlalis.model; import nl.andrewlalis.model.database.BaseEntity; +import nl.andrewlalis.ui.view.components.Detailable; +import nl.andrewlalis.util.Pair; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Table; +import java.util.ArrayList; +import java.util.List; /** * A generic object that students, teaching assistants, and professors can extend from. This covers all the basic @@ -12,7 +16,7 @@ import javax.persistence.Table; */ @Entity(name = "Person") @Table(name = "persons") -public abstract class Person extends BaseEntity { +public abstract class Person extends BaseEntity implements Detailable { /** * The unique identification number for this person. (P- or S-Number) @@ -138,4 +142,24 @@ public abstract class Person extends BaseEntity { public String toString() { return this.getName() + ", " + this.getNumber() + ", " + this.getEmailAddress() + ", " + this.getGithubUsername(); } + + @Override + public String getDetailName() { + return this.getName() + ", " + this.getNumber(); + } + + @Override + public String getDetailDescription() { + return null; + } + + @Override + public List> getDetailPairs() { + List> pairs = new ArrayList<>(); + pairs.add(new Pair<>("Name", this.getName())); + pairs.add(new Pair<>("Number", String.valueOf(this.getNumber()))); + pairs.add(new Pair<>("Email Address", this.getEmailAddress())); + pairs.add(new Pair<>("Github Username", this.getGithubUsername())); + return pairs; + } } diff --git a/src/main/java/nl/andrewlalis/model/database/DbHelper.java b/src/main/java/nl/andrewlalis/model/database/DbHelper.java index fde57e2..e9e163a 100644 --- a/src/main/java/nl/andrewlalis/model/database/DbHelper.java +++ b/src/main/java/nl/andrewlalis/model/database/DbHelper.java @@ -1,8 +1,10 @@ package nl.andrewlalis.model.database; import nl.andrewlalis.model.Student; +import nl.andrewlalis.model.StudentTeam; import org.hibernate.Session; import org.hibernate.SessionFactory; +import org.hibernate.Transaction; import java.util.List; @@ -23,4 +25,22 @@ public class DbHelper { return students; } + /** + * Saves a list of student teams to the database. + */ + public static void saveStudentTeams(List teams) { + Session session = DbUtil.getSessionFactory().openSession(); + Transaction tx = session.beginTransaction(); + + for (StudentTeam team : teams) { + for (Student s : team.getStudents()) { + session.save(s); + } + session.save(team); + } + + tx.commit(); + session.close(); + } + } diff --git a/src/main/java/nl/andrewlalis/ui/control/listeners/input_students_file_view/FileSelectListener.java b/src/main/java/nl/andrewlalis/ui/control/listeners/input_students_file_view/FileSelectListener.java index afc053b..3c014b5 100644 --- a/src/main/java/nl/andrewlalis/ui/control/listeners/input_students_file_view/FileSelectListener.java +++ b/src/main/java/nl/andrewlalis/ui/control/listeners/input_students_file_view/FileSelectListener.java @@ -1,13 +1,10 @@ package nl.andrewlalis.ui.control.listeners.input_students_file_view; import nl.andrewlalis.Main; -import nl.andrewlalis.model.Student; import nl.andrewlalis.model.StudentTeam; -import nl.andrewlalis.model.database.DbUtil; +import nl.andrewlalis.model.database.DbHelper; import nl.andrewlalis.ui.view.InputStudentsFileView; import nl.andrewlalis.util.TeamGenerator; -import org.hibernate.Session; -import org.hibernate.Transaction; import javax.swing.*; import javax.swing.filechooser.FileFilter; @@ -42,10 +39,7 @@ public class FileSelectListener implements ActionListener { chooser.addChoosableFileFilter(new FileFilter() { @Override public boolean accept(File file) { - if (file.isDirectory()) { - return true; - } - return file.getName().toLowerCase().endsWith(".csv"); + return file.isDirectory() || file.getName().toLowerCase().endsWith(".csv"); } @Override @@ -59,19 +53,7 @@ public class FileSelectListener implements ActionListener { int teamSize = this.fileView.getStudentsPerTeam(); try { List teams = TeamGenerator.generateFromCSV(chooser.getSelectedFile().getAbsolutePath(), teamSize); - Session session = DbUtil.getSessionFactory().openSession(); - Transaction tx = session.beginTransaction(); - - for (StudentTeam team : teams) { - for (Student s : team.getStudents()) { - session.save(s); - } - session.save(team); - } - - tx.commit(); - session.close(); - + DbHelper.saveStudentTeams(teams); Main.getManagementView().updateModels(); } catch (IOException e) { e.printStackTrace(); diff --git a/src/main/java/nl/andrewlalis/ui/view/ManagementView.java b/src/main/java/nl/andrewlalis/ui/view/ManagementView.java index e0462a8..f76005f 100644 --- a/src/main/java/nl/andrewlalis/ui/view/ManagementView.java +++ b/src/main/java/nl/andrewlalis/ui/view/ManagementView.java @@ -3,6 +3,7 @@ package nl.andrewlalis.ui.view; import nl.andrewlalis.git_api.GithubManager; import nl.andrewlalis.model.database.DbHelper; import nl.andrewlalis.model.database.DbUtil; +import nl.andrewlalis.ui.view.components.DetailPanel; import nl.andrewlalis.ui.view.table_models.StudentTableModel; import javax.swing.*; @@ -20,6 +21,11 @@ public class ManagementView extends AbstractView { */ private StudentTableModel studentsModel; + /** + * A panel which displays the details of selected entities. + */ + private DetailPanel detailPanel; + public ManagementView(GithubManager githubManager) { super( "Course Management", @@ -30,14 +36,14 @@ public class ManagementView extends AbstractView { ); this.setExtendedState(this.getExtendedState() | JFrame.MAXIMIZED_BOTH); - // Dispose of all parents when this window closes. + // Dispose of all parents when this window closes. This is unique to the management view. this.addWindowListener(new WindowAdapter() { @Override public void windowClosed(WindowEvent windowEvent) { for (AbstractView parent : getParentViews()) { parent.dispose(); } - DbUtil.tearDown(); + DbUtil.tearDown(); // Shut down the database session factory once everything is done. } }); } @@ -45,9 +51,12 @@ public class ManagementView extends AbstractView { @Override protected JPanel buildContentPane() { JPanel contentPane = new JPanel(new BorderLayout()); + contentPane.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); + + this.detailPanel = new DetailPanel(); contentPane.add(this.buildCommandPanel(), BorderLayout.WEST); - contentPane.add(this.buildDetailPanel(), BorderLayout.EAST); + contentPane.add(this.detailPanel, BorderLayout.EAST); contentPane.add(this.buildOverviewPanel(), BorderLayout.CENTER); return contentPane; @@ -58,6 +67,7 @@ public class ManagementView extends AbstractView { */ private JPanel buildCommandPanel() { JPanel commandPanel = new JPanel(new BorderLayout()); + commandPanel.setBorder(BorderFactory.createLoweredBevelBorder()); commandPanel.add(new JLabel("Commands", SwingConstants.CENTER), BorderLayout.NORTH); commandPanel.add(new JTextArea("Command prompt area goes here."), BorderLayout.CENTER); @@ -72,38 +82,55 @@ public class ManagementView extends AbstractView { return commandPanel; } - /** - * @return A JPanel for the entity details section. - */ - private JPanel buildDetailPanel() { - JPanel detailPanel = new JPanel(new BorderLayout()); - - detailPanel.add(new JLabel("Details", SwingConstants.CENTER), BorderLayout.NORTH); - - return detailPanel; - } - /** * @return Builds the overview panel, containing a listing of entities. */ private JPanel buildOverviewPanel() { JPanel overviewPanel = new JPanel(new BorderLayout()); + overviewPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); - overviewPanel.add(new JLabel("Overview"), BorderLayout.NORTH); + overviewPanel.add(new JLabel("Overview", SwingConstants.CENTER), BorderLayout.NORTH); // The real container for all the data views. JTabbedPane tabbedPane = new JTabbedPane(); - this.studentsModel = new StudentTableModel(DbHelper.getStudents()); - JTable studentsTable = new JTable(this.studentsModel); - JScrollPane studentsScrollPane = new JScrollPane(studentsTable); - tabbedPane.addTab("Students", studentsScrollPane); + tabbedPane.addTab("Students", this.buildStudentsTablePanel()); + tabbedPane.addTab("Student Teams", this.buildStudentTeamsTablePanel()); + tabbedPane.addTab("Teaching Assistants", this.buildTAsTablePanel()); overviewPanel.add(tabbedPane, BorderLayout.CENTER); return overviewPanel; } + /** + * @return A JPanel to be put into a tab for display of a list of students. + */ + private JPanel buildStudentsTablePanel() { + // Initialize the model, table, and a surrounding scroll pane. + this.studentsModel = new StudentTableModel(DbHelper.getStudents()); + + JPanel surroundingPanel = new JPanel(new BorderLayout()); + + JTable table = new JTable(this.studentsModel); + table.setFillsViewportHeight(true); + table.getSelectionModel().addListSelectionListener(listSelectionEvent -> { + detailPanel.setDetailableEntity(studentsModel.getStudentAt(table.getSelectedRow())); + }); + JScrollPane scrollPane = new JScrollPane(table); + + surroundingPanel.add(scrollPane, BorderLayout.CENTER); + return surroundingPanel; + } + + private JPanel buildStudentTeamsTablePanel() { + return new JPanel(); + } + + private JPanel buildTAsTablePanel() { + return new JPanel(); + } + /** * Updates all models in the management view in accordance with the database. */ diff --git a/src/main/java/nl/andrewlalis/ui/view/components/DetailPanel.java b/src/main/java/nl/andrewlalis/ui/view/components/DetailPanel.java new file mode 100644 index 0000000..fe279cb --- /dev/null +++ b/src/main/java/nl/andrewlalis/ui/view/components/DetailPanel.java @@ -0,0 +1,95 @@ +package nl.andrewlalis.ui.view.components; + +import nl.andrewlalis.ui.view.table_models.DetailPairsModel; + +import javax.swing.*; +import java.awt.*; + +/** + * The detail panel is meant for displaying the details of a specific entity. The actual content/details to display is + * given by classes which implement the Detailable interface. + */ +public class DetailPanel extends JPanel { + + /** + * The name field shows the entity's name. + */ + private JTextField nameField; + + /** + * The description area shows the entity's description. + */ + private JTextArea descriptionTextArea; + + /** + * A model to represent the key-value pairs of this entity. + */ + private DetailPairsModel detailPairsModel; + + /** + * Creates the panel with some basic empty components. + */ + public DetailPanel() { + super(); + this.setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS)); + this.add(new JLabel("Details", SwingConstants.CENTER)); + this.add(this.buildNamePanel()); + this.add(this.buildDescriptionPanel()); + this.add(this.buildPairsTablePanel()); + } + + /** + * Sets this panel's properties according to the given entity. + * @param entity The entity to get details from. + */ + public void setDetailableEntity(Detailable entity) { + this.nameField.setText(entity.getDetailName()); + this.descriptionTextArea.setText(entity.getDetailDescription()); + this.detailPairsModel.setPairs(entity.getDetailPairs()); + } + + /** + * @return A JPanel containing the name field. + */ + private JPanel buildNamePanel() { + this.nameField = new JTextField(); + this.nameField.setEditable(false); + + JPanel namePanel = new JPanel(new BorderLayout()); + namePanel.add(new JLabel("Name:", SwingConstants.LEFT), BorderLayout.WEST); + namePanel.add(this.nameField, BorderLayout.CENTER); + + return namePanel; + } + + /** + * @return A JPanel containing the description text area. + */ + private JPanel buildDescriptionPanel() { + this.descriptionTextArea = new JTextArea(); + this.descriptionTextArea.setEditable(false); + + JPanel descriptionPanel = new JPanel(new BorderLayout()); + descriptionPanel.add(new JLabel("Description:", SwingConstants.CENTER), BorderLayout.NORTH); + descriptionPanel.add(this.descriptionTextArea, BorderLayout.CENTER); + + return descriptionPanel; + } + + /** + * @return A JPanel containing a table of properties. + */ + private JPanel buildPairsTablePanel() { + this.detailPairsModel = new DetailPairsModel(); + + JPanel tablePanel = new JPanel(new BorderLayout()); + tablePanel.add(new JLabel("Properties:", SwingConstants.LEFT), BorderLayout.NORTH); + + JTable pairsTable = new JTable(this.detailPairsModel); + JScrollPane scrollPane = new JScrollPane(pairsTable); + tablePanel.add(scrollPane, BorderLayout.CENTER); + + return tablePanel; + } + +} diff --git a/src/main/java/nl/andrewlalis/ui/view/components/Detailable.java b/src/main/java/nl/andrewlalis/ui/view/components/Detailable.java new file mode 100644 index 0000000..241c29b --- /dev/null +++ b/src/main/java/nl/andrewlalis/ui/view/components/Detailable.java @@ -0,0 +1,27 @@ +package nl.andrewlalis.ui.view.components; + +import nl.andrewlalis.util.Pair; + +import java.util.List; + +/** + * Objects which implement this interface must provide + */ +public interface Detailable { + + /** + * @return The display name for this object. + */ + String getDetailName(); + + /** + * @return Some more information to display below the name for this object. + */ + String getDetailDescription(); + + /** + * @return A String-to-String mapping for some key value pairs of properties to display. + */ + List> getDetailPairs(); + +} diff --git a/src/main/java/nl/andrewlalis/ui/view/table_models/DetailPairsModel.java b/src/main/java/nl/andrewlalis/ui/view/table_models/DetailPairsModel.java new file mode 100644 index 0000000..ccf5951 --- /dev/null +++ b/src/main/java/nl/andrewlalis/ui/view/table_models/DetailPairsModel.java @@ -0,0 +1,64 @@ +package nl.andrewlalis.ui.view.table_models; + +import nl.andrewlalis.util.Pair; + +import javax.swing.table.AbstractTableModel; +import java.util.ArrayList; +import java.util.List; + +/** + * Represents the small (2 column) table to display properties of a detailable entity. + */ +public class DetailPairsModel extends AbstractTableModel { + + /** + * The pairs of properties. + */ + private List> pairs; + + /** + * Columns for this model. + */ + private String[] columns = {"Property", "Value"}; + + /** + * Constructs an empty list of pairs. + */ + public DetailPairsModel() { + this.pairs = new ArrayList<>(); + } + + public void setPairs(List> pairs) { + this.pairs = pairs; + this.fireTableDataChanged(); + } + + @Override + public int getRowCount() { + return this.pairs.size(); + } + + @Override + public int getColumnCount() { + return this.columns.length; + } + + @Override + public String getColumnName(int i) { + return this.columns[i]; + } + + @Override + public Object getValueAt(int i, int i1) { + Pair pair = this.pairs.get(i); + + switch (i1) { + case 0: + return pair.getFirst(); + case 1: + return pair.getSecond(); + default: + return null; + } + } +} diff --git a/src/main/java/nl/andrewlalis/ui/view/table_models/StudentTableModel.java b/src/main/java/nl/andrewlalis/ui/view/table_models/StudentTableModel.java index 03fb2a3..5361704 100644 --- a/src/main/java/nl/andrewlalis/ui/view/table_models/StudentTableModel.java +++ b/src/main/java/nl/andrewlalis/ui/view/table_models/StudentTableModel.java @@ -37,6 +37,15 @@ public class StudentTableModel extends AbstractTableModel { this.fireTableDataChanged(); } + /** + * Gets the student in a particular row. + * @param row The row of the table. + * @return The student object at the specified row, or null if none is found. + */ + public Student getStudentAt(int row) { + return this.studentsList.get(row); + } + @Override public int getRowCount() { return studentsList.size(); @@ -54,7 +63,7 @@ public class StudentTableModel extends AbstractTableModel { @Override public Object getValueAt(int row, int col) { - Student student = studentsList.get(row); + Student student = this.getStudentAt(row); switch(col) { case 0: diff --git a/src/main/java/nl/andrewlalis/util/Pair.java b/src/main/java/nl/andrewlalis/util/Pair.java new file mode 100644 index 0000000..518a5e6 --- /dev/null +++ b/src/main/java/nl/andrewlalis/util/Pair.java @@ -0,0 +1,25 @@ +package nl.andrewlalis.util; + +/** + * A pair of objects. + * @param The first object. + * @param The second object. + */ +public class Pair { + + private T1 first; + private T2 second; + + public Pair(T1 first, T2 second) { + this.first = first; + this.second = second; + } + + public T1 getFirst() { + return first; + } + + public T2 getSecond() { + return second; + } +} From 926c28fd9a688241a57ec6294a70861a19378ae8 Mon Sep 17 00:00:00 2001 From: andrewlalis Date: Mon, 22 Oct 2018 16:23:21 +0200 Subject: [PATCH 10/12] Fixed bug in team generation code. --- src/main/java/nl/andrewlalis/Main.java | 1 + .../java/nl/andrewlalis/model/Student.java | 20 ++++++++++++++++++- .../nl/andrewlalis/model/StudentTeam.java | 2 +- .../view/table_models/StudentTableModel.java | 5 ++++- .../java/nl/andrewlalis/util/Logging.java | 7 +++++-- .../nl/andrewlalis/util/TeamGenerator.java | 2 ++ 6 files changed, 32 insertions(+), 5 deletions(-) diff --git a/src/main/java/nl/andrewlalis/Main.java b/src/main/java/nl/andrewlalis/Main.java index 0f3658a..af2f226 100644 --- a/src/main/java/nl/andrewlalis/Main.java +++ b/src/main/java/nl/andrewlalis/Main.java @@ -60,6 +60,7 @@ public class Main { private static void initializeTestingData() { try { List teams = TeamGenerator.generateFromCSV("/home/andrew/Documents/School/ta/GithubInitializer/sampleAOOP.csv", 2); + System.out.println(teams); DbHelper.saveStudentTeams(teams); managementView.updateModels(); } catch (IOException e) { diff --git a/src/main/java/nl/andrewlalis/model/Student.java b/src/main/java/nl/andrewlalis/model/Student.java index dbf9360..0ded9a1 100644 --- a/src/main/java/nl/andrewlalis/model/Student.java +++ b/src/main/java/nl/andrewlalis/model/Student.java @@ -1,5 +1,7 @@ package nl.andrewlalis.model; +import nl.andrewlalis.util.Pair; + import javax.persistence.*; import java.util.ArrayList; import java.util.List; @@ -20,7 +22,7 @@ public class Student extends Person { /** * A list of partners that the student has said that they would like to be partners with. */ - @ManyToMany + @ManyToMany(fetch = FetchType.EAGER) @JoinTable( name = "student_preferred_partners", joinColumns = { @JoinColumn(name = "student_id")}, @@ -95,4 +97,20 @@ public class Student extends Person { public StudentTeam getAssignedTeam() { return this.team; } + + @Override + public List> getDetailPairs() { + List> pairs = super.getDetailPairs(); + String teamNumber = "None"; + if (this.getAssignedTeam() != null) { + teamNumber = String.valueOf(this.getAssignedTeam().getNumber()); + } + pairs.add(new Pair<>("Team Number", teamNumber)); + + for (int i = 0; i < this.preferredPartners.size(); i++) { + pairs.add(new Pair<>("Preferred partner " + (i + 1), this.preferredPartners.get(i).getDetailName())); + } + + return pairs; + } } diff --git a/src/main/java/nl/andrewlalis/model/StudentTeam.java b/src/main/java/nl/andrewlalis/model/StudentTeam.java index 6d987e1..e862411 100644 --- a/src/main/java/nl/andrewlalis/model/StudentTeam.java +++ b/src/main/java/nl/andrewlalis/model/StudentTeam.java @@ -54,7 +54,7 @@ public class StudentTeam extends Team { // If the student doesn't have an preferred partners, then assume that this is valid. if (!studentA.getPreferredPartners().isEmpty()) { for (Student studentB : this.getStudents()) { - if (!studentA.equals(studentB) && !studentA.getPreferredPartners().contains(studentB.getNumber())) { + if (!studentA.equals(studentB) && !studentA.getPreferredPartners().contains(studentB)) { return false; } } diff --git a/src/main/java/nl/andrewlalis/ui/view/table_models/StudentTableModel.java b/src/main/java/nl/andrewlalis/ui/view/table_models/StudentTableModel.java index 5361704..6234223 100644 --- a/src/main/java/nl/andrewlalis/ui/view/table_models/StudentTableModel.java +++ b/src/main/java/nl/andrewlalis/ui/view/table_models/StudentTableModel.java @@ -43,7 +43,10 @@ public class StudentTableModel extends AbstractTableModel { * @return The student object at the specified row, or null if none is found. */ public Student getStudentAt(int row) { - return this.studentsList.get(row); + if (row >= 0 && row < this.studentsList.size()) { + return this.studentsList.get(row); + } + return null; } @Override diff --git a/src/main/java/nl/andrewlalis/util/Logging.java b/src/main/java/nl/andrewlalis/util/Logging.java index f0014ca..3145278 100644 --- a/src/main/java/nl/andrewlalis/util/Logging.java +++ b/src/main/java/nl/andrewlalis/util/Logging.java @@ -1,7 +1,10 @@ package nl.andrewlalis.util; import java.io.IOException; -import java.util.logging.*; +import java.util.logging.FileHandler; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.logging.SimpleFormatter; /** * Responsible for creating logs to standard output and writing to files. @@ -25,7 +28,7 @@ public class Logging { e.printStackTrace(); } - logger.setLevel(Level.ALL); + logger.setLevel(Level.FINEST); Logger.getLogger("").setLevel(Level.OFF); } diff --git a/src/main/java/nl/andrewlalis/util/TeamGenerator.java b/src/main/java/nl/andrewlalis/util/TeamGenerator.java index 15f6fe9..5487960 100644 --- a/src/main/java/nl/andrewlalis/util/TeamGenerator.java +++ b/src/main/java/nl/andrewlalis/util/TeamGenerator.java @@ -91,12 +91,14 @@ public class TeamGenerator { for (StudentTeam team : studentTeams) { if (newTeam.hasSameMembers(team)) { matchFound = true; + logger.finest("A team was found with the same members: " + team.getId()); break; } } if (!matchFound) { // Once we know this team is completely valid, we remove all the students in it from the list of singles. newTeam.setNumber(teamCount++); + studentTeams.add(newTeam); singleStudents.removeAll(Arrays.asList(newTeam.getStudents())); assignStudentsToTeam(newTeam); logger.fine("Created team:\n" + newTeam); From 134d2ede644810ce3849a9fa2990e9eac2509306 Mon Sep 17 00:00:00 2001 From: andrewlalis Date: Tue, 23 Oct 2018 10:55:55 +0200 Subject: [PATCH 11/12] Display both students and student teams. --- src/main/java/nl/andrewlalis/Main.java | 3 +- .../nl/andrewlalis/model/StudentTeam.java | 5 + src/main/java/nl/andrewlalis/model/Team.java | 34 ++++- .../andrewlalis/model/database/DbHelper.java | 16 +- .../andrewlalis/ui/view/ManagementView.java | 54 +++++-- .../table_models/StudentTeamTableModel.java | 142 ++++++++++++++++++ 6 files changed, 238 insertions(+), 16 deletions(-) create mode 100644 src/main/java/nl/andrewlalis/ui/view/table_models/StudentTeamTableModel.java diff --git a/src/main/java/nl/andrewlalis/Main.java b/src/main/java/nl/andrewlalis/Main.java index af2f226..a3d727f 100644 --- a/src/main/java/nl/andrewlalis/Main.java +++ b/src/main/java/nl/andrewlalis/Main.java @@ -59,8 +59,7 @@ public class Main { private static void initializeTestingData() { try { - List teams = TeamGenerator.generateFromCSV("/home/andrew/Documents/School/ta/GithubInitializer/sampleAOOP.csv", 2); - System.out.println(teams); + List teams = TeamGenerator.generateFromCSV("/home/andrew/Documents/School/ta/GithubInitializer/student-groups.csv", 2); DbHelper.saveStudentTeams(teams); managementView.updateModels(); } catch (IOException e) { diff --git a/src/main/java/nl/andrewlalis/model/StudentTeam.java b/src/main/java/nl/andrewlalis/model/StudentTeam.java index e862411..232cd0b 100644 --- a/src/main/java/nl/andrewlalis/model/StudentTeam.java +++ b/src/main/java/nl/andrewlalis/model/StudentTeam.java @@ -112,4 +112,9 @@ public class StudentTeam extends Team { public void setTaTeam(TATeam team) { this.taTeam = team; } + + @Override + public String getDetailName() { + return this.generateRepoDescription(); + } } diff --git a/src/main/java/nl/andrewlalis/model/Team.java b/src/main/java/nl/andrewlalis/model/Team.java index eee12cb..0f153f8 100644 --- a/src/main/java/nl/andrewlalis/model/Team.java +++ b/src/main/java/nl/andrewlalis/model/Team.java @@ -1,6 +1,8 @@ package nl.andrewlalis.model; import nl.andrewlalis.model.database.BaseEntity; +import nl.andrewlalis.ui.view.components.Detailable; +import nl.andrewlalis.util.Pair; import javax.persistence.*; import java.util.ArrayList; @@ -13,7 +15,7 @@ import java.util.List; */ @Entity(name = "Team") @Table(name = "teams") -public abstract class Team extends BaseEntity { +public abstract class Team extends BaseEntity implements Detailable { /** * An identification number unique to this team alone. @@ -24,7 +26,7 @@ public abstract class Team extends BaseEntity { /** * A list of members of this team. */ - @OneToMany + @OneToMany(fetch = FetchType.EAGER) @JoinTable( name = "team_members", joinColumns = {@JoinColumn(name = "team_id")}, @@ -41,6 +43,13 @@ public abstract class Team extends BaseEntity { this.members = new ArrayList<>(); } + /** + * Constructs an empty team with a default id of -1. + */ + protected Team() { + this(-1); + } + /** * @param newId The new number number to assign to this team. */ @@ -162,4 +171,25 @@ public abstract class Team extends BaseEntity { return sb.toString(); } + @Override + public String getDetailName() { + return String.valueOf(this.getNumber()); + } + + @Override + public String getDetailDescription() { + return null; + } + + @Override + public List> getDetailPairs() { + List> pairs = new ArrayList<>(); + pairs.add(new Pair<>("Number", this.getDetailName())); + + for (int i = 0; i < this.memberCount(); i++) { + pairs.add(new Pair<>("Member " + (i + 1), this.members.get(i).getDetailName())); + } + + return pairs; + } } diff --git a/src/main/java/nl/andrewlalis/model/database/DbHelper.java b/src/main/java/nl/andrewlalis/model/database/DbHelper.java index e9e163a..f8f007c 100644 --- a/src/main/java/nl/andrewlalis/model/database/DbHelper.java +++ b/src/main/java/nl/andrewlalis/model/database/DbHelper.java @@ -3,7 +3,6 @@ package nl.andrewlalis.model.database; import nl.andrewlalis.model.Student; import nl.andrewlalis.model.StudentTeam; import org.hibernate.Session; -import org.hibernate.SessionFactory; import org.hibernate.Transaction; import java.util.List; @@ -11,6 +10,7 @@ import java.util.List; /** * This class will contain some static methods to help in the retrieval of commonly used information. */ +@SuppressWarnings("unchecked") public class DbHelper { /** @@ -18,8 +18,7 @@ public class DbHelper { * @return A list of students. */ public static List getStudents() { - SessionFactory sessionFactory = DbUtil.getSessionFactory(); - Session session = sessionFactory.openSession(); + Session session = DbUtil.getSessionFactory().openSession(); List students = (List) session.createQuery("from Student").list(); session.close(); return students; @@ -43,4 +42,15 @@ public class DbHelper { session.close(); } + /** + * Gets a list of student teams in the database. + * @return A list of student teams. + */ + public static List getStudentTeams() { + Session session = DbUtil.getSessionFactory().openSession(); + List studentTeams = (List) session.createQuery("from StudentTeam").list(); + session.close(); + return studentTeams; + } + } diff --git a/src/main/java/nl/andrewlalis/ui/view/ManagementView.java b/src/main/java/nl/andrewlalis/ui/view/ManagementView.java index f76005f..669a65f 100644 --- a/src/main/java/nl/andrewlalis/ui/view/ManagementView.java +++ b/src/main/java/nl/andrewlalis/ui/view/ManagementView.java @@ -5,6 +5,7 @@ import nl.andrewlalis.model.database.DbHelper; import nl.andrewlalis.model.database.DbUtil; import nl.andrewlalis.ui.view.components.DetailPanel; import nl.andrewlalis.ui.view.table_models.StudentTableModel; +import nl.andrewlalis.ui.view.table_models.StudentTeamTableModel; import javax.swing.*; import java.awt.*; @@ -21,6 +22,11 @@ public class ManagementView extends AbstractView { */ private StudentTableModel studentsModel; + /** + * The model for the student teams table. + */ + private StudentTeamTableModel studentTeamModel; + /** * A panel which displays the details of selected entities. */ @@ -89,7 +95,7 @@ public class ManagementView extends AbstractView { JPanel overviewPanel = new JPanel(new BorderLayout()); overviewPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); - overviewPanel.add(new JLabel("Overview", SwingConstants.CENTER), BorderLayout.NORTH); + overviewPanel.add(this.buildSearchPanel(), BorderLayout.NORTH); // The real container for all the data views. JTabbedPane tabbedPane = new JTabbedPane(); @@ -103,6 +109,30 @@ public class ManagementView extends AbstractView { return overviewPanel; } + /** + * Builds a JPanel containing utilities to search the data in the various tables in the application. + * @return A JPanel containing search functionality. + */ + private JPanel buildSearchPanel() { + JPanel searchPanel = new JPanel(new BorderLayout()); + + searchPanel.add(new JLabel("Search", SwingConstants.LEFT), BorderLayout.WEST); + searchPanel.add(new JTextField(), BorderLayout.CENTER); + + return searchPanel; + } + + /** + * Provides a JScrollPane and JPanel to surround a table. + * @param table The table to wrap. + * @return The JPanel containing the table, wrapped in a JScrollPane. + */ + private JPanel buildGenericTablePanel(JTable table) { + JPanel surroundingPanel = new JPanel(new BorderLayout()); + surroundingPanel.add(new JScrollPane(table), BorderLayout.CENTER); + return surroundingPanel; + } + /** * @return A JPanel to be put into a tab for display of a list of students. */ @@ -110,21 +140,27 @@ public class ManagementView extends AbstractView { // Initialize the model, table, and a surrounding scroll pane. this.studentsModel = new StudentTableModel(DbHelper.getStudents()); - JPanel surroundingPanel = new JPanel(new BorderLayout()); - JTable table = new JTable(this.studentsModel); table.setFillsViewportHeight(true); table.getSelectionModel().addListSelectionListener(listSelectionEvent -> { detailPanel.setDetailableEntity(studentsModel.getStudentAt(table.getSelectedRow())); }); - JScrollPane scrollPane = new JScrollPane(table); - - surroundingPanel.add(scrollPane, BorderLayout.CENTER); - return surroundingPanel; + return this.buildGenericTablePanel(table); } + /** + * @return A JPanel to be put into a tab for display of a list of student teams. + */ private JPanel buildStudentTeamsTablePanel() { - return new JPanel(); + this.studentTeamModel = new StudentTeamTableModel(DbHelper.getStudentTeams()); + + JTable table = new JTable(this.studentTeamModel); + table.setFillsViewportHeight(true); + table.getSelectionModel().addListSelectionListener(listSelectionEvent -> { + detailPanel.setDetailableEntity(studentTeamModel.getStudentTeamAt(table.getSelectedRow())); + }); + + return this.buildGenericTablePanel(table); } private JPanel buildTAsTablePanel() { @@ -136,6 +172,6 @@ public class ManagementView extends AbstractView { */ public void updateModels() { this.studentsModel.setStudentsList(DbHelper.getStudents()); - + this.studentTeamModel.setStudentTeamsList(DbHelper.getStudentTeams()); } } diff --git a/src/main/java/nl/andrewlalis/ui/view/table_models/StudentTeamTableModel.java b/src/main/java/nl/andrewlalis/ui/view/table_models/StudentTeamTableModel.java new file mode 100644 index 0000000..c88ae14 --- /dev/null +++ b/src/main/java/nl/andrewlalis/ui/view/table_models/StudentTeamTableModel.java @@ -0,0 +1,142 @@ +package nl.andrewlalis.ui.view.table_models; + +import nl.andrewlalis.model.Student; +import nl.andrewlalis.model.StudentTeam; + +import javax.swing.table.AbstractTableModel; +import java.util.ArrayList; +import java.util.List; + +/** + * This table model represents the list of student teams. + */ +public class StudentTeamTableModel extends AbstractTableModel { + + /** + * The container for the data objects. + */ + private List studentTeamsList; + + /** + * The column headers for this model. In addition to these headers, this model will dynamically create headers for + * each additional student to be listed in the table. + */ + private final String[] staticColumns = {"Number", "Member Count"}; + + /** + * Dynamic columns which are generated depending on the teams. + */ + private String[] columns = {}; + + public StudentTeamTableModel() { + this.studentTeamsList = new ArrayList<>(); + } + + public StudentTeamTableModel(List teams) { + super(); + this.setStudentTeamsList(teams); + } + + /** + * Sets a new list of student teams as the data for this list model. + * @param newList A list of student teams to display in the table model. + */ + public void setStudentTeamsList(List newList) { + this.studentTeamsList = newList; + int maxMembers = this.getMaxMemberCount(); + if (this.columns.length != maxMembers) { + this.generateColumnNames(maxMembers); + this.fireTableStructureChanged(); + } + this.fireTableDataChanged(); + } + + /** + * Gets the student team in a particular row. + * @param row The row of the table. + * @return The student team object at the specified row, or null if none is found. + */ + public StudentTeam getStudentTeamAt(int row) { + if (row >= 0 && row < this.studentTeamsList.size()) { + return this.studentTeamsList.get(row); + } + return null; + } + + @Override + public int getRowCount() { + return this.studentTeamsList.size(); + } + + @Override + public int getColumnCount() { + return this.columns.length; + } + + @Override + public String getColumnName(int i) { + if (i >= 0 && i < this.columns.length) { + return this.columns[i]; + } else { + return null; + } + } + + @Override + public Object getValueAt(int i, int i1) { + StudentTeam team = this.getStudentTeamAt(i); + + switch (i1) { + case 0: + return team.getId(); + case 1: + return team.memberCount(); + default: + return this.getMemberInColumn(team, i1); + } + } + + /** + * Gets a particular student name in a column of the table. This is used for the staticColumns which show all members in + * the team. + * @param team The team for which to search for a student in. + * @param column The table column. + * @return The student detail name in a particular column, or null if none exists. + */ + private String getMemberInColumn(StudentTeam team, int column) { + Student[] students = team.getStudents(); + int index = column - this.staticColumns.length; // Subtract the number of static staticColumns. + if (index >= 0 && index < students.length) { + return students[index].getDetailName(); + } else { + return null; + } + } + + /** + * Gets the highest member count in the list of student teams. + * @return The maximum member count of all teams. + */ + private int getMaxMemberCount() { + int max = 0; + for (StudentTeam team : this.studentTeamsList) { + if (team.memberCount() > max) { + max = team.memberCount(); + } + } + return max; + } + + /** + * Generates column names, including some procedurally generated headers based on the number of members in the team. + * @param maxMembers The highest number of members a team has. + */ + private void generateColumnNames(int maxMembers) { + this.columns = new String[this.staticColumns.length + maxMembers]; + this.columns[0] = this.staticColumns[0]; + this.columns[1] = this.staticColumns[1]; + for (int i = 0; i < maxMembers; i++) { + this.columns[i + 2] = "Member " + (i + 1); + } + } +} From a1d23b82ad8a2aa3773f8b22508b63ceef275dad Mon Sep 17 00:00:00 2001 From: andrewlalis Date: Tue, 23 Oct 2018 14:19:31 +0200 Subject: [PATCH 12/12] Added some right-click functionality, and made dependencies safe. --- pom.xml | 8 ++-- .../nl/andrewlalis/git_api/GithubManager.java | 4 +- .../java/nl/andrewlalis/model/Person.java | 8 ++-- .../nl/andrewlalis/model/StudentTeam.java | 30 +++++++++---- .../management_view/PopupSelector.java | 42 +++++++++++++++++++ .../management_view/TableRowListener.java | 39 +++++++++++++++++ .../RemoveFromCourseListener.java | 28 +++++++++++++ .../student_actions/SetTeamListener.java | 31 ++++++++++++++ .../nl/andrewlalis/ui/view/AbstractView.java | 6 +++ .../andrewlalis/ui/view/ManagementView.java | 9 ++++ .../ui/view/dialogs/TeamChooserDialog.java | 18 ++++++++ .../table_models/StudentTeamTableModel.java | 9 ++-- 12 files changed, 211 insertions(+), 21 deletions(-) create mode 100644 src/main/java/nl/andrewlalis/ui/control/listeners/management_view/PopupSelector.java create mode 100644 src/main/java/nl/andrewlalis/ui/control/listeners/management_view/TableRowListener.java create mode 100644 src/main/java/nl/andrewlalis/ui/control/listeners/management_view/student_actions/RemoveFromCourseListener.java create mode 100644 src/main/java/nl/andrewlalis/ui/control/listeners/management_view/student_actions/SetTeamListener.java create mode 100644 src/main/java/nl/andrewlalis/ui/view/dialogs/TeamChooserDialog.java diff --git a/pom.xml b/pom.xml index 18af176..a6b89a7 100644 --- a/pom.xml +++ b/pom.xml @@ -30,12 +30,12 @@ commons-cli commons-cli - RELEASE + 1.4 - commons-email + org.apache.commons commons-email - RELEASE + 1.5 org.jetbrains @@ -46,7 +46,7 @@ org.apache.httpcomponents httpclient - RELEASE + 4.5.6 diff --git a/src/main/java/nl/andrewlalis/git_api/GithubManager.java b/src/main/java/nl/andrewlalis/git_api/GithubManager.java index 1f7c2a3..fe15ce7 100644 --- a/src/main/java/nl/andrewlalis/git_api/GithubManager.java +++ b/src/main/java/nl/andrewlalis/git_api/GithubManager.java @@ -251,7 +251,7 @@ public class GithubManager { return; } - team.setRepository(repo); + team.setRepositoryName(repo.getName()); team.setTaTeam(taTeam); this.protectMasterBranch(repo, taTeam.getGithubTeam()); @@ -339,7 +339,7 @@ public class GithubManager { GHUser user = this.github.getUser(student.getGithubUsername()); this.addCollaboratorToRepo(user, assignmentsRepo); - this.addCollaboratorToRepo(user, team.getRepository()); + this.addCollaboratorToRepo(user, this.organization.getRepository(team.getRepositoryName())); } } catch (IOException e) { logger.severe("Could not add students as collaborators to assignments or their repo.\n" + team); diff --git a/src/main/java/nl/andrewlalis/model/Person.java b/src/main/java/nl/andrewlalis/model/Person.java index 10b2488..d374761 100644 --- a/src/main/java/nl/andrewlalis/model/Person.java +++ b/src/main/java/nl/andrewlalis/model/Person.java @@ -21,25 +21,25 @@ public abstract class Person extends BaseEntity implements Detailable { /** * The unique identification number for this person. (P- or S-Number) */ - @Column(name="number") + @Column(name="number", nullable = false) protected int number; /** * The person's first and last name. */ - @Column(name="name") + @Column(name="name", nullable = false) protected String name; /** * The person's email address. */ - @Column(name="email_address") + @Column(name="email_address", nullable = false) protected String emailAddress; /** * The person's github username. */ - @Column(name="github_username") + @Column(name="github_username", nullable = false) protected String githubUsername; /** diff --git a/src/main/java/nl/andrewlalis/model/StudentTeam.java b/src/main/java/nl/andrewlalis/model/StudentTeam.java index 232cd0b..c545771 100644 --- a/src/main/java/nl/andrewlalis/model/StudentTeam.java +++ b/src/main/java/nl/andrewlalis/model/StudentTeam.java @@ -1,12 +1,13 @@ package nl.andrewlalis.model; -import org.kohsuke.github.GHRepository; +import nl.andrewlalis.util.Pair; +import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.ManyToOne; import javax.persistence.Table; -import javax.persistence.Transient; import java.util.Arrays; +import java.util.List; /** * Represents one or more students' collective information. @@ -18,8 +19,8 @@ public class StudentTeam extends Team { /** * The repository belonging to this team. */ - @Transient - private GHRepository repository; + @Column(name = "repository_name", unique = true) + private String repositoryName; /** * The TATeam responsible for this student team. @@ -97,12 +98,12 @@ public class StudentTeam extends Team { return sb.toString(); } - public GHRepository getRepository() { - return this.repository; + public String getRepositoryName() { + return this.repositoryName; } - public void setRepository(GHRepository repo) { - this.repository = repo; + public void setRepositoryName(String repositoryName) { + this.repositoryName = repositoryName; } public TATeam getTaTeam() { @@ -117,4 +118,17 @@ public class StudentTeam extends Team { public String getDetailName() { return this.generateRepoDescription(); } + + @Override + public List> getDetailPairs() { + List> pairs = super.getDetailPairs(); + pairs.add(new Pair<>("Repository Name", this.getRepositoryName())); + String taTeamName = "None"; + if (this.getTaTeam() != null) { + taTeamName = this.getTaTeam().getDetailName(); + } + pairs.add(new Pair<>("TA Team", taTeamName)); + + return pairs; + } } diff --git a/src/main/java/nl/andrewlalis/ui/control/listeners/management_view/PopupSelector.java b/src/main/java/nl/andrewlalis/ui/control/listeners/management_view/PopupSelector.java new file mode 100644 index 0000000..28e5f18 --- /dev/null +++ b/src/main/java/nl/andrewlalis/ui/control/listeners/management_view/PopupSelector.java @@ -0,0 +1,42 @@ +package nl.andrewlalis.ui.control.listeners.management_view; + +import javax.swing.*; +import javax.swing.event.PopupMenuEvent; +import javax.swing.event.PopupMenuListener; +import java.awt.*; + +/** + * This listener, when added to a JPopupMenu, will select the clicked row when a user right-clicks on the table. + */ +public class PopupSelector implements PopupMenuListener { + + /** + * The table on which to select rows. + */ + private JTable table; + + public PopupSelector(JTable table) { + this.table = table; + } + + @Override + public void popupMenuWillBecomeVisible(PopupMenuEvent popupMenuEvent) { + JPopupMenu popupMenu = (JPopupMenu) popupMenuEvent.getSource(); + SwingUtilities.invokeLater(() -> { + int rowAtPoint = table.rowAtPoint(SwingUtilities.convertPoint(popupMenu, new Point(0, 0), table)); + if (rowAtPoint > -1) { + table.setRowSelectionInterval(rowAtPoint, rowAtPoint); + } + }); + } + + @Override + public void popupMenuWillBecomeInvisible(PopupMenuEvent popupMenuEvent) { + + } + + @Override + public void popupMenuCanceled(PopupMenuEvent popupMenuEvent) { + + } +} diff --git a/src/main/java/nl/andrewlalis/ui/control/listeners/management_view/TableRowListener.java b/src/main/java/nl/andrewlalis/ui/control/listeners/management_view/TableRowListener.java new file mode 100644 index 0000000..4904d5a --- /dev/null +++ b/src/main/java/nl/andrewlalis/ui/control/listeners/management_view/TableRowListener.java @@ -0,0 +1,39 @@ +package nl.andrewlalis.ui.control.listeners.management_view; + +import javax.swing.*; +import java.awt.event.ActionListener; + +/** + * This abstract class defines listeners which listen to tables, that is, a table row is clicked on in the table, and + * that is passed to children. + */ +public abstract class TableRowListener implements ActionListener { + + /** + * The table of which to get the row. + */ + private JTable table; + + /** + * Constructs a table row listener. + * @param table The table on which to get selected rows. + */ + public TableRowListener(JTable table) { + this.table = table; + } + + /** + * @return The selected row. + */ + protected final int getSelectedRow() { + return this.table.getSelectedRow(); + } + + /** + * @return The table that this listener is attached to. + */ + protected final JTable getTable() { + return this.table; + } + +} diff --git a/src/main/java/nl/andrewlalis/ui/control/listeners/management_view/student_actions/RemoveFromCourseListener.java b/src/main/java/nl/andrewlalis/ui/control/listeners/management_view/student_actions/RemoveFromCourseListener.java new file mode 100644 index 0000000..c0c9c3b --- /dev/null +++ b/src/main/java/nl/andrewlalis/ui/control/listeners/management_view/student_actions/RemoveFromCourseListener.java @@ -0,0 +1,28 @@ +package nl.andrewlalis.ui.control.listeners.management_view.student_actions; + +import nl.andrewlalis.ui.control.listeners.management_view.TableRowListener; +import nl.andrewlalis.ui.view.table_models.StudentTableModel; + +import javax.swing.*; +import java.awt.event.ActionEvent; + +/** + * Listens for when the user intends to remove a selected student from the course entirely. This entails a few things: + * 1. Remove them from any team they are in. + * 2. Archive any repository that is empty as a result of removing them. + * 3. Remove the student from the list of students. + * (This should not actually remove the record, just set it as removed.) + */ +public class RemoveFromCourseListener extends TableRowListener { + + public RemoveFromCourseListener(JTable table) { + super(table); + } + + @Override + public void actionPerformed(ActionEvent actionEvent) { + StudentTableModel model = (StudentTableModel) this.getTable().getModel(); + + System.out.println(model.getStudentAt(this.getSelectedRow())); + } +} diff --git a/src/main/java/nl/andrewlalis/ui/control/listeners/management_view/student_actions/SetTeamListener.java b/src/main/java/nl/andrewlalis/ui/control/listeners/management_view/student_actions/SetTeamListener.java new file mode 100644 index 0000000..0456f61 --- /dev/null +++ b/src/main/java/nl/andrewlalis/ui/control/listeners/management_view/student_actions/SetTeamListener.java @@ -0,0 +1,31 @@ +package nl.andrewlalis.ui.control.listeners.management_view.student_actions; + +import nl.andrewlalis.model.Student; +import nl.andrewlalis.model.StudentTeam; +import nl.andrewlalis.ui.control.listeners.management_view.TableRowListener; +import nl.andrewlalis.ui.view.dialogs.TeamChooserDialog; +import nl.andrewlalis.ui.view.table_models.StudentTableModel; + +import javax.swing.*; +import java.awt.event.ActionEvent; + +/** + * Listens for when the user wishes to set the team of a certain student. This should do the following: + * 1. User selects team to set student to, or chooses to create a new team. + * 2. StudentTeam object is created or modified. + * 3. The repository is updated automatically. + */ +public class SetTeamListener extends TableRowListener { + + public SetTeamListener(JTable table) { + super(table); + } + + @Override + public void actionPerformed(ActionEvent actionEvent) { + StudentTableModel model = (StudentTableModel) this.getTable().getModel(); + Student student = model.getStudentAt(this.getSelectedRow()); + + StudentTeam chosenTeam = (StudentTeam) new TeamChooserDialog(SwingUtilities.getWindowAncestor(this.getTable())).getSelectedTeam(); + } +} diff --git a/src/main/java/nl/andrewlalis/ui/view/AbstractView.java b/src/main/java/nl/andrewlalis/ui/view/AbstractView.java index 0d29dfe..7499af4 100644 --- a/src/main/java/nl/andrewlalis/ui/view/AbstractView.java +++ b/src/main/java/nl/andrewlalis/ui/view/AbstractView.java @@ -28,6 +28,11 @@ public abstract class AbstractView extends JFrame { */ private List parentViews; + /** + * The image icon for all abstract views. + */ + private static final ImageIcon imageIcon = new ImageIcon(AbstractView.class.getResource("/image/icon.png")); + /** * Initializes the view by packing the content pane as it is defined by any child, and setting some generic swing * values. @@ -42,6 +47,7 @@ public abstract class AbstractView extends JFrame { this.githubManager = githubManager; this.childViews = new ArrayList<>(); this.parentViews = new ArrayList<>(); + this.setIconImage(imageIcon.getImage()); this.setContentPane(this.buildContentPane()); this.setDefaultCloseOperation(defaultCloseOperation); if (preferredSize != null) { diff --git a/src/main/java/nl/andrewlalis/ui/view/ManagementView.java b/src/main/java/nl/andrewlalis/ui/view/ManagementView.java index 669a65f..5e890f1 100644 --- a/src/main/java/nl/andrewlalis/ui/view/ManagementView.java +++ b/src/main/java/nl/andrewlalis/ui/view/ManagementView.java @@ -3,6 +3,8 @@ package nl.andrewlalis.ui.view; import nl.andrewlalis.git_api.GithubManager; import nl.andrewlalis.model.database.DbHelper; import nl.andrewlalis.model.database.DbUtil; +import nl.andrewlalis.ui.control.listeners.management_view.PopupSelector; +import nl.andrewlalis.ui.control.listeners.management_view.student_actions.RemoveFromCourseListener; import nl.andrewlalis.ui.view.components.DetailPanel; import nl.andrewlalis.ui.view.table_models.StudentTableModel; import nl.andrewlalis.ui.view.table_models.StudentTeamTableModel; @@ -145,6 +147,13 @@ public class ManagementView extends AbstractView { table.getSelectionModel().addListSelectionListener(listSelectionEvent -> { detailPanel.setDetailableEntity(studentsModel.getStudentAt(table.getSelectedRow())); }); + JPopupMenu menu = new JPopupMenu("Menu"); + JMenuItem removeItem = new JMenuItem("Remove from course"); + removeItem.addActionListener(new RemoveFromCourseListener(table)); + menu.add(removeItem); + menu.addPopupMenuListener(new PopupSelector(table)); + table.setComponentPopupMenu(menu); + return this.buildGenericTablePanel(table); } diff --git a/src/main/java/nl/andrewlalis/ui/view/dialogs/TeamChooserDialog.java b/src/main/java/nl/andrewlalis/ui/view/dialogs/TeamChooserDialog.java new file mode 100644 index 0000000..3431df4 --- /dev/null +++ b/src/main/java/nl/andrewlalis/ui/view/dialogs/TeamChooserDialog.java @@ -0,0 +1,18 @@ +package nl.andrewlalis.ui.view.dialogs; + +import nl.andrewlalis.model.Team; + +import javax.swing.*; +import java.awt.*; + +public class TeamChooserDialog extends JDialog { + + public TeamChooserDialog(Window parent) { + super(parent); + } + + public Team getSelectedTeam() { + return null; + } + +} diff --git a/src/main/java/nl/andrewlalis/ui/view/table_models/StudentTeamTableModel.java b/src/main/java/nl/andrewlalis/ui/view/table_models/StudentTeamTableModel.java index c88ae14..a6300af 100644 --- a/src/main/java/nl/andrewlalis/ui/view/table_models/StudentTeamTableModel.java +++ b/src/main/java/nl/andrewlalis/ui/view/table_models/StudentTeamTableModel.java @@ -21,7 +21,7 @@ public class StudentTeamTableModel extends AbstractTableModel { * The column headers for this model. In addition to these headers, this model will dynamically create headers for * each additional student to be listed in the table. */ - private final String[] staticColumns = {"Number", "Member Count"}; + private final String[] staticColumns = {"Number", "Repository Name", "TA Team"}; /** * Dynamic columns which are generated depending on the teams. @@ -90,7 +90,9 @@ public class StudentTeamTableModel extends AbstractTableModel { case 0: return team.getId(); case 1: - return team.memberCount(); + return (team.getRepositoryName() == null) ? "None" : team.getRepositoryName(); + case 2: + return (team.getTaTeam() == null) ? "None" : team.getTaTeam().getDetailName(); default: return this.getMemberInColumn(team, i1); } @@ -135,8 +137,9 @@ public class StudentTeamTableModel extends AbstractTableModel { this.columns = new String[this.staticColumns.length + maxMembers]; this.columns[0] = this.staticColumns[0]; this.columns[1] = this.staticColumns[1]; + this.columns[2] = this.staticColumns[2]; for (int i = 0; i < maxMembers; i++) { - this.columns[i + 2] = "Member " + (i + 1); + this.columns[i + this.staticColumns.length] = "Member " + (i + 1); } } }