diff --git a/cs240/record-indexer/.gitignore b/cs240/record-indexer/.gitignore
new file mode 100644
index 0000000..3c0160d
--- /dev/null
+++ b/cs240/record-indexer/.gitignore
@@ -0,0 +1,2 @@
+build/
+out/
diff --git a/cs240/record-indexer/.idea/.name b/cs240/record-indexer/.idea/.name
new file mode 100644
index 0000000..a5799ab
--- /dev/null
+++ b/cs240/record-indexer/.idea/.name
@@ -0,0 +1 @@
+6-record-indexer
\ No newline at end of file
diff --git a/cs240/record-indexer/.idea/artifacts/search_gui_jar.xml b/cs240/record-indexer/.idea/artifacts/search_gui_jar.xml
new file mode 100644
index 0000000..b1f5970
--- /dev/null
+++ b/cs240/record-indexer/.idea/artifacts/search_gui_jar.xml
@@ -0,0 +1,17 @@
+
+
+ $PROJECT_DIR$/out/artifacts/search_gui_jar
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/cs240/record-indexer/.idea/codeStyleSettings.xml b/cs240/record-indexer/.idea/codeStyleSettings.xml
new file mode 100644
index 0000000..9178b38
--- /dev/null
+++ b/cs240/record-indexer/.idea/codeStyleSettings.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
diff --git a/cs240/record-indexer/.idea/compiler.xml b/cs240/record-indexer/.idea/compiler.xml
new file mode 100644
index 0000000..217af47
--- /dev/null
+++ b/cs240/record-indexer/.idea/compiler.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/cs240/record-indexer/.idea/copyright/profiles_settings.xml b/cs240/record-indexer/.idea/copyright/profiles_settings.xml
new file mode 100644
index 0000000..3572571
--- /dev/null
+++ b/cs240/record-indexer/.idea/copyright/profiles_settings.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/cs240/record-indexer/.idea/dictionaries/film42.xml b/cs240/record-indexer/.idea/dictionaries/film42.xml
new file mode 100644
index 0000000..08f6891
--- /dev/null
+++ b/cs240/record-indexer/.idea/dictionaries/film42.xml
@@ -0,0 +1,11 @@
+
+
+
+ accessors
+ congratz
+ hackery
+ thornburg
+ tuples
+
+
+
\ No newline at end of file
diff --git a/cs240/record-indexer/.idea/encodings.xml b/cs240/record-indexer/.idea/encodings.xml
new file mode 100644
index 0000000..e206d70
--- /dev/null
+++ b/cs240/record-indexer/.idea/encodings.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/cs240/record-indexer/.idea/libraries/checkstyle_5_3_all.xml b/cs240/record-indexer/.idea/libraries/checkstyle_5_3_all.xml
new file mode 100644
index 0000000..4e63b58
--- /dev/null
+++ b/cs240/record-indexer/.idea/libraries/checkstyle_5_3_all.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/cs240/record-indexer/.idea/libraries/commons_io_2.xml b/cs240/record-indexer/.idea/libraries/commons_io_2.xml
new file mode 100644
index 0000000..3b01e2d
--- /dev/null
+++ b/cs240/record-indexer/.idea/libraries/commons_io_2.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/cs240/record-indexer/.idea/libraries/sqlite_jdbc_3_8_0_20130827_035027_1.xml b/cs240/record-indexer/.idea/libraries/sqlite_jdbc_3_8_0_20130827_035027_1.xml
new file mode 100644
index 0000000..f292ed4
--- /dev/null
+++ b/cs240/record-indexer/.idea/libraries/sqlite_jdbc_3_8_0_20130827_035027_1.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/cs240/record-indexer/.idea/misc.xml b/cs240/record-indexer/.idea/misc.xml
new file mode 100644
index 0000000..b4fdef3
--- /dev/null
+++ b/cs240/record-indexer/.idea/misc.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/cs240/record-indexer/.idea/modules.xml b/cs240/record-indexer/.idea/modules.xml
new file mode 100644
index 0000000..41818d9
--- /dev/null
+++ b/cs240/record-indexer/.idea/modules.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/cs240/record-indexer/.idea/scopes/scope_settings.xml b/cs240/record-indexer/.idea/scopes/scope_settings.xml
new file mode 100644
index 0000000..922003b
--- /dev/null
+++ b/cs240/record-indexer/.idea/scopes/scope_settings.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/cs240/record-indexer/.idea/uiDesigner.xml b/cs240/record-indexer/.idea/uiDesigner.xml
new file mode 100644
index 0000000..3b00020
--- /dev/null
+++ b/cs240/record-indexer/.idea/uiDesigner.xml
@@ -0,0 +1,125 @@
+
+
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+
+
+ -
+
+
+ -
+
+
+
+
+
+
diff --git a/cs240/record-indexer/.idea/vcs.xml b/cs240/record-indexer/.idea/vcs.xml
new file mode 100644
index 0000000..def6a6a
--- /dev/null
+++ b/cs240/record-indexer/.idea/vcs.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/cs240/record-indexer/.idea/workspace.xml b/cs240/record-indexer/.idea/workspace.xml
new file mode 100644
index 0000000..8d7bda9
--- /dev/null
+++ b/cs240/record-indexer/.idea/workspace.xml
@@ -0,0 +1,1427 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Abstraction issues
+
+
+ Android Lint
+
+
+ Code style issues
+
+
+ Declaration redundancy
+
+
+ General
+
+
+ JUnit issues
+
+
+ Threading issues
+
+
+
+
+ Abstraction issues
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ localhost
+ 5050
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ false
+
+
+
+
+ 1381705926175
+ 1381705926175
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ search_gui:jar
+
+
+
+
+
+
+
+
+
+
+
+
+ No facets are configured
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1.7
+
+
+
+
+
+
+
+
+
+
+
+ 6-record-indexer
+
+
+
+
+
+
+
+
+
+
+
+ 1.7
+
+
+
+
+
+
+
+
+
+
+
+ commons-io-2
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/cs240/record-indexer/build.xml b/cs240/record-indexer/build.xml
new file mode 100644
index 0000000..1489dbd
--- /dev/null
+++ b/cs240/record-indexer/build.xml
@@ -0,0 +1,200 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/cs240/record-indexer/checkstyle.xml b/cs240/record-indexer/checkstyle.xml
new file mode 100644
index 0000000..9d9d206
--- /dev/null
+++ b/cs240/record-indexer/checkstyle.xml
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/cs240/record-indexer/src/client/Client.java b/cs240/record-indexer/src/client/Client.java
new file mode 100644
index 0000000..8398868
--- /dev/null
+++ b/cs240/record-indexer/src/client/Client.java
@@ -0,0 +1,107 @@
+package client;
+
+import client.communication.Communicator;
+import client.components.MainWindow;
+import client.components.loginWindow.ErrorLoginDialog;
+import client.components.loginWindow.LoginWindow;
+import client.components.loginWindow.SuccessLoginDialog;
+import shared.communication.params.ValidateUser_Param;
+import shared.communication.responses.ValidateUser_Res;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.*;
+
+public class Client {
+
+ private LoginWindow loginWindow;
+ private Communicator communicator;
+
+ public Client(Communicator communicator) {
+ this.communicator = communicator;
+ }
+
+ public void run() {
+ loginWindow = new LoginWindow(communicator);
+ loginWindow.addLoginListener(loginListener);
+
+ // Run
+ EventQueue.invokeLater(new Runnable() {
+ public void run() {
+ loginWindow.setVisible(true);
+
+ }
+ });
+ }
+
+ public static void main(String[] args) {
+ // Create Window
+ String host = args[0];
+ String port = args[1];
+ String server = "http://"+host+":"+port+"/";
+ Communicator communicator = new Communicator(server);
+
+ Client client = new Client(communicator);
+ client.run();
+ }
+
+ private ActionListener loginListener = new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ ValidateUser_Param param = new ValidateUser_Param();
+ param.setUsername(loginWindow.getUsername());
+ param.setPassword(loginWindow.getPassword());
+
+ try {
+ ValidateUser_Res validateUserRes;
+ validateUserRes = communicator.validateUser(param);
+
+ loginWindow.setVisible(false);
+
+ SuccessLoginDialog successLoginDialog = new SuccessLoginDialog(validateUserRes);
+ successLoginDialog.addWindowListener(openMainWindowListener);
+ successLoginDialog.setVisible(true);
+
+
+ } catch (Exception execption) {
+ ErrorLoginDialog errorLoginDialog = new ErrorLoginDialog();
+ errorLoginDialog.setVisible(true);
+ }
+ }
+ };
+
+ private WindowListener openMainWindowListener = new WindowAdapter() {
+ @Override
+ public void windowClosed(WindowEvent e) {
+ super.windowClosed(e);
+
+ // Run
+ EventQueue.invokeLater(new Runnable() {
+ public void run() {
+ MainWindow frame = new MainWindow(communicator, loginWindow.getUsername(),
+ loginWindow.getPassword());
+ frame.addWindowListener(logoutListener);
+ frame.setVisible(true);
+ }
+ });
+ }
+ };
+
+ private WindowListener logoutListener = new WindowAdapter() {
+ @Override
+ public void windowClosed(WindowEvent e) {
+ super.windowClosed(e);
+
+ loginWindow = new LoginWindow(communicator);
+ loginWindow.addLoginListener(loginListener);
+
+ // Run
+ EventQueue.invokeLater(new Runnable() {
+ public void run() {
+ loginWindow.setVisible(true);
+ }
+ });
+ }
+ };
+
+}
diff --git a/cs240/record-indexer/src/client/communication/Communicator.java b/cs240/record-indexer/src/client/communication/Communicator.java
new file mode 100644
index 0000000..3de3a69
--- /dev/null
+++ b/cs240/record-indexer/src/client/communication/Communicator.java
@@ -0,0 +1,119 @@
+package client.communication;
+
+import client.communication.errors.RemoteServerErrorException;
+import client.communication.errors.UnauthorizedAccessException;
+import client.communication.modules.HttpClient;
+import shared.communication.params.*;
+import shared.communication.responses.*;
+
+import java.io.ByteArrayOutputStream;
+
+public class Communicator {
+
+ public Communicator(String serverPath) {
+ this.serverPath = serverPath;
+ }
+
+ private String serverPath;
+
+ public String getServerPath() {
+ return serverPath;
+ }
+
+ public ValidateUser_Res validateUser(ValidateUser_Param user)
+ throws UnauthorizedAccessException, RemoteServerErrorException {
+
+ String resource = "validateUser/";
+ String response = HttpClient.post(serverPath + resource, user.toXML());
+
+ if(response == null)
+ return null;
+
+ return ValidateUser_Res.serialize(response);
+ }
+
+ public Projects_Res getProjects(Projects_Param projects)
+ throws UnauthorizedAccessException, RemoteServerErrorException {
+
+ String resource = "getProjects/";
+ String response = HttpClient.post(serverPath + resource, projects.toXML());
+
+ if(response == null)
+ return null;
+
+ return Projects_Res.serialize(response);
+ }
+
+ public SampleImage_Res getSampleImage(SampleImage_Param sampleImage)
+ throws UnauthorizedAccessException, RemoteServerErrorException {
+
+ String resource = "getSampleImage/";
+ String response = HttpClient.post(serverPath + resource, sampleImage.toXML());
+
+ if(response == null)
+ return null;
+
+ return SampleImage_Res.serialize(response);
+ }
+
+ public DownloadBatch_Res downloadBatch(DownloadBatch_Param downloadBatch)
+ throws UnauthorizedAccessException, RemoteServerErrorException {
+
+ String resource = "downloadBatch/";
+ String response = HttpClient.post(serverPath + resource, downloadBatch.toXML());
+
+ if(response == null)
+ return null;
+
+ return DownloadBatch_Res.serialize(response);
+ }
+
+ public SubmitBatch_Res submitBatch(SubmitBatch_Param submitBatch)
+ throws UnauthorizedAccessException, RemoteServerErrorException {
+
+ String resource = "submitBatch/";
+ String response = HttpClient.post(serverPath + resource, submitBatch.toXML());
+
+ if(response == null)
+ return null;
+
+ return SubmitBatch_Res.serialize(response);
+ }
+
+ public Fields_Res getFields(Fields_Param fields)
+ throws UnauthorizedAccessException, RemoteServerErrorException {
+
+ String resource = "getFields/";
+ String response = HttpClient.post(serverPath + resource, fields.toXML());
+
+ if(response == null)
+ return null;
+
+ return Fields_Res.serialize(response);
+ }
+
+ public Search_Res search(Search_Param search)
+ throws UnauthorizedAccessException, RemoteServerErrorException {
+
+ String resource = "search/";
+ String response = HttpClient.post(serverPath + resource, search.toXML());
+
+ if(response == null)
+ return null;
+
+ return Search_Res.serialize(response);
+ }
+
+ public ByteArrayOutputStream downloadStatic(String resource) {
+
+ try {
+ return HttpClient.getStatic(serverPath+resource);
+ } catch (UnauthorizedAccessException e) {
+ return null;
+ } catch (RemoteServerErrorException e) {
+ return null;
+ }
+
+ }
+
+}
diff --git a/cs240/record-indexer/src/client/communication/errors/RemoteServerErrorException.java b/cs240/record-indexer/src/client/communication/errors/RemoteServerErrorException.java
new file mode 100644
index 0000000..22a5541
--- /dev/null
+++ b/cs240/record-indexer/src/client/communication/errors/RemoteServerErrorException.java
@@ -0,0 +1,20 @@
+package client.communication.errors;
+
+public class RemoteServerErrorException extends Exception {
+
+ public RemoteServerErrorException() {
+ }
+
+ public RemoteServerErrorException(String message) {
+ super(message);
+ }
+
+ public RemoteServerErrorException(Throwable throwable) {
+ super(throwable);
+ }
+
+ public RemoteServerErrorException(String message, Throwable throwable) {
+ super(message, throwable);
+ }
+
+}
diff --git a/cs240/record-indexer/src/client/communication/errors/UnauthorizedAccessException.java b/cs240/record-indexer/src/client/communication/errors/UnauthorizedAccessException.java
new file mode 100644
index 0000000..104a3fd
--- /dev/null
+++ b/cs240/record-indexer/src/client/communication/errors/UnauthorizedAccessException.java
@@ -0,0 +1,21 @@
+package client.communication.errors;
+
+@SuppressWarnings("serial")
+public class UnauthorizedAccessException extends Exception {
+
+ public UnauthorizedAccessException() {
+ }
+
+ public UnauthorizedAccessException(String message) {
+ super(message);
+ }
+
+ public UnauthorizedAccessException(Throwable throwable) {
+ super(throwable);
+ }
+
+ public UnauthorizedAccessException(String message, Throwable throwable) {
+ super(message, throwable);
+ }
+
+}
diff --git a/cs240/record-indexer/src/client/communication/modules/HttpClient.java b/cs240/record-indexer/src/client/communication/modules/HttpClient.java
new file mode 100644
index 0000000..00ea3d9
--- /dev/null
+++ b/cs240/record-indexer/src/client/communication/modules/HttpClient.java
@@ -0,0 +1,113 @@
+package client.communication.modules;
+
+import client.communication.errors.RemoteServerErrorException;
+import client.communication.errors.UnauthorizedAccessException;
+
+import java.io.*;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+public class HttpClient {
+
+ private static InputStream request(String url, String method, String request)
+ throws RemoteServerErrorException, UnauthorizedAccessException{
+
+ try {
+ URL requestURL = new URL(url);
+ HttpURLConnection connection = (HttpURLConnection) requestURL.openConnection();
+
+ // We can generalize, whatever
+ connection.setDoOutput(true);
+
+ connection.setRequestMethod(method);
+
+ OutputStreamWriter outputStreamWriter = new OutputStreamWriter(
+ connection.getOutputStream());
+ outputStreamWriter.write(request);
+ outputStreamWriter.close();
+
+ switch (connection.getResponseCode()) {
+ case HttpURLConnection.HTTP_OK:
+ return connection.getInputStream();
+ case HttpURLConnection.HTTP_UNAUTHORIZED:
+ throw new UnauthorizedAccessException();
+ default:
+ throw new RemoteServerErrorException();
+ }
+
+ } catch (MalformedURLException e) {
+ throw new RemoteServerErrorException();
+ } catch (IOException e) {
+ throw new RemoteServerErrorException();
+ }
+ }
+
+ public static String get(String url)
+ throws UnauthorizedAccessException, RemoteServerErrorException {
+
+ InputStream response = request(url, "GET", "");
+ return inputStreamToString(response);
+ }
+
+ public static String post(String url, String req)
+ throws UnauthorizedAccessException, RemoteServerErrorException {
+
+ InputStream response = request(url, "POST", req);
+ return inputStreamToString(response);
+ }
+
+ public static ByteArrayOutputStream getStatic(String url)
+ throws UnauthorizedAccessException, RemoteServerErrorException {
+
+ InputStream response = request(url, "GET", "");
+
+ if(response != null) {
+ try {
+
+ ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+
+
+ byte[] byteArray = new byte[512];
+
+ int bytesRead = 0;
+ while((bytesRead = response.read(byteArray)) != -1) {
+ byteArrayOutputStream.write(byteArray, 0, bytesRead);
+ }
+
+ return byteArrayOutputStream;
+
+
+ } catch (IOException e) {
+ return null;
+ }
+ }
+ return null;
+ }
+
+ private static String inputStreamToString(InputStream inputStream) {
+ StringBuilder stringBuilder = new StringBuilder();
+ BufferedReader bufferedReader = new BufferedReader(
+ new InputStreamReader(inputStream));
+
+ String line;
+ try {
+ while ((line = bufferedReader.readLine()) != null) {
+ stringBuilder.append(line);
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ } finally {
+ try { bufferedReader.close(); } catch (IOException e) {
+ e.printStackTrace(); }
+ }
+
+ try {
+ inputStream.close();
+ } catch (IOException e) {
+
+ }
+ return stringBuilder.toString();
+ }
+
+}
diff --git a/cs240/record-indexer/src/client/components/FileMenu.java b/cs240/record-indexer/src/client/components/FileMenu.java
new file mode 100644
index 0000000..bda1b6c
--- /dev/null
+++ b/cs240/record-indexer/src/client/components/FileMenu.java
@@ -0,0 +1,102 @@
+package client.components;
+
+import client.communication.Communicator;
+import client.components.downloadModal.DownloadModal;
+import client.components.loginWindow.ErrorLoginDialog;
+import client.persistence.ImageState;
+import client.persistence.NewProjectListener;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.WindowEvent;
+
+public class FileMenu extends JMenuBar {
+ private MainWindow mainWindow;
+ private Communicator communicator;
+ private ImageState imageState;
+ private JMenuItem eMenuItem1;
+
+ public FileMenu(MainWindow mainWindow, Communicator communicator, ImageState imageState) {
+ this.mainWindow = mainWindow;
+ this.communicator = communicator;
+ this.imageState = imageState;
+
+ this.imageState.addNewProjectListener(newProjectListener);
+
+ setupView();
+ }
+
+ private void setupView() {
+ // Prevents menu items from filling the whole length
+ this.setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
+
+ JMenu file1 = new JMenu("File");
+
+ eMenuItem1 = new JMenuItem("Download Batch");
+ eMenuItem1.addActionListener(downloadBatchAction);
+ eMenuItem1.setEnabled(!imageState.isHasImage());
+ eMenuItem1.setToolTipText("Exit application");
+
+ JMenuItem eMenuItem2 = new JMenuItem("Logout");
+ eMenuItem2.addActionListener(logoutAction);
+ eMenuItem2.setToolTipText("Exit application");
+
+ JMenuItem eMenuItem3 = new JMenuItem("Exit");
+ eMenuItem3.addActionListener(exitAction);
+ eMenuItem2.setToolTipText("Exit application");
+
+ file1.add(eMenuItem1);
+ file1.add(eMenuItem2);
+ file1.add(eMenuItem3);
+
+ // Add to self
+ this.add(file1);
+ this.setBackground(Color.WHITE);
+ }
+
+ private void updateSettings() {
+ imageState.getSettings().setWindowHeight(mainWindow.getHeight());
+ imageState.getSettings().setWindowWidth(mainWindow.getWidth());
+
+ Point point = mainWindow.getLocationOnScreen();
+ imageState.getSettings().setWindowPositionX((int) point.getX());
+ imageState.getSettings().setWindowPositionY((int) point.getY());
+ imageState.save();
+ }
+
+
+ private ActionListener downloadBatchAction = new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ DownloadModal downloadModal = new DownloadModal(imageState, communicator);
+ downloadModal.setVisible(true);
+ }
+ };
+
+ private ActionListener logoutAction = new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ updateSettings();
+ mainWindow.dispose();
+ }
+ };
+
+ private ActionListener exitAction = new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ updateSettings();
+ System.exit(1);
+ }
+ };
+
+ private NewProjectListener newProjectListener = new NewProjectListener() {
+ @Override
+ public void hasNewProject() {
+ boolean status = imageState.isHasImage();
+ eMenuItem1.setEnabled(!status);
+ }
+ };
+
+}
diff --git a/cs240/record-indexer/src/client/components/MainWindow.java b/cs240/record-indexer/src/client/components/MainWindow.java
new file mode 100644
index 0000000..dfc169e
--- /dev/null
+++ b/cs240/record-indexer/src/client/components/MainWindow.java
@@ -0,0 +1,147 @@
+package client.components;
+
+import client.communication.Communicator;
+import client.components.downloadModal.DownloadModal;
+import client.components.imagePanel.ImagePanel;
+import client.persistence.Cell;
+import client.persistence.ImageState;
+import client.persistence.NewProjectListener;
+import client.persistence.Settings;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.awt.event.WindowListener;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.io.*;
+
+public class MainWindow extends JFrame implements Serializable {
+
+ public ImageState imageState;
+ private Communicator communicator;
+
+ JSplitPane body = new JSplitPane(JSplitPane.VERTICAL_SPLIT, new JPanel(), new JPanel());
+
+ public MainWindow(Communicator communicator, String username, String password) {
+ Settings settings = loadSettings(username);
+
+ this.imageState = loadImageState(username);
+
+ if(this.imageState == null) {
+ this.imageState = new ImageState(settings, communicator, username, password);
+ } else {
+ this.imageState.setCommunicator(communicator);
+ }
+
+ this.imageState.setSettings(settings);
+
+ this.imageState.addNewProjectListener(newProjectListener);
+ this.communicator = communicator;
+
+ this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
+
+ this.setSize(settings.getWindowWidth(), settings.getWindowHeight());
+ this.setLocation(settings.getWindowPositionX(), settings.getWindowPositionY());
+
+ setupView();
+
+ this.imageState.initEvents();
+ this.addWindowListener(windowListener);
+ }
+
+ private void setupView() {
+ setupFileMenu();
+ setupImagePanel();
+ setupSplitView();
+
+ this.add(body, BorderLayout.CENTER);
+
+ body.addPropertyChangeListener(JSplitPane.DIVIDER_LOCATION_PROPERTY, new PropertyChangeListener() {
+ @Override
+ public void propertyChange(PropertyChangeEvent pce) {
+ int dividerLocation = body.getDividerLocation();
+
+ imageState.getSettings().setBaseSplitY(dividerLocation);
+ }
+ });
+ }
+
+ private void setupFileMenu() {
+ // Setup File Menu
+ this.add(new FileMenu(this, communicator, imageState), BorderLayout.NORTH);
+ }
+
+ private void setupImagePanel() {
+ body.setTopComponent(new ImagePanel(imageState));
+ }
+
+ private void setupSplitView() {
+ SplitBase splitBase = new SplitBase(imageState, communicator);
+
+ body.setBottomComponent(splitBase);
+ body.setBorder(null);
+ body.setDividerLocation(imageState.getSettings().getBaseSplitY());
+ }
+
+ public ImageState loadImageState(String username) {
+ File dest = new File("profiles/"+username);
+ if(dest.exists()) {
+ FileInputStream fis = null;
+ ObjectInputStream in = null;
+ try {
+ fis = new FileInputStream("profiles/"+username+"/state.ser");
+ in = new ObjectInputStream(fis);
+ return (ImageState)in.readObject();
+ } catch (IOException e) {
+ e.printStackTrace();
+ } catch (ClassNotFoundException e) {
+ e.printStackTrace();
+ }
+ }
+
+ return null;
+ }
+
+ public Settings loadSettings(String username) {
+ File dest = new File("profiles/"+username);
+ if(dest.exists()) {
+ FileInputStream fis = null;
+ ObjectInputStream in = null;
+ try {
+ fis = new FileInputStream("profiles/"+username+"/settings.ser");
+ in = new ObjectInputStream(fis);
+ return (Settings)in.readObject();
+ } catch (IOException e) {
+ e.printStackTrace();
+ } catch (ClassNotFoundException e) {
+ e.printStackTrace();
+ }
+ }
+
+ return Settings.defaultSettings();
+ }
+
+ private WindowListener windowListener = new WindowAdapter() {
+ @Override
+ public void windowClosing(WindowEvent e) {
+ super.windowClosing(e);
+
+ }
+
+ @Override
+ public void windowClosed(WindowEvent e) {
+ super.windowClosed(e);
+
+ }
+ };
+
+ private NewProjectListener newProjectListener = new NewProjectListener() {
+ @Override
+ public void hasNewProject() {
+
+
+ }
+ };
+}
diff --git a/cs240/record-indexer/src/client/components/SplitBase.java b/cs240/record-indexer/src/client/components/SplitBase.java
new file mode 100644
index 0000000..48b45db
--- /dev/null
+++ b/cs240/record-indexer/src/client/components/SplitBase.java
@@ -0,0 +1,74 @@
+package client.components;
+
+import client.communication.Communicator;
+import client.components.fieldHelp.FieldHelp;
+import client.components.formEntry.FormEntry;
+import client.persistence.SyncContext;
+import client.components.tableEntry.TableEntry;
+import client.persistence.Cell;
+import client.persistence.ImageState;
+
+import javax.swing.*;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+
+public class SplitBase extends JSplitPane {
+
+ private ImageState imageState;
+ private JTabbedPane tabbedPane;
+ private TableEntry tableEntry;
+ private FormEntry formEntry;
+ private Communicator communicator;
+
+ public SplitBase(ImageState imageState, Communicator communicator) {
+ this.imageState = imageState;
+ this.communicator = communicator;
+
+ setupView();
+
+ }
+
+ private void setupView() {
+ tabbedPane = new JTabbedPane();
+ tabbedPane.addChangeListener(changeListener);
+
+ tableEntry = new TableEntry(imageState);
+ tabbedPane.addTab("Table Entry", tableEntry);
+
+ formEntry = new FormEntry(imageState);
+ tabbedPane.addTab("Form Entry", formEntry);
+
+ this.setLeftComponent(tabbedPane);
+
+ JTabbedPane tabbedPane2 = new JTabbedPane();
+ tabbedPane2.addTab("Field Help", new FieldHelp(imageState, communicator));
+ tabbedPane2.addTab("Image Navigator", new JPanel());
+
+ this.setRightComponent(tabbedPane2);
+
+ this.addPropertyChangeListener(JSplitPane.DIVIDER_LOCATION_PROPERTY, new PropertyChangeListener() {
+ @Override
+ public void propertyChange(PropertyChangeEvent pce) {
+ int dividerLocation = getDividerLocation();
+
+ imageState.getSettings().setBaseSplitX(dividerLocation);
+ }
+ });
+
+
+ this.setDividerLocation(imageState.getSettings().getBaseSplitX());
+
+ }
+
+ private ChangeListener changeListener = new ChangeListener() {
+ @Override
+ public void stateChanged(ChangeEvent e) {
+ if(tabbedPane.getSelectedIndex() == 1) {
+ formEntry.becameVisible();
+ }
+ }
+ };
+
+}
diff --git a/cs240/record-indexer/src/client/components/downloadModal/DownloadModal.java b/cs240/record-indexer/src/client/components/downloadModal/DownloadModal.java
new file mode 100644
index 0000000..c79151b
--- /dev/null
+++ b/cs240/record-indexer/src/client/components/downloadModal/DownloadModal.java
@@ -0,0 +1,156 @@
+package client.components.downloadModal;
+
+import client.communication.Communicator;
+import client.communication.errors.RemoteServerErrorException;
+import client.communication.errors.UnauthorizedAccessException;
+import client.persistence.ImageState;
+import shared.communication.common.Project_Res;
+import shared.communication.params.Projects_Param;
+import shared.communication.params.SampleImage_Param;
+import shared.communication.responses.Projects_Res;
+import shared.communication.responses.SampleImage_Res;
+
+import javax.imageio.ImageIO;
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.WindowEvent;
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import java.net.URL;
+import java.util.*;
+import java.util.List;
+
+public class DownloadModal extends JDialog {
+
+ private ImageState imageState;
+ private Communicator communicator;
+ private List projects;
+ private JComboBox batchSelect;
+
+
+ public DownloadModal(ImageState imageState, Communicator communicator) {
+ this.imageState = imageState;
+ this.communicator = communicator;
+
+ setupView();
+ }
+
+ private void setupView() {
+ this.setModalityType(Dialog.ModalityType.DOCUMENT_MODAL);
+ this.setTitle("Download Image");
+ this.setSize(350, 100);
+ this.setResizable(false);
+ this.setLocationRelativeTo(null);
+ this.setLayout(new FlowLayout());
+
+ JLabel label = new JLabel("Project: ");
+ this.add(label);
+
+ projects = getProjects().getProjectsList();
+
+ List values = getProjectNames();
+
+ batchSelect = new JComboBox(values.toArray());
+ this.add(batchSelect);
+
+ JButton sampleImageButton = new JButton("Sample Image?");
+ sampleImageButton.addActionListener(getSampleImageListener);
+ this.add(sampleImageButton);
+
+ JButton cancelButton = new JButton("Cancel");
+ cancelButton.addActionListener(closeListener);
+ this.add(cancelButton);
+
+ JButton downloadButton = new JButton("Download");
+ downloadButton.addActionListener(downloadListener);
+ this.add(downloadButton);
+ }
+
+ private Projects_Res getProjects() {
+ Projects_Param param = new Projects_Param();
+ param.setUsername(imageState.getUsername());
+ param.setPassword(imageState.getPassword());
+
+ try {
+ return communicator.getProjects(param);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ return null;
+ }
+
+ private SampleImage_Res getSampleImage(int projectId) {
+ SampleImage_Param param = new SampleImage_Param();
+ param.setUsername(imageState.getUsername());
+ param.setPassword(imageState.getPassword());
+ param.setProjectId(projectId);
+
+ try {
+ return communicator.getSampleImage(param);
+ } catch (Exception e) {
+
+ }
+ return null;
+ }
+
+ private ActionListener getSampleImageListener = new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ String projectTitle = (String)batchSelect.getSelectedItem();
+ int projectId = getProjectIdForName(projectTitle);
+ SampleImage_Res res = getSampleImage(projectId);
+
+ String fullPath = communicator.getServerPath() + res.getUrl();
+
+ SampleImageModal sampleImageModal = new SampleImageModal(fullPath);
+ sampleImageModal.setVisible(true);
+
+ }
+ };
+
+ private ActionListener closeListener = new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ setVisible(false);
+ dispatchEvent(new WindowEvent(DownloadModal.this, WindowEvent.WINDOW_CLOSING));
+ }
+ };
+
+ private ActionListener downloadListener = new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ String projectTitle = (String)batchSelect.getSelectedItem();
+ int projectId = getProjectIdForName(projectTitle);
+
+ imageState.downloadProject(projectId);
+
+ setVisible(false);
+ dispatchEvent(new WindowEvent(DownloadModal.this, WindowEvent.WINDOW_CLOSING));
+ }
+ };
+
+ private int getProjectIdForName(String title) {
+
+ for(Project_Res project : projects) {
+
+ if(title.equals(project.getTitle())) {
+ return project.getId();
+ }
+ }
+
+ return -1;
+ }
+
+ private List getProjectNames() {
+ ArrayList titles = new ArrayList<>();
+
+ for(Project_Res project : projects) {
+ titles.add(project.getTitle());
+ }
+
+ return titles;
+ }
+}
diff --git a/cs240/record-indexer/src/client/components/downloadModal/SampleImage.java b/cs240/record-indexer/src/client/components/downloadModal/SampleImage.java
new file mode 100644
index 0000000..20632e7
--- /dev/null
+++ b/cs240/record-indexer/src/client/components/downloadModal/SampleImage.java
@@ -0,0 +1,25 @@
+package client.components.downloadModal;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.image.BufferedImage;
+
+
+public class SampleImage extends JPanel {
+
+ BufferedImage image;
+
+ public SampleImage(BufferedImage image) {
+ this.image = image;
+ }
+
+ @Override
+ protected void paintComponent(Graphics g) {
+ super.paintComponent(g);
+ Graphics2D g2 = (Graphics2D)g;
+
+ g2.scale(0.7, 0.7);
+ g2.drawImage(image, 0, 0, null);
+ }
+
+}
diff --git a/cs240/record-indexer/src/client/components/downloadModal/SampleImageModal.java b/cs240/record-indexer/src/client/components/downloadModal/SampleImageModal.java
new file mode 100644
index 0000000..314b9c4
--- /dev/null
+++ b/cs240/record-indexer/src/client/components/downloadModal/SampleImageModal.java
@@ -0,0 +1,52 @@
+package client.components.downloadModal;
+
+import javax.imageio.ImageIO;
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.WindowEvent;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+
+public class SampleImageModal extends JDialog {
+
+ BufferedImage image;
+
+ public SampleImageModal(String path) {
+ try {
+ image = ImageIO.read(new URL(path));
+ } catch (Exception e1) {
+ return;
+ }
+
+ setupView();
+ }
+
+ private void setupView() {
+ this.setTitle("Sample Image from XXXXXXX");
+ this.setModalityType(Dialog.ModalityType.DOCUMENT_MODAL);
+ this.setSize(500, 410);
+ this.setResizable(false);
+ this.setLocationRelativeTo(null);
+
+ SampleImage sampleImage = new SampleImage(image);
+
+ this.add(sampleImage, BorderLayout.CENTER);
+
+ JButton closeButton = new JButton("Close");
+ closeButton.addActionListener(closeListener);
+ this.add(closeButton, BorderLayout.SOUTH);
+
+ }
+
+ private ActionListener closeListener = new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ setVisible(false);
+ dispatchEvent(new WindowEvent(SampleImageModal.this, WindowEvent.WINDOW_CLOSING));
+ }
+ };
+}
diff --git a/cs240/record-indexer/src/client/components/fieldHelp/FieldHelp.java b/cs240/record-indexer/src/client/components/fieldHelp/FieldHelp.java
new file mode 100644
index 0000000..4baad53
--- /dev/null
+++ b/cs240/record-indexer/src/client/components/fieldHelp/FieldHelp.java
@@ -0,0 +1,77 @@
+package client.components.fieldHelp;
+
+import client.communication.Communicator;
+import client.persistence.Cell;
+import client.persistence.ImageState;
+import client.persistence.ImageStateListener;
+import client.persistence.NewProjectListener;
+import shared.communication.common.Fields;
+
+import javax.swing.*;
+import java.awt.*;
+import java.io.IOException;
+
+public class FieldHelp extends JPanel {
+
+ private ImageState imageState;
+
+ private String[] columns;
+ private int currentColumn;
+ private JEditorPane editorPane;
+ private Communicator communicator;
+
+ public FieldHelp(ImageState imageState, Communicator communicator) {
+ this.imageState = imageState;
+ this.communicator = communicator;
+
+ this.currentColumn = 0;
+ this.columns = imageState.getColumnNames();
+
+ setupView();
+
+ this.imageState.addListener(imageStateListener);
+ this.imageState.addNewProjectListener(newProjectListener);
+ }
+
+ private void setupView() {
+ editorPane = new JEditorPane();
+ editorPane.setContentType("text/html");
+ editorPane.setEditable(false);
+
+ this.setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));
+ this.add(new JScrollPane(editorPane), BorderLayout.CENTER);
+ }
+
+ private void updateView() {
+ if(!imageState.isHasImage()) return;
+
+ Fields field = imageState.getFieldsMetaData().get(currentColumn);
+ String path = communicator.getServerPath() + field.getHelpUrl();
+
+ try {
+ editorPane.setPage(path);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ private ImageStateListener imageStateListener = new ImageStateListener() {
+ @Override
+ public void valueChanged(Cell cell, String newValue) {
+
+ }
+
+ @Override
+ public void selectedCellChanged(Cell newSelectedCell) {
+ currentColumn = newSelectedCell.getField();
+ updateView();
+ }
+ };
+
+ private NewProjectListener newProjectListener = new NewProjectListener() {
+ @Override
+ public void hasNewProject() {
+ editorPane.setText("");
+ }
+ };
+}
diff --git a/cs240/record-indexer/src/client/components/formEntry/FormEntry.java b/cs240/record-indexer/src/client/components/formEntry/FormEntry.java
new file mode 100644
index 0000000..191b248
--- /dev/null
+++ b/cs240/record-indexer/src/client/components/formEntry/FormEntry.java
@@ -0,0 +1,141 @@
+package client.components.formEntry;
+
+import client.modules.spellChecker.KnownData;
+import client.persistence.*;
+
+import javax.swing.*;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+import java.awt.*;
+import java.awt.event.*;
+
+public class FormEntry extends JPanel {
+
+ private JList rowNumberList;
+ private FormTable formTable;
+ private JSplitPane splitPane;
+
+ private Cell currentCell;
+ private String[][] model;
+ private String[] columnNames;
+ private Integer[] rowIds;
+
+ private ImageState imageState;
+
+ public FormEntry(ImageState imageState) {
+ this.imageState = imageState;
+
+ this.model = this.imageState.getModel();
+ this.columnNames = this.imageState.getColumnNames();
+
+ this.imageState.addNewProjectListener(newProjectListener);
+
+ setupView();
+ }
+
+ private void setupView() {
+ this.setLayout(new GridLayout(1,1));
+
+ this.rowIds = new Integer[model.length];
+ generateListData();
+
+ splitPane = new JSplitPane();
+ splitPane.setDividerLocation(50);
+ splitPane.setBorder(null);
+
+ formTable = new FormTable(imageState);
+
+ splitPane.setRightComponent(new JScrollPane(formTable));
+
+ rowNumberList = new JList(rowIds);
+ rowNumberList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+ rowNumberList.setLayoutOrientation(JList.VERTICAL);
+ rowNumberList.setVisibleRowCount(-1);
+ rowNumberList.addListSelectionListener(listSelectionListener);
+ splitPane.setLeftComponent(new JScrollPane(rowNumberList));
+
+
+ this.add(splitPane);
+
+ imageState.addListener(imageStateListener);
+
+ }
+
+ public void generateListData() {
+ for(int i = 0; i < rowIds.length; i ++) {
+ rowIds[i] = (i+1);
+ }
+ }
+
+ @Override
+ public Dimension getMinimumSize() {
+ Dimension dim = super.getMinimumSize();
+ dim.width = 350;
+ return dim;
+ }
+
+ private ImageStateListener imageStateListener = new ImageStateListener() {
+ @Override
+ public void valueChanged(Cell cell, String newValue) {
+ int row = cell.getRecord();
+ int column = cell.getField();
+
+ rowNumberList.setSelectedIndex(row);
+ formTable.setValue(newValue, row, column);
+
+ splitPane.repaint();
+ }
+
+ @Override
+ public void selectedCellChanged(Cell newSelectedCell) {
+ int row = newSelectedCell.getRecord();
+ int column = newSelectedCell.getField();
+
+ currentCell = newSelectedCell;
+
+ rowNumberList.setSelectedIndex(row);
+ formTable.setCurrentCell(row, column);
+
+ splitPane.repaint();
+ }
+ };
+
+ private NewProjectListener newProjectListener = new NewProjectListener() {
+ @Override
+ public void hasNewProject() {
+ model = imageState.getModel();
+ columnNames = imageState.getColumnNames();
+
+ formTable.setDeactivated(true);
+ formTable = new FormTable(imageState);
+ splitPane.setRightComponent(new JScrollPane(formTable));
+
+ rowIds = new Integer[model.length];
+ generateListData();
+ rowNumberList.setListData(rowIds);
+ }
+ };
+
+ private ListSelectionListener listSelectionListener = new ListSelectionListener() {
+ @Override
+ public void valueChanged(ListSelectionEvent e) {
+ if(!getParent().getParent().getParent().isVisible()) return;
+
+ int newRow = rowNumberList.getSelectedIndex();
+
+ Cell cell = new Cell();
+ cell.setRecord(newRow);
+ cell.setField(currentCell.getField());
+
+ imageState.setSelectedCell(cell);
+ repaint();
+
+ }
+ };
+
+ public void becameVisible() {
+ formTable.setCurrentCellForce();
+ }
+
+
+}
diff --git a/cs240/record-indexer/src/client/components/formEntry/FormTable.java b/cs240/record-indexer/src/client/components/formEntry/FormTable.java
new file mode 100644
index 0000000..b84f69a
--- /dev/null
+++ b/cs240/record-indexer/src/client/components/formEntry/FormTable.java
@@ -0,0 +1,195 @@
+package client.components.formEntry;
+
+import client.modules.spellChecker.KnownData;
+import client.persistence.Cell;
+import client.persistence.ImageState;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.FocusEvent;
+import java.awt.event.FocusListener;
+
+public class FormTable extends JPanel {
+
+ private String[] fieldNames;
+ private String[][] values;
+
+ private boolean updatingCell;
+
+ private ImageState imageState;
+
+ private int currentRow;
+ private boolean deactivated = false;
+
+ public FormTable(ImageState imageState) {
+
+ this.imageState = imageState;
+
+ this.fieldNames = this.imageState.getColumnNames();
+ this.values = this.imageState.getModel();
+
+ this.currentRow = 0;
+
+ setupView();
+ }
+
+ private void setupView() {
+ this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
+
+ currentRow = 0;
+
+ initForm();
+
+ }
+
+ private void initForm() {
+ for(int i = 0; i < fieldNames.length; i++) {
+ String labelString = fieldNames[i];
+ String textFieldString = values[currentRow][i];
+
+ JPanel formContainer = new JPanel();
+
+ JLabel label = new JLabel(labelString);
+ label.setPreferredSize(new Dimension(100,30));
+ formContainer.add(label, BorderLayout.WEST);
+
+ JTextField textField = new JTextField(textFieldString);
+ textField.addFocusListener(generateFocusListener(textField, i));
+ textField.setPreferredSize(new Dimension(150, 30));
+
+ if(hasSuggestion(textField.getText(), i)) {
+ textField.setBackground(Color.RED);
+ }
+
+ formContainer.add(textField, BorderLayout.CENTER);
+
+ this.add(formContainer);
+ }
+ }
+
+ public void updateCurrentCell(JTextField textField, int index) {
+ if(updatingCell || deactivated) return;
+
+ Cell cell = new Cell();
+ cell.setRecord(currentRow);
+ cell.setField(index);
+
+ updatingCell = true;
+ imageState.setSelectedCell(cell);
+ updatingCell = false;
+ }
+
+ public void updateCellValue(JTextField textField, int index) {
+ if(updatingCell || deactivated) return;
+
+ Cell cell = new Cell();
+ cell.setRecord(currentRow);
+ cell.setField(index);
+
+ values[currentRow][index] = textField.getText();
+
+ if(hasSuggestion(textField.getText(), index)) {
+ textField.setBackground(Color.RED);
+ } else {
+ textField.setBackground(Color.WHITE);
+ }
+
+ updatingCell = true;
+ imageState.setValue(cell, textField.getText());
+ updatingCell = false;
+ }
+
+ public boolean hasSuggestion(String value, int column) {
+ if(value.equals("")) return false;
+ KnownData knownData = imageState.getKnownDataValues().get(column);
+
+ String[] words = knownData.getWordArray();
+
+ for(String val : words) {
+ if(val.toLowerCase().equals(value.toLowerCase())) return false;
+ }
+
+ return true;
+ }
+
+ private FocusListener generateFocusListener(final JTextField textField, final int index) {
+ return new FocusListener() {
+ @Override
+ public void focusGained(FocusEvent e) {
+ updateCurrentCell(textField, index);
+ }
+
+ @Override
+ public void focusLost(FocusEvent e) {
+ updateCellValue(textField, index);
+ }
+ };
+ }
+
+ private void updateView() {
+ if(deactivated) return;
+
+ updatingCell = true;
+ for(int i = 0; i < this.getComponents().length; i++) {
+ JPanel formSet = (JPanel)this.getComponent(i);
+ JTextField form = (JTextField)formSet.getComponent(1);
+ form.setText(values[currentRow][i]);
+
+ if(hasSuggestion(form.getText(), i)) {
+ form.setBackground(Color.RED);
+ } else {
+ form.setBackground(Color.WHITE);
+ }
+ }
+ updatingCell = false;
+ }
+
+ public void setValue(String newValue, int row, int column) {
+ if(updatingCell || deactivated) return;
+
+ this.updateView();
+ this.repaint();
+ }
+
+ public void setCurrentCell(int row, int column) {
+ if(updatingCell || deactivated) return;
+
+ this.currentRow = row;
+
+ this.updateView();
+ this.repaint();
+
+ this.setFocus(column);
+ }
+
+ public void setCurrentCellForce() {
+ if(deactivated) return;
+
+ final Cell cell = imageState.getSelectedCell();
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ setFocus(cell.getField());
+ }
+ });
+ }
+
+ public void setFocus(int columnField) {
+ if(deactivated) return;
+
+ // offset is x - 1, cause start at 0.
+ int column = columnField;
+
+ if(values.length == 0) return;
+
+ // get the column textField and request focus
+ JPanel formList = (JPanel)this.getComponent(column);
+ final JTextField form = (JTextField)formList.getComponent(1);
+
+ form.requestFocus();
+ }
+
+ public void setDeactivated(boolean deactivated) {
+ this.deactivated = deactivated;
+ }
+
+}
diff --git a/cs240/record-indexer/src/client/components/imagePanel/ImageCell.java b/cs240/record-indexer/src/client/components/imagePanel/ImageCell.java
new file mode 100644
index 0000000..ba5c07c
--- /dev/null
+++ b/cs240/record-indexer/src/client/components/imagePanel/ImageCell.java
@@ -0,0 +1,45 @@
+package client.components.imagePanel;
+
+import java.awt.*;
+import java.awt.geom.Rectangle2D;
+
+public class ImageCell {
+
+ private double x;
+ private double y;
+ private double width;
+ private double height;
+ private boolean isSelected;
+ Rectangle2D.Double rectangle2D;
+
+ public ImageCell(Rectangle2D.Double rectangle2D) {
+ this.rectangle2D = rectangle2D;
+
+ this.x = rectangle2D.getBounds2D().getX();
+ this.y = rectangle2D.getBounds2D().getY();
+ this.width = this.x = rectangle2D.getBounds2D().getWidth();
+ this.height = this.x = rectangle2D.getBounds2D().getHeight();
+
+ this.isSelected = false;
+ }
+
+ public void paint(Graphics2D g2, boolean isSelected) {
+ if(isSelected) {
+ g2.setColor(new Color(0,119,204, 150));
+ } else {
+ g2.setColor(new Color(0,0,0, 0));
+ }
+ g2.fill(rectangle2D);
+ }
+
+ public boolean contains(double x, double y) {
+ return rectangle2D.contains(x, y);
+ }
+
+ public double getWidth() {
+ return width;
+ }
+
+ public void setWidth(double width) {
+ this.width = width;
+ }}
diff --git a/cs240/record-indexer/src/client/components/imagePanel/ImageControl.java b/cs240/record-indexer/src/client/components/imagePanel/ImageControl.java
new file mode 100644
index 0000000..9a48f3d
--- /dev/null
+++ b/cs240/record-indexer/src/client/components/imagePanel/ImageControl.java
@@ -0,0 +1,174 @@
+package client.components.imagePanel;
+
+import client.components.imagePanel.listeners.ImageControlsListener;
+import client.persistence.ImageState;
+import client.persistence.NewProjectListener;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.ArrayList;
+
+public class ImageControl extends JPanel {
+
+ private ArrayList imageControlsListeners;
+
+ private ImageState imageState;
+
+ private JButton zoomInButton;
+ private JButton zoomOutButton;
+ private JButton invertButton;
+ private JButton toggleHighlightsButton;
+ private JButton saveButton;
+ private JButton submitButton;
+
+ public ImageControl(ImageState imageState) {
+ this.imageState = imageState;
+
+ this.imageState.addNewProjectListener(newProjectListener);
+
+ setupView();
+
+ imageControlsListeners = new ArrayList<>();
+ }
+
+ private void setupView() {
+ boolean enabled = false;
+
+ if(imageState.isHasImage()) enabled = true;
+
+ zoomInButton = new JButton("Zoom In");
+ zoomInButton.addActionListener(zoomInAction);
+ zoomInButton.setEnabled(enabled);
+ this.add(zoomInButton, BorderLayout.WEST);
+
+ zoomOutButton = new JButton("Zoom Out");
+ zoomOutButton.addActionListener(zoomOutAction);
+ zoomOutButton.setEnabled(enabled);
+ this.add(zoomOutButton, BorderLayout.WEST);
+
+ invertButton = new JButton("Invert");
+ invertButton.addActionListener(invertImageAction);
+ invertButton.setEnabled(enabled);
+ this.add(invertButton, BorderLayout.WEST);
+
+ toggleHighlightsButton = new JButton("Toggle Highlights");
+ toggleHighlightsButton.addActionListener(toggleHighlightsAction);
+ toggleHighlightsButton.setEnabled(enabled);
+ this.add(toggleHighlightsButton, BorderLayout.WEST);
+
+ saveButton = new JButton("Save");
+ saveButton.setEnabled(enabled);
+ saveButton.addActionListener(saveAction);
+ this.add(saveButton, BorderLayout.WEST);
+
+ submitButton = new JButton("Submit");
+ submitButton.setEnabled(enabled);
+ submitButton.addActionListener(submitAction);
+ this.add(submitButton, BorderLayout.WEST);
+
+ this.setComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT);
+ }
+
+ @Override
+ public Dimension getMaximumSize() {
+ Dimension dim = super.getMaximumSize();
+ dim.height = 60;
+ return dim;
+ }
+
+ public void addControlsListener(ImageControlsListener imageControlsListener) {
+ imageControlsListeners.add(imageControlsListener);
+ }
+
+ private void updateZoomInListeners() {
+ for(ImageControlsListener cL : imageControlsListeners) {
+ cL.onScrollIncrease();
+ }
+ }
+
+ private void updateZoomOutListeners() {
+ for(ImageControlsListener cL : imageControlsListeners) {
+ cL.onScrollDecrease();
+ }
+ }
+
+ private void updateInvertImageListeners() {
+ for(ImageControlsListener cL : imageControlsListeners) {
+ cL.onInvertImage();
+ }
+ }
+
+ private void updateToggleHighlightsListeners() {
+ for(ImageControlsListener cL : imageControlsListeners) {
+ cL.onToggleHighlights();
+ }
+ }
+
+ private void updateSaveListeners() {
+ imageState.save();
+ }
+
+ private void updateSubmitListeners() {
+ imageState.submitProject();
+ }
+
+
+ private ActionListener zoomInAction = new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ updateZoomInListeners();
+ }
+ };
+
+ private ActionListener zoomOutAction = new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ updateZoomOutListeners();
+ }
+ };
+
+ private ActionListener invertImageAction = new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ updateInvertImageListeners();
+ }
+ };
+
+ private ActionListener toggleHighlightsAction = new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ updateToggleHighlightsListeners();
+ }
+ };
+
+ private ActionListener saveAction = new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ updateSaveListeners();
+ }
+ };
+
+ private ActionListener submitAction = new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ updateSubmitListeners();
+ }
+ };
+
+ private NewProjectListener newProjectListener = new NewProjectListener() {
+ @Override
+ public void hasNewProject() {
+ boolean status = imageState.isHasImage();
+
+ zoomInButton.setEnabled(status);
+ zoomOutButton.setEnabled(status);
+ invertButton.setEnabled(status);
+ toggleHighlightsButton.setEnabled(status);
+ saveButton.setEnabled(status);
+ submitButton.setEnabled(status);
+ }
+ };
+
+}
diff --git a/cs240/record-indexer/src/client/components/imagePanel/ImagePanel.java b/cs240/record-indexer/src/client/components/imagePanel/ImagePanel.java
new file mode 100644
index 0000000..e8df0b5
--- /dev/null
+++ b/cs240/record-indexer/src/client/components/imagePanel/ImagePanel.java
@@ -0,0 +1,36 @@
+package client.components.imagePanel;
+
+import client.components.imagePanel.listeners.ImageControlsListener;
+import client.persistence.*;
+
+import javax.swing.*;
+import java.awt.*;
+
+public class ImagePanel extends JPanel {
+
+ private ImageControl imageControl;
+ private ScalableImage scalableImage;
+
+ private ImageState imageState;
+
+ public ImagePanel(ImageState imageState) {
+ this.imageState = imageState;
+
+ setupView();
+ }
+
+ private void setupView() {
+ this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
+ this.setBackground(Color.DARK_GRAY);
+
+ scalableImage = new ScalableImage(imageState);
+ ImageControlsListener imageControlsListener = scalableImage.getImageControlsListener();
+
+ imageControl = new ImageControl(imageState);
+ imageControl.addControlsListener(imageControlsListener);
+
+ this.add(imageControl, Component.LEFT_ALIGNMENT);
+
+ this.add(scalableImage, BorderLayout.CENTER);
+ }
+}
diff --git a/cs240/record-indexer/src/client/components/imagePanel/ImageTable.java b/cs240/record-indexer/src/client/components/imagePanel/ImageTable.java
new file mode 100644
index 0000000..96a4556
--- /dev/null
+++ b/cs240/record-indexer/src/client/components/imagePanel/ImageTable.java
@@ -0,0 +1,147 @@
+package client.components.imagePanel;
+
+import client.components.imagePanel.ImageCell;
+import client.persistence.Cell;
+import client.persistence.ImageState;
+import client.persistence.ImageStateListener;
+import client.persistence.SyncContext;
+
+import java.awt.*;
+import java.awt.geom.Rectangle2D;
+import java.util.ArrayList;
+
+public class ImageTable {
+
+ private int recordsPerImage;
+ private int firstYCoord;
+ private int recordHeight;
+ private int columnCount;
+
+ private ArrayList fieldXValues;
+ private ArrayList fieldWidthValues;
+
+ private ImageCell[][] model;
+
+ private Rectangle2D.Double tableBoundaries;
+ private boolean highlightsEnabled;
+
+ private ImageCell currentSelected;
+
+ private ImageState imageState;
+ private boolean deactivated = false;
+
+ public ImageTable(ImageState imageState) {
+
+ this.imageState = imageState;
+
+ this.recordsPerImage = imageState.getRecordsPerImage();
+ this.firstYCoord = imageState.getFirstYCoord();
+ this.recordHeight = imageState.getRecordHeight();
+ this.columnCount = imageState.getColumnCount();
+ this.fieldXValues = imageState.getFieldXValues();
+ this.fieldWidthValues = imageState.getFieldWidthValues();
+
+ // Note we go [y][x]
+ model = new ImageCell[recordsPerImage][columnCount];
+
+ highlightsEnabled = imageState.getSettings().isImageHighlights();
+
+ if(!imageState.isHasImage()) return;
+
+ generateModel();
+ generateTableBoundaries();
+ }
+
+ private void generateModel() {
+ for(int y = 0; y < recordsPerImage; y++) {
+ for(int x = 0; x < columnCount; x++) {
+ Rectangle2D.Double rect = new Rectangle2D.Double();
+
+ int rectY = firstYCoord + (y * recordHeight); // y starts at 0.
+ int rectX = fieldXValues.get(x);
+ int width = fieldWidthValues.get(x);
+ int height = recordHeight;
+
+ rect.setRect(rectX, rectY, width, height);
+
+ model[y][x] = new ImageCell(rect);
+ }
+ }
+ }
+
+ public void paint(Graphics2D g2) {
+ for(int y = 0; y < recordsPerImage; y++) {
+ for(int x = 0; x < columnCount; x++) {
+ ImageCell imageCell = model[y][x];
+
+ if(imageCell == currentSelected && highlightsEnabled) {
+ model[y][x].paint(g2, true);
+ } else {
+ model[y][x].paint(g2, false);
+ }
+ }
+ }
+ }
+
+ public void contains(int worldX, int worldY) {
+ // Make sure in table boundaries first.
+ if(!tableBoundaries.contains(worldX, worldY)) {
+ return;
+ }
+
+ for(int y = 0; y < recordsPerImage; y++) {
+ for(int x = 0; x < columnCount; x++) {
+ ImageCell imageCell = model[y][x];
+ if(imageCell.contains(worldX, worldY)) {
+ this.currentSelected = imageCell;
+ setCurrentCell(x, y);
+
+ Cell cell = new Cell();
+ cell.setField(x);
+ cell.setRecord(y);
+ this.imageState.setSelectedCell(cell);
+ }
+ }
+ }
+ }
+
+ public void setCurrentCell(int x, int y) {
+ if(model.length == 0 || deactivated) return;
+
+ this.currentSelected = model[y][x];
+ }
+
+ private void generateTableBoundaries() {
+
+ int y = firstYCoord;
+ int x = fieldXValues.get(0);
+
+ int width = 0;
+ for(int fw : fieldWidthValues) {
+ width += fw;
+ }
+
+ int height = recordsPerImage * recordHeight;
+
+ tableBoundaries = new Rectangle2D.Double(x, y, width, height);
+ }
+
+ public void enableHighlights(boolean value) {
+ this.highlightsEnabled = value;
+
+ imageState.getSettings().setImageHighlights(value);
+ }
+
+ public boolean isHighlightsEnabled() {
+ return highlightsEnabled;
+ }
+
+
+ public void setDeactivated(boolean deactivated) {
+ this.deactivated = deactivated;
+ }
+
+ public boolean isDeactivated() {
+ return deactivated;
+ }
+}
diff --git a/cs240/record-indexer/src/client/components/imagePanel/ScalableImage.java b/cs240/record-indexer/src/client/components/imagePanel/ScalableImage.java
new file mode 100644
index 0000000..13baf50
--- /dev/null
+++ b/cs240/record-indexer/src/client/components/imagePanel/ScalableImage.java
@@ -0,0 +1,291 @@
+package client.components.imagePanel;
+
+import client.components.imagePanel.listeners.ImageControlsListener;
+import client.components.listeners.DrawingListener;
+import client.persistence.*;
+
+import javax.imageio.ImageIO;
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseWheelEvent;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.NoninvertibleTransformException;
+import java.awt.geom.Point2D;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+
+public class ScalableImage extends JPanel {
+
+ private BufferedImage image;
+
+ private int w_originX;
+ private int w_originY;
+ private double scale;
+
+ private boolean dragging;
+ private int w_dragStartX;
+ private int w_dragStartY;
+ private int w_dragStartOriginX;
+ private int w_dragStartOriginY;
+
+ private ImageTable imageTable;
+
+ private double MAX_SCROLL = 1.0f;
+ private double MIN_SCROLL = 0.2f;
+
+ private ImageState imageState;
+
+
+ public ScalableImage(ImageState imageState) {
+ this.imageState = imageState;
+
+ this.setBackground(Color.DARK_GRAY);
+
+ initDrag();
+
+ this.addMouseListener(mouseAdapter);
+ this.addMouseMotionListener(mouseAdapter);
+ this.addMouseWheelListener(mouseAdapter);
+
+ setupView();
+
+ this.imageState.addListener(imageStateListener);
+ this.imageState.addNewProjectListener(newProjectListener);
+
+ }
+
+ private void setupView() {
+ image = imageState.getImage();
+
+ imageTable = new ImageTable(imageState);
+
+ this.setOrigin(imageState.getSettings().getImageOriginX(),
+ imageState.getSettings().getImageOriginY());
+
+ this.setScale(imageState.getSettings().getImageScaleLevel());
+
+ this.repaint();
+ }
+
+ public void refreshImage() {
+ imageTable.setDeactivated(true);
+ setupView();
+ }
+
+ boolean redrawHack = false;
+
+ @Override
+ protected void paintComponent(Graphics g) {
+ super.paintComponent(g);
+
+ Graphics2D g2 = (Graphics2D)g;
+
+ g2.translate(this.getWidth() / 2, this.getHeight() / 2);
+ g2.scale(scale, scale);
+ g2.translate(-w_originX, -w_originY);
+ g2.drawImage(image, 0, 0, null);
+
+ imageTable.paint(g2);
+
+ if(!redrawHack) {
+ redrawHack = true;
+ this.repaint();
+ }
+ }
+
+ public void invertImage(BufferedImage b) {
+ for (int x = 0; x < b.getWidth(); x++) {
+ for (int y = 0; y < b.getHeight(); y++) {
+ int rgba = b.getRGB(x, y);
+ Color col = new Color(rgba, true);
+ col = new Color(255 - col.getRed(),
+ 255 - col.getGreen(),
+ 255 - col.getBlue());
+ b.setRGB(x, y, col.getRGB());
+ }
+ }
+
+ boolean current = imageState.getSettings().isImageInverted();
+ imageState.getSettings().setImageInverted(!current);
+
+ redrawHack = false;
+ this.repaint();
+ }
+
+ public void setValue(Cell cell, String value) {
+ imageTable.setCurrentCell(cell.getField(), cell.getRecord());
+ this.repaint();
+ }
+
+ public void setCurrentCell(Cell cell) {
+ imageTable.setCurrentCell(cell.getField(), cell.getRecord());
+ this.repaint();
+ }
+
+ private void initDrag() {
+ dragging = false;
+ w_dragStartX = 0;
+ w_dragStartY = 0;
+ w_dragStartOriginX = 0;
+ w_dragStartOriginY = 0;
+ }
+
+ public void setScale(double newScale) {
+ scale = newScale;
+ this.repaint();
+ }
+
+ public void setOrigin(int w_newOriginX, int w_newOriginY) {
+ w_originX = w_newOriginX;
+ w_originY = w_newOriginY;
+ this.repaint();
+ }
+
+ private MouseAdapter mouseAdapter = new MouseAdapter() {
+
+ @Override
+ public void mousePressed(MouseEvent e) {
+ if(!imageState.isHasImage()) return;
+
+ int d_X = e.getX();
+ int d_Y = e.getY();
+
+ AffineTransform transform = new AffineTransform();
+ transform.translate(getWidth()/2, getHeight()/2);
+ transform.scale(scale, scale);
+ transform.translate(-w_originX, -w_originY);
+
+ Point2D d_Pt = new Point2D.Double(d_X, d_Y);
+ Point2D w_Pt = new Point2D.Double();
+ try {
+ transform.inverseTransform(d_Pt, w_Pt);
+ } catch (NoninvertibleTransformException ex) {
+ return;
+ }
+ int w_X = (int)w_Pt.getX();
+ int w_Y = (int)w_Pt.getY();
+
+ imageTable.contains(w_X, w_Y);
+
+ dragging = true;
+ w_dragStartX = w_X;
+ w_dragStartY = w_Y;
+ w_dragStartOriginX = w_originX;
+ w_dragStartOriginY = w_originY;
+
+ repaint();
+ }
+
+ @Override
+ public void mouseDragged(MouseEvent e) {
+ if (dragging) {
+ int d_X = e.getX();
+ int d_Y = e.getY();
+
+ AffineTransform transform = new AffineTransform();
+ transform.translate(getWidth()/2, getHeight()/2);
+ transform.scale(scale, scale);
+ transform.translate(-w_dragStartOriginX, -w_dragStartOriginY);
+
+ Point2D d_Pt = new Point2D.Double(d_X, d_Y);
+ Point2D w_Pt = new Point2D.Double();
+ try {
+ transform.inverseTransform(d_Pt, w_Pt);
+ } catch (NoninvertibleTransformException ex) {
+ return;
+ }
+ int w_X = (int)w_Pt.getX();
+ int w_Y = (int)w_Pt.getY();
+
+ int w_deltaX = w_X - w_dragStartX;
+ int w_deltaY = w_Y - w_dragStartY;
+
+ w_originX = w_dragStartOriginX - w_deltaX;
+ w_originY = w_dragStartOriginY - w_deltaY;
+
+ repaint();
+ }
+ }
+
+ @Override
+ public void mouseReleased(MouseEvent e) {
+ initDrag();
+
+ imageState.getSettings().setImageOriginX(w_originX);
+ imageState.getSettings().setImageOriginY(w_originY);
+ }
+
+ @Override
+ public void mouseWheelMoved(MouseWheelEvent e) {
+ int rotation = e.getWheelRotation();
+ if((rotation > 0) && scale > MIN_SCROLL ) {
+ setScale(scale - 0.02f);
+ } else if (scale < MAX_SCROLL) {
+ setScale(scale + 0.02f);
+ }
+
+ imageState.getSettings().setImageScaleLevel(scale);
+ }
+ };
+
+ private ImageStateListener imageStateListener = new ImageStateListener() {
+ @Override
+ public void valueChanged(Cell cell, String newValue) {
+ return;
+ }
+
+ @Override
+ public void selectedCellChanged(Cell newSelectedCell) {
+ imageTable.setCurrentCell(newSelectedCell.getField(), newSelectedCell.getRecord());
+
+ repaint();
+ }
+ };
+
+ private NewProjectListener newProjectListener = new NewProjectListener() {
+ @Override
+ public void hasNewProject() {
+ refreshImage();
+ }
+ };
+
+ private ImageControlsListener imageControlsListener = new ImageControlsListener() {
+ @Override
+ public void onScrollIncrease() {
+ if (scale < MAX_SCROLL) {
+ setScale(scale + 0.02f);
+ }
+ }
+
+ @Override
+ public void onScrollDecrease() {
+ if(scale > MIN_SCROLL ) {
+ setScale(scale - 0.02f);
+ }
+ }
+
+ @Override
+ public void onInvertImage() {
+ invertImage(image);
+ }
+
+ @Override
+ public void onToggleHighlights() {
+ if(imageTable.isHighlightsEnabled()) {
+ imageTable.enableHighlights(false);
+ } else {
+ imageTable.enableHighlights(true);
+ }
+ repaint();
+ }
+ };
+
+ public ImageControlsListener getImageControlsListener() {
+ return imageControlsListener;
+ }
+
+}
diff --git a/cs240/record-indexer/src/client/components/imagePanel/listeners/ImageControlsListener.java b/cs240/record-indexer/src/client/components/imagePanel/listeners/ImageControlsListener.java
new file mode 100644
index 0000000..01ec45c
--- /dev/null
+++ b/cs240/record-indexer/src/client/components/imagePanel/listeners/ImageControlsListener.java
@@ -0,0 +1,12 @@
+package client.components.imagePanel.listeners;
+
+public interface ImageControlsListener {
+
+ public void onScrollIncrease();
+
+ public void onScrollDecrease();
+
+ public void onInvertImage();
+
+ public void onToggleHighlights();
+}
diff --git a/cs240/record-indexer/src/client/components/listeners/DrawingListener.java b/cs240/record-indexer/src/client/components/listeners/DrawingListener.java
new file mode 100644
index 0000000..7151e8b
--- /dev/null
+++ b/cs240/record-indexer/src/client/components/listeners/DrawingListener.java
@@ -0,0 +1,5 @@
+package client.components.listeners;
+
+public interface DrawingListener {
+ void originChanged(int w_newOriginX, int w_newOriginY);
+}
diff --git a/cs240/record-indexer/src/client/components/loginWindow/ErrorLoginDialog.java b/cs240/record-indexer/src/client/components/loginWindow/ErrorLoginDialog.java
new file mode 100644
index 0000000..b237b78
--- /dev/null
+++ b/cs240/record-indexer/src/client/components/loginWindow/ErrorLoginDialog.java
@@ -0,0 +1,42 @@
+package client.components.loginWindow;
+
+import client.components.downloadModal.DownloadModal;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.WindowEvent;
+
+public class ErrorLoginDialog extends JDialog {
+
+ public ErrorLoginDialog() {
+ setupView();
+ }
+
+ private void setupView() {
+ this.setModalityType(Dialog.ModalityType.DOCUMENT_MODAL);
+ this.setTitle("Error!");
+ this.setSize(270, 80);
+ this.setResizable(false);
+ this.setLocationRelativeTo(null);
+ this.setLayout(new FlowLayout());
+
+ JLabel label = new JLabel("Error, incorrect Username or Password!");
+ this.add(label);
+
+ JButton closeButton = new JButton("Close");
+ closeButton.addActionListener(closeListener);
+ this.add(closeButton);
+ }
+
+
+ private ActionListener closeListener = new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ setVisible(false);
+ dispatchEvent(new WindowEvent(ErrorLoginDialog.this, WindowEvent.WINDOW_CLOSING));
+ }
+ };
+
+}
diff --git a/cs240/record-indexer/src/client/components/loginWindow/LoginWindow.java b/cs240/record-indexer/src/client/components/loginWindow/LoginWindow.java
new file mode 100644
index 0000000..55d17c2
--- /dev/null
+++ b/cs240/record-indexer/src/client/components/loginWindow/LoginWindow.java
@@ -0,0 +1,74 @@
+package client.components.loginWindow;
+
+import client.communication.Communicator;
+import client.communication.errors.RemoteServerErrorException;
+import client.communication.errors.UnauthorizedAccessException;
+import shared.communication.params.ValidateUser_Param;
+import shared.communication.responses.ValidateUser_Res;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+public class LoginWindow extends JFrame {
+
+ private Communicator communicator;
+ private JPasswordField passwordTextField;
+ private JTextField userTextField;
+ private JButton loginButton;
+
+ public LoginWindow(Communicator communicator) {
+ this.communicator = communicator;
+
+ setupView();
+ }
+
+ private void setupView() {
+ this.setTitle("Login to Indexer");
+ this.setSize(350, 130);
+ this.setResizable(false);
+ this.setLayout(new FlowLayout());
+ this.setLocationRelativeTo(null);
+
+ this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
+
+ JLabel usernameLabel = new JLabel("Username: ");
+ this.add(usernameLabel);
+ userTextField = new JTextField();
+ userTextField.setPreferredSize(new Dimension(250, 30));
+ this.add(userTextField);
+
+ JLabel passwordLabel = new JLabel("Password: ");
+ this.add(passwordLabel);
+ passwordTextField = new JPasswordField();
+ passwordTextField.setPreferredSize(new Dimension(250, 30));
+ this.add(passwordTextField);
+
+ loginButton = new JButton("Login");
+ this.add(loginButton);
+
+ JButton exitButton = new JButton("Exit");
+ exitButton.addActionListener(exitListener);
+ this.add(exitButton);
+ }
+
+ public void addLoginListener(ActionListener actionListener) {
+ loginButton.addActionListener(actionListener);
+ }
+
+ private ActionListener exitListener = new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ System.exit(1);
+ }
+ };
+
+ public String getUsername() {
+ return this.userTextField.getText();
+ }
+
+ public String getPassword() {
+ return this.passwordTextField.getText();
+ }
+}
diff --git a/cs240/record-indexer/src/client/components/loginWindow/SuccessLoginDialog.java b/cs240/record-indexer/src/client/components/loginWindow/SuccessLoginDialog.java
new file mode 100644
index 0000000..7dd5384
--- /dev/null
+++ b/cs240/record-indexer/src/client/components/loginWindow/SuccessLoginDialog.java
@@ -0,0 +1,53 @@
+package client.components.loginWindow;
+
+import shared.communication.responses.ValidateUser_Res;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.WindowEvent;
+
+public class SuccessLoginDialog extends JDialog {
+
+ private ValidateUser_Res validateUserRes;
+
+ public SuccessLoginDialog(ValidateUser_Res validateUserRes) {
+ this.validateUserRes = validateUserRes;
+ setupView();
+ }
+
+ private void setupView() {
+ this.setModalityType(Dialog.ModalityType.DOCUMENT_MODAL);
+ this.setTitle("Error!");
+ this.setSize(230, 100);
+ this.setResizable(false);
+ this.setLocationRelativeTo(null);
+ this.setLayout(new FlowLayout());
+
+ String welcome = "Welcome, " + validateUserRes.getFirstName() + " " +
+ validateUserRes.getLastName() + ".";
+
+ String record = "You have inexed " + validateUserRes.getIndexedRecords() + " records.";
+
+
+ JLabel label = new JLabel(welcome);
+ this.add(label);
+
+ JLabel label2 = new JLabel(record);
+ this.add(label2);
+
+ JButton closeButton = new JButton("Close");
+ closeButton.addActionListener(closeListener);
+ this.add(closeButton);
+ }
+
+
+ private ActionListener closeListener = new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ setVisible(false);
+ dispatchEvent(new WindowEvent(SuccessLoginDialog.this, WindowEvent.WINDOW_CLOSED));
+ }
+ };
+}
diff --git a/cs240/record-indexer/src/client/components/menus/SpellCheckPopup.java b/cs240/record-indexer/src/client/components/menus/SpellCheckPopup.java
new file mode 100644
index 0000000..4c5a165
--- /dev/null
+++ b/cs240/record-indexer/src/client/components/menus/SpellCheckPopup.java
@@ -0,0 +1,22 @@
+package client.components.menus;
+
+import javax.swing.*;
+import java.awt.event.ActionListener;
+
+public class SpellCheckPopup extends JPopupMenu {
+
+ private JMenuItem show;
+
+ public SpellCheckPopup() {
+ setupView();
+ }
+
+ private void setupView() {
+ show = new JMenuItem("See Suggestions?");
+ this.add(show);
+ }
+
+ public void addShowAction(ActionListener actionListener) {
+ show.addActionListener(actionListener);
+ }
+}
diff --git a/cs240/record-indexer/src/client/components/spellCheck/SpellingModal.java b/cs240/record-indexer/src/client/components/spellCheck/SpellingModal.java
new file mode 100644
index 0000000..7f1bfb3
--- /dev/null
+++ b/cs240/record-indexer/src/client/components/spellCheck/SpellingModal.java
@@ -0,0 +1,89 @@
+package client.components.spellCheck;
+
+import client.components.downloadModal.DownloadModal;
+import client.modules.spellChecker.KnownData;
+import client.modules.spellChecker.SpellChecker;
+import client.modules.spellChecker.WordSelectedListener;
+import client.persistence.Cell;
+import client.persistence.ImageState;
+
+import javax.swing.*;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.WindowEvent;
+
+public class SpellingModal extends JDialog {
+
+ private ImageState imageState;
+ private int column;
+ private String word;
+ private JButton button;
+ private JList jList;
+ private String[] words;
+ private WordSelectedListener wordSelectedListener;
+
+ public SpellingModal(ImageState imageState, String word, int column,
+ WordSelectedListener wordSelectedListener) {
+ this.imageState = imageState;
+ this.column = column;
+ this.word = word;
+ this.wordSelectedListener = wordSelectedListener;
+
+ setupView();
+ }
+
+ private void setupView() {
+ this.setModalityType(Dialog.ModalityType.DOCUMENT_MODAL);
+ this.setTitle("Spelling Suggestions");
+ this.setSize(350, 500);
+ this.setResizable(false);
+ this.setLocationRelativeTo(null);
+
+ KnownData knownData = imageState.getKnownDataValues().get(column);
+
+ jList = new JList<>();
+
+ SpellChecker spellChecker = new SpellChecker();
+ spellChecker.getSuggestionsForString(word);
+ spellChecker.restrictToList(knownData.getWords());
+
+ words = spellChecker.getWordArray();
+
+ jList.setListData(words);
+ jList.addListSelectionListener(listSelectionListener);
+
+ this.add(new JScrollPane(jList), BorderLayout.CENTER);
+
+ button = new JButton("Use Suggestion");
+ button.setEnabled(false);
+ button.addActionListener(actionListener);
+ this.add(button, BorderLayout.SOUTH);
+ }
+
+ private ActionListener actionListener = new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ int index = jList.getSelectedIndex();
+
+ String word = words[index];
+
+ wordSelectedListener.wordSelected(word);
+
+ dispatchEvent(new WindowEvent(SpellingModal.this, WindowEvent.WINDOW_CLOSING));
+ setVisible(false);
+ }
+ };
+
+ private ListSelectionListener listSelectionListener = new ListSelectionListener() {
+ @Override
+ public void valueChanged(ListSelectionEvent e) {
+ button.setEnabled(true);
+ repaint();
+ }
+ };
+
+
+}
diff --git a/cs240/record-indexer/src/client/components/tableEntry/EntryCellEditor.java b/cs240/record-indexer/src/client/components/tableEntry/EntryCellEditor.java
new file mode 100644
index 0000000..e982f64
--- /dev/null
+++ b/cs240/record-indexer/src/client/components/tableEntry/EntryCellEditor.java
@@ -0,0 +1,115 @@
+package client.components.tableEntry;
+
+import client.components.menus.SpellCheckPopup;
+import client.components.spellCheck.SpellingModal;
+import client.modules.spellChecker.KnownData;
+import client.modules.spellChecker.WordSelectedListener;
+import client.persistence.Cell;
+import client.persistence.ImageState;
+
+import javax.swing.*;
+import javax.swing.table.TableCellEditor;
+import java.awt.*;
+import java.awt.event.*;
+
+public class EntryCellEditor extends AbstractCellEditor implements TableCellEditor {
+
+ private String currentValue;
+ private JTextField textField;
+
+ private ImageState imageState;
+ private SpellCheckPopup spellCheckPopup;
+ private int column;
+
+ public EntryCellEditor(ImageState imageState) {
+ this.imageState = imageState;
+
+ this.spellCheckPopup = new SpellCheckPopup();
+ this.spellCheckPopup.addShowAction(showSuggestionsListener);
+ }
+
+ public boolean hasSuggestion(String value, int column) {
+ if(value.equals("")) return false;
+
+ KnownData knownData = imageState.getKnownDataValues().get(column);
+
+ for(String val : knownData.getWords()) {
+ if(val.toLowerCase().equals(value.toLowerCase())) return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected,
+ int row, int column) {
+
+ textField = new JTextField();
+ currentValue = (String)value;
+
+ textField.setText(currentValue);
+ textField.setBorder(BorderFactory.createLineBorder(Color.BLUE, 1));
+
+ textField.addMouseListener(generateMouseListener(row, column));
+
+ this.column = column;
+
+ if(isSelected) {
+ Cell cell = new Cell();
+ cell.setRecord(row);
+ cell.setField(column);
+ }
+
+ if(hasSuggestion((String)value, column - 1)) {
+ textField.setBackground(Color.RED);
+ textField.addMouseListener(rightClickPopupAction);
+ }
+
+ return textField;
+ }
+
+ @Override
+ public Object getCellEditorValue() {
+ return textField.getText();
+ }
+
+ private MouseListener generateMouseListener(final int row, final int column){
+ return new MouseAdapter() {
+ @Override
+ public void mousePressed(MouseEvent e) {
+ super.mouseClicked(e);
+
+ Cell cell = new Cell();
+ cell.setRecord(row);
+ cell.setField(column - 1);
+ imageState.setSelectedCell(cell);
+ }
+ };
+ }
+
+ private MouseListener rightClickPopupAction = new MouseAdapter() {
+ @Override
+ public void mousePressed(MouseEvent e) {
+ if(e.isPopupTrigger()) {;
+ spellCheckPopup.show(textField, e.getX(), e.getY());
+ }
+ }
+
+ };
+
+ private WordSelectedListener wordSelectedListener = new WordSelectedListener() {
+ @Override
+ public void wordSelected(String word) {
+ textField.setText(word);
+ }
+ };
+
+ private ActionListener showSuggestionsListener = new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ SpellingModal spellingModal = new SpellingModal(imageState, textField.getText(),
+ column - 1, wordSelectedListener);
+ spellingModal.setVisible(true);
+ }
+ };
+}
diff --git a/cs240/record-indexer/src/client/components/tableEntry/EntryCellRenderer.java b/cs240/record-indexer/src/client/components/tableEntry/EntryCellRenderer.java
new file mode 100644
index 0000000..5e135a9
--- /dev/null
+++ b/cs240/record-indexer/src/client/components/tableEntry/EntryCellRenderer.java
@@ -0,0 +1,58 @@
+package client.components.tableEntry;
+
+import client.components.menus.SpellCheckPopup;
+import client.modules.spellChecker.KnownData;
+import client.persistence.Cell;
+import client.persistence.ImageState;
+
+import javax.swing.*;
+import javax.swing.table.TableCellRenderer;
+import java.awt.*;
+import java.awt.event.*;
+
+public class EntryCellRenderer extends JLabel implements TableCellRenderer {
+
+ private ImageState imageState;
+
+ public EntryCellRenderer(ImageState imageState) {
+ this.imageState = imageState;
+ }
+
+ public boolean hasSuggestion(String value, int column) {
+
+ if(value.equals("")) return false;
+
+ KnownData knownData = imageState.getKnownDataValues().get(column);
+
+ for(String val : knownData.getWords()) {
+ if(val.toLowerCase().equals(value.toLowerCase())) return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
+ boolean hasFocus, int row, int column) {
+
+ this.setText((String)value);
+ this.setBorder(BorderFactory.createLineBorder(Color.LIGHT_GRAY, 1));
+
+ if(isSelected) {
+ this.setBorder(BorderFactory.createLineBorder(Color.BLACK, 1));
+
+ Cell cell = new Cell();
+ cell.setRecord(row);
+ cell.setField(column - 1);
+ imageState.setSelectedCell(cell);
+ }
+
+ if(hasSuggestion((String)value, column - 1)) {
+ this.setBorder(BorderFactory.createLineBorder(Color.RED, 1));
+ this.setBackground(Color.RED);
+ }
+
+ return this;
+ }
+
+}
diff --git a/cs240/record-indexer/src/client/components/tableEntry/RecordCellEditor.java b/cs240/record-indexer/src/client/components/tableEntry/RecordCellEditor.java
new file mode 100644
index 0000000..5bf115f
--- /dev/null
+++ b/cs240/record-indexer/src/client/components/tableEntry/RecordCellEditor.java
@@ -0,0 +1,65 @@
+package client.components.tableEntry;
+
+import client.components.menus.SpellCheckPopup;
+import client.persistence.Cell;
+import client.persistence.ImageState;
+
+import javax.swing.*;
+import javax.swing.table.TableCellEditor;
+import java.awt.*;
+import java.awt.event.*;
+
+public class RecordCellEditor extends AbstractCellEditor implements TableCellEditor {
+
+ private String currentValue;
+ private JTextField textField;
+
+ private ImageState imageState;
+
+ public RecordCellEditor(ImageState imageState) {
+ this.imageState = imageState;
+ }
+
+ @Override
+ public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected,
+ int row, int column) {
+
+ textField = new JTextField();
+ currentValue = (String)value;
+
+ textField.setText(currentValue);
+ textField.setBorder(BorderFactory.createLineBorder(Color.BLUE, 1));
+ textField.setEnabled(false);
+
+ textField.addMouseListener(generateMouseListener(row, column));
+
+ textField.addFocusListener(new FocusAdapter() {
+ @Override
+ public void focusGained(FocusEvent e) {
+ super.focusGained(e);
+ System.out.println("focs");
+ }
+ });
+
+ return textField;
+ }
+
+ @Override
+ public Object getCellEditorValue() {
+ return textField.getText();
+ }
+
+ private MouseListener generateMouseListener(final int row, final int column){
+ return new MouseAdapter() {
+ @Override
+ public void mousePressed(MouseEvent e) {
+ super.mouseClicked(e);
+
+ Cell cell = new Cell();
+ cell.setRecord(row);
+ cell.setField(0);
+ imageState.setSelectedCell(cell);
+ }
+ };
+ }
+}
diff --git a/cs240/record-indexer/src/client/components/tableEntry/RecordCellRenderer.java b/cs240/record-indexer/src/client/components/tableEntry/RecordCellRenderer.java
new file mode 100644
index 0000000..445a98c
--- /dev/null
+++ b/cs240/record-indexer/src/client/components/tableEntry/RecordCellRenderer.java
@@ -0,0 +1,36 @@
+package client.components.tableEntry;
+
+import client.modules.spellChecker.KnownData;
+import client.persistence.Cell;
+import client.persistence.ImageState;
+
+import javax.swing.*;
+import javax.swing.table.TableCellRenderer;
+import java.awt.*;
+
+public class RecordCellRenderer extends JLabel implements TableCellRenderer {
+
+ private ImageState imageState;
+
+ public RecordCellRenderer(ImageState imageState) {
+ this.imageState = imageState;
+ }
+
+ @Override
+ public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
+ boolean hasFocus, int row, int column) {
+
+ this.setText((String)value);
+ this.setBorder(BorderFactory.createLineBorder(Color.LIGHT_GRAY, 1));
+
+ if(isSelected) {
+ Cell cell = new Cell();
+ cell.setRecord(row);
+ cell.setField(0);
+ imageState.setSelectedCell(cell);
+ }
+
+ return this;
+ }
+
+}
diff --git a/cs240/record-indexer/src/client/components/tableEntry/TableEntry.java b/cs240/record-indexer/src/client/components/tableEntry/TableEntry.java
new file mode 100644
index 0000000..76754ba
--- /dev/null
+++ b/cs240/record-indexer/src/client/components/tableEntry/TableEntry.java
@@ -0,0 +1,106 @@
+package client.components.tableEntry;
+
+import client.components.menus.SpellCheckPopup;
+import client.persistence.*;
+
+import javax.swing.*;
+import javax.swing.event.TableModelEvent;
+import javax.swing.event.TableModelListener;
+import javax.swing.table.*;
+import java.awt.*;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+
+public class TableEntry extends JScrollPane {
+
+ private TableModel tableModel;
+ private JTable table;
+
+ private ImageState imageState;
+
+
+ public TableEntry(ImageState imageState) {
+
+ this.imageState = imageState;
+ this.tableModel = new TableModel(imageState);
+
+ this.imageState.addNewProjectListener(newProjectListener);
+
+ setupView();
+
+ if(imageState.isHasImage())
+ imageState.addListener(imageStateListener);
+ }
+
+ private void setupView() {
+
+ table = new JTable(tableModel);
+
+ table.setRowHeight(20);
+ table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+ table.setCellSelectionEnabled(true);
+ table.getTableHeader().setReorderingAllowed(false);
+ table.getTableHeader().setResizingAllowed(false);
+ table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
+
+ tableInit();
+
+ }
+
+ private void tableInit() {
+ TableColumnModel columnModel = table.getColumnModel();
+
+ for (int i = 1; i < tableModel.getColumnCount(); ++i) {
+ TableColumn column = columnModel.getColumn(i);
+ EntryCellRenderer entryCellRenderer = new EntryCellRenderer(imageState);
+ column.setCellRenderer(entryCellRenderer);
+ column.setCellEditor(new EntryCellEditor(imageState));
+ }
+ this.getViewport().add(table.getTableHeader());
+ this.getViewport().add(table);
+
+ if(!imageState.isHasImage()) return;
+
+ TableColumn column = columnModel.getColumn(0);
+ column.setCellRenderer(new RecordCellRenderer(imageState));
+ column.setCellEditor(new RecordCellEditor(imageState));
+ }
+
+ private ImageStateListener imageStateListener = new ImageStateListener() {
+ @Override
+ public void valueChanged(Cell cell, String newValue) {
+ }
+
+ @Override
+ public void selectedCellChanged(Cell newSelectedCell) {
+ tableModel.setQuiet(true);
+
+ table.changeSelection(newSelectedCell.getRecord(),
+ newSelectedCell.getField() + 1, false, false);
+
+ table.editCellAt(newSelectedCell.getRecord(), newSelectedCell.getField() + 1);
+
+ tableModel.setQuiet(false);
+ }
+ };
+
+ private NewProjectListener newProjectListener = new NewProjectListener() {
+ @Override
+ public void hasNewProject() {
+ tableModel.setDeactivated(true);
+
+ if(imageState.isHasImage())
+ imageState.addListener(imageStateListener);
+
+ tableModel = new TableModel(imageState);
+ table.setModel(tableModel);
+
+ tableInit();
+ }
+ };
+
+
+}
diff --git a/cs240/record-indexer/src/client/components/tableEntry/TableModel.java b/cs240/record-indexer/src/client/components/tableEntry/TableModel.java
new file mode 100644
index 0000000..4fb7873
--- /dev/null
+++ b/cs240/record-indexer/src/client/components/tableEntry/TableModel.java
@@ -0,0 +1,138 @@
+package client.components.tableEntry;
+
+import client.persistence.Cell;
+import client.persistence.ImageState;
+import client.persistence.ImageStateListener;
+
+import javax.swing.table.AbstractTableModel;
+
+public class TableModel extends AbstractTableModel {
+
+ private String[] columnNames;
+
+ private String[][] model;
+
+ private ImageState imageState;
+
+ private boolean quiet = false;
+
+ private boolean updating = false;
+
+ private boolean deactivated = false;
+
+ public TableModel(ImageState imageState) {
+
+ this.imageState = imageState;
+ this.model = this.imageState.getModel();
+ this.columnNames = this.imageState.getColumnNames();
+
+ this.imageState.addListener(imageStateListener);
+
+ if(!this.imageState.isHasImage()) return;
+ overrideTableModel();
+
+ }
+
+ private void overrideTableModel() {
+ String[] imageStateColumns = this.imageState.getColumnNames();
+ int width = imageStateColumns.length;
+
+ String[][] imageStateModel = this.imageState.getModel();
+
+ this.model = new String[imageStateModel.length][width + 1];
+
+ this.columnNames = new String[width + 1];
+ this.columnNames[0] = "Record Number";
+
+
+ // Copy Column names from image state so we can have record number
+ for(int i = 0; i < width; i++) {
+ this.columnNames[i+1] = imageStateColumns[i];
+ }
+
+ // Copy Model values
+ for(int x = 0; x < imageStateModel.length; x ++) {
+ // Set row number first
+ this.model[x][0] = Integer.toString(x + 1);
+
+ // Copy the values with the new offset
+ for(int i = 0; i < width; i++) {
+ this.model[x][i+1] = imageStateModel[x][i];
+ }
+ }
+ }
+
+ public void setDeactivated(boolean deactivated) {
+ this.deactivated = deactivated;
+ }
+
+ @Override
+ public boolean isCellEditable(int row, int column) {
+ return true;
+ }
+
+ @Override
+ public String getColumnName(int column) {
+ return columnNames[column];
+ }
+
+ @Override
+ public int getRowCount() {
+ return model.length;
+ }
+
+ @Override
+ public int getColumnCount() {
+ return columnNames.length;
+ }
+
+ @Override
+ public Object getValueAt(int rowIndex, int columnIndex) {
+ return model[rowIndex][columnIndex];
+ }
+
+ @Override
+ public void setValueAt(Object value, int row, int column) {
+ if(quiet || deactivated) return;
+
+ model[row][column] = (String)value;
+
+ updating = true;
+ Cell cell = new Cell();
+ cell.setField(column - 1);
+
+ // Check for record number col which is 0
+ if(column == 0) {
+ cell.setField(0);
+ }
+
+ cell.setRecord(row);
+ imageState.setValue(cell, (String)value);
+ updating = false;
+ }
+
+
+ private ImageStateListener imageStateListener = new ImageStateListener() {
+ @Override
+ public void valueChanged(Cell cell, String newValue) {
+ if(updating || deactivated) return;
+
+ model[cell.getRecord()][cell.getField() + 1] = newValue;
+ }
+
+ @Override
+ public void selectedCellChanged(Cell newSelectedCell) {
+ if(updating) return;
+ }
+ };
+
+ public void setValueQuiet(String newValue, int row, int column) {
+ if(deactivated) return;
+
+ model[row][column] = newValue;
+ }
+
+ public void setQuiet(boolean quiet) {
+ this.quiet = quiet;
+ }
+}
diff --git a/cs240/record-indexer/src/client/modules/spellChecker/KnownData.java b/cs240/record-indexer/src/client/modules/spellChecker/KnownData.java
new file mode 100644
index 0000000..32313db
--- /dev/null
+++ b/cs240/record-indexer/src/client/modules/spellChecker/KnownData.java
@@ -0,0 +1,81 @@
+package client.modules.spellChecker;
+
+import org.apache.commons.io.FileUtils;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Serializable;
+import java.lang.reflect.Array;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+public class KnownData implements Serializable {
+
+ private List words;
+
+ public KnownData(ArrayList knownDataValue) {
+ this.words = knownDataValue;
+ }
+
+ public KnownData() {
+ this.words = new ArrayList<>();
+ }
+
+ public void addWord(String word) {
+ words.add(word);
+ }
+
+ public String[] getWordArray() {
+
+ String[] arr = new String[words.size()];
+
+ for(int i = 0; i < words.size(); i++) {
+ arr[i] = words.get(i);
+ }
+
+ return arr;
+ }
+
+ public List getWords() {
+ return words;
+ }
+
+ public void setWords(List words) {
+ this.words = words;
+ }
+
+ private static String downloadValues(String path) {
+ StringBuilder stringBuilder = new StringBuilder();
+ try {
+
+ URL url = new URL(path);
+ BufferedReader in = new BufferedReader(
+ new InputStreamReader(url.openStream()));
+
+ String temp;
+ while ((temp = in.readLine()) != null) {
+ stringBuilder.append(temp);
+ }
+ in.close();
+
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ return stringBuilder.toString();
+ }
+
+ public static KnownData getList(String knownDataFullPath) {
+ ArrayList knownDataValue = null;
+
+ String knownValuesString = downloadValues(knownDataFullPath);
+ String[] values = knownValuesString.split(",");
+
+ knownDataValue = new ArrayList<>(Arrays.asList(values));
+
+ return new KnownData(knownDataValue);
+ }
+}
diff --git a/cs240/record-indexer/src/client/modules/spellChecker/SpellChecker.java b/cs240/record-indexer/src/client/modules/spellChecker/SpellChecker.java
new file mode 100644
index 0000000..6ff1730
--- /dev/null
+++ b/cs240/record-indexer/src/client/modules/spellChecker/SpellChecker.java
@@ -0,0 +1,120 @@
+package client.modules.spellChecker;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
+
+public class SpellChecker {
+
+ private Set words;
+
+ public Set getSuggestionsForString(String word) {
+
+ words = new TreeSet<>();
+
+ // First Distance
+ words.addAll(alterationDistance(word));
+ words.addAll(insertionDistance(word));
+ words.addAll(transpositionDistance(word));
+ words.addAll(deletionDistance(word));
+
+ Set wordsCopy = new TreeSet<>(words);
+
+ for(String w : wordsCopy) {
+ words.addAll(alterationDistance(w));
+ words.addAll(insertionDistance(w));
+ words.addAll(transpositionDistance(w));
+ words.addAll(deletionDistance(w));
+ }
+
+ return words;
+ }
+
+ public void restrictToList(List list) {
+ Set set = new TreeSet<>();
+
+ for(String similar : words) {
+ for(String foregin : list) {
+ if(foregin.toLowerCase().equals(similar.toLowerCase())) {
+ set.add(foregin);
+ }
+ }
+ }
+
+ words = set;
+ }
+
+ public String[] getWordArray() {
+
+ String[] arr = new String[words.size()];
+
+ ArrayList temp = new ArrayList<>(words);
+
+ for(int i = 0; i < temp.size(); i++) {
+ arr[i] = temp.get(i);
+ }
+
+ return arr;
+ }
+
+ public List alterationDistance(String word) {
+ List wordList = new ArrayList();
+ for(int i = 0; i <= (word.length() - 1); i++ ) {
+ String firstHalf = word.substring(0, i);
+ String lastHalf = word.substring(i+1);
+
+ for(int y = 0; y < 26; y++) {
+ String letter = Character.toString((char) ('a' + y));
+ String cutlet = firstHalf + letter + lastHalf;
+ if(word.equals(cutlet)) continue;
+ wordList.add(cutlet);
+ }
+ }
+ return wordList;
+ }
+
+ public List insertionDistance(String word) {
+ // Declare a new String list (to avoid making a comparator).
+ List wordList = new ArrayList();
+
+ for(int i = 0; i <= (word.length()); i++ ) {
+ String firstHalf = word.substring(0, i);
+ String lastHalf = word.substring(i);
+
+ for(int y = 0; y < 26; y++) {
+ String letter = Character.toString((char) ('a' + y));
+ String cutlet = firstHalf + letter + lastHalf;
+ if(word.equals(cutlet)) continue;
+ wordList.add(cutlet);
+ }
+ }
+ return wordList;
+ }
+
+ public List transpositionDistance(String word) {
+ List wordList = new ArrayList();
+ for(int i = 0; i <= (word.length() - 2); i++ ) {
+ String letterOne = word.substring(i,i+1);
+ String letterTwo = word.substring(i+1,i+2);
+ String firstHalf = word.substring(0, i);
+ String lastHalf = word.substring(i+2);
+ String cutlet = firstHalf + letterTwo + letterOne + lastHalf;
+ wordList.add(cutlet);
+ }
+
+ return wordList;
+ }
+
+ public List deletionDistance(String word) {
+ List wordList = new ArrayList();
+ for(int i = 1; i <= word.length(); i++ ) {
+ String base = word.substring(0,i-1);
+ String cutlet = base + word.substring(i);
+ wordList.add(cutlet);
+
+ }
+ return wordList;
+ }
+
+}
diff --git a/cs240/record-indexer/src/client/modules/spellChecker/WordSelectedListener.java b/cs240/record-indexer/src/client/modules/spellChecker/WordSelectedListener.java
new file mode 100644
index 0000000..4d97c3c
--- /dev/null
+++ b/cs240/record-indexer/src/client/modules/spellChecker/WordSelectedListener.java
@@ -0,0 +1,6 @@
+package client.modules.spellChecker;
+
+public interface WordSelectedListener {
+
+ public void wordSelected(String word);
+}
diff --git a/cs240/record-indexer/src/client/persistence/Cell.java b/cs240/record-indexer/src/client/persistence/Cell.java
new file mode 100644
index 0000000..4fdb3cc
--- /dev/null
+++ b/cs240/record-indexer/src/client/persistence/Cell.java
@@ -0,0 +1,25 @@
+package client.persistence;
+
+import java.io.Serializable;
+
+public class Cell implements Serializable {
+
+ private int record;
+ private int field;
+
+ public int getRecord() {
+ return record;
+ }
+
+ public void setRecord(int record) {
+ this.record = record;
+ }
+
+ public int getField() {
+ return field;
+ }
+
+ public void setField(int field) {
+ this.field = field;
+ }
+}
diff --git a/cs240/record-indexer/src/client/persistence/ImageState.java b/cs240/record-indexer/src/client/persistence/ImageState.java
new file mode 100644
index 0000000..9ce9144
--- /dev/null
+++ b/cs240/record-indexer/src/client/persistence/ImageState.java
@@ -0,0 +1,349 @@
+package client.persistence;
+
+import client.communication.Communicator;
+import client.communication.errors.RemoteServerErrorException;
+import client.communication.errors.UnauthorizedAccessException;
+import client.modules.spellChecker.KnownData;
+import shared.communication.common.Fields;
+import shared.communication.params.DownloadBatch_Param;
+import shared.communication.params.SubmitBatch_Param;
+import shared.communication.responses.DownloadBatch_Res;
+import shared.models.Value;
+
+import javax.imageio.ImageIO;
+import java.awt.image.BufferedImage;
+import java.io.*;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+
+public class ImageState implements Serializable {
+
+ private String[][] values;
+ private String[] columns;
+
+ private Cell selectedCell;
+ private transient List listeners;
+ private transient List projectListeners;
+ private List fieldsMetaData;
+ private transient Communicator communicator;
+
+ private String username;
+ private String password;
+ private int imageId = -1;
+ private int firstYCoord = 0;
+ private int recordHeight = 0;
+ private int columnCount = 0;
+ private int recordsPerImage = 0;
+ private transient BufferedImage image;
+ private boolean hasImage;
+ private ArrayList fieldXValues;
+ private ArrayList fieldWidthValues;
+ private ArrayList knownDataValues;
+ private Settings settings;
+
+
+ public ImageState(Settings settings, Communicator communicator,
+ String username, String password) {
+ this.settings = settings;
+ this.communicator = communicator;
+ this.username = username;
+ this.password = password;
+
+ // Init Listeners
+ listeners = new ArrayList<>();
+ projectListeners = new ArrayList<>();
+
+ loadFromNoSettings();
+ }
+
+ public void loadFromNoSettings() {
+ values = new String[0][0];
+
+ firstYCoord = 0;
+ recordHeight = 0;
+ columnCount = 0;
+ recordsPerImage = 0;
+ hasImage = false;
+ image = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
+ knownDataValues = new ArrayList<>();
+
+ columns = new String[0];
+ values = new String[0][0];
+
+ Cell initCell = new Cell();
+ initCell.setField(0);
+ initCell.setRecord(0);
+
+ selectedCell = initCell;
+ }
+
+ public void initEvents() {
+ setSelectedCell(selectedCell);
+ }
+
+ public void addListener(ImageStateListener imageStateListener) {
+ listeners.add(imageStateListener);
+ }
+
+ public void addNewProjectListener(NewProjectListener npl) {
+ projectListeners.add(npl);
+ }
+
+ public void setValue(Cell cell, String value) {
+ values[cell.getRecord()][cell.getField()] = value;
+
+ for(ImageStateListener isl : listeners) {
+ isl.valueChanged(cell, value);
+ }
+ }
+
+ public String getValue(Cell cell) {
+ return values[cell.getRecord()][cell.getField()];
+ }
+
+ public void setSelectedCell(Cell cell) {
+ this.selectedCell = cell;
+
+ for(ImageStateListener isl : listeners) {
+ isl.selectedCellChanged(cell);
+ }
+ }
+
+ public void save() {
+ try{
+ // Create path
+ File dest = new File("profiles/"+username);
+ if(!dest.exists()) dest.mkdirs();
+
+ ObjectOutputStream out = new ObjectOutputStream(
+ new FileOutputStream("profiles/"+username+"/settings.ser"));
+ out.writeObject(settings);
+ out.close();
+
+ ByteArrayOutputStream bos = new ByteArrayOutputStream() ;
+ out = new ObjectOutputStream(bos) ;
+ out.writeObject(settings);
+ out.close();
+
+
+ ObjectOutputStream out1 = new ObjectOutputStream(
+ new FileOutputStream("profiles/"+username+"/state.ser"));
+ out1.writeObject(this);
+ out1.close();
+
+ ByteArrayOutputStream bos1 = new ByteArrayOutputStream() ;
+ out1 = new ObjectOutputStream(bos1) ;
+ out1.writeObject(this);
+ out1.close();
+
+
+
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ private void writeObject(ObjectOutputStream out) throws IOException {
+ out.defaultWriteObject();
+ ImageIO.write(image, "png", out); // png is lossless
+ }
+
+ private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
+ in.defaultReadObject();
+ image = ImageIO.read(in);
+
+ listeners = new ArrayList<>();
+ projectListeners = new ArrayList<>();
+ }
+
+ private void initWithProject(DownloadBatch_Res downloadBatch) {
+ hasImage = true;
+ firstYCoord = downloadBatch.getFirstYCoord();
+ recordHeight = downloadBatch.getRecordHeight();
+ columnCount = downloadBatch.getNumberOfFields();
+ recordsPerImage = downloadBatch.getRecordsPerImage();
+ imageId = downloadBatch.getBatchId();
+
+ values = new String[recordsPerImage][columnCount];
+ columns = new String[columnCount];
+
+ knownDataValues = new ArrayList<>();
+
+ fieldsMetaData = downloadBatch.getFields();
+
+ fieldXValues = new ArrayList<>();
+ fieldWidthValues = new ArrayList<>();
+
+ List fields = downloadBatch.getFields();
+ for(int i = 0; i < fields.size(); i++) {
+ // Copy essential values and store the rest
+ columns[i] = fields.get(i).getTitle();
+ fieldXValues.add(fields.get(i).getxCoord());
+ fieldWidthValues.add(fields.get(i).getPixelWidth());
+
+ if(fields.get(i).getKnownData() != null) {
+ knownDataValues.add(KnownData.getList(communicator.getServerPath() +
+ fields.get(i).getKnownData()));
+ } else {
+ knownDataValues.add(new KnownData());
+ }
+
+ for(int y = 0; y < recordsPerImage; y++) {
+ // Ensure we have no null values.
+ values[y][i] = "";
+ }
+ }
+
+ String path = communicator.getServerPath() + downloadBatch.getImageUrl();
+ try {
+ image = ImageIO.read(new URL(path));
+ } catch (Exception e1) {
+ return;
+ }
+
+ for(NewProjectListener npl : projectListeners) {
+ npl.hasNewProject();
+ }
+
+ Cell cell = new Cell();
+ cell.setField(0);
+ cell.setRecord(0);
+ setSelectedCell(cell);
+ }
+
+ public void submitProject() {
+
+ SubmitBatch_Param param = new SubmitBatch_Param();
+ param.setImageId(imageId);
+ param.setUsername(username);
+ param.setPassword(password);
+
+ for(String[] vs : values) {
+ List valueList = new ArrayList<>();
+ for(String v : vs) {
+ Value model = new Value();
+ model.setValue(v);
+ model.setType("String");
+ valueList.add(model);
+ }
+ param.addRecord(valueList);
+ }
+
+ try {
+ communicator.submitBatch(param);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ loadFromNoSettings();
+ save();
+
+ for(NewProjectListener npl : projectListeners) {
+ npl.hasNewProject();
+ }
+ }
+
+ public void downloadProject(int projectId) {
+ DownloadBatch_Param param = new DownloadBatch_Param();
+ param.setUsername(this.username);
+ param.setPassword(this.password);
+ param.setProjectId(projectId);
+
+ DownloadBatch_Res downloadBatchRes = null;
+ try {
+ downloadBatchRes = communicator.downloadBatch(param);
+ initWithProject(downloadBatchRes);
+ } catch (UnauthorizedAccessException e) {
+ e.printStackTrace();
+ } catch (RemoteServerErrorException e) {
+ e.printStackTrace();
+ }
+
+ }
+
+ public Cell getSelectedCell() {
+ return this.selectedCell;
+ }
+
+
+ public Settings getSettings() {
+ return settings;
+ }
+
+ public String[][] getModel() {
+ return values;
+ }
+
+ public String[] getColumnNames() {
+ return columns;
+ }
+
+ public ArrayList getFieldWidthValues() {
+ return fieldWidthValues;
+ }
+
+ public ArrayList getFieldXValues() {
+ return fieldXValues;
+ }
+
+ public int getRecordsPerImage() {
+ return recordsPerImage;
+ }
+
+ public int getColumnCount() {
+ return columnCount;
+ }
+
+ public int getRecordHeight() {
+ return recordHeight;
+ }
+
+ public int getFirstYCoord() {
+ return firstYCoord;
+ }
+
+ public String getPassword() {
+ return password;
+ }
+
+ public String getUsername() {
+ return username;
+ }
+
+ public String[] getColumns() {
+ return columns;
+ }
+
+ public String[][] getValues() {
+ return values;
+ }
+
+ public List getFieldsMetaData() {
+ return fieldsMetaData;
+ }
+
+ public void setCommunicator(Communicator communicator) {
+ this.communicator = communicator;
+ }
+
+ public boolean isHasImage() {
+ return hasImage;
+ }
+
+ public void setHasImage(boolean hasImage) {
+ this.hasImage = hasImage;
+ }
+
+ public void setSettings(Settings settings) {
+ this.settings = settings;
+ }
+
+ public BufferedImage getImage() {
+ return image;
+ }
+
+ public ArrayList getKnownDataValues() {
+ return knownDataValues;
+ }
+}
diff --git a/cs240/record-indexer/src/client/persistence/ImageStateListener.java b/cs240/record-indexer/src/client/persistence/ImageStateListener.java
new file mode 100644
index 0000000..b3c1f1e
--- /dev/null
+++ b/cs240/record-indexer/src/client/persistence/ImageStateListener.java
@@ -0,0 +1,9 @@
+package client.persistence;
+
+public interface ImageStateListener {
+
+ public void valueChanged(Cell cell, String newValue);
+
+ public void selectedCellChanged(Cell newSelectedCell);
+
+}
diff --git a/cs240/record-indexer/src/client/persistence/NewProjectListener.java b/cs240/record-indexer/src/client/persistence/NewProjectListener.java
new file mode 100644
index 0000000..755dab8
--- /dev/null
+++ b/cs240/record-indexer/src/client/persistence/NewProjectListener.java
@@ -0,0 +1,7 @@
+package client.persistence;
+
+public interface NewProjectListener {
+
+ public void hasNewProject();
+
+}
diff --git a/cs240/record-indexer/src/client/persistence/Settings.java b/cs240/record-indexer/src/client/persistence/Settings.java
new file mode 100644
index 0000000..2aebce0
--- /dev/null
+++ b/cs240/record-indexer/src/client/persistence/Settings.java
@@ -0,0 +1,131 @@
+package client.persistence;
+
+import java.awt.image.BufferedImage;
+import java.io.Serializable;
+
+public class Settings implements Serializable {
+
+ private int baseSplitY;
+ private int baseSplitX;
+ private int windowHeight;
+ private int windowWidth;
+ private int windowPositionY;
+ private int windowPositionX;
+ private double imageScaleLevel;
+ private int imageOriginX;
+ private int imageOriginY;
+ private boolean imageInverted;
+ private boolean imageHighlights;
+
+ public Settings() {
+ loadDefaults();
+ }
+
+ private void loadDefaults() {
+ this.windowHeight = 650;
+ this.windowWidth = 1000;
+ this.windowPositionX = 36;
+ this.windowPositionY = 73;
+ this.baseSplitY = 400;
+ this.baseSplitX = 500;
+ this.imageScaleLevel = 0.8f;
+ this.imageOriginX = 0;
+ this.imageOriginY = 0;
+ this.imageHighlights = true;
+ this.imageInverted = false;
+ }
+
+ public int getBaseSplitY() {
+ return baseSplitY;
+ }
+
+ public void setBaseSplitY(int baseSplitY) {
+ this.baseSplitY = baseSplitY;
+ }
+
+ public int getBaseSplitX() {
+ return baseSplitX;
+ }
+
+ public void setBaseSplitX(int baseSplitX) {
+ this.baseSplitX = baseSplitX;
+ }
+
+ public int getWindowHeight() {
+ return windowHeight;
+ }
+
+ public void setWindowHeight(int windowHeight) {
+ this.windowHeight = windowHeight;
+ }
+
+ public int getWindowWidth() {
+ return windowWidth;
+ }
+
+ public void setWindowWidth(int windowWidth) {
+ this.windowWidth = windowWidth;
+ }
+
+ public int getWindowPositionY() {
+ return windowPositionY;
+ }
+
+ public void setWindowPositionY(int windowPositionY) {
+ this.windowPositionY = windowPositionY;
+ }
+
+ public int getWindowPositionX() {
+ return windowPositionX;
+ }
+
+ public void setWindowPositionX(int windowPositionX) {
+ this.windowPositionX = windowPositionX;
+ }
+
+ public double getImageScaleLevel() {
+ return imageScaleLevel;
+ }
+
+ public void setImageScaleLevel(double imageScaleLevel) {
+ this.imageScaleLevel = imageScaleLevel;
+ }
+
+ public boolean isImageInverted() {
+ return imageInverted;
+ }
+
+ public void setImageInverted(boolean imageInverted) {
+ this.imageInverted = imageInverted;
+ }
+
+ public boolean isImageHighlights() {
+ return imageHighlights;
+ }
+
+ public void setImageHighlights(boolean imageHighlights) {
+ this.imageHighlights = imageHighlights;
+ }
+
+ public int getImageOriginX() {
+ return imageOriginX;
+ }
+
+ public void setImageOriginX(int imageOriginX) {
+ this.imageOriginX = imageOriginX;
+ }
+
+ public int getImageOriginY() {
+ return imageOriginY;
+ }
+
+ public void setImageOriginY(int imageOriginY) {
+ this.imageOriginY = imageOriginY;
+ }
+
+ public static Settings defaultSettings() {
+ Settings settings = new Settings();
+ settings.loadDefaults();
+ return settings;
+ }
+}
diff --git a/cs240/record-indexer/src/client/persistence/SyncContext.java b/cs240/record-indexer/src/client/persistence/SyncContext.java
new file mode 100644
index 0000000..c4ab55b
--- /dev/null
+++ b/cs240/record-indexer/src/client/persistence/SyncContext.java
@@ -0,0 +1,11 @@
+package client.persistence;
+
+import client.persistence.Cell;
+
+public interface SyncContext {
+
+ public void onChangeCurrentCell(Cell cell);
+
+ public void onChangeCellValue(Cell cell, String value);
+
+}
diff --git a/cs240/record-indexer/src/search/Main.java b/cs240/record-indexer/src/search/Main.java
new file mode 100644
index 0000000..c4705b7
--- /dev/null
+++ b/cs240/record-indexer/src/search/Main.java
@@ -0,0 +1,251 @@
+package search;
+
+import search.elements.FieldButton;
+import search.elements.ProjectGroup;
+import search.forms.InputField;
+import search.helpers.Networking;
+import search.helpers.dataModels.ProjectContainer;
+import shared.communication.common.Fields;
+import shared.communication.common.Tuple;
+import shared.communication.responses.Search_Res;
+
+import java.awt.*;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.image.BufferedImage;
+import java.awt.image.DataBufferByte;
+import java.io.ByteArrayOutputStream;
+import java.util.HashSet;
+import java.util.List;
+import javax.imageio.ImageIO;
+import javax.swing.*;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.Set;
+
+public class Main extends JFrame {
+
+ private JPanel menuPanel;
+ private JPanel sidebarPanel;
+ private JPanel mainBody;
+ private JPanel searchResults;
+
+ private JSplitPane searchArea;
+ private JSplitPane searchForm;
+
+ public Main() {
+
+
+ this.setTitle("Search Program");
+ this.setSize(800, 650);
+ this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+
+ setupMenu();
+ setupSidebar();
+ setupBody();
+ setupSearchResults();
+
+ searchArea = new JSplitPane(JSplitPane.VERTICAL_SPLIT, mainBody, new JScrollPane());
+ searchArea.setOneTouchExpandable(false);
+ searchArea.setEnabled(false);
+ searchArea.setDividerLocation(80);
+ searchArea.setDividerSize(0);
+
+ searchForm = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, sidebarPanel, searchArea);
+ searchForm.setOneTouchExpandable(false);
+ searchForm.setDividerLocation(120);
+ searchForm.setEnabled(false);
+ searchForm.setDividerSize(1);
+ searchForm.setPreferredSize(new Dimension(120, 500));
+
+
+ JSplitPane jSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, menuPanel, searchForm);
+ jSplitPane.setOneTouchExpandable(false);
+ jSplitPane.setDividerLocation(40);
+ jSplitPane.setDividerSize(1);
+ jSplitPane.setEnabled(false);
+
+ this.add(jSplitPane);
+
+ this.setVisible(true);
+ }
+
+ private final InputField usernameField = new InputField("Username", 15);
+ private final JTextField passwordField = new JTextField("Password", 15);
+ private final JTextField hostField = new JTextField("localhost", 7);
+ private final JTextField portField = new JTextField("39640", 4);
+
+ private void setupMenu() {
+ // Add buttons
+ FlowLayout flowLayout = new FlowLayout();
+ final JPanel jPanel = new JPanel();
+ jPanel.setLayout(flowLayout);
+ flowLayout.setAlignment(FlowLayout.LEFT);
+
+ JButton button = new JButton("Get Projects");
+
+ jPanel.add(hostField);
+ jPanel.add(portField);
+ jPanel.add(usernameField);
+ jPanel.add(passwordField);
+ jPanel.add(button);
+ jPanel.setComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT);
+
+ button.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ Networking networking = new Networking(
+ hostField.getText(), Integer.parseInt(portField.getText()));
+
+ String username = usernameField.getText();
+ String password = passwordField.getText();
+
+ List projectContainerList =
+ networking.getProjectsWithFields(username, password);
+
+ JPanel search = new JPanel();
+
+ if(projectContainerList != null)
+ for(ProjectContainer projectContainer : projectContainerList) {
+ search.add(new ProjectGroup(projectContainer));
+
+ for(final Fields field : projectContainer.getFieldsList()) {
+ FieldButton fieldButton = new FieldButton(field);
+ fieldButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ String fieldId = Integer.toString(field.getId());
+
+ updateFieldsField(fieldId);
+ }
+ });
+ search.add(fieldButton);
+ }
+ }
+
+ searchForm.setLeftComponent(search);
+ searchForm.setDividerLocation(120);
+ searchForm.validate();
+ searchForm.repaint();
+
+
+ String fieldName = "";
+ if(projectContainerList != null)
+ fieldName = projectContainerList.get(0).getFieldsList().get(0).getTitle();
+
+ System.out.println(fieldName);
+ }
+ });
+
+ menuPanel = jPanel;
+ }
+
+ private void setupSidebar() {
+ final JPanel jPanel = new JPanel();
+
+
+ jPanel.setComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT);
+
+ sidebarPanel = jPanel;
+ }
+
+ private final JTextField fieldsField = new JTextField("",48);
+
+ private void updateFieldsField(String fieldId) {
+
+ String text = fieldsField.getText();
+
+ String[] options = text.split(",");
+
+ if(options.length > 0 && !text.isEmpty())
+ text += ",";
+
+ fieldsField.setText(text + fieldId);
+ }
+
+ private void setupBody() {
+ final JPanel jPanel = new JPanel();
+
+ jPanel.setComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT);
+
+ final JButton searchButton = new JButton("Search");
+ final JTextField queryField = new JTextField("",41);
+
+ jPanel.add(new JLabel("Fields: "));
+ jPanel.add(fieldsField);
+ jPanel.add(new JLabel("Terms: "));
+ jPanel.add(queryField);
+ jPanel.add(searchButton);
+
+ searchButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ JPanel results = new JPanel();
+ Networking networking = new Networking(
+ hostField.getText(), Integer.parseInt(portField.getText()));
+
+ searchResults.setVisible(false);
+
+ String username = usernameField.getText();
+ String password = passwordField.getText();
+
+ Search_Res searchRes = networking.search(username,password,
+ fieldsField.getText(), queryField.getText());
+
+ if(searchRes != null)
+ for(Tuple searchResult : searchRes.getSearchResults()) {
+ String imageUrl = searchResult.getImageUrl();
+
+ final BufferedImage image = networking.getImage(imageUrl);
+
+ if(image == null) return;
+
+ BufferedImage scaledImage = resize(image, 200, 150);
+
+ JLabel imageLabel = new JLabel(new ImageIcon(scaledImage));
+
+ imageLabel.addMouseListener(new MouseAdapter() {
+ @Override
+ public void mouseClicked(MouseEvent e) {
+ JFrame frame2 = new JFrame();
+ JLabel bigImage = new JLabel(new ImageIcon(image));
+ frame2.add(bigImage);
+ frame2.setSize(bigImage.getPreferredSize());
+ frame2.setVisible(true);
+ }
+ });
+
+ imageLabel.setPreferredSize(new Dimension(200, 150));
+ results.add(imageLabel);
+ }
+
+ JScrollPane scrollPane = new JScrollPane();
+ results.setPreferredSize(new Dimension(600, 2000));
+ scrollPane.getViewport().add(results);
+ searchArea.setRightComponent(scrollPane);
+ searchArea.validate();
+ searchArea.setDividerLocation(80);
+ searchArea.repaint();
+ }
+ });
+
+ mainBody = jPanel;
+ }
+
+ private void setupSearchResults() {
+ JPanel jPanel = new JPanel();
+
+ searchResults = jPanel;
+ }
+
+ public BufferedImage resize(BufferedImage image, int width, int height) {
+ BufferedImage bi = new BufferedImage(width, height, BufferedImage.TRANSLUCENT);
+ Graphics2D g2d = (Graphics2D) bi.createGraphics();
+ g2d.addRenderingHints(new RenderingHints(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY));
+ g2d.drawImage(image, 0, 0, width, height, null);
+ g2d.dispose();
+ return bi;
+ }
+
+}
diff --git a/cs240/record-indexer/src/search/MainLayout.java b/cs240/record-indexer/src/search/MainLayout.java
new file mode 100644
index 0000000..d631fbc
--- /dev/null
+++ b/cs240/record-indexer/src/search/MainLayout.java
@@ -0,0 +1,24 @@
+package search;
+
+import javax.swing.*;
+import java.awt.*;
+
+public class MainLayout extends JFrame {
+
+ public MainLayout() {
+ initView();
+ }
+
+ FlowLayout flowLayout = new FlowLayout();
+
+ public void initView() {
+
+
+ final JPanel component = new JPanel();
+ component.setLayout(flowLayout);
+ }
+
+ public void open() {
+ setVisible(true);
+ }
+}
diff --git a/cs240/record-indexer/src/search/Search.java b/cs240/record-indexer/src/search/Search.java
new file mode 100644
index 0000000..e76e7ac
--- /dev/null
+++ b/cs240/record-indexer/src/search/Search.java
@@ -0,0 +1,19 @@
+package search;
+
+import javax.swing.*;
+import java.awt.*;
+
+public class Search {
+
+
+ public static void main(String[] args) {
+
+ javax.swing.SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ Main main = new Main();
+ main.setVisible(true);
+ }
+ });
+
+ }
+}
diff --git a/cs240/record-indexer/src/search/elements/FieldButton.java b/cs240/record-indexer/src/search/elements/FieldButton.java
new file mode 100644
index 0000000..6e3ff23
--- /dev/null
+++ b/cs240/record-indexer/src/search/elements/FieldButton.java
@@ -0,0 +1,22 @@
+package search.elements;
+
+import shared.communication.common.Fields;
+
+import javax.swing.*;
+
+public class FieldButton extends JButton {
+
+ private Fields field;
+
+ public FieldButton(Fields field) {
+ super(field.getTitle());
+
+ this.field = field;
+
+ setupView();
+ }
+
+ public void setupView() {
+
+ }
+}
diff --git a/cs240/record-indexer/src/search/elements/ImageButton.java b/cs240/record-indexer/src/search/elements/ImageButton.java
new file mode 100644
index 0000000..91f0a56
--- /dev/null
+++ b/cs240/record-indexer/src/search/elements/ImageButton.java
@@ -0,0 +1,10 @@
+package search.elements;
+
+import javax.swing.*;
+
+public class ImageButton extends JPanel {
+
+ public ImageButton() {
+
+ }
+}
diff --git a/cs240/record-indexer/src/search/elements/ProjectGroup.java b/cs240/record-indexer/src/search/elements/ProjectGroup.java
new file mode 100644
index 0000000..55098ff
--- /dev/null
+++ b/cs240/record-indexer/src/search/elements/ProjectGroup.java
@@ -0,0 +1,25 @@
+package search.elements;
+
+import search.helpers.dataModels.ProjectContainer;
+import shared.communication.common.Fields;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.ActionListener;
+import java.util.List;
+
+public class ProjectGroup extends JPanel {
+
+ private ProjectContainer projectContainer;
+
+ public ProjectGroup(ProjectContainer projectContainer) {
+ this.projectContainer = projectContainer;
+
+ setupView();
+ }
+
+ private void setupView() {
+ add(new JLabel(projectContainer.getTitle()));
+ }
+
+}
diff --git a/cs240/record-indexer/src/search/forms/InputField.java b/cs240/record-indexer/src/search/forms/InputField.java
new file mode 100644
index 0000000..2e936c5
--- /dev/null
+++ b/cs240/record-indexer/src/search/forms/InputField.java
@@ -0,0 +1,10 @@
+package search.forms;
+
+import javax.swing.*;
+
+public class InputField extends JTextField {
+
+ public InputField(String text, int width) {
+ super(text, width);
+ }
+}
diff --git a/cs240/record-indexer/src/search/helpers/Networking.java b/cs240/record-indexer/src/search/helpers/Networking.java
new file mode 100644
index 0000000..85fd001
--- /dev/null
+++ b/cs240/record-indexer/src/search/helpers/Networking.java
@@ -0,0 +1,132 @@
+package search.helpers;
+
+import client.communication.Communicator;
+import client.communication.errors.RemoteServerErrorException;
+import client.communication.errors.UnauthorizedAccessException;
+import search.helpers.dataModels.ProjectContainer;
+import shared.communication.common.Fields;
+import shared.communication.common.Project_Res;
+import shared.communication.params.Fields_Param;
+import shared.communication.params.Projects_Param;
+import shared.communication.params.Search_Param;
+import shared.communication.responses.Fields_Res;
+import shared.communication.responses.Projects_Res;
+import shared.communication.responses.Search_Res;
+
+import javax.imageio.ImageIO;
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+
+public class Networking {
+
+ public static int DEFAULT = 1;
+
+ private String serverPath;
+
+ public Networking(String host, int port) {
+ serverPath = "http://"+host+":"+port+"/";
+ this.communicator = new Communicator(serverPath);
+ }
+
+ public Networking(int startCode) {
+ if(startCode == 1) {
+ serverPath = "http://localhost:39640/";
+ this.communicator = new Communicator(serverPath);
+ }
+ }
+
+ private Communicator communicator;
+
+ public Projects_Res getProjects(String username, String password) {
+ Projects_Param projectsParam = new Projects_Param();
+
+ projectsParam.setUsername(username);
+ projectsParam.setPassword(password);
+
+ try {
+ return communicator.getProjects(projectsParam);
+ } catch (UnauthorizedAccessException e) {
+ return null;
+ } catch (RemoteServerErrorException e) {
+ return null;
+ }
+ }
+
+ public Fields_Res getFields(String username, String password, int projectId) {
+ Fields_Param fieldsParam = new Fields_Param();
+
+ fieldsParam.setUsername(username);
+ fieldsParam.setPassword(password);
+ fieldsParam.setProjectId(projectId);
+
+ try {
+ return communicator.getFields(fieldsParam);
+ } catch (UnauthorizedAccessException e) {
+ return null;
+ } catch (RemoteServerErrorException e) {
+ return null;
+ }
+ }
+
+ public List getProjectsWithFields(String username, String password) {
+ Projects_Res projectsRes = getProjects(username, password);
+ Fields_Res allFieldsRes = getFields(username, password, -1);
+ if(projectsRes == null || allFieldsRes == null) return null;
+ List projectContainerList = new ArrayList();
+ for(Project_Res projectRes : projectsRes.getProjectsList()) {
+ ProjectContainer projectContainer = new ProjectContainer(projectRes);
+ for(Fields field : allFieldsRes.getFields()) {
+ if(field.getProjectId() == projectRes.getId()) {
+ projectContainer.addField(field);
+ }
+ }
+ projectContainerList.add(projectContainer);
+ }
+
+ return projectContainerList;
+ }
+
+ public Search_Res search(String username, String password, String fieldIds, String queryWords) {
+ Search_Param searchParam = new Search_Param();
+ searchParam.setUsername(username);
+ searchParam.setPassword(password);
+ // Add FieldIds
+ for(String fieldId : parseCommaString(fieldIds)) {
+ searchParam.addFieldId(Integer.parseInt(fieldId));
+ }
+ // Add Query Strings
+ for(String query : parseCommaString(queryWords)) {
+ searchParam.addSearchParam(query);
+ }
+
+ try {
+ return communicator.search(searchParam);
+ } catch (UnauthorizedAccessException e) {
+ return null;
+ } catch (RemoteServerErrorException e) {
+ return null;
+ }
+ }
+
+ public BufferedImage getImage(String imageUrl) {
+
+ try {
+ return ImageIO.read(new URL(serverPath+imageUrl));
+ } catch (IOException e) {
+ return null;
+ }
+
+ }
+
+ // For search
+ private String[] parseCommaString(String stringWithCommas) {
+ return stringWithCommas.split(",");
+ }
+
+
+}
diff --git a/cs240/record-indexer/src/search/helpers/dataModels/ProjectContainer.java b/cs240/record-indexer/src/search/helpers/dataModels/ProjectContainer.java
new file mode 100644
index 0000000..17d0322
--- /dev/null
+++ b/cs240/record-indexer/src/search/helpers/dataModels/ProjectContainer.java
@@ -0,0 +1,25 @@
+package search.helpers.dataModels;
+
+import shared.communication.common.Fields;
+import shared.communication.common.Project_Res;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ProjectContainer extends Project_Res{
+
+ private List fieldsList = new ArrayList();
+
+ public ProjectContainer(Project_Res projectRes) {
+ super(projectRes.getId(), projectRes.getTitle());
+ }
+
+ public void addField(Fields field) {
+ fieldsList.add(field);
+ }
+
+ public List getFieldsList() {
+ return this.fieldsList;
+ }
+
+}
diff --git a/cs240/record-indexer/src/server/Server.java b/cs240/record-indexer/src/server/Server.java
new file mode 100644
index 0000000..eb82573
--- /dev/null
+++ b/cs240/record-indexer/src/server/Server.java
@@ -0,0 +1,49 @@
+package server;
+
+import com.sun.net.httpserver.HttpServer;
+import server.db.common.Database;
+import server.errors.ServerException;
+import server.handlers.*;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+
+public class Server {
+
+ private static int SERVER_LISTENING_PORT = 8090;
+ private static int MAX_WAITING_CONNECTIONS = 10;
+
+ private HttpServer server;
+
+ public void run(int port) throws ServerException {
+
+ SERVER_LISTENING_PORT = port;
+
+ Database.init(Database.PRODUCTION_MODE);
+
+ try {
+ server = HttpServer.create(new InetSocketAddress(SERVER_LISTENING_PORT),
+ MAX_WAITING_CONNECTIONS);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ server.setExecutor(null);
+
+ server.createContext("/getProjects", new GetProjectsHandler().getHandler());
+ server.createContext("/getSampleImage", new GetSampleImageHandler().getHandler());
+ server.createContext("/validateUser", new ValidateUserHandler().getHandler());
+ server.createContext("/downloadBatch", new DownloadBatchHandler().getHandler());
+ server.createContext("/getFields", new GetFieldsHandler().getHandler());
+ server.createContext("/submitBatch", new SubmitBatchHandler().getHandler());
+ server.createContext("/search", new SearchHandler().getHandler());
+ server.createContext("/", new StaticsHandler().getHandler());
+
+ System.out.println("Starting server on port: " + SERVER_LISTENING_PORT);
+ server.start();
+ }
+
+ public static void main(String[] args) throws ServerException {
+ new Server().run(Integer.parseInt(args[0]));
+ }
+}
diff --git a/cs240/record-indexer/src/server/controllers/UsersController.java b/cs240/record-indexer/src/server/controllers/UsersController.java
new file mode 100644
index 0000000..c943364
--- /dev/null
+++ b/cs240/record-indexer/src/server/controllers/UsersController.java
@@ -0,0 +1,15 @@
+package server.controllers;
+
+import com.thoughtworks.xstream.XStream;
+import com.thoughtworks.xstream.io.xml.StaxDriver;
+import shared.communication.params.ValidateUser_Param;
+import shared.communication.responses.ValidateUser_Res;
+
+import java.io.InputStream;
+
+public class UsersController {
+
+ public static ValidateUser_Res validateUser(ValidateUser_Param validateUserParam) {
+ return null;
+ }
+}
diff --git a/cs240/record-indexer/src/server/db/common/Database.java b/cs240/record-indexer/src/server/db/common/Database.java
new file mode 100644
index 0000000..e8c8715
--- /dev/null
+++ b/cs240/record-indexer/src/server/db/common/Database.java
@@ -0,0 +1,142 @@
+package server.db.common;
+
+import server.errors.ServerException;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.logging.Logger;
+
+import java.sql.*;
+
+public class Database {
+
+ public static final boolean AUTO_PRIMARY_KEY = true;
+ public static final boolean SPECIFIED_PRIMARY_KEY = false;
+ public static final boolean PRODUCTION_MODE = true;
+ public static final boolean DEVELOPMENT_MODE = true;
+ public static final boolean TEST_MODE = false;
+
+ private static Logger logger;
+
+ private Connection connection;
+
+ private static String dbUrl = "";
+
+ public static void init(boolean production) throws ServerException {
+ try {
+ final String driver = "org.sqlite.JDBC";
+ Class.forName(driver);
+
+ if(production) dbUrl = "jdbc:sqlite:db/database.sqlite3";
+ else dbUrl = "jdbc:sqlite:db/test.sqlite3";
+ } catch (ClassNotFoundException e) {
+ e.printStackTrace();
+ throw new ServerException("SQLite was not found");
+ }
+ }
+
+ public static void erase() throws ServerException, SQLException {
+ Database database = new Database();
+ database.openConnection();
+ database.voidQuery("delete from users;");
+ database.voidQuery("delete from projects;");
+ database.voidQuery("delete from records;");
+ database.voidQuery("delete from images;");
+ database.voidQuery("delete from 'values';");
+ database.voidQuery("delete from fields;");
+ database.commit();
+ database.closeConnection();
+ }
+
+ public void openConnection() throws ServerException {
+ try {
+ // Ensure we don't hit a collision
+ if(connection != null) return;
+
+ connection = DriverManager.getConnection(dbUrl);
+ connection.setAutoCommit(false);
+ } catch (SQLException e) {
+ throw new ServerException("Error establishing connection to: " + dbUrl);
+ }
+ }
+
+ public void closeConnection() throws SQLException {
+ connection.close();
+ connection = null;
+ }
+
+ public void commit() throws ServerException, SQLException {
+ try {
+ for(PreparedStatement query : queryBatch) {
+ int response = 0;
+ response = query.executeUpdate();
+ if(response != 1) {
+ connection.rollback();
+ throw new ServerException("Bad query update");
+ }
+ }
+ try {
+ connection.commit();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ } catch (SQLException e) {
+ //e.printStackTrace();
+ throw new ServerException(String.format("Error committing %d queries, rolling back!",
+ queryBatch.size()));
+ } finally {
+ queryBatch.clear();
+ }
+ }
+
+ public ResultSet query(String sql) throws ServerException, SQLException {
+ PreparedStatement preparedStatement;
+ try {
+
+ preparedStatement = connection.prepareStatement(sql);
+
+ return preparedStatement.executeQuery();
+
+ } catch (SQLException e) {
+ e.printStackTrace();
+ throw new ServerException("Error running query!");
+ }
+ }
+
+ public void voidQuery(String sql) throws ServerException, SQLException {
+ Statement statement;
+ try {
+ statement = connection.createStatement();
+ statement.addBatch(sql);
+ statement.executeBatch();
+ } catch (SQLException e) {
+ e.printStackTrace();
+ throw new ServerException("Error running query!");
+ }
+ }
+
+ private List queryBatch = new ArrayList();
+
+ public void addQuery(String sql) throws SQLException {
+ PreparedStatement preparedStatement;
+
+ preparedStatement = connection.prepareStatement(sql);
+ queryBatch.add(preparedStatement);
+ }
+
+ public int getLastIdForTable(String table) throws SQLException, ServerException {
+ ResultSet resultSet = query("SELECT MAX(ID) FROM '" +table+ "';" );
+ if(resultSet.next()) return resultSet.getInt(1);
+
+ return -1;
+ }
+
+ public String LAST_PROJECT = "(SELECT MAX(ID) FROM projects)";
+ public String LAST_IMAGE = "(SELECT MAX(ID) FROM images)";
+ public String LAST_RECORD = "(SELECT MAX(ID) FROM records)";
+ public String LAST_FIELD = "(SELECT MAX(ID) FROM fields)";
+ public String LAST_USER = "(SELECT MAX(ID) FROM users)";
+ public String LAST_VALUE = "(SELECT MAX(ID) FROM \'values\')";
+
+}
diff --git a/cs240/record-indexer/src/server/db/common/SQL.java b/cs240/record-indexer/src/server/db/common/SQL.java
new file mode 100644
index 0000000..a923bfc
--- /dev/null
+++ b/cs240/record-indexer/src/server/db/common/SQL.java
@@ -0,0 +1,10 @@
+package server.db.common;
+
+public class SQL {
+
+ public static String format(String string) {
+ if(string == null) return null;
+ return "\'" + string + "\'";
+ }
+
+}
diff --git a/cs240/record-indexer/src/server/db/common/Transaction.java b/cs240/record-indexer/src/server/db/common/Transaction.java
new file mode 100644
index 0000000..7727f62
--- /dev/null
+++ b/cs240/record-indexer/src/server/db/common/Transaction.java
@@ -0,0 +1,80 @@
+package server.db.common;
+
+import server.errors.ServerException;
+import shared.models.*;
+
+import java.sql.SQLException;
+import java.util.List;
+
+public abstract class Transaction {
+
+ public static boolean logic(Transaction transaction, Database database) {
+ try {
+ return transaction.logic();
+ } catch (SQLException e) {
+ System.out.println("TODO: SWAP WITH LOGGER LOGIC SAVE FUNCTION ERROR");
+ } catch (ServerException e) {
+ System.out.println("TODO: SWAP WITH LOGGER LOGIC COMMIT FUNCTION ERROR");
+ } finally {
+ try {
+ database.closeConnection();
+ } catch (SQLException e) {
+ e.printStackTrace();
+ }
+ }
+
+ return false;
+ }
+
+ public static Object object(Transaction transaction, Database database) {
+ try {
+ return transaction.object();
+ } catch (SQLException e) {
+ System.out.println("TODO: SWAP WITH LOGGER object SAVE FUNCTION ERROR");
+ } catch (ServerException e) {
+ System.out.println("TODO: SWAP WITH LOGGER object COMMIT FUNCTION ERROR");
+ } finally {
+ try {
+ database.closeConnection();
+ } catch (SQLException e) {
+ e.printStackTrace();
+ }
+ }
+
+ return null;
+ }
+
+ public static List