diff --git a/design/SystemDiagram.odg b/design/SystemDiagram.odg
new file mode 100644
index 0000000..f857bab
Binary files /dev/null and b/design/SystemDiagram.odg differ
diff --git a/design/SystemDiagram.pdf b/design/SystemDiagram.pdf
new file mode 100644
index 0000000..f2b342c
Binary files /dev/null and b/design/SystemDiagram.pdf differ
diff --git a/pom.xml b/pom.xml
index 7137563..a6b89a7 100644
--- a/pom.xml
+++ b/pom.xml
@@ -21,10 +21,6 @@
jar
-
- 2.9.6
-
-
org.apache.commons
@@ -34,12 +30,12 @@
commons-cli
commons-cli
- RELEASE
+ 1.4
- commons-email
+ org.apache.commons
commons-email
- RELEASE
+ 1.5
org.jetbrains
@@ -50,22 +46,7 @@
org.apache.httpcomponents
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}
+ 4.5.6
@@ -73,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 ed72ebc..a3d727f 100644
--- a/src/main/java/nl/andrewlalis/Main.java
+++ b/src/main/java/nl/andrewlalis/Main.java
@@ -1,11 +1,19 @@
package nl.andrewlalis;
-import nl.andrewlalis.ui.control.command.CommandExecutor;
-import nl.andrewlalis.ui.control.command.executables.*;
+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;
@@ -16,6 +24,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.
@@ -24,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/student-groups.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();
@@ -41,10 +89,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));
-
- logger.info("GithubManager for Github Repositories in Educational Organizations.\n" +
- "© Andrew Lalis (2018), All rights reserved.\n" +
- "Program initialized.");
+ executor.registerCommand("list_repos", new ListRepos());
}
}
diff --git a/src/main/java/nl/andrewlalis/ui/control/command/CommandExecutor.java b/src/main/java/nl/andrewlalis/command/CommandExecutor.java
similarity index 70%
rename from src/main/java/nl/andrewlalis/ui/control/command/CommandExecutor.java
rename to src/main/java/nl/andrewlalis/command/CommandExecutor.java
index d66bb01..f646a54 100644
--- a/src/main/java/nl/andrewlalis/ui/control/command/CommandExecutor.java
+++ b/src/main/java/nl/andrewlalis/command/CommandExecutor.java
@@ -1,8 +1,8 @@
-package nl.andrewlalis.ui.control.command;
+package nl.andrewlalis.command;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.Map;
+import nl.andrewlalis.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/Executable.java b/src/main/java/nl/andrewlalis/command/Executable.java
similarity index 90%
rename from src/main/java/nl/andrewlalis/ui/control/command/Executable.java
rename to src/main/java/nl/andrewlalis/command/Executable.java
index 77ed4bd..f826ba9 100644
--- a/src/main/java/nl/andrewlalis/ui/control/command/Executable.java
+++ b/src/main/java/nl/andrewlalis/command/Executable.java
@@ -1,4 +1,4 @@
-package nl.andrewlalis.ui.control.command;
+package nl.andrewlalis.command;
/**
* Classes which implement this interface tell that they may be 'executed', either via command-line, or through the use
diff --git a/src/main/java/nl/andrewlalis/ui/control/command/executables/ArchiveRepos.java b/src/main/java/nl/andrewlalis/command/executables/ArchiveRepos.java
similarity index 90%
rename from src/main/java/nl/andrewlalis/ui/control/command/executables/ArchiveRepos.java
rename to src/main/java/nl/andrewlalis/command/executables/ArchiveRepos.java
index ef10441..28f1e08 100644
--- a/src/main/java/nl/andrewlalis/ui/control/command/executables/ArchiveRepos.java
+++ b/src/main/java/nl/andrewlalis/command/executables/ArchiveRepos.java
@@ -1,4 +1,4 @@
-package nl.andrewlalis.ui.control.command.executables;
+package nl.andrewlalis.command.executables;
import nl.andrewlalis.git_api.GithubManager;
diff --git a/src/main/java/nl/andrewlalis/ui/control/command/executables/DefineTaTeams.java b/src/main/java/nl/andrewlalis/command/executables/DefineTaTeams.java
similarity index 94%
rename from src/main/java/nl/andrewlalis/ui/control/command/executables/DefineTaTeams.java
rename to src/main/java/nl/andrewlalis/command/executables/DefineTaTeams.java
index add587d..8cd9703 100644
--- a/src/main/java/nl/andrewlalis/ui/control/command/executables/DefineTaTeams.java
+++ b/src/main/java/nl/andrewlalis/command/executables/DefineTaTeams.java
@@ -1,8 +1,8 @@
-package nl.andrewlalis.ui.control.command.executables;
+package nl.andrewlalis.command.executables;
import nl.andrewlalis.git_api.GithubManager;
-import nl.andrewlalis.ui.view.dialogs.DefineTaTeamsDialog;
import nl.andrewlalis.ui.view.InitializerApp;
+import nl.andrewlalis.ui.view.dialogs.DefineTaTeamsDialog;
/**
* This executable is slightly different from the others, in that it opens up a user interface to make editing TA teams
diff --git a/src/main/java/nl/andrewlalis/ui/control/command/executables/DelegateStudentTeams.java b/src/main/java/nl/andrewlalis/command/executables/DelegateStudentTeams.java
similarity index 98%
rename from src/main/java/nl/andrewlalis/ui/control/command/executables/DelegateStudentTeams.java
rename to src/main/java/nl/andrewlalis/command/executables/DelegateStudentTeams.java
index 69ed042..95757d0 100644
--- a/src/main/java/nl/andrewlalis/ui/control/command/executables/DelegateStudentTeams.java
+++ b/src/main/java/nl/andrewlalis/command/executables/DelegateStudentTeams.java
@@ -1,4 +1,4 @@
-package nl.andrewlalis.ui.control.command.executables;
+package nl.andrewlalis.command.executables;
import nl.andrewlalis.git_api.GithubManager;
import nl.andrewlalis.model.StudentTeam;
diff --git a/src/main/java/nl/andrewlalis/ui/control/command/executables/DeleteRepos.java b/src/main/java/nl/andrewlalis/command/executables/DeleteRepos.java
similarity index 87%
rename from src/main/java/nl/andrewlalis/ui/control/command/executables/DeleteRepos.java
rename to src/main/java/nl/andrewlalis/command/executables/DeleteRepos.java
index d38b615..67293f5 100644
--- a/src/main/java/nl/andrewlalis/ui/control/command/executables/DeleteRepos.java
+++ b/src/main/java/nl/andrewlalis/command/executables/DeleteRepos.java
@@ -1,4 +1,4 @@
-package nl.andrewlalis.ui.control.command.executables;
+package nl.andrewlalis.command.executables;
import nl.andrewlalis.git_api.GithubManager;
diff --git a/src/main/java/nl/andrewlalis/command/executables/ExecutableContext.java b/src/main/java/nl/andrewlalis/command/executables/ExecutableContext.java
new file mode 100644
index 0000000..a2d0f36
--- /dev/null
+++ b/src/main/java/nl/andrewlalis/command/executables/ExecutableContext.java
@@ -0,0 +1,34 @@
+package nl.andrewlalis.command.executables;
+
+import nl.andrewlalis.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);
+ }
+
+}
diff --git a/src/main/java/nl/andrewlalis/ui/control/command/executables/GenerateAssignmentsRepo.java b/src/main/java/nl/andrewlalis/command/executables/GenerateAssignmentsRepo.java
similarity index 93%
rename from src/main/java/nl/andrewlalis/ui/control/command/executables/GenerateAssignmentsRepo.java
rename to src/main/java/nl/andrewlalis/command/executables/GenerateAssignmentsRepo.java
index 2ccb06b..be4dec0 100644
--- a/src/main/java/nl/andrewlalis/ui/control/command/executables/GenerateAssignmentsRepo.java
+++ b/src/main/java/nl/andrewlalis/command/executables/GenerateAssignmentsRepo.java
@@ -1,4 +1,4 @@
-package nl.andrewlalis.ui.control.command.executables;
+package nl.andrewlalis.command.executables;
import nl.andrewlalis.git_api.GithubManager;
diff --git a/src/main/java/nl/andrewlalis/ui/control/command/executables/GithubExecutable.java b/src/main/java/nl/andrewlalis/command/executables/GithubExecutable.java
similarity index 92%
rename from src/main/java/nl/andrewlalis/ui/control/command/executables/GithubExecutable.java
rename to src/main/java/nl/andrewlalis/command/executables/GithubExecutable.java
index 1cde937..d56ef51 100644
--- a/src/main/java/nl/andrewlalis/ui/control/command/executables/GithubExecutable.java
+++ b/src/main/java/nl/andrewlalis/command/executables/GithubExecutable.java
@@ -1,7 +1,7 @@
-package nl.andrewlalis.ui.control.command.executables;
+package nl.andrewlalis.command.executables;
+import nl.andrewlalis.command.Executable;
import nl.andrewlalis.git_api.GithubManager;
-import nl.andrewlalis.ui.control.command.Executable;
/**
* Represents an executable which interacts with github, and therefore needs access to a Github
diff --git a/src/main/java/nl/andrewlalis/ui/control/command/executables/ListErrors.java b/src/main/java/nl/andrewlalis/command/executables/ListErrors.java
similarity index 91%
rename from src/main/java/nl/andrewlalis/ui/control/command/executables/ListErrors.java
rename to src/main/java/nl/andrewlalis/command/executables/ListErrors.java
index b99c07a..21cdc10 100644
--- a/src/main/java/nl/andrewlalis/ui/control/command/executables/ListErrors.java
+++ b/src/main/java/nl/andrewlalis/command/executables/ListErrors.java
@@ -1,7 +1,7 @@
-package nl.andrewlalis.ui.control.command.executables;
+package nl.andrewlalis.command.executables;
+import nl.andrewlalis.command.Executable;
import nl.andrewlalis.model.error.Error;
-import nl.andrewlalis.ui.control.command.Executable;
import nl.andrewlalis.ui.view.InitializerApp;
import java.util.logging.Logger;
diff --git a/src/main/java/nl/andrewlalis/command/executables/ListRepos.java b/src/main/java/nl/andrewlalis/command/executables/ListRepos.java
new file mode 100644
index 0000000..1ae728d
--- /dev/null
+++ b/src/main/java/nl/andrewlalis/command/executables/ListRepos.java
@@ -0,0 +1,48 @@
+package nl.andrewlalis.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();
+ }
+}
diff --git a/src/main/java/nl/andrewlalis/ui/control/command/executables/ReadStudentsFile.java b/src/main/java/nl/andrewlalis/command/executables/ReadStudentsFile.java
similarity index 91%
rename from src/main/java/nl/andrewlalis/ui/control/command/executables/ReadStudentsFile.java
rename to src/main/java/nl/andrewlalis/command/executables/ReadStudentsFile.java
index cb0b4a0..c89b42e 100644
--- a/src/main/java/nl/andrewlalis/ui/control/command/executables/ReadStudentsFile.java
+++ b/src/main/java/nl/andrewlalis/command/executables/ReadStudentsFile.java
@@ -1,7 +1,7 @@
-package nl.andrewlalis.ui.control.command.executables;
+package nl.andrewlalis.command.executables;
+import nl.andrewlalis.command.Executable;
import nl.andrewlalis.model.StudentTeam;
-import nl.andrewlalis.ui.control.command.Executable;
import nl.andrewlalis.ui.view.InitializerApp;
import nl.andrewlalis.util.FileUtils;
diff --git a/src/main/java/nl/andrewlalis/ui/control/command/executables/SetupStudentRepos.java b/src/main/java/nl/andrewlalis/command/executables/SetupStudentRepos.java
similarity index 74%
rename from src/main/java/nl/andrewlalis/ui/control/command/executables/SetupStudentRepos.java
rename to src/main/java/nl/andrewlalis/command/executables/SetupStudentRepos.java
index 411f255..081bb03 100644
--- a/src/main/java/nl/andrewlalis/ui/control/command/executables/SetupStudentRepos.java
+++ b/src/main/java/nl/andrewlalis/command/executables/SetupStudentRepos.java
@@ -1,9 +1,10 @@
-package nl.andrewlalis.ui.control.command.executables;
+package nl.andrewlalis.command.executables;
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;
diff --git a/src/main/java/nl/andrewlalis/git_api/GithubManager.java b/src/main/java/nl/andrewlalis/git_api/GithubManager.java
index c5b6450..fe15ce7 100644
--- a/src/main/java/nl/andrewlalis/git_api/GithubManager.java
+++ b/src/main/java/nl/andrewlalis/git_api/GithubManager.java
@@ -25,17 +25,13 @@ 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.
*/
private GitHub github;
private GHOrganization organization;
private String accessToken;
+ private String organizationName;
/**
* The logger for outputting debug info.
@@ -45,15 +41,121 @@ public class GithubManager {
logger.setParent(Logger.getGlobal());
}
+ /**
+ * Creates an empty github manager, which at this point, essentially just allocates the object in memory.
+ */
+ public GithubManager() {
+
+ }
+
public GithubManager(String organizationName, String accessToken) {
+ this.organizationName = organizationName;
this.accessToken = accessToken;
+ }
+
+ /**
+ * Gets the organization that this manager manages.
+ * @return The Organization object that this manager was constructed for.
+ * @throws IOException If the organization does not exist or some other error occurs.
+ */
+ private GHOrganization getOrganization() throws IOException {
+ if (this.github == null || this.organization == null) {
+ this.github = GitHub.connectUsingOAuth(this.accessToken);
+ this.organization = this.github.getOrganization(this.organizationName);
+ }
+
+ return this.organization;
+ }
+
+ /**
+ * Determine if the manager is currently connected to github with the current organization name and access token.
+ * @return True if the manager is connected, or false otherwise.
+ */
+ public boolean validate() {
try {
- this.github = GitHub.connectUsingOAuth(accessToken);
- this.organization = this.github.getOrganization(organizationName);
+ this.organization = this.getOrganization();
+ return this.organization != null;
} catch (IOException e) {
- logger.severe("Unable to make a GithubManager with organization name: " + organizationName + " and access token: " + accessToken);
+ return false;
+ }
+ }
+
+ public void setOrganizationName(String name) {
+ this.organizationName = name;
+ this.github = null;
+ this.organization = null;
+ }
+
+ public void setAccessToken(String token) {
+ this.accessToken = token;
+ this.github = null;
+ this.organization = null;
+ }
+
+ /**
+ * Determines if a repository exists in the current organization.
+ * @param repoName The name of the repository.
+ * @return True if the repository exists, false otherwise.
+ */
+ public boolean repoExists(String repoName) {
+ try {
+ GHRepository repo = this.getOrganization().getRepository(repoName);
+ return repo != null;
+ } catch (IOException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Determines if a team exists in the current organization.
+ * @param teamName The name of the team.
+ * @return True if the team exists, false otherwise.
+ */
+ public boolean teamExists(String teamName) {
+ try {
+ GHTeam team = this.getOrganization().getTeamByName(teamName);
+ return team != null;
+ } catch (IOException e) {
+ return false;
+ }
+ }
+
+ /**
+ * 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.getOrganization().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.
+ * @return The repository with the given name, or null if none exists.
+ */
+ public GHRepository getRepository(String name) {
+ System.out.println(name);
+ try {
+ return this.getOrganization().getRepository(name);
+ } catch (IOException e) {
+ logger.severe("No repository with name: " + name + " exists.");
+ e.printStackTrace();
+ return null;
+ }
}
/**
@@ -64,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()) {
@@ -86,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) {
@@ -104,25 +206,25 @@ 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.");
}
- 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,28 +234,30 @@ 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;
}
- 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.");
+ 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;
}
+ team.setRepositoryName(repo.getName());
+ 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);
}
/**
@@ -161,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();
}
}
@@ -180,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();
}
}
@@ -216,25 +330,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<>();
+ logger.finest("Adding students from team: " + team.getNumber() + " as collaborators.");
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, this.organization.getRepository(team.getRepositoryName()));
+ }
} 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.
@@ -294,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_(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/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/model/Person.java b/src/main/java/nl/andrewlalis/model/Person.java
index 50b420a..d374761 100644
--- a/src/main/java/nl/andrewlalis/model/Person.java
+++ b/src/main/java/nl/andrewlalis/model/Person.java
@@ -1,31 +1,57 @@
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
* functionality that applies to anyone in the system.
*/
-public abstract class Person {
+@Entity(name = "Person")
+@Table(name = "persons")
+public abstract class Person extends BaseEntity implements Detailable {
/**
* The unique identification number for this person. (P- or S-Number)
*/
+ @Column(name="number", nullable = false)
protected int number;
/**
* The person's first and last name.
*/
+ @Column(name="name", nullable = false)
protected String name;
/**
* The person's email address.
*/
+ @Column(name="email_address", nullable = false)
protected String emailAddress;
/**
* The person's github username.
*/
+ @Column(name="github_username", nullable = false)
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 +84,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
@@ -100,4 +142,24 @@ public abstract class Person {
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/Student.java b/src/main/java/nl/andrewlalis/model/Student.java
index e347b58..0ded9a1 100644
--- a/src/main/java/nl/andrewlalis/model/Student.java
+++ b/src/main/java/nl/andrewlalis/model/Student.java
@@ -1,12 +1,17 @@
package nl.andrewlalis.model;
+import nl.andrewlalis.util.Pair;
+
+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 +22,27 @@ 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(fetch = FetchType.EAGER)
+ @JoinTable(
+ name = "student_preferred_partners",
+ joinColumns = { @JoinColumn(name = "student_id")},
+ inverseJoinColumns = {@JoinColumn(name = "preferred_partner_id")}
+ )
+ private List preferredPartners;
+
+ /**
+ * The team that this student is assigned to.
+ */
+ @ManyToOne
+ @JoinColumn(name = "team_id")
+ private StudentTeam team;
+
+ /**
+ * 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,26 +53,64 @@ 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;
}
+
+ /**
+ * 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;
+ }
+
+ @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 7da57ff..c545771 100644
--- a/src/main/java/nl/andrewlalis/model/StudentTeam.java
+++ b/src/main/java/nl/andrewlalis/model/StudentTeam.java
@@ -1,22 +1,31 @@
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 java.util.Arrays;
+import java.util.List;
/**
* 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.
*/
- private GHRepository repository;
+ @Column(name = "repository_name", unique = true)
+ private String repositoryName;
/**
* The TATeam responsible for this student team.
*/
+ @ManyToOne
private TATeam taTeam;
public StudentTeam() {
@@ -46,7 +55,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;
}
}
@@ -62,11 +71,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 +88,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) {
@@ -89,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() {
@@ -104,4 +113,22 @@ public class StudentTeam extends Team{
public void setTaTeam(TATeam team) {
this.taTeam = team;
}
+
+ @Override
+ 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/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..0f153f8 100644
--- a/src/main/java/nl/andrewlalis/model/Team.java
+++ b/src/main/java/nl/andrewlalis/model/Team.java
@@ -1,5 +1,10 @@
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;
import java.util.Arrays;
import java.util.List;
@@ -8,39 +13,55 @@ 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 implements Detailable {
/**
* 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(fetch = FetchType.EAGER)
+ @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.
+ * Constructs an empty team with a default id of -1.
*/
- public void setId(int newId) {
- this.id = newId;
+ protected Team() {
+ this(-1);
}
/**
- * @return This team's id number.
+ * @param newId The new number number to assign to this team.
*/
- public int getId() {
- return this.id;
+ public void setNumber(int newId) {
+ this.number = newId;
+ }
+
+ /**
+ * @return This team's number number.
+ */
+ public int getNumber() {
+ return this.number;
}
/**
@@ -123,7 +144,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 +153,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,11 +164,32 @@ 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');
}
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/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/DbHelper.java b/src/main/java/nl/andrewlalis/model/database/DbHelper.java
new file mode 100644
index 0000000..f8f007c
--- /dev/null
+++ b/src/main/java/nl/andrewlalis/model/database/DbHelper.java
@@ -0,0 +1,56 @@
+package nl.andrewlalis.model.database;
+
+import nl.andrewlalis.model.Student;
+import nl.andrewlalis.model.StudentTeam;
+import org.hibernate.Session;
+import org.hibernate.Transaction;
+
+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 {
+
+ /**
+ * Gets a list of students in the database.
+ * @return A list of students.
+ */
+ public static List getStudents() {
+ Session session = DbUtil.getSessionFactory().openSession();
+ List students = (List) session.createQuery("from Student").list();
+ session.close();
+ 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();
+ }
+
+ /**
+ * 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/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/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..2970d7c
--- /dev/null
+++ b/src/main/java/nl/andrewlalis/ui/control/listeners/create_assignments_view/NextListener.java
@@ -0,0 +1,70 @@
+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();
+
+ // 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?",
+ "Create new repository?",
+ JOptionPane.YES_NO_OPTION);
+ if (reply == JOptionPane.YES_OPTION) {
+ try {
+ 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();
+ JOptionPane.showMessageDialog(assignmentsView, "Could not create repository:\n" + e.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
+ return false;
+ }
+ } else {
+ return false;
+ }
+ }
+ }
+
+ // 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/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
new file mode 100644
index 0000000..3c014b5
--- /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.Main;
+import nl.andrewlalis.model.StudentTeam;
+import nl.andrewlalis.model.database.DbHelper;
+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) {
+ return file.isDirectory() || 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);
+ DbHelper.saveStudentTeams(teams);
+ Main.getManagementView().updateModels();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+}
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/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..7499af4
--- /dev/null
+++ b/src/main/java/nl/andrewlalis/ui/view/AbstractView.java
@@ -0,0 +1,140 @@
+package nl.andrewlalis.ui.view;
+
+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
+ * components are made.
+ */
+public abstract class AbstractView extends JFrame {
+
+ /**
+ * A GithubManager object which can be used to interact with github.
+ */
+ private GithubManager githubManager;
+
+ /**
+ * A list of views which are linked to this one via buttons in the component pane.
+ */
+ private List childViews;
+
+ /**
+ * A list of views which lead to this one.
+ */
+ 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.
+ * @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.
+ * @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.setIconImage(imageIcon.getImage());
+ 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.
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * 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);
+ }
+
+ /**
+ * @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
new file mode 100644
index 0000000..a85880e
--- /dev/null
+++ b/src/main/java/nl/andrewlalis/ui/view/CreateAssignmentsView.java
@@ -0,0 +1,53 @@
+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());
+
+ JPanel fieldPanel = new JPanel();
+ fieldPanel.setLayout(new BoxLayout(fieldPanel, BoxLayout.PAGE_AXIS));
+
+ this.repositoryNameField = new JTextField();
+ fieldPanel.add(this.generateTextFieldPanel("Assignments repository name:", this.repositoryNameField));
+ contentPane.add(fieldPanel, BorderLayout.CENTER);
+
+
+ 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);
+
+ 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..18324c6
--- /dev/null
+++ b/src/main/java/nl/andrewlalis/ui/view/InputStudentsFileView.java
@@ -0,0 +1,56 @@
+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.*;
+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 {
+
+ private JTextField studentsPerTeamField;
+
+ InputStudentsFileView(GithubManager manager) {
+ super("Input Students CSV",
+ false,
+ DISPOSE_ON_CLOSE,
+ null,
+ 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));
+ // 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));
+ selectFileButton.addActionListener(new FileSelectListener(this));
+ inputPanel.add(selectFileButton);
+
+ 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..5e890f1
--- /dev/null
+++ b/src/main/java/nl/andrewlalis/ui/view/ManagementView.java
@@ -0,0 +1,186 @@
+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;
+
+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;
+
+ /**
+ * The model for the student teams table.
+ */
+ private StudentTeamTableModel studentTeamModel;
+
+ /**
+ * A panel which displays the details of selected entities.
+ */
+ private DetailPanel detailPanel;
+
+ 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 is unique to the management view.
+ this.addWindowListener(new WindowAdapter() {
+ @Override
+ public void windowClosed(WindowEvent windowEvent) {
+ for (AbstractView parent : getParentViews()) {
+ parent.dispose();
+ }
+ DbUtil.tearDown(); // Shut down the database session factory once everything is done.
+ }
+ });
+ }
+
+ @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.detailPanel, 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.setBorder(BorderFactory.createLoweredBevelBorder());
+
+ 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 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(this.buildSearchPanel(), BorderLayout.NORTH);
+
+ // The real container for all the data views.
+ JTabbedPane tabbedPane = new JTabbedPane();
+
+ tabbedPane.addTab("Students", this.buildStudentsTablePanel());
+ tabbedPane.addTab("Student Teams", this.buildStudentTeamsTablePanel());
+ tabbedPane.addTab("Teaching Assistants", this.buildTAsTablePanel());
+
+ overviewPanel.add(tabbedPane, BorderLayout.CENTER);
+
+ 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.
+ */
+ private JPanel buildStudentsTablePanel() {
+ // Initialize the model, table, and a surrounding scroll pane.
+ this.studentsModel = new StudentTableModel(DbHelper.getStudents());
+
+ JTable table = new JTable(this.studentsModel);
+ table.setFillsViewportHeight(true);
+ 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);
+ }
+
+ /**
+ * @return A JPanel to be put into a tab for display of a list of student teams.
+ */
+ private JPanel buildStudentTeamsTablePanel() {
+ 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() {
+ return new JPanel();
+ }
+
+ /**
+ * Updates all models in the management view in accordance with the database.
+ */
+ public void updateModels() {
+ this.studentsModel.setStudentsList(DbHelper.getStudents());
+ this.studentTeamModel.setStudentTeamsList(DbHelper.getStudentTeams());
+ }
+}
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..a90efc1
--- /dev/null
+++ b/src/main/java/nl/andrewlalis/ui/view/StartView.java
@@ -0,0 +1,86 @@
+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.*;
+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);
+ }
+
+ /**
+ * 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();
+ }
+
+ 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();
+ this.accessTokenField = new JTextField();
+ infoInputPanel.add(this.generateTextFieldPanel("Organization name:", this.organizationNameField));
+ 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);
+
+ contentPane.add(infoInputPanel, BorderLayout.CENTER);
+ contentPane.add(buttonsPanel, BorderLayout.SOUTH);
+ return contentPane;
+ }
+}
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/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/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
new file mode 100644
index 0000000..6234223
--- /dev/null
+++ b/src/main/java/nl/andrewlalis/ui/view/table_models/StudentTableModel.java
@@ -0,0 +1,86 @@
+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();
+ }
+
+ /**
+ * 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) {
+ if (row >= 0 && row < this.studentsList.size()) {
+ return this.studentsList.get(row);
+ }
+ return null;
+ }
+
+ @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 = this.getStudentAt(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/ui/view/table_models/StudentTeamTableModel.java b/src/main/java/nl/andrewlalis/ui/view/table_models/StudentTeamTableModel.java
new file mode 100644
index 0000000..a6300af
--- /dev/null
+++ b/src/main/java/nl/andrewlalis/ui/view/table_models/StudentTeamTableModel.java
@@ -0,0 +1,145 @@
+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", "Repository Name", "TA Team"};
+
+ /**
+ * 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.getRepositoryName() == null) ? "None" : team.getRepositoryName();
+ case 2:
+ return (team.getTaTeam() == null) ? "None" : team.getTaTeam().getDetailName();
+ 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];
+ this.columns[2] = this.staticColumns[2];
+ for (int i = 0; i < maxMembers; i++) {
+ this.columns[i + this.staticColumns.length] = "Member " + (i + 1);
+ }
+ }
+}
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/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;
+ }
+}
diff --git a/src/main/java/nl/andrewlalis/util/TeamGenerator.java b/src/main/java/nl/andrewlalis/util/TeamGenerator.java
index 89b8b35..5487960 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,35 +68,39 @@ 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;
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.setId(teamCount++);
- singleStudents.removeAll(Arrays.asList(newTeam.getStudents()));
+ newTeam.setNumber(teamCount++);
studentTeams.add(newTeam);
+ singleStudents.removeAll(Arrays.asList(newTeam.getStudents()));
+ assignStudentsToTeam(newTeam);
logger.fine("Created team:\n" + newTeam);
}
}
@@ -114,14 +115,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);
@@ -129,6 +130,7 @@ public class TeamGenerator {
t.addMember(s);
}
studentTeams.add(t);
+ assignStudentsToTeam(t);
logger.fine("Created team:\n" + t);
}
return studentTeams;
@@ -141,8 +143,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 +156,49 @@ 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;
+ }
+
+ /**
+ * 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);
+ }
}
}
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
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