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