Improvements and new version in development #5
			
				
			
		
		
		
	
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										46
									
								
								pom.xml
								
								
								
								
							
							
						
						
									
										46
									
								
								pom.xml
								
								
								
								
							|  | @ -21,10 +21,6 @@ | |||
|     </build> | ||||
|     <packaging>jar</packaging> | ||||
| 
 | ||||
|     <properties> | ||||
|         <jackson.version>2.9.6</jackson.version> | ||||
|     </properties> | ||||
| 
 | ||||
|     <dependencies> | ||||
|         <dependency> | ||||
|             <groupId>org.apache.commons</groupId> | ||||
|  | @ -34,12 +30,12 @@ | |||
|         <dependency> | ||||
|             <groupId>commons-cli</groupId> | ||||
|             <artifactId>commons-cli</artifactId> | ||||
|             <version>RELEASE</version> | ||||
|             <version>1.4</version> | ||||
|         </dependency> | ||||
|         <dependency> | ||||
|             <groupId>commons-email</groupId> | ||||
|             <groupId>org.apache.commons</groupId> | ||||
|             <artifactId>commons-email</artifactId> | ||||
|             <version>RELEASE</version> | ||||
|             <version>1.5</version> | ||||
|         </dependency> | ||||
|         <dependency> | ||||
|             <groupId>org.jetbrains</groupId> | ||||
|  | @ -50,22 +46,7 @@ | |||
|         <dependency> | ||||
|             <groupId>org.apache.httpcomponents</groupId> | ||||
|             <artifactId>httpclient</artifactId> | ||||
|             <version>RELEASE</version> | ||||
|         </dependency> | ||||
|         <dependency> | ||||
|             <groupId>com.fasterxml.jackson.core</groupId> | ||||
|             <artifactId>jackson-core</artifactId> | ||||
|             <version>${jackson.version}</version> | ||||
|         </dependency> | ||||
|         <dependency> | ||||
|             <groupId>com.fasterxml.jackson.core</groupId> | ||||
|             <artifactId>jackson-databind</artifactId> | ||||
|             <version>${jackson.version}</version> | ||||
|         </dependency> | ||||
|         <dependency> | ||||
|             <groupId>com.fasterxml.jackson.core</groupId> | ||||
|             <artifactId>jackson-annotations</artifactId> | ||||
|             <version>${jackson.version}</version> | ||||
|             <version>4.5.6</version> | ||||
|         </dependency> | ||||
|         <!-- Github API Object Library --> | ||||
|         <dependency> | ||||
|  | @ -73,11 +54,22 @@ | |||
|             <artifactId>github-api</artifactId> | ||||
|             <version>1.93</version> | ||||
|         </dependency> | ||||
|         <!-- SQLite JDBC Driver --> | ||||
|         <!-- MySQL JDBC Driver --> | ||||
|         <dependency> | ||||
|             <groupId>org.xerial</groupId> | ||||
|             <artifactId>sqlite-jdbc</artifactId> | ||||
|             <version>3.23.1</version> | ||||
|             <groupId>mysql</groupId> | ||||
|             <artifactId>mysql-connector-java</artifactId> | ||||
|             <version>8.0.12</version> | ||||
|         </dependency> | ||||
|         <dependency> | ||||
|             <groupId>com.h2database</groupId> | ||||
|             <artifactId>h2</artifactId> | ||||
|             <version>1.4.197</version> | ||||
|         </dependency> | ||||
|         <!-- Hibernate Database ORM --> | ||||
|         <dependency> | ||||
|             <groupId>org.hibernate</groupId> | ||||
|             <artifactId>hibernate-core</artifactId> | ||||
|             <version>5.3.6.Final</version> | ||||
|         </dependency> | ||||
|     </dependencies> | ||||
|      | ||||
|  |  | |||
|  | @ -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<StudentTeam> 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<String, String> 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()); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -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<String, Executable> commands; | ||||
| 
 | ||||
|     /** | ||||
|      * A list of all the executables which have failed to execute. | ||||
|      */ | ||||
|     private List<ExecutableContext> failedExecutables; | ||||
| 
 | ||||
|     /** | ||||
|      * A list of all executables which have been run successfully. | ||||
|      */ | ||||
|     private List<ExecutableContext> 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); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -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 | ||||
|  | @ -1,4 +1,4 @@ | |||
| package nl.andrewlalis.ui.control.command.executables; | ||||
| package nl.andrewlalis.command.executables; | ||||
| 
 | ||||
| import nl.andrewlalis.git_api.GithubManager; | ||||
| 
 | ||||
|  | @ -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 | ||||
|  | @ -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; | ||||
|  | @ -1,4 +1,4 @@ | |||
| package nl.andrewlalis.ui.control.command.executables; | ||||
| package nl.andrewlalis.command.executables; | ||||
| 
 | ||||
| import nl.andrewlalis.git_api.GithubManager; | ||||
| 
 | ||||
|  | @ -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); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -1,4 +1,4 @@ | |||
| package nl.andrewlalis.ui.control.command.executables; | ||||
| package nl.andrewlalis.command.executables; | ||||
| 
 | ||||
| import nl.andrewlalis.git_api.GithubManager; | ||||
| 
 | ||||
|  | @ -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 | ||||
|  | @ -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; | ||||
|  | @ -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<GHRepository> 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<GHRepository> 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(); | ||||
|     } | ||||
| } | ||||
|  | @ -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; | ||||
| 
 | ||||
|  | @ -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<TATeam> 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; | ||||
|  | @ -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<GHRepository> listReposWithPrefix(String substring) { | ||||
|         List<GHRepository> repos = new ArrayList<>(); | ||||
|         try { | ||||
|             List<GHRepository> 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<TATeam> teams = new ArrayList<>(); | ||||
|         try { | ||||
|             Random rand = new Random(); | ||||
|             for (Map.Entry<String, GHTeam> entry : this.organization.getTeams().entrySet()) { | ||||
|             for (Map.Entry<String, GHTeam> 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<TeachingAssistant> getMembers() { | ||||
|         List<TeachingAssistant> 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,7 +265,8 @@ public class GithubManager { | |||
|      * @param substring The substring which repository names should contain to be deleted. | ||||
|      */ | ||||
|     public void deleteAllRepositories(String substring) { | ||||
|         List<GHRepository> repositories = this.organization.listRepositories().asList(); | ||||
|         try { | ||||
|             List<GHRepository> repositories = this.getOrganization().listRepositories().asList(); | ||||
|             for (GHRepository repo : repositories) { | ||||
|                 if (repo.getName().contains(substring)) { | ||||
|                     try { | ||||
|  | @ -173,6 +278,10 @@ public class GithubManager { | |||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } catch (IOException e) { | ||||
|             logger.severe("Could not get Organization for listing repositories."); | ||||
|             e.printStackTrace(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  | @ -180,12 +289,17 @@ public class GithubManager { | |||
|      * @param sub Any repository containing this substring will be archived. | ||||
|      */ | ||||
|     public void archiveAllRepositories(String sub) { | ||||
|         List<GHRepository> repositories = this.organization.listRepositories().asList(); | ||||
|         try { | ||||
|             List<GHRepository> 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<GHUser> 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); | ||||
|     private GHRepository createRepository(String name, GHTeam taTeam, String description, boolean hasWiki, boolean hasIssues, boolean isPrivate) throws IOException { | ||||
|         GHCreateRepositoryBuilder builder = this.getOrganization().createRepository(name); | ||||
|         builder.team(taTeam); | ||||
|         builder.wiki(hasWiki); | ||||
|         builder.issues(hasIssues); | ||||
|         builder.description(description); | ||||
|         builder.gitignoreTemplate("Java"); | ||||
|             builder.private_(isPrivate); | ||||
|         builder.private_(false); // TODO: Testing value of false. Production uses true. | ||||
|         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; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -1,9 +0,0 @@ | |||
| package nl.andrewlalis.model; | ||||
| 
 | ||||
| public abstract class DatabaseObject { | ||||
| 
 | ||||
|     public abstract DatabaseObject retrieve(); | ||||
| 
 | ||||
|     public abstract boolean store(); | ||||
| 
 | ||||
| } | ||||
|  | @ -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<Pair<String, String>> getDetailPairs() { | ||||
|         List<Pair<String, String>> 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; | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -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<Integer> preferredPartners; | ||||
|     @ManyToMany(fetch = FetchType.EAGER) | ||||
|     @JoinTable( | ||||
|             name = "student_preferred_partners", | ||||
|             joinColumns = { @JoinColumn(name = "student_id")}, | ||||
|             inverseJoinColumns = {@JoinColumn(name = "preferred_partner_id")} | ||||
|     ) | ||||
|     private List<Student> 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<Integer> preferredPartners) { | ||||
|     public Student(int number, String name, String emailAddress, String githubUsername, List<Student> preferredPartners) { | ||||
|         super(number, name, emailAddress, githubUsername); | ||||
|         this.preferredPartners = preferredPartners; | ||||
|     } | ||||
| 
 | ||||
|     public List<Integer> getPreferredPartners() { | ||||
|     public List<Student> getPreferredPartners() { | ||||
|         return this.preferredPartners; | ||||
|     } | ||||
| 
 | ||||
|     public void setPreferredPartners(List<Student> 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<Integer, Student> 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<Pair<String, String>> getDetailPairs() { | ||||
|         List<Pair<String, String>> 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; | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -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<Pair<String, String>> getDetailPairs() { | ||||
|         List<Pair<String, String>> 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; | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -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<StudentTeam> studentTeams; | ||||
| 
 | ||||
|     /** | ||||
|  |  | |||
|  | @ -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 { | ||||
| 
 | ||||
|     /** | ||||
|  |  | |||
|  | @ -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<Person> 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<Pair<String, String>> getDetailPairs() { | ||||
|         List<Pair<String, String>> 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; | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -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; | ||||
|     } | ||||
| } | ||||
|  | @ -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<PreparedStatement> 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<PreparedStatement> 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<StudentTeam> 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<Integer> 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<Integer> 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; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -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<Student> getStudents() { | ||||
|         Session session = DbUtil.getSessionFactory().openSession(); | ||||
|         List<Student> students = (List<Student>) session.createQuery("from Student").list(); | ||||
|         session.close(); | ||||
|         return students; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Saves a list of student teams to the database. | ||||
|      */ | ||||
|     public static void saveStudentTeams(List<StudentTeam> 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<StudentTeam> getStudentTeams() { | ||||
|         Session session = DbUtil.getSessionFactory().openSession(); | ||||
|         List<StudentTeam> studentTeams = (List<StudentTeam>) session.createQuery("from StudentTeam").list(); | ||||
|         session.close(); | ||||
|         return studentTeams; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -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; | ||||
|     } | ||||
| } | ||||
|  | @ -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<PreparedStatement> prepareStatementsFromFile(String filename, Connection connection) { | ||||
|         String string = FileUtils.readStringFromFile(filename); | ||||
|         if (string == null || string.isEmpty()) { | ||||
|             return new ArrayList<>(); | ||||
|         } | ||||
|         String[] splits = string.split(";"); | ||||
|         List<PreparedStatement> 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; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -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.*; | ||||
|  |  | |||
|  | @ -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; | ||||
|  |  | |||
|  | @ -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; | ||||
|  |  | |||
|  | @ -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; | ||||
|  |  | |||
|  | @ -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.*; | ||||
|  |  | |||
|  | @ -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; | ||||
|  |  | |||
|  | @ -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.*; | ||||
|  |  | |||
|  | @ -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.*; | ||||
|  |  | |||
|  | @ -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); | ||||
|     } | ||||
| } | ||||
|  | @ -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; | ||||
|     } | ||||
| } | ||||
|  | @ -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); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -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<StudentTeam> teams = TeamGenerator.generateFromCSV(chooser.getSelectedFile().getAbsolutePath(), teamSize); | ||||
|                 DbHelper.saveStudentTeams(teams); | ||||
|                 Main.getManagementView().updateModels(); | ||||
|             } catch (IOException e) { | ||||
|                 e.printStackTrace(); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -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) { | ||||
| 
 | ||||
|     } | ||||
| } | ||||
|  | @ -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; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -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())); | ||||
|     } | ||||
| } | ||||
|  | @ -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(); | ||||
|     } | ||||
| } | ||||
|  | @ -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(); | ||||
|     } | ||||
| } | ||||
|  | @ -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<AbstractView> childViews; | ||||
| 
 | ||||
|     /** | ||||
|      * A list of views which lead to this one. | ||||
|      */ | ||||
|     private List<AbstractView> 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<AbstractView> 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<AbstractView> getParentViews() { | ||||
|         return this.parentViews; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Removes all parents registered to this view. | ||||
|      */ | ||||
|     protected final void removeParents() { | ||||
|         this.parentViews.clear(); | ||||
|     } | ||||
| } | ||||
|  | @ -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; | ||||
|     } | ||||
| } | ||||
|  | @ -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. | ||||
|  |  | |||
|  | @ -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; | ||||
|     } | ||||
| } | ||||
|  | @ -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()); | ||||
|     } | ||||
| } | ||||
|  | @ -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; | ||||
|     } | ||||
| } | ||||
|  | @ -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; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -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<Pair<String, String>> getDetailPairs(); | ||||
| 
 | ||||
| } | ||||
|  | @ -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; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -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<Pair<String, String>> 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<Pair<String, String>> 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; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -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<Student> 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<Student> 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<Student> 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; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -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<StudentTeam> 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<StudentTeam> 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<StudentTeam> 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); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -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); | ||||
| 
 | ||||
|     } | ||||
|  |  | |||
|  | @ -0,0 +1,25 @@ | |||
| package nl.andrewlalis.util; | ||||
| 
 | ||||
| /** | ||||
|  * A pair of objects. | ||||
|  * @param <T1> The first object. | ||||
|  * @param <T2> The second object. | ||||
|  */ | ||||
| public class Pair<T1, T2> { | ||||
| 
 | ||||
|     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; | ||||
|     } | ||||
| } | ||||
|  | @ -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<CSVRecord> records = CSVFormat.DEFAULT.withFirstRecordAsHeader().parse(new FileReader(filename)); | ||||
| 
 | ||||
|         logger.fine("Reading all records into map."); | ||||
|         Map<Integer, Student> studentMap; | ||||
|         List<Student> 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<StudentTeam> generateAllValidTeams(Map<Integer, Student> studentMap, int teamSize) { | ||||
|         List<Student> singleStudents = new ArrayList<>(studentMap.values()); | ||||
|     private static List<StudentTeam> generateAllValidTeams(List<Student> students, int teamSize) { | ||||
|         List<Student> singleStudents = new ArrayList<>(students); | ||||
|         List<StudentTeam> 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<Integer, Student> 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<StudentTeam> mergeSingleStudents(List<Student> singleStudents, int teamSize, int teamIndex) { | ||||
|         List<StudentTeam> 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<Integer, Student> readAllStudents(Iterable<CSVRecord> records, int teamSize) throws ArrayIndexOutOfBoundsException { | ||||
|     private static List<Student> readAllStudents(Iterable<CSVRecord> records, int teamSize) throws ArrayIndexOutOfBoundsException { | ||||
|         Map<Integer, Student> studentMap = new HashMap<>(); | ||||
|         Map<Student, List<Integer>> studentPreferredIds = new HashMap<>(); | ||||
|         // Perform the initial read of the students. | ||||
|         for (CSVRecord record : records) { | ||||
|             logger.finest("Read record: " + record); | ||||
|             List<Integer> 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<Integer, Student> 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<Student> students = new ArrayList<>(); | ||||
| 
 | ||||
|         // Assign students to their preferred students. | ||||
|         for (Map.Entry<Student, List<Integer>> 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); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -0,0 +1,30 @@ | |||
| <?xml version="1.0" encoding="UTF-8" ?> | ||||
| <!DOCTYPE hibernate-configuration PUBLIC | ||||
|         "-//Hibernate/Hibernate Configuration DTD 3.0//EN" | ||||
|         "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd"> | ||||
| <hibernate-configuration> | ||||
|     <session-factory> | ||||
|         <!-- Database connection settings --> | ||||
|         <property name="hibernate.connection.driver_class">org.h2.Driver</property> | ||||
|         <property name="hibernate.connection.url">jdbc:h2:./initializer.h2</property> | ||||
|         <property name="hibernate.dialect">org.hibernate.dialect.H2Dialect</property> | ||||
|         <property name="hibernate.connection.username">root</property> | ||||
|         <property name="hibernate.connection.password">root</property> | ||||
| 
 | ||||
|         <!-- JDBC connection pool --> | ||||
|         <property name="connection.pool_size">1</property> | ||||
| 
 | ||||
|         <!-- Debugging to show sql queries. --> | ||||
|         <property name="show_sql">false</property> | ||||
| 
 | ||||
|         <!-- Set Hibernate to create tables beforehand. --> | ||||
|         <property name="hibernate.hbm2ddl.auto">create-drop</property> | ||||
| 
 | ||||
|         <!-- Mapping --> | ||||
|         <mapping class="nl.andrewlalis.model.Team"/> | ||||
|         <mapping class="nl.andrewlalis.model.Student"/> | ||||
|         <mapping class="nl.andrewlalis.model.StudentTeam"/> | ||||
|         <mapping class="nl.andrewlalis.model.TeachingAssistant"/> | ||||
|         <mapping class="nl.andrewlalis.model.TATeam"/> | ||||
|     </session-factory> | ||||
| </hibernate-configuration> | ||||
|  | @ -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'); | ||||
|  | @ -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 | ||||
| ); | ||||
		Loading…
	
		Reference in New Issue