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> |     </build> | ||||||
|     <packaging>jar</packaging> |     <packaging>jar</packaging> | ||||||
| 
 | 
 | ||||||
|     <properties> |  | ||||||
|         <jackson.version>2.9.6</jackson.version> |  | ||||||
|     </properties> |  | ||||||
| 
 |  | ||||||
|     <dependencies> |     <dependencies> | ||||||
|         <dependency> |         <dependency> | ||||||
|             <groupId>org.apache.commons</groupId> |             <groupId>org.apache.commons</groupId> | ||||||
|  | @ -34,12 +30,12 @@ | ||||||
|         <dependency> |         <dependency> | ||||||
|             <groupId>commons-cli</groupId> |             <groupId>commons-cli</groupId> | ||||||
|             <artifactId>commons-cli</artifactId> |             <artifactId>commons-cli</artifactId> | ||||||
|             <version>RELEASE</version> |             <version>1.4</version> | ||||||
|         </dependency> |         </dependency> | ||||||
|         <dependency> |         <dependency> | ||||||
|             <groupId>commons-email</groupId> |             <groupId>org.apache.commons</groupId> | ||||||
|             <artifactId>commons-email</artifactId> |             <artifactId>commons-email</artifactId> | ||||||
|             <version>RELEASE</version> |             <version>1.5</version> | ||||||
|         </dependency> |         </dependency> | ||||||
|         <dependency> |         <dependency> | ||||||
|             <groupId>org.jetbrains</groupId> |             <groupId>org.jetbrains</groupId> | ||||||
|  | @ -50,22 +46,7 @@ | ||||||
|         <dependency> |         <dependency> | ||||||
|             <groupId>org.apache.httpcomponents</groupId> |             <groupId>org.apache.httpcomponents</groupId> | ||||||
|             <artifactId>httpclient</artifactId> |             <artifactId>httpclient</artifactId> | ||||||
|             <version>RELEASE</version> |             <version>4.5.6</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> |  | ||||||
|         </dependency> |         </dependency> | ||||||
|         <!-- Github API Object Library --> |         <!-- Github API Object Library --> | ||||||
|         <dependency> |         <dependency> | ||||||
|  | @ -73,11 +54,22 @@ | ||||||
|             <artifactId>github-api</artifactId> |             <artifactId>github-api</artifactId> | ||||||
|             <version>1.93</version> |             <version>1.93</version> | ||||||
|         </dependency> |         </dependency> | ||||||
|         <!-- SQLite JDBC Driver --> |         <!-- MySQL JDBC Driver --> | ||||||
|         <dependency> |         <dependency> | ||||||
|             <groupId>org.xerial</groupId> |             <groupId>mysql</groupId> | ||||||
|             <artifactId>sqlite-jdbc</artifactId> |             <artifactId>mysql-connector-java</artifactId> | ||||||
|             <version>3.23.1</version> |             <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> |         </dependency> | ||||||
|     </dependencies> |     </dependencies> | ||||||
|      |      | ||||||
|  |  | ||||||
|  | @ -1,11 +1,19 @@ | ||||||
| package nl.andrewlalis; | package nl.andrewlalis; | ||||||
| 
 | 
 | ||||||
| import nl.andrewlalis.ui.control.command.CommandExecutor; | import nl.andrewlalis.command.CommandExecutor; | ||||||
| import nl.andrewlalis.ui.control.command.executables.*; | 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.InitializerApp; | ||||||
|  | import nl.andrewlalis.ui.view.ManagementView; | ||||||
|  | import nl.andrewlalis.ui.view.StartView; | ||||||
| import nl.andrewlalis.util.CommandLine; | import nl.andrewlalis.util.CommandLine; | ||||||
| import nl.andrewlalis.util.Logging; | 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.Map; | ||||||
| import java.util.logging.Logger; | import java.util.logging.Logger; | ||||||
| 
 | 
 | ||||||
|  | @ -16,6 +24,11 @@ public class Main { | ||||||
| 
 | 
 | ||||||
|     private static final Logger logger = Logger.getGlobal(); |     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) { |     public static void main(String[] args) { | ||||||
| 
 | 
 | ||||||
|         // Parsed command line arguments. |         // Parsed command line arguments. | ||||||
|  | @ -24,6 +37,41 @@ public class Main { | ||||||
|         // Initialize logger. |         // Initialize logger. | ||||||
|         Logging.setup(); |         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. |         // Command executor which will be used by all actions the user can do. | ||||||
|         CommandExecutor executor = new CommandExecutor(); |         CommandExecutor executor = new CommandExecutor(); | ||||||
| 
 | 
 | ||||||
|  | @ -41,10 +89,7 @@ public class Main { | ||||||
|         executor.registerCommand("delete_repos", new DeleteRepos()); |         executor.registerCommand("delete_repos", new DeleteRepos()); | ||||||
|         executor.registerCommand("delegate_student_teams", new DelegateStudentTeams(app)); |         executor.registerCommand("delegate_student_teams", new DelegateStudentTeams(app)); | ||||||
|         executor.registerCommand("setup_student_repos", new SetupStudentRepos(app)); |         executor.registerCommand("setup_student_repos", new SetupStudentRepos(app)); | ||||||
| 
 |         executor.registerCommand("list_repos", new ListRepos()); | ||||||
|         logger.info("GithubManager for Github Repositories in Educational Organizations.\n" + |  | ||||||
|                 "© Andrew Lalis (2018), All rights reserved.\n" + |  | ||||||
|                 "Program initialized."); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,8 +1,8 @@ | ||||||
| package nl.andrewlalis.ui.control.command; | package nl.andrewlalis.command; | ||||||
| 
 | 
 | ||||||
| import java.util.Arrays; | import nl.andrewlalis.command.executables.ExecutableContext; | ||||||
| import java.util.HashMap; | 
 | ||||||
| import java.util.Map; | import java.util.*; | ||||||
| import java.util.logging.Logger; | import java.util.logging.Logger; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  | @ -23,8 +23,20 @@ public class CommandExecutor { | ||||||
|      */ |      */ | ||||||
|     private Map<String, Executable> commands; |     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() { |     public CommandExecutor() { | ||||||
|         this.commands = new HashMap<>(); |         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) { |     public void executeCommand(String commandName, String[] args) { | ||||||
|         if (this.commands.containsKey(commandName)) { |         if (this.commands.containsKey(commandName)) { | ||||||
|             logger.info("Command executed: " + commandName + ' ' + Arrays.toString(args)); |             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."); |                 logger.warning("Command did not execute successfully."); | ||||||
|  |                 this.failedExecutables.add(context); | ||||||
|  |             } else { | ||||||
|  |                 this.successfulExecutables.add(context); | ||||||
|             } |             } | ||||||
|         } else { |         } else { | ||||||
|             logger.warning(commandName + " is not a valid command."); |             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 |  * 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; | 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.git_api.GithubManager; | ||||||
| import nl.andrewlalis.ui.view.dialogs.DefineTaTeamsDialog; |  | ||||||
| import nl.andrewlalis.ui.view.InitializerApp; | 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 |  * 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.git_api.GithubManager; | ||||||
| import nl.andrewlalis.model.StudentTeam; | 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; | 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; | 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.git_api.GithubManager; | ||||||
| import nl.andrewlalis.ui.control.command.Executable; |  | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Represents an executable which interacts with github, and therefore needs access to a Github |  * 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.model.error.Error; | ||||||
| import nl.andrewlalis.ui.control.command.Executable; |  | ||||||
| import nl.andrewlalis.ui.view.InitializerApp; | import nl.andrewlalis.ui.view.InitializerApp; | ||||||
| 
 | 
 | ||||||
| import java.util.logging.Logger; | 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.model.StudentTeam; | ||||||
| import nl.andrewlalis.ui.control.command.Executable; |  | ||||||
| import nl.andrewlalis.ui.view.InitializerApp; | import nl.andrewlalis.ui.view.InitializerApp; | ||||||
| import nl.andrewlalis.util.FileUtils; | 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.git_api.GithubManager; | ||||||
| import nl.andrewlalis.model.StudentTeam; | import nl.andrewlalis.model.StudentTeam; | ||||||
| import nl.andrewlalis.model.TATeam; | import nl.andrewlalis.model.TATeam; | ||||||
| import nl.andrewlalis.ui.view.InitializerApp; | import nl.andrewlalis.ui.view.InitializerApp; | ||||||
|  | import org.kohsuke.github.GHRepository; | ||||||
| 
 | 
 | ||||||
| import java.util.List; | import java.util.List; | ||||||
| 
 | 
 | ||||||
|  | @ -23,13 +24,17 @@ public class SetupStudentRepos extends GithubExecutable { | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|     protected boolean executeWithManager(GithubManager manager, String[] args) { |     protected boolean executeWithManager(GithubManager manager, String[] args) { | ||||||
|         if (args.length < 1) { |         if (args.length < 2) { | ||||||
|             return false; |             return false; | ||||||
|         } |         } | ||||||
|         List<TATeam> taTeams = this.app.getOrganization().getTaTeams(); |         List<TATeam> taTeams = this.app.getOrganization().getTaTeams(); | ||||||
|         for (TATeam team : taTeams) { |         for (TATeam team : taTeams) { | ||||||
|             for (StudentTeam studentTeam : team.getStudentTeams()) { |             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; |         return true; | ||||||
|  | @ -25,17 +25,13 @@ import java.util.logging.Logger; | ||||||
|  */ |  */ | ||||||
| public class GithubManager { | public class GithubManager { | ||||||
| 
 | 
 | ||||||
|     /** |  | ||||||
|      * The assignments repository where students will get assignments from. |  | ||||||
|      */ |  | ||||||
|     private GHRepository assignmentsRepo; |  | ||||||
| 
 |  | ||||||
|     /** |     /** | ||||||
|      * Github object for API interactions. |      * Github object for API interactions. | ||||||
|      */ |      */ | ||||||
|     private GitHub github; |     private GitHub github; | ||||||
|     private GHOrganization organization; |     private GHOrganization organization; | ||||||
|     private String accessToken; |     private String accessToken; | ||||||
|  |     private String organizationName; | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * The logger for outputting debug info. |      * The logger for outputting debug info. | ||||||
|  | @ -45,15 +41,121 @@ public class GithubManager { | ||||||
|         logger.setParent(Logger.getGlobal()); |         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) { |     public GithubManager(String organizationName, String accessToken) { | ||||||
|  |         this.organizationName = organizationName; | ||||||
|         this.accessToken = accessToken; |         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 { |         try { | ||||||
|             this.github = GitHub.connectUsingOAuth(accessToken); |             this.organization = this.getOrganization(); | ||||||
|             this.organization = this.github.getOrganization(organizationName); |             return this.organization != null; | ||||||
|         } catch (IOException e) { |         } 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(); |             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<>(); |         List<TATeam> teams = new ArrayList<>(); | ||||||
|         try { |         try { | ||||||
|             Random rand = new Random(); |             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()); |                 TATeam team = new TATeam(entry.getKey(), entry.getValue().getId()); | ||||||
|                 team.setGithubTeam(entry.getValue()); |                 team.setGithubTeam(entry.getValue()); | ||||||
|                 for (GHUser user : entry.getValue().listMembers().asList()) { |                 for (GHUser user : entry.getValue().listMembers().asList()) { | ||||||
|  | @ -86,7 +188,7 @@ public class GithubManager { | ||||||
|     public List<TeachingAssistant> getMembers() { |     public List<TeachingAssistant> getMembers() { | ||||||
|         List<TeachingAssistant> teachingAssistants = new ArrayList<>(); |         List<TeachingAssistant> teachingAssistants = new ArrayList<>(); | ||||||
|         try { |         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())); |                 teachingAssistants.add(new TeachingAssistant(-1, member.getName(), member.getEmail(), member.getLogin())); | ||||||
|             } |             } | ||||||
|         } catch (IOException e) { |         } catch (IOException e) { | ||||||
|  | @ -104,25 +206,25 @@ public class GithubManager { | ||||||
|      * @throws IOException If an HTTP request failed. |      * @throws IOException If an HTTP request failed. | ||||||
|      */ |      */ | ||||||
|     public void setupAssignmentsRepo(String assignmentsRepoName, String description, String allTeachingAssistants) throws IOException { |     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. |         // Check if the repository already exists. | ||||||
|         GHRepository existingRepo = this.organization.getRepository(assignmentsRepoName); |         GHRepository existingRepo = this.getOrganization().getRepository(assignmentsRepoName); | ||||||
|         if (existingRepo != null) { |         if (existingRepo != null) { | ||||||
|             existingRepo.delete(); |             existingRepo.delete(); | ||||||
|             logger.fine("Deleted pre-existing assignments repository."); |             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."); |             logger.severe("Could not create assignments repository."); | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         this.protectMasterBranch(this.assignmentsRepo, team); |         this.protectMasterBranch(assignmentsRepo, team); | ||||||
| 
 | 
 | ||||||
|         // Grant all teaching assistants write access. |         // 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()); |         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 team The student team to set up. | ||||||
|      * @param taTeam The team of teaching assistants that is responsible for these students. |      * @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 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. |         // 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."); |             logger.warning("Assignments repository must be created before student repositories."); | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         GHRepository repo = this.createRepository(team.generateUniqueName(prefix), taTeam.getGithubTeam(), team.generateRepoDescription(), false, true, true); |         GHRepository repo; | ||||||
| 
 |         try { | ||||||
|         if (repo == null) { |             repo = this.createRepository(team.generateUniqueName(prefix), taTeam.getGithubTeam(), team.generateRepoDescription(), false, true, true); | ||||||
|             logger.severe("Repository for student team " + team.getId() + " could not be created."); |         } catch (IOException e) { | ||||||
|  |             logger.severe("Repository for student team " + team.getNumber() + " could not be created."); | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  |         team.setRepositoryName(repo.getName()); | ||||||
|  |         team.setTaTeam(taTeam); | ||||||
|  | 
 | ||||||
|         this.protectMasterBranch(repo, taTeam.getGithubTeam()); |         this.protectMasterBranch(repo, taTeam.getGithubTeam()); | ||||||
|         this.createDevelopmentBranch(repo); |         this.createDevelopmentBranch(repo); | ||||||
|         this.addTATeamAsAdmin(repo, taTeam.getGithubTeam()); |         this.addTATeamAsAdmin(repo, taTeam.getGithubTeam()); | ||||||
|         this.inviteStudentsToRepos(team, repo); |         this.inviteStudentsToRepos(team, assignmentsRepo); | ||||||
| 
 |  | ||||||
|         team.setRepository(repo); |  | ||||||
|         team.setTaTeam(taTeam); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|  | @ -161,17 +265,22 @@ public class GithubManager { | ||||||
|      * @param substring The substring which repository names should contain to be deleted. |      * @param substring The substring which repository names should contain to be deleted. | ||||||
|      */ |      */ | ||||||
|     public void deleteAllRepositories(String substring) { |     public void deleteAllRepositories(String substring) { | ||||||
|         List<GHRepository> repositories = this.organization.listRepositories().asList(); |         try { | ||||||
|         for (GHRepository repo : repositories) { |             List<GHRepository> repositories = this.getOrganization().listRepositories().asList(); | ||||||
|             if (repo.getName().contains(substring)) { |             for (GHRepository repo : repositories) { | ||||||
|                 try { |                 if (repo.getName().contains(substring)) { | ||||||
|                     repo.delete(); |                     try { | ||||||
|                     logger.info("Deleted repository: " + repo.getName()); |                         repo.delete(); | ||||||
|                 } catch (IOException e) { |                         logger.info("Deleted repository: " + repo.getName()); | ||||||
|                     logger.severe("Could not delete repository: " + repo.getName()); |                     } catch (IOException e) { | ||||||
|                     e.printStackTrace(); |                         logger.severe("Could not delete repository: " + repo.getName()); | ||||||
|  |                         e.printStackTrace(); | ||||||
|  |                     } | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|  |         } catch (IOException e) { | ||||||
|  |             logger.severe("Could not get Organization for listing repositories."); | ||||||
|  |             e.printStackTrace(); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -180,11 +289,16 @@ public class GithubManager { | ||||||
|      * @param sub Any repository containing this substring will be archived. |      * @param sub Any repository containing this substring will be archived. | ||||||
|      */ |      */ | ||||||
|     public void archiveAllRepositories(String sub) { |     public void archiveAllRepositories(String sub) { | ||||||
|         List<GHRepository> repositories = this.organization.listRepositories().asList(); |         try { | ||||||
|         for (GHRepository repo : repositories) { |             List<GHRepository> repositories = this.getOrganization().listRepositories().asList(); | ||||||
|             if (repo.getName().contains(sub)) { |             for (GHRepository repo : repositories) { | ||||||
|                 archiveRepository(repo); |                 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. |      * Invites students in a team to their repository, and the assignments repository. | ||||||
|      * @param team The team of students to invite as collaborators. |      * @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 { |         try { | ||||||
|             logger.finest("Adding students from team: " + team.getId() + " as collaborators."); |             logger.finest("Adding students from team: " + team.getNumber() + " as collaborators."); | ||||||
|             List<GHUser> users = new ArrayList<>(); |  | ||||||
|             for (Student student : team.getStudents()) { |             for (Student student : team.getStudents()) { | ||||||
|                 GHUser user = this.github.getUser(student.getGithubUsername()); |                 GHUser user = this.github.getUser(student.getGithubUsername()); | ||||||
|                 users.add(user); |  | ||||||
|             } |  | ||||||
| 
 | 
 | ||||||
|             repo.addCollaborators(users); |                 this.addCollaboratorToRepo(user, assignmentsRepo); | ||||||
|             this.assignmentsRepo.addCollaborators(users); |                 this.addCollaboratorToRepo(user, this.organization.getRepository(team.getRepositoryName())); | ||||||
|  |             } | ||||||
|         } catch (IOException e) { |         } catch (IOException e) { | ||||||
|             logger.severe("Could not add students as collaborators to assignments or their repo.\n" + team); |             logger.severe("Could not add students as collaborators to assignments or their repo.\n" + team); | ||||||
|             e.printStackTrace(); |             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. |      * Adds a teaching assistant team as admins to a particular student repository. | ||||||
|      * @param studentRepo The student repository. |      * @param studentRepo The student repository. | ||||||
|  | @ -294,25 +419,20 @@ public class GithubManager { | ||||||
|      * @param hasWiki Whether the repo has a wiki enabled. |      * @param hasWiki Whether the repo has a wiki enabled. | ||||||
|      * @param hasIssues Whether the repo has issues enabled. |      * @param hasIssues Whether the repo has issues enabled. | ||||||
|      * @param isPrivate Whether or not the repository is private. |      * @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){ |     private GHRepository createRepository(String name, GHTeam taTeam, String description, boolean hasWiki, boolean hasIssues, boolean isPrivate) throws IOException { | ||||||
|         try { |         GHCreateRepositoryBuilder builder = this.getOrganization().createRepository(name); | ||||||
|             GHCreateRepositoryBuilder builder = this.organization.createRepository(name); |         builder.team(taTeam); | ||||||
|             builder.team(taTeam); |         builder.wiki(hasWiki); | ||||||
|             builder.wiki(hasWiki); |         builder.issues(hasIssues); | ||||||
|             builder.issues(hasIssues); |         builder.description(description); | ||||||
|             builder.description(description); |         builder.gitignoreTemplate("Java"); | ||||||
|             builder.gitignoreTemplate("Java"); |         builder.private_(false); // TODO: Testing value of false. Production uses true. | ||||||
|             builder.private_(isPrivate); |         GHRepository repo = builder.create(); | ||||||
|             GHRepository repo = builder.create(); |         logger.fine("Created repository: " + repo.getName()); | ||||||
|             logger.fine("Created repository: " + repo.getName()); |         return repo; | ||||||
|             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; | 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 |  * 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. |  * 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) |      * The unique identification number for this person. (P- or S-Number) | ||||||
|      */ |      */ | ||||||
|  |     @Column(name="number", nullable = false) | ||||||
|     protected int number; |     protected int number; | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * The person's first and last name. |      * The person's first and last name. | ||||||
|      */ |      */ | ||||||
|  |     @Column(name="name", nullable = false) | ||||||
|     protected String name; |     protected String name; | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * The person's email address. |      * The person's email address. | ||||||
|      */ |      */ | ||||||
|  |     @Column(name="email_address", nullable = false) | ||||||
|     protected String emailAddress; |     protected String emailAddress; | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * The person's github username. |      * The person's github username. | ||||||
|      */ |      */ | ||||||
|  |     @Column(name="github_username", nullable = false) | ||||||
|     protected String githubUsername; |     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. |      * 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. |      * @param githubUsername The person's github username. | ||||||
|  | @ -58,18 +84,34 @@ public abstract class Person  { | ||||||
|         return this.number; |         return this.number; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     public void setNumber(int number) { | ||||||
|  |         this.number = number; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     public String getName(){ |     public String getName(){ | ||||||
|         return this.name; |         return this.name; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     public void setName(String name) { | ||||||
|  |         this.name = name; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     public String getEmailAddress(){ |     public String getEmailAddress(){ | ||||||
|         return this.emailAddress; |         return this.emailAddress; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     public void setEmailAddress(String emailAddress) { | ||||||
|  |         this.emailAddress = emailAddress; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     public String getGithubUsername(){ |     public String getGithubUsername(){ | ||||||
|         return this.githubUsername; |         return this.githubUsername; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     public void setGithubUsername(String githubUsername) { | ||||||
|  |         this.githubUsername = githubUsername; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
|      * Determines if two persons are the same. This is defined as: |      * 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 |      * 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() { |     public String toString() { | ||||||
|         return this.getName() + ", " + this.getNumber() + ", " + this.getEmailAddress() + ", " + this.getGithubUsername(); |         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; | package nl.andrewlalis.model; | ||||||
| 
 | 
 | ||||||
|  | import nl.andrewlalis.util.Pair; | ||||||
|  | 
 | ||||||
|  | import javax.persistence.*; | ||||||
|  | import java.util.ArrayList; | ||||||
| import java.util.List; | import java.util.List; | ||||||
| import java.util.Map; |  | ||||||
| import java.util.logging.Logger; | import java.util.logging.Logger; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Represents one student's github information. |  * Represents one student's github information. | ||||||
|  */ |  */ | ||||||
|  | @Entity(name = "Student") | ||||||
|  | @Table(name="students") | ||||||
| public class Student extends Person { | public class Student extends Person { | ||||||
| 
 | 
 | ||||||
|     private static final Logger logger = Logger.getLogger(Student.class.getName()); |     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. |      * 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. |      * 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 |      * @param preferredPartners A list of this student's preferred partners, as a list of integers representing the | ||||||
|      * other students' numbers. |      * 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); |         super(number, name, emailAddress, githubUsername); | ||||||
|         this.preferredPartners = preferredPartners; |         this.preferredPartners = preferredPartners; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public List<Integer> getPreferredPartners() { |     public List<Student> getPreferredPartners() { | ||||||
|         return this.preferredPartners; |         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. |      * Returns a student's preferred team, including himself. | ||||||
|      * @param studentMap A mapping from student number to student for all students who have signed up. |      * @return A team with unknown number, comprised of this student's preferred partners. | ||||||
|      * @return A team with unknown id, comprised of this student's preferred partners. |  | ||||||
|      */ |      */ | ||||||
|     public StudentTeam getPreferredTeam(Map<Integer, Student> studentMap) { |     public StudentTeam getPreferredTeam() { | ||||||
|         StudentTeam t = new StudentTeam(); |         StudentTeam t = new StudentTeam(); | ||||||
|         for (int partnerNumber : this.getPreferredPartners()) { |         for (Student partner : this.getPreferredPartners()) { | ||||||
|             t.addMember(studentMap.get(partnerNumber)); |             t.addMember(partner); | ||||||
|         } |         } | ||||||
|         t.addMember(this); |         t.addMember(this); | ||||||
|         return t; |         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; | 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.Arrays; | ||||||
|  | import java.util.List; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Represents one or more students' collective information. |  * 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. |      * 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. |      * The TATeam responsible for this student team. | ||||||
|      */ |      */ | ||||||
|  |     @ManyToOne | ||||||
|     private TATeam taTeam; |     private TATeam taTeam; | ||||||
| 
 | 
 | ||||||
|     public StudentTeam() { |     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 the student doesn't have an preferred partners, then assume that this is valid. | ||||||
|                 if (!studentA.getPreferredPartners().isEmpty()) { |                 if (!studentA.getPreferredPartners().isEmpty()) { | ||||||
|                     for (Student studentB : this.getStudents()) { |                     for (Student studentB : this.getStudents()) { | ||||||
|                         if (!studentA.equals(studentB) && !studentA.getPreferredPartners().contains(studentB.getNumber())) { |                         if (!studentA.equals(studentB) && !studentA.getPreferredPartners().contains(studentB)) { | ||||||
|                             return false; |                             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. |      * 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. |      * @param prefix A prefix to further reduce the chances of duplicate names. | ||||||
|      *               It is suggested to use something like "2018_OOP" |      *               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) { |     public String generateUniqueName(String prefix) { | ||||||
|         StringBuilder sb = new StringBuilder(prefix); |         StringBuilder sb = new StringBuilder(prefix); | ||||||
|         sb.append("_team_").append(this.id); |         sb.append("_team_").append(this.number); | ||||||
|         for (Student s : this.getStudents()) { |         for (Student s : this.getStudents()) { | ||||||
|             sb.append('_').append(s.getNumber()); |             sb.append('_').append(s.getNumber()); | ||||||
|         } |         } | ||||||
|  | @ -79,7 +88,7 @@ public class StudentTeam extends Team{ | ||||||
|      */ |      */ | ||||||
|     public String generateRepoDescription() { |     public String generateRepoDescription() { | ||||||
|         StringBuilder sb = new StringBuilder(); |         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++) { |         for (int i = 0; i < this.memberCount(); i++) { | ||||||
|             sb.append(this.getStudents()[i].getName()); |             sb.append(this.getStudents()[i].getName()); | ||||||
|             if (i != this.memberCount()-1) { |             if (i != this.memberCount()-1) { | ||||||
|  | @ -89,12 +98,12 @@ public class StudentTeam extends Team{ | ||||||
|         return sb.toString(); |         return sb.toString(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public GHRepository getRepository() { |     public String getRepositoryName() { | ||||||
|         return this.repository; |         return this.repositoryName; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public void setRepository(GHRepository repo) { |     public void setRepositoryName(String repositoryName) { | ||||||
|         this.repository = repo; |         this.repositoryName = repositoryName; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public TATeam getTaTeam() { |     public TATeam getTaTeam() { | ||||||
|  | @ -104,4 +113,22 @@ public class StudentTeam extends Team{ | ||||||
|     public void setTaTeam(TATeam team) { |     public void setTaTeam(TATeam team) { | ||||||
|         this.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 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.ArrayList; | ||||||
| import java.util.Arrays; | import java.util.Arrays; | ||||||
| import java.util.List; | import java.util.List; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Represents a teaching assistant team, which is itself a 'team' in the organization. This class is used for parsing |  * Represents a teaching assistant team, which is itself a 'team' in the organization. | ||||||
|  * json from requests to github to get a list of all teams in the organization. |  | ||||||
|  */ |  */ | ||||||
|  | @Entity(name = "TATeam") | ||||||
|  | @Table(name = "ta_teams") | ||||||
| public class TATeam extends Team { | public class TATeam extends Team { | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|  | @ -20,11 +25,13 @@ public class TATeam extends Team { | ||||||
|     /** |     /** | ||||||
|      * The Github team associated with this team. |      * The Github team associated with this team. | ||||||
|      */ |      */ | ||||||
|  |     @Transient | ||||||
|     private GHTeam githubTeam; |     private GHTeam githubTeam; | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * A list of all student teams for which this TA team is responsible. |      * A list of all student teams for which this TA team is responsible. | ||||||
|      */ |      */ | ||||||
|  |     @OneToMany | ||||||
|     private List<StudentTeam> studentTeams; |     private List<StudentTeam> studentTeams; | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|  |  | ||||||
|  | @ -1,8 +1,13 @@ | ||||||
| package nl.andrewlalis.model; | package nl.andrewlalis.model; | ||||||
| 
 | 
 | ||||||
|  | import javax.persistence.Entity; | ||||||
|  | import javax.persistence.Table; | ||||||
|  | 
 | ||||||
| /** | /** | ||||||
|  * Represents an administrator in the organization, who manages student teams. |  * Represents an administrator in the organization, who manages student teams. | ||||||
|  */ |  */ | ||||||
|  | @Entity(name = "TeachingAssistant") | ||||||
|  | @Table(name = "teaching_assistants") | ||||||
| public class TeachingAssistant extends Person { | public class TeachingAssistant extends Person { | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|  |  | ||||||
|  | @ -1,5 +1,10 @@ | ||||||
| package nl.andrewlalis.model; | 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.ArrayList; | ||||||
| import java.util.Arrays; | import java.util.Arrays; | ||||||
| import java.util.List; | 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 |  * 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. |  * 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. |      * An identification number unique to this team alone. | ||||||
|      */ |      */ | ||||||
|     protected int id; |     @Column(name = "number") | ||||||
|  |     protected int number; | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * A list of members of this team. |      * 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; |     private List<Person> members; | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * Constructs this team with the given id. |      * Constructs this team with the given number. | ||||||
|      * @param id The id to assign to this team. |      * @param number The number to assign to this team. | ||||||
|      */ |      */ | ||||||
|     public Team(int id) { |     public Team(int number) { | ||||||
|         this.id = id; |         this.number = number; | ||||||
|         this.members = new ArrayList<>(); |         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) { |     protected Team() { | ||||||
|         this.id = newId; |         this(-1); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * @return This team's id number. |      * @param newId The new number number to assign to this team. | ||||||
|      */ |      */ | ||||||
|     public int getId() { |     public void setNumber(int newId) { | ||||||
|         return this.id; |         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 |      * 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. |      * same. | ||||||
|      * @param obj The object to check for equality. |      * @param obj The object to check for equality. | ||||||
|      * @return True if the two objects represent the same team, or false otherwise. |      * @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) { |     public boolean equals(Object obj) { | ||||||
|         if (obj instanceof Team) { |         if (obj instanceof Team) { | ||||||
|             Team team = (Team) obj; |             Team team = (Team) obj; | ||||||
|             return team.getId() == this.getId() && this.hasSameMembers(team); |             return team.getNumber() == this.getNumber() && this.hasSameMembers(team); | ||||||
|         } |         } | ||||||
|         return false; |         return false; | ||||||
|     } |     } | ||||||
|  | @ -143,11 +164,32 @@ public abstract class Team { | ||||||
|     @Override |     @Override | ||||||
|     public String toString() { |     public String toString() { | ||||||
|         StringBuilder sb = new StringBuilder(); |         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) { |         for (Person person : this.members) { | ||||||
|             sb.append(person.toString()).append('\n'); |             sb.append(person.toString()).append('\n'); | ||||||
|         } |         } | ||||||
|         return sb.toString(); |         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; | 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 nl.andrewlalis.ui.view.InitializerApp; | ||||||
| 
 | 
 | ||||||
| import javax.swing.*; | import javax.swing.*; | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| package nl.andrewlalis.ui.control.listeners; | package nl.andrewlalis.ui.control.listeners; | ||||||
| 
 | 
 | ||||||
| import nl.andrewlalis.ui.control.command.CommandExecutor; | import nl.andrewlalis.command.CommandExecutor; | ||||||
| 
 | 
 | ||||||
| import javax.swing.*; | import javax.swing.*; | ||||||
| import java.awt.event.KeyEvent; | import java.awt.event.KeyEvent; | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| package nl.andrewlalis.ui.control.listeners; | 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 nl.andrewlalis.ui.view.InitializerApp; | ||||||
| 
 | 
 | ||||||
| import java.awt.event.ActionEvent; | import java.awt.event.ActionEvent; | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| package nl.andrewlalis.ui.control.listeners; | 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 nl.andrewlalis.ui.view.InitializerApp; | ||||||
| 
 | 
 | ||||||
| import java.awt.event.ActionEvent; | import java.awt.event.ActionEvent; | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| package nl.andrewlalis.ui.control.listeners; | 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 nl.andrewlalis.ui.view.InitializerApp; | ||||||
| 
 | 
 | ||||||
| import javax.swing.*; | import javax.swing.*; | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| package nl.andrewlalis.ui.control.listeners; | 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 nl.andrewlalis.ui.view.InitializerApp; | ||||||
| 
 | 
 | ||||||
| import java.awt.event.ActionListener; | import java.awt.event.ActionListener; | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| package nl.andrewlalis.ui.control.listeners; | 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 nl.andrewlalis.ui.view.InitializerApp; | ||||||
| 
 | 
 | ||||||
| import javax.swing.*; | import javax.swing.*; | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| package nl.andrewlalis.ui.control.listeners; | 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 nl.andrewlalis.ui.view.InitializerApp; | ||||||
| 
 | 
 | ||||||
| import javax.swing.*; | 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; | package nl.andrewlalis.ui.view; | ||||||
| 
 | 
 | ||||||
|  | import nl.andrewlalis.command.CommandExecutor; | ||||||
| import nl.andrewlalis.model.Organization; | import nl.andrewlalis.model.Organization; | ||||||
| import nl.andrewlalis.ui.control.OutputTextHandler; | import nl.andrewlalis.ui.control.OutputTextHandler; | ||||||
| import nl.andrewlalis.ui.control.command.CommandExecutor; |  | ||||||
| import nl.andrewlalis.ui.control.listeners.*; | import nl.andrewlalis.ui.control.listeners.*; | ||||||
| 
 | 
 | ||||||
| import javax.swing.*; | 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("Delegate Student Teams", new DelegateStudentTeamsListener(this.executor, this))); | ||||||
|         commonActionsPanel.add(this.generateButtonPanel("Generate Assignments Repo", new GenerateAssignmentsRepoListener(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))); |         commonActionsPanel.add(this.generateButtonPanel("Delete Repos", new DeleteReposListener(this.executor, this))); | ||||||
| 
 | 
 | ||||||
|         // Extra panel to push buttons to the top. |         // 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; | package nl.andrewlalis.util; | ||||||
| 
 | 
 | ||||||
| import java.io.IOException; | 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. |  * Responsible for creating logs to standard output and writing to files. | ||||||
|  | @ -25,7 +28,7 @@ public class Logging { | ||||||
|             e.printStackTrace(); |             e.printStackTrace(); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         logger.setLevel(Level.ALL); |         logger.setLevel(Level.FINEST); | ||||||
|         Logger.getLogger("").setLevel(Level.OFF); |         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.Student; | ||||||
| import nl.andrewlalis.model.StudentTeam; | 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.CSVFormat; | ||||||
| import org.apache.commons.csv.CSVRecord; | import org.apache.commons.csv.CSVRecord; | ||||||
| 
 | 
 | ||||||
|  | @ -45,9 +42,9 @@ public class TeamGenerator { | ||||||
|         Iterable<CSVRecord> records = CSVFormat.DEFAULT.withFirstRecordAsHeader().parse(new FileReader(filename)); |         Iterable<CSVRecord> records = CSVFormat.DEFAULT.withFirstRecordAsHeader().parse(new FileReader(filename)); | ||||||
| 
 | 
 | ||||||
|         logger.fine("Reading all records into map."); |         logger.fine("Reading all records into map."); | ||||||
|         Map<Integer, Student> studentMap; |         List<Student> students; | ||||||
|         try { |         try { | ||||||
|             studentMap = readAllStudents(records, teamSize); |             students = readAllStudents(records, teamSize); | ||||||
|         } catch (ArrayIndexOutOfBoundsException e) { |         } catch (ArrayIndexOutOfBoundsException e) { | ||||||
|             logger.severe("StudentTeam size does not match column count in records."); |             logger.severe("StudentTeam size does not match column count in records."); | ||||||
|             throw new IllegalArgumentException("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."); |         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 |      * After all students with preferred partners are placed in teams, the single students are merged, and their teams | ||||||
|      * are added afterwards. |      * 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. |      * @param teamSize The preferred maximum size for a team. | ||||||
|      * @return A list of teams, most of which are of teamSize size. |      * @return A list of teams, most of which are of teamSize size. | ||||||
|      */ |      */ | ||||||
|     private static List<StudentTeam> generateAllValidTeams(Map<Integer, Student> studentMap, int teamSize) { |     private static List<StudentTeam> generateAllValidTeams(List<Student> students, int teamSize) { | ||||||
|         List<Student> singleStudents = new ArrayList<>(studentMap.values()); |         List<Student> singleStudents = new ArrayList<>(students); | ||||||
|         List<StudentTeam> studentTeams = new ArrayList<>(); |         List<StudentTeam> studentTeams = new ArrayList<>(); | ||||||
| 
 | 
 | ||||||
|  |         // An integer which increments for each valid team. Used to create identifiers for each team. | ||||||
|         int teamCount = 1; |         int teamCount = 1; | ||||||
|  | 
 | ||||||
|         // For each student, try to make a team from its preferred partners. |         // For each student, try to make a team from its preferred partners. | ||||||
|         for (Map.Entry<Integer, Student> e : studentMap.entrySet()) { |         for (Student student : students) { | ||||||
|             StudentTeam newTeam = e.getValue().getPreferredTeam(studentMap); |             StudentTeam newTeam = student.getPreferredTeam(); | ||||||
|             logger.finest("Checking if student's preferred team is valid:\n" + newTeam); |             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. |             // 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)) { |             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. |                 // 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; |                 boolean matchFound = false; | ||||||
|                 for (StudentTeam team : studentTeams) { |                 for (StudentTeam team : studentTeams) { | ||||||
|                     if (newTeam.hasSameMembers(team)) { |                     if (newTeam.hasSameMembers(team)) { | ||||||
|                         matchFound = true; |                         matchFound = true; | ||||||
|  |                         logger.finest("A team was found with the same members: " + team.getId()); | ||||||
|                         break; |                         break; | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|                 if (!matchFound) { |                 if (!matchFound) { | ||||||
|                     // Once we know this team is completely valid, we remove all the students in it from the list of singles. |                     // Once we know this team is completely valid, we remove all the students in it from the list of singles. | ||||||
|                     newTeam.setId(teamCount++); |                     newTeam.setNumber(teamCount++); | ||||||
|                     singleStudents.removeAll(Arrays.asList(newTeam.getStudents())); |  | ||||||
|                     studentTeams.add(newTeam); |                     studentTeams.add(newTeam); | ||||||
|  |                     singleStudents.removeAll(Arrays.asList(newTeam.getStudents())); | ||||||
|  |                     assignStudentsToTeam(newTeam); | ||||||
|                     logger.fine("Created team:\n" + newTeam); |                     logger.fine("Created team:\n" + newTeam); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|  | @ -114,14 +115,14 @@ public class TeamGenerator { | ||||||
|      * size as possible. |      * size as possible. | ||||||
|      * @param singleStudents A list of students who have no preferred partners. |      * @param singleStudents A list of students who have no preferred partners. | ||||||
|      * @param teamSize The preferred team size. |      * @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. |      * @return A list of teams comprising of single students. | ||||||
|      */ |      */ | ||||||
|     private static List<StudentTeam> mergeSingleStudents(List<Student> singleStudents, int teamSize, int teamIndex) { |     private static List<StudentTeam> mergeSingleStudents(List<Student> singleStudents, int teamSize, int teamIndex) { | ||||||
|         List<StudentTeam> studentTeams = new ArrayList<>(); |         List<StudentTeam> studentTeams = new ArrayList<>(); | ||||||
|         while (!singleStudents.isEmpty()) { |         while (!singleStudents.isEmpty()) { | ||||||
|             StudentTeam t = new StudentTeam(); |             StudentTeam t = new StudentTeam(); | ||||||
|             t.setId(teamIndex++); |             t.setNumber(teamIndex++); | ||||||
|             logger.fine("Creating new team of single students:\n" + t); |             logger.fine("Creating new team of single students:\n" + t); | ||||||
|             while (t.memberCount() < teamSize && !singleStudents.isEmpty()) { |             while (t.memberCount() < teamSize && !singleStudents.isEmpty()) { | ||||||
|                 Student s = singleStudents.remove(0); |                 Student s = singleStudents.remove(0); | ||||||
|  | @ -129,6 +130,7 @@ public class TeamGenerator { | ||||||
|                 t.addMember(s); |                 t.addMember(s); | ||||||
|             } |             } | ||||||
|             studentTeams.add(t); |             studentTeams.add(t); | ||||||
|  |             assignStudentsToTeam(t); | ||||||
|             logger.fine("Created team:\n" + t); |             logger.fine("Created team:\n" + t); | ||||||
|         } |         } | ||||||
|         return studentTeams; |         return studentTeams; | ||||||
|  | @ -141,8 +143,10 @@ public class TeamGenerator { | ||||||
|      * @return A map of all students in the file. |      * @return A map of all students in the file. | ||||||
|      * @throws ArrayIndexOutOfBoundsException if the teamSize does not work with the columns in the record. |      * @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<Integer, Student> studentMap = new HashMap<>(); | ||||||
|  |         Map<Student, List<Integer>> studentPreferredIds = new HashMap<>(); | ||||||
|  |         // Perform the initial read of the students. | ||||||
|         for (CSVRecord record : records) { |         for (CSVRecord record : records) { | ||||||
|             logger.finest("Read record: " + record); |             logger.finest("Read record: " + record); | ||||||
|             List<Integer> preferredIds = new ArrayList<>(); |             List<Integer> preferredIds = new ArrayList<>(); | ||||||
|  | @ -152,21 +156,49 @@ public class TeamGenerator { | ||||||
|                     preferredIds.add(Integer.parseInt(record.get(columnOffset + i))); |                     preferredIds.add(Integer.parseInt(record.get(columnOffset + i))); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|             Student s = new Student(Integer.parseInt(record.get(3)), record.get(2), record.get(1), record.get(4), preferredIds); |             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)) { |             if (studentMap.containsValue(s)) { | ||||||
|                 logger.warning("Duplicate entry found for student: " + s + "\nOverwriting previous value."); |                 logger.warning("Duplicate entry found for student: " + s + "\nOverwriting previous value."); | ||||||
|             } |             } | ||||||
|             studentMap.put(s.getNumber(), s); |             studentMap.put(s.getNumber(), s); | ||||||
|  |             studentPreferredIds.put(s, preferredIds); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         // Perform a safety check to ensure all preferred partners are valid students. |         // The final list of students, with preferred partners set. | ||||||
|         for (Map.Entry<Integer, Student> entry : studentMap.entrySet()) { |         List<Student> students = new ArrayList<>(); | ||||||
|             // Remove any ids that don't exist in the whole list of students. | 
 | ||||||
|             entry.getValue().getPreferredPartners().removeIf(partnerId -> !studentMap.containsKey(partnerId)); |         // 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. |         // At this point, all students are valid, and all preferred partners are valid. | ||||||
|         logger.fine("Read " + studentMap.size() + " students from records."); |         logger.fine("Read " + students.size() + " students from records."); | ||||||
|         return studentMap; |         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