├── .travis.yml
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── clean.sh
├── make.sh
├── run_client.sh
├── run_relay.sh
├── run_server.sh
├── run_simple_gui_client.sh
├── src
└── codeu
│ └── chat
│ ├── ClientMain.java
│ ├── RelayMain.java
│ ├── ServerMain.java
│ ├── SimpleGuiClientMain.java
│ ├── client
│ ├── ClientContext.java
│ ├── ClientConversation.java
│ ├── ClientMessage.java
│ ├── ClientUser.java
│ ├── Controller.java
│ ├── View.java
│ ├── commandline
│ │ ├── Chat.java
│ │ └── ListNavigator.java
│ └── simplegui
│ │ ├── ChatSimpleGui.java
│ │ ├── ConversationPanel.java
│ │ ├── MessagePanel.java
│ │ └── UserPanel.java
│ ├── common
│ ├── BasicController.java
│ ├── BasicView.java
│ ├── Conversation.java
│ ├── ConversationSummary.java
│ ├── LinearUuidGenerator.java
│ ├── ListViewable.java
│ ├── LogicalView.java
│ ├── Message.java
│ ├── NetworkCode.java
│ ├── RandomUuidGenerator.java
│ ├── RawController.java
│ ├── Relay.java
│ ├── Secret.java
│ ├── SinglesView.java
│ └── User.java
│ ├── relay
│ ├── Server.java
│ └── ServerFrontEnd.java
│ ├── server
│ ├── Controller.java
│ ├── Model.java
│ ├── NoOpRelay.java
│ ├── RemoteRelay.java
│ ├── Server.java
│ └── View.java
│ └── util
│ ├── Logger.java
│ ├── Method.java
│ ├── RemoteAddress.java
│ ├── Serializer.java
│ ├── Serializers.java
│ ├── Time.java
│ ├── Timeline.java
│ ├── Uuid.java
│ ├── connections
│ ├── ClientConnectionSource.java
│ ├── Connection.java
│ ├── ConnectionSource.java
│ └── ServerConnectionSource.java
│ └── store
│ ├── LinkIterable.java
│ ├── LinkIterator.java
│ ├── Store.java
│ ├── StoreAccessor.java
│ └── StoreLink.java
├── teams
├── test.sh
├── test
└── codeu
│ └── chat
│ ├── TestRunner.java
│ ├── common
│ └── SecretTest.java
│ ├── relay
│ └── ServerTest.java
│ ├── server
│ ├── BasicControllerTest.java
│ └── RawControllerTest.java
│ └── util
│ ├── TimeTest.java
│ ├── UuidTest.java
│ └── store
│ └── StoreTest.java
└── third_party
├── LICENSE
├── hamcrest-core-1.3.jar
├── hamcrest-core.jar
├── junit4-4.11.jar
└── junit4.jar
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: java
2 |
3 | sudo: false
4 |
5 | jdk:
6 | - openjdk8
7 |
8 | os:
9 | - linux
10 |
11 |
12 | script:
13 | - ./clean.sh
14 | - ./make.sh
15 | - ./test.sh
16 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # How to contribute
2 |
3 | We'd love to accept your patches and contributions to this project. Just remember
4 | this project is designed as a playground for people to develop their skills. As
5 | such your proposed changes should keep in mind this project's intended purpose as
6 | platform for skill development. If you simply want to develop cool features based
7 | on this project, that's great - just fork this project and go wild.
8 |
9 | ## Contributor License Agreement
10 |
11 | Contributions to this project must be accompanied by a Contributor License
12 | Agreement. You (or your employer) retain the copyright to your contribution,
13 | this simply gives us permission to use and redistribute your contributions as
14 | part of the project. Head over to to see
15 | your current agreements on file or to sign a new one.
16 |
17 | You generally only need to submit a CLA once, so if you've already submitted one
18 | (even if it was for a different project), you probably don't need to do it
19 | again.
20 |
21 | ## Code reviews
22 |
23 | All submissions, including submissions by project members, require review. We
24 | use GitHub pull requests for this purpose. Consult [GitHub Help] for more
25 | information on using pull requests.
26 |
27 | [GitHub Help]: https://help.github.com/articles/about-pull-requests/
28 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | # CODEU CHAT SERVER | README
3 |
4 |
5 | ## DISCLAIMER
6 |
7 | CODEU is a program created by Google to develop the skills of future software
8 | engineers. This project is not an official Google Product. This project is a
9 | playground for those looking to develop their coding and software engineering
10 | skills.
11 |
12 |
13 | ## ENVIRONMENT
14 |
15 | All instructions here are relative to a LINUX environment. There will be some
16 | differences if you are working on a non-LINUX system. We will not support any
17 | other development environment.
18 |
19 | This project was built using JAVA 7. It is recommended that you install
20 | JAVA 7 when working with this project.
21 |
22 |
23 | ## GETTING STARTED
24 |
25 | 1. To build the project:
26 | ```
27 | $ sh clean.sh
28 | $ sh make.sh
29 | ```
30 |
31 | 1. To test the project:
32 | ```
33 | $ sh test.sh
34 | ```
35 |
36 | 1. To run the project you will need to run both the client and the server. Run
37 | the following two commands in separate shells:
38 |
39 | ```
40 | $ sh run_server.sh
41 | $ sh run_client.sh
42 | ```
43 |
44 | You must specify the following startup arguments for `run_server.sh:
45 | + `` and ``: a numeric id for your team, and a secret
46 | code, which are used to authenticate your server with the Relay server.
47 | You can specify any integer value for ``, and a value expressed
48 | in hexadecimal format (using numbers `0-9` and letters in the range
49 | `A-F`) for `` when you launch the server in your local setup
50 | since it will not connect to the Relay server.
51 | + ``: the TCP port that your Server will listen on for connections
52 | from the Client. You can use any value between 1024 and 65535, as long as
53 | there is no other service currently listening on that port in your
54 | system. The server will return an error:
55 |
56 | ```
57 | java.net.BindException: Address already in use (Bind failed)
58 | ```
59 |
60 | if the port is already in use.
61 | + ``: the path where you want the server to save data between
62 | runs.
63 |
64 | The startup arguments for `run_client.sh` are the following:
65 | + ``: the hostname or IP address of the computer on which the server
66 | is listening. If you are running server and client on the same computer,
67 | you can use `localhost` here.
68 | + ``: the port on which your server is listening. Must be the same
69 | port number you have specified when you launched `run_server.sh`.
70 |
71 | All running images write informational and exceptional events to log files.
72 | The default setting for log messages is "INFO". You may change this to get
73 | more or fewer messages, and you are encouraged to add more LOG statements
74 | to the code. The logging is implemented in `codeu.chat.util.Logger.java`,
75 | which is built on top of `java.util.logging.Logger`, which you can refer to
76 | for more information.
77 |
78 | In addition to your team's client and server, the project also includes a
79 | Relay Server and a script that runs it (`run_relay.sh`).
80 | This is not needed to get started with the project.
81 |
82 |
83 | ## Finding your way around the project
84 |
85 | All the source files (except test-related source files) are in
86 | `./src/codeu/chat`. The test source files are in `./test/codeu/chat`. If you
87 | use the supplied scripts to build the project, the `.class` files will be placed
88 | in `./bin`. There is a `./third_party` directory that holds the jar files for
89 | JUnit (a Java testing framework). Your environment may or may not already have
90 | this installed. The supplied scripts use the version in `./third_party`.
91 |
92 | Finally, there are some high-level design documents in the project Wiki. Please
93 | review them as they can help you find your way around the sources.
94 |
95 |
96 |
97 | ## Source Directories
98 |
99 | The major project components have been separated into their own packages. The
100 | main packages/directories under `src/codeu/chat` are:
101 |
102 | ### codeu.chat.client
103 |
104 | Classes for building the two clients (`codeu.chat.ClientMain` and
105 | `codeu.chat.SimpleGuiClientMain`).
106 |
107 | ### codeu.chat.server
108 |
109 | Classes for building the server (`codeu.chat.ServerMain`).
110 |
111 | ### codeu.chat.relay
112 |
113 | Classes for building the Relay Server (`codeu.chat.RelayMain`). The Relay Server
114 | is not needed to get started.
115 |
116 | ### codeu.chat.common
117 |
118 | Classes that are shared by the clients and servers.
119 |
120 | ### codeu.chat.util
121 |
122 | Some basic infrastructure classes used throughout the project.
123 |
--------------------------------------------------------------------------------
/clean.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Copyright 2017 Google Inc.
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 |
17 | set -e
18 |
19 | rm -rf ./bin/*
20 |
--------------------------------------------------------------------------------
/make.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Copyright 2017 Google Inc.
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 |
17 | set -e
18 |
19 | mkdir -p bin
20 |
21 | javac -Xlint $(find * | grep "\\.java$") -d ./bin -sourcepath ./src -cp ./third_party/junit4.jar:./bin
22 | javac -Xlint $(find * | grep "\\.java$") -d ./bin -sourcepath ./test -cp ./third_party/junit4.jar:./bin
23 |
--------------------------------------------------------------------------------
/run_client.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Copyright 2017 Google Inc.
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 |
17 | HOST="$1"
18 | PORT="$2"
19 |
20 | if [[ "${HOST}" == "" || "${PORT}" == "" ]] ; then
21 | echo 'usage: '
22 | exit 1
23 | fi
24 |
25 | cd './bin'
26 | java codeu.chat.ClientMain "${HOST}@${PORT}"
27 |
--------------------------------------------------------------------------------
/run_relay.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Copyright 2017 Google Inc.
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 |
17 | PORT="$1"
18 | TEAM_FILE="$2"
19 |
20 | if [[ "${PORT}" == "" || "${TEAM_FILE}" == "" ]] ; then
21 | echo 'usage: '
22 | exit 1
23 | fi
24 |
25 | if [ ! -f "${TEAM_FILE}" ] ; then
26 | echo "No file at ${TEAM_FILE}"
27 | exit 1
28 | fi
29 |
30 | cd './bin'
31 | java codeu.chat.RelayMain "${PORT}" "${TEAM_FILE}"
32 |
--------------------------------------------------------------------------------
/run_server.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Copyright 2017 Google Inc.
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 |
17 | TEAM_ID="$1"
18 | TEAM_SECRET="$2"
19 | PORT="$3"
20 | PERSISTENT_DIR="$4"
21 | RELAY_ADDRESS="$5"
22 |
23 | if [[ "${TEAM_ID}" == "" || "${TEAM_SECRET}" == "" || "${PORT}" == "" || "${PERSISTENT_DIR}" == "" ]] ; then
24 | echo 'usage: [RELAY ADDRESS]'
25 | echo ''
26 | echo 'TEAM ID : The id registered with the relay server. If you are'
27 | echo ' not connecting to a relay server, use "100".'
28 | echo 'TEAM SECRET : The secret registerd with the relay server. If you are'
29 | echo ' not connecting to a relay server, use "ABABAB".'
30 | echo 'PORT : The port that the server will listen to for incoming '
31 | echo ' connections. This can be anything from 1024 to 65535.'
32 | echo 'PERSISTENT DIR : The directory where the server can save data that will'
33 | echo ' exists between runs.'
34 | echo 'RELAY ADDRESS : This value is optional. If you want to connect to a '
35 | echo ' relay server, the address must be IP@PORT where IP is'
36 | echo ' the ip address of the relay server and PORT is the port'
37 | echo ' the relay server is listing on.'
38 | echo ''
39 | exit 1
40 | fi
41 |
42 |
43 | cd './bin'
44 | if [ "${RELAY_ADDRESS}" == "" ] ; then
45 | java codeu.chat.ServerMain \
46 | "${TEAM_ID}" \
47 | "${TEAM_SECRET}" \
48 | "${PORT}" \
49 | "${PERSISTENT_DIR}"
50 | else
51 | java codeu.chat.ServerMain \
52 | "${TEAM_ID}" \
53 | "${TEAM_SECRET}" \
54 | "${PORT}" \
55 | "${PERSISTENT_DIR}" \
56 | "${RELAY_ADDRESS}"
57 | fi
58 |
--------------------------------------------------------------------------------
/run_simple_gui_client.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Copyright 2017 Google Inc.
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 |
17 | LOCAL_MACHINE="localhost@2007"
18 |
19 | cd './bin'
20 |
21 | java codeu.chat.SimpleGuiClientMain "${LOCAL_MACHINE}"
22 |
--------------------------------------------------------------------------------
/src/codeu/chat/ClientMain.java:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package codeu.chat;
16 |
17 | import java.io.IOException;
18 | import java.util.Scanner;
19 |
20 | import codeu.chat.client.commandline.Chat;
21 | import codeu.chat.client.Controller;
22 | import codeu.chat.client.View;
23 | import codeu.chat.util.Logger;
24 | import codeu.chat.util.RemoteAddress;
25 | import codeu.chat.util.connections.ClientConnectionSource;
26 | import codeu.chat.util.connections.ConnectionSource;
27 |
28 | final class ClientMain {
29 |
30 | private static final Logger.Log LOG = Logger.newLog(ClientMain.class);
31 |
32 | public static void main(String [] args) {
33 |
34 | try {
35 | Logger.enableFileOutput("chat_client_log.log");
36 | } catch (IOException ex) {
37 | LOG.error(ex, "Failed to set logger to write to file");
38 | }
39 |
40 | LOG.info("============================= START OF LOG =============================");
41 |
42 | LOG.info("Starting chat client...");
43 |
44 | final RemoteAddress address = RemoteAddress.parse(args[0]);
45 |
46 | final ConnectionSource source = new ClientConnectionSource(address.host, address.port);
47 | final Controller controller = new Controller(source);
48 | final View view = new View(source);
49 |
50 | LOG.info("Creating client...");
51 | final Chat chat = new Chat(controller, view);
52 |
53 | LOG.info("Created client");
54 |
55 | final Scanner input = new Scanner(System.in);
56 |
57 | while (chat.handleCommand(input)) {
58 | // everything is done in "run"
59 | }
60 |
61 | input.close();
62 |
63 | LOG.info("chat client has exited.");
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/codeu/chat/RelayMain.java:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package codeu.chat;
16 |
17 | import java.io.BufferedReader;
18 | import java.io.FileReader;
19 | import java.io.IOException;
20 |
21 | import codeu.chat.common.Secret;
22 | import codeu.chat.relay.Server;
23 | import codeu.chat.relay.ServerFrontEnd;
24 | import codeu.chat.util.Logger;
25 | import codeu.chat.util.Timeline;
26 | import codeu.chat.util.Uuid;
27 | import codeu.chat.util.connections.Connection;
28 | import codeu.chat.util.connections.ConnectionSource;
29 | import codeu.chat.util.connections.ServerConnectionSource;
30 |
31 | final class RelayMain {
32 |
33 | private static final Logger.Log LOG = Logger.newLog(RelayMain.class);
34 |
35 | public static void main(String[] args) {
36 |
37 | Logger.enableConsoleOutput();
38 |
39 | try {
40 | Logger.enableFileOutput("chat_relay_log.log");
41 | } catch (IOException ex) {
42 | LOG.error(ex, "Failed to set logger to write to file");
43 | }
44 |
45 | LOG.info("============================= START OF LOG =============================");
46 |
47 | final int myPort = Integer.parseInt(args[0]);
48 |
49 | try (final ConnectionSource source = ServerConnectionSource.forPort(myPort)) {
50 |
51 | // Limit the number of messages that the server tracks to be 1024 and limit the
52 | // max number of messages that the relay will send out to be 16.
53 | final Server relay = new Server(1024, 16);
54 |
55 | LOG.info("Relay object created.");
56 |
57 | LOG.info("Starting relay...");
58 |
59 | startRelay(relay, source, args[1]);
60 |
61 | } catch (IOException ex) {
62 | LOG.error(ex, "Failed to establish server accept port");
63 | }
64 | }
65 |
66 | private static void startRelay(final Server relay,
67 | final ConnectionSource source,
68 | final String teamFile) {
69 |
70 | final ServerFrontEnd frontEnd = new ServerFrontEnd(relay);
71 | LOG.info("Relay front end object created.");
72 |
73 | final Timeline timeline = new Timeline();
74 | LOG.info("Relay timeline created.");
75 |
76 | timeline.scheduleNow(new Runnable() {
77 | @Override
78 | public void run() {
79 | LOG.info("Loading team data...");
80 | loadTeamInfo(relay, teamFile);
81 | LOG.info("Done loading team data.");
82 |
83 | // Add this again in 1 minute so that new team entries will be added to
84 | // the relay. This won't support updating entries.
85 | timeline.scheduleIn(60000, this);
86 | }
87 | });
88 |
89 | LOG.info("Starting relay main loop...");
90 |
91 | while (true) {
92 | try {
93 |
94 | LOG.info("Establishing connection...");
95 | final Connection connection = source.connect();
96 | LOG.info("Connection established.");
97 |
98 | timeline.scheduleNow(new Runnable() {
99 | @Override
100 | public void run() {
101 | try {
102 | frontEnd.handleConnection(connection);
103 | } catch (Exception ex) {
104 | LOG.error(ex, "Exception handling connection.");
105 | }
106 | }
107 | });
108 |
109 | } catch (IOException ex) {
110 | LOG.error(ex, "Failed to establish connection.");
111 | }
112 | }
113 | }
114 |
115 | private static void loadTeamInfo(Server relay, String file) {
116 |
117 | try (final BufferedReader reader = new BufferedReader(new FileReader(file))) {
118 |
119 | String line;
120 | for (line = reader.readLine();
121 | line != null;
122 | line = reader.readLine()) {
123 |
124 | line = line.trim();
125 |
126 | if (line.length() == 0) {
127 | // This line is blank, skip it
128 | } else if (line.startsWith("#")) {
129 | // this is a comment, skip it
130 | } else {
131 |
132 | try {
133 |
134 | final String[] tokens = line.split(":");
135 |
136 | // There are just so many things that could go wrong when parsing
137 | // this line that it is not worth trying to handle ahead of time.
138 | // So instead just try to parse it and catch any exception.
139 |
140 | final Uuid id = Uuid.parse(tokens[0].trim());
141 | final byte[] secret = Secret.parse(tokens[1].trim());
142 |
143 | relay.addTeam(id, secret);
144 | } catch (Exception ex) {
145 | LOG.error(ex, "Skipping line \"%s\". Could not parse", line);
146 | }
147 | }
148 | }
149 | } catch (IOException ex) {
150 | LOG.error(ex, "Failed to load team data");
151 | }
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/src/codeu/chat/ServerMain.java:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 | //
15 |
16 | package codeu.chat;
17 |
18 | import java.io.IOException;
19 |
20 | import codeu.chat.common.Relay;
21 | import codeu.chat.common.Secret;
22 | import codeu.chat.server.NoOpRelay;
23 | import codeu.chat.server.RemoteRelay;
24 | import codeu.chat.server.Server;
25 | import codeu.chat.util.Logger;
26 | import codeu.chat.util.RemoteAddress;
27 | import codeu.chat.util.Uuid;
28 | import codeu.chat.util.connections.ClientConnectionSource;
29 | import codeu.chat.util.connections.Connection;
30 | import codeu.chat.util.connections.ConnectionSource;
31 | import codeu.chat.util.connections.ServerConnectionSource;
32 |
33 | final class ServerMain {
34 |
35 | private static final Logger.Log LOG = Logger.newLog(ServerMain.class);
36 |
37 | public static void main(String[] args) {
38 |
39 | Logger.enableConsoleOutput();
40 |
41 | try {
42 | Logger.enableFileOutput("chat_server_log.log");
43 | } catch (IOException ex) {
44 | LOG.error(ex, "Failed to set logger to write to file");
45 | }
46 |
47 | LOG.info("============================= START OF LOG =============================");
48 |
49 | final int myPort = Integer.parseInt(args[2]);
50 | final byte[] secret = Secret.parse(args[1]);
51 |
52 | Uuid id = null;
53 | try {
54 | id = Uuid.parse(args[0]);
55 | } catch (IOException ex) {
56 | System.out.println("Invalid id - shutting down server");
57 | System.exit(1);
58 | }
59 |
60 | // This is the directory where it is safe to store data accross runs
61 | // of the server.
62 | final String persistentPath = args[3];
63 |
64 | final RemoteAddress relayAddress = args.length > 4 ?
65 | RemoteAddress.parse(args[4]) :
66 | null;
67 |
68 | try (
69 | final ConnectionSource serverSource = ServerConnectionSource.forPort(myPort);
70 | final ConnectionSource relaySource = relayAddress == null ? null : new ClientConnectionSource(relayAddress.host, relayAddress.port)
71 | ) {
72 |
73 | LOG.info("Starting server...");
74 | runServer(id, secret, serverSource, relaySource);
75 |
76 | } catch (IOException ex) {
77 |
78 | LOG.error(ex, "Failed to establish connections");
79 |
80 | }
81 | }
82 |
83 | private static void runServer(Uuid id,
84 | byte[] secret,
85 | ConnectionSource serverSource,
86 | ConnectionSource relaySource) {
87 |
88 | final Relay relay = relaySource == null ?
89 | new NoOpRelay() :
90 | new RemoteRelay(relaySource);
91 |
92 | final Server server = new Server(id, secret, relay);
93 |
94 | LOG.info("Created server.");
95 |
96 | while (true) {
97 |
98 | try {
99 |
100 | LOG.info("Established connection...");
101 | final Connection connection = serverSource.connect();
102 | LOG.info("Connection established.");
103 |
104 | server.handleConnection(connection);
105 |
106 | } catch (IOException ex) {
107 | LOG.error(ex, "Failed to establish connection.");
108 | }
109 | }
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/src/codeu/chat/SimpleGuiClientMain.java:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | //Licensed under the Apache License, Version 2.0 (the "License");
4 | //you may not use this file except in compliance with the License.
5 | //You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package codeu.chat;
16 |
17 | import java.io.IOException;
18 |
19 | import codeu.chat.client.Controller;
20 | import codeu.chat.client.simplegui.ChatSimpleGui;
21 | import codeu.chat.client.View;
22 | import codeu.chat.util.Logger;
23 | import codeu.chat.util.RemoteAddress;
24 | import codeu.chat.util.connections.ClientConnectionSource;
25 | import codeu.chat.util.connections.ConnectionSource;
26 |
27 | final class SimpleGuiClientMain {
28 |
29 | private static final Logger.Log LOG = Logger.newLog(SimpleGuiClientMain.class);
30 |
31 | public static void main(String [] args) {
32 |
33 | try {
34 | Logger.enableFileOutput("chat_simple_gui_client_log.log");
35 | } catch (IOException ex) {
36 | LOG.error(ex, "Failed to set logger to write to file");
37 | }
38 |
39 | LOG.info("============================= START OF LOG =============================");
40 |
41 | LOG.info("Starting chat client...");
42 |
43 | // Start up server connection/interface.
44 |
45 | final RemoteAddress address = RemoteAddress.parse(args[0]);
46 |
47 | try (
48 | final ConnectionSource source = new ClientConnectionSource(address.host, address.port)
49 | ) {
50 | final Controller controller = new Controller(source);
51 | final View view = new View(source);
52 |
53 | LOG.info("Creating client...");
54 |
55 | runClient(controller, view);
56 |
57 | } catch (Exception ex) {
58 | System.out.println("ERROR: Exception setting up client. Check log for details.");
59 | LOG.error(ex, "Exception setting up client.");
60 | }
61 | }
62 |
63 | private static void runClient(Controller controller, View view) {
64 |
65 | final ChatSimpleGui chatSimpleGui = new ChatSimpleGui(controller, view);
66 |
67 | LOG.info("Created client");
68 |
69 | chatSimpleGui.run();
70 |
71 | LOG.info("chat client is running.");
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/codeu/chat/client/ClientContext.java:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package codeu.chat.client;
16 |
17 | import codeu.chat.client.ClientConversation;
18 | import codeu.chat.client.ClientMessage;
19 | import codeu.chat.client.ClientUser;
20 | import codeu.chat.client.Controller;
21 | import codeu.chat.client.View;
22 |
23 | public final class ClientContext {
24 |
25 | public final ClientUser user;
26 | public final ClientConversation conversation;
27 | public final ClientMessage message;
28 |
29 | public ClientContext(Controller controller, View view) {
30 | user = new ClientUser(controller, view);
31 | conversation = new ClientConversation(controller, view, user);
32 | message = new ClientMessage(controller, view, user, conversation);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/codeu/chat/client/ClientConversation.java:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package codeu.chat.client;
16 |
17 | import java.util.Arrays;
18 | import java.util.HashMap;
19 | import java.util.Map;
20 |
21 | import codeu.chat.common.Conversation;
22 | import codeu.chat.common.ConversationSummary;
23 | import codeu.chat.util.Logger;
24 | import codeu.chat.util.Method;
25 | import codeu.chat.util.Uuid;
26 | import codeu.chat.util.store.Store;
27 |
28 | public final class ClientConversation {
29 |
30 | private final static Logger.Log LOG = Logger.newLog(ClientConversation.class);
31 |
32 | private final Controller controller;
33 | private final View view;
34 |
35 | private ConversationSummary currentSummary = null;
36 | private Conversation currentConversation = null;
37 |
38 | private final ClientUser userContext;
39 | private ClientMessage messageContext = null;
40 |
41 | // This is the set of conversations known to the server.
42 | private final Map summariesByUuid = new HashMap<>();
43 |
44 | // This is the set of conversations known to the server, sorted by title.
45 | private Store summariesSortedByTitle =
46 | new Store<>(String.CASE_INSENSITIVE_ORDER);
47 |
48 | public ClientConversation(Controller controller, View view, ClientUser userContext) {
49 | this.controller = controller;
50 | this.view = view;
51 | this.userContext = userContext;
52 | }
53 |
54 | public void setMessageContext(ClientMessage messageContext) {
55 | this.messageContext = messageContext;
56 | }
57 |
58 | // Validate the title of the conversation
59 | static public boolean isValidTitle(String title) {
60 | boolean clean = true;
61 | if ((title.length() <= 0) || (title.length() > 64)) {
62 | clean = false;
63 | } else {
64 |
65 | // TODO: check for invalid characters
66 |
67 | }
68 | return clean;
69 | }
70 |
71 | public boolean hasCurrent() {
72 | return (currentSummary != null);
73 | }
74 |
75 | public ConversationSummary getCurrent() {
76 | return currentSummary;
77 | }
78 |
79 | public Uuid getCurrentId() { return (currentSummary != null) ? currentSummary.id : null; }
80 |
81 | public int currentMessageCount() {
82 | return messageContext.currentMessageCount();
83 | }
84 |
85 | public void showCurrent() {
86 | printConversation(currentSummary, userContext);
87 | }
88 |
89 | public void startConversation(String title, Uuid owner) {
90 | final boolean validInputs = isValidTitle(title);
91 |
92 | final Conversation conv = (validInputs) ? controller.newConversation(title, owner) : null;
93 |
94 | if (conv == null) {
95 | System.out.format("Error: conversation not created - %s.\n",
96 | (validInputs) ? "server failure" : "bad input value");
97 | } else {
98 | LOG.info("New conversation: Title= \"%s\" UUID= %s", conv.title, conv.id);
99 |
100 | currentSummary = conv.summary;
101 |
102 | updateAllConversations(currentSummary != null);
103 | }
104 | }
105 |
106 | public void setCurrent(ConversationSummary conv) { currentSummary = conv; }
107 |
108 | public void showAllConversations() {
109 | updateAllConversations(false);
110 |
111 | for (final ConversationSummary c : summariesByUuid.values()) {
112 | printConversation(c, userContext);
113 | }
114 | }
115 |
116 | // Get a single conversation from the server.
117 | public Conversation getConversation(Uuid conversationId) {
118 | for (final Conversation c : view.getConversations(Arrays.asList(conversationId))) {
119 | return c;
120 | }
121 | return null;
122 | }
123 |
124 | private void joinConversation(String match) {
125 | Method.notImplemented();
126 | }
127 |
128 | private void leaveCurrentConversation() {
129 | Method.notImplemented();
130 | }
131 |
132 | private void updateCurrentConversation() {
133 | if (currentSummary == null) {
134 | currentConversation = null;
135 | } else {
136 | currentConversation = getConversation(currentSummary.id);
137 | if (currentConversation == null) {
138 | LOG.info("GetConversation: current=%s, current.id=%s, but currentConversation == null",
139 | currentSummary, currentSummary.id);
140 | } else {
141 | LOG.info("Get Conversation: Title=\"%s\" UUID=%s first=%s last=%s\n",
142 | currentConversation.title, currentConversation.id, currentConversation.firstMessage,
143 | currentConversation.lastMessage);
144 | }
145 | }
146 | }
147 |
148 | public int conversationsCount() {
149 | return summariesByUuid.size();
150 | }
151 |
152 | public Iterable getConversationSummaries() {
153 | return summariesSortedByTitle.all();
154 | }
155 |
156 | // Update the list of known Conversations.
157 | // If the input currentChanged is true, then re-establish the state of
158 | // the current Conversation, including its messages.
159 | public void updateAllConversations(boolean currentChanged) {
160 |
161 | summariesByUuid.clear();
162 | summariesSortedByTitle = new Store<>(String.CASE_INSENSITIVE_ORDER);
163 |
164 | for (final ConversationSummary cs : view.getAllConversations()) {
165 | summariesByUuid.put(cs.id, cs);
166 | summariesSortedByTitle.insert(cs.title, cs);
167 | }
168 |
169 | if (currentChanged) {
170 | updateCurrentConversation();
171 | messageContext.resetCurrent(true);
172 | }
173 | }
174 |
175 | // Print Conversation. User context is used to map from owner UUID to name.
176 | public static void printConversation(ConversationSummary c, ClientUser userContext) {
177 | if (c == null) {
178 | System.out.println("Null conversation");
179 | } else {
180 | final String name = (userContext == null) ? null : userContext.getName(c.owner);
181 | final String ownerName = (name == null) ? "" : String.format(" (%s)", name);
182 | System.out.format(" Title: %s\n", c.title);
183 | System.out.format(" Id: %s owner: %s%s created %s\n", c.id, c.owner, ownerName, c.creation);
184 | }
185 | }
186 |
187 | // Print Conversation outside of User context.
188 | public static void printConversation(ConversationSummary c) {
189 | printConversation(c, null);
190 | }
191 | }
192 |
--------------------------------------------------------------------------------
/src/codeu/chat/client/ClientUser.java:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package codeu.chat.client;
16 |
17 | import java.util.Arrays;
18 | import java.util.Collection;
19 | import java.util.HashMap;
20 | import java.util.Map;
21 |
22 | import codeu.chat.common.User;
23 | import codeu.chat.util.Logger;
24 | import codeu.chat.util.Uuid;
25 | import codeu.chat.util.store.Store;
26 |
27 | public final class ClientUser {
28 |
29 | private final static Logger.Log LOG = Logger.newLog(ClientUser.class);
30 |
31 | private static final Collection EMPTY = Arrays.asList(new Uuid[0]);
32 | private final Controller controller;
33 | private final View view;
34 |
35 | private User current = null;
36 |
37 | private final Map usersById = new HashMap<>();
38 |
39 | // This is the set of users known to the server, sorted by name.
40 | private Store usersByName = new Store<>(String.CASE_INSENSITIVE_ORDER);
41 |
42 | public ClientUser(Controller controller, View view) {
43 | this.controller = controller;
44 | this.view = view;
45 | }
46 |
47 | // Validate the username string
48 | static public boolean isValidName(String userName) {
49 | boolean clean = true;
50 | if (userName.length() == 0) {
51 | clean = false;
52 | } else {
53 |
54 | // TODO: check for invalid characters
55 |
56 | }
57 | return clean;
58 | }
59 |
60 | public boolean hasCurrent() {
61 | return (current != null);
62 | }
63 |
64 | public User getCurrent() {
65 | return current;
66 | }
67 |
68 | public boolean signInUser(String name) {
69 | updateUsers();
70 |
71 | final User prev = current;
72 | if (name != null) {
73 | final User newCurrent = usersByName.first(name);
74 | if (newCurrent != null) {
75 | current = newCurrent;
76 | }
77 | }
78 | return (prev != current);
79 | }
80 |
81 | public boolean signOutUser() {
82 | boolean hadCurrent = hasCurrent();
83 | current = null;
84 | return hadCurrent;
85 | }
86 |
87 | public void showCurrent() {
88 | printUser(current);
89 | }
90 |
91 | public void addUser(String name) {
92 | final boolean validInputs = isValidName(name);
93 |
94 | final User user = (validInputs) ? controller.newUser(name) : null;
95 |
96 | if (user == null) {
97 | System.out.format("Error: user not created - %s.\n",
98 | (validInputs) ? "server failure" : "bad input value");
99 | } else {
100 | LOG.info("New user complete, Name= \"%s\" UUID=%s", user.name, user.id);
101 | updateUsers();
102 | }
103 | }
104 |
105 | public void showAllUsers() {
106 | updateUsers();
107 | for (final User u : usersByName.all()) {
108 | printUser(u);
109 | }
110 | }
111 |
112 | public User lookup(Uuid id) {
113 | return (usersById.containsKey(id)) ? usersById.get(id) : null;
114 | }
115 |
116 | public String getName(Uuid id) {
117 | final User user = lookup(id);
118 | if (user == null) {
119 | LOG.warning("userContext.lookup() failed on ID: %s", id);
120 | return null;
121 | } else {
122 | return user.name;
123 | }
124 | }
125 |
126 | public Iterable getUsers() {
127 | return usersByName.all();
128 | }
129 |
130 | public void updateUsers() {
131 | usersById.clear();
132 | usersByName = new Store<>(String.CASE_INSENSITIVE_ORDER);
133 |
134 | for (final User user : view.getUsersExcluding(EMPTY)) {
135 | usersById.put(user.id, user);
136 | usersByName.insert(user.name, user);
137 | }
138 | }
139 |
140 | public static String getUserInfoString(User user) {
141 | return (user == null) ? "Null user" :
142 | String.format(" User: %s\n Id: %s\n created: %s\n", user.name, user.id, user.creation);
143 | }
144 |
145 | public String showUserInfo(String uname) {
146 | return getUserInfoString(usersByName.first(uname));
147 | }
148 |
149 | // Move to User's toString()
150 | public static void printUser(User user) {
151 | System.out.println(getUserInfoString(user));
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/src/codeu/chat/client/Controller.java:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package codeu.chat.client;
16 |
17 | import java.io.PrintWriter;
18 | import java.io.StringWriter;
19 | import java.lang.Thread;
20 |
21 | import codeu.chat.common.BasicController;
22 | import codeu.chat.common.Conversation;
23 | import codeu.chat.common.Message;
24 | import codeu.chat.common.NetworkCode;
25 | import codeu.chat.common.User;
26 | import codeu.chat.util.Logger;
27 | import codeu.chat.util.Serializers;
28 | import codeu.chat.util.Uuid;
29 | import codeu.chat.util.connections.Connection;
30 | import codeu.chat.util.connections.ConnectionSource;
31 |
32 | public class Controller implements BasicController {
33 |
34 | private final static Logger.Log LOG = Logger.newLog(Controller.class);
35 |
36 | private final ConnectionSource source;
37 |
38 | public Controller(ConnectionSource source) {
39 | this.source = source;
40 | }
41 |
42 | @Override
43 | public Message newMessage(Uuid author, Uuid conversation, String body) {
44 |
45 | Message response = null;
46 |
47 | try (final Connection connection = source.connect()) {
48 |
49 | Serializers.INTEGER.write(connection.out(), NetworkCode.NEW_MESSAGE_REQUEST);
50 | Uuid.SERIALIZER.write(connection.out(), author);
51 | Uuid.SERIALIZER.write(connection.out(), conversation);
52 | Serializers.STRING.write(connection.out(), body);
53 |
54 | if (Serializers.INTEGER.read(connection.in()) == NetworkCode.NEW_MESSAGE_RESPONSE) {
55 | response = Serializers.nullable(Message.SERIALIZER).read(connection.in());
56 | } else {
57 | LOG.error("Response from server failed.");
58 | }
59 | } catch (Exception ex) {
60 | System.out.println("ERROR: Exception during call on server. Check log for details.");
61 | LOG.error(ex, "Exception during call on server.");
62 | }
63 |
64 | return response;
65 | }
66 |
67 | @Override
68 | public User newUser(String name) {
69 |
70 | User response = null;
71 |
72 | try (final Connection connection = source.connect()) {
73 |
74 | Serializers.INTEGER.write(connection.out(), NetworkCode.NEW_USER_REQUEST);
75 | Serializers.STRING.write(connection.out(), name);
76 | LOG.info("newUser: Request completed.");
77 |
78 | if (Serializers.INTEGER.read(connection.in()) == NetworkCode.NEW_USER_RESPONSE) {
79 | response = Serializers.nullable(User.SERIALIZER).read(connection.in());
80 | LOG.info("newUser: Response completed.");
81 | } else {
82 | LOG.error("Response from server failed.");
83 | }
84 | } catch (Exception ex) {
85 | System.out.println("ERROR: Exception during call on server. Check log for details.");
86 | LOG.error(ex, "Exception during call on server.");
87 | }
88 |
89 | return response;
90 | }
91 |
92 | @Override
93 | public Conversation newConversation(String title, Uuid owner) {
94 |
95 | Conversation response = null;
96 |
97 | try (final Connection connection = source.connect()) {
98 |
99 | Serializers.INTEGER.write(connection.out(), NetworkCode.NEW_CONVERSATION_REQUEST);
100 | Serializers.STRING.write(connection.out(), title);
101 | Uuid.SERIALIZER.write(connection.out(), owner);
102 |
103 | if (Serializers.INTEGER.read(connection.in()) == NetworkCode.NEW_CONVERSATION_RESPONSE) {
104 | response = Serializers.nullable(Conversation.SERIALIZER).read(connection.in());
105 | } else {
106 | LOG.error("Response from server failed.");
107 | }
108 | } catch (Exception ex) {
109 | System.out.println("ERROR: Exception during call on server. Check log for details.");
110 | LOG.error(ex, "Exception during call on server.");
111 | }
112 |
113 | return response;
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/src/codeu/chat/client/commandline/ListNavigator.java:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package codeu.chat.client.commandline;
16 |
17 | import java.lang.Math;
18 | import java.util.ArrayList;
19 | import java.util.Collection;
20 | import java.util.List;
21 | import java.util.Scanner;
22 |
23 | import codeu.chat.common.ListViewable;
24 |
25 | // Page up and down through a list of objects and allow user to select one.
26 | // T must implement ListViewable. This gives access to listView(), which produces
27 | // the human-readable string that identifies the object in the list.
28 | public final class ListNavigator {
29 |
30 | private final List selection = new ArrayList();
31 | private final Scanner lineScanner;
32 | private int top;
33 | private int bottom;
34 | private int pageSize;
35 | private boolean hasInt;
36 | private int intValue;
37 | private String choice;
38 |
39 | // Creates a ListNavigator of the desired ListViewable subclass
40 | public ListNavigator(Iterable objectList, Scanner scanner, int pageSize) {
41 | this.pageSize = pageSize;
42 | this.lineScanner = scanner;
43 | for (final T e : objectList) {
44 | this.selection.add(e);
45 | }
46 | moveUp(); // set top and bottom for first page.
47 | }
48 |
49 | // Run the chooser. Returns true when a selection has been made, or false on an error
50 | // or if the user cancels the operation.
51 | public boolean chooseFromList() {
52 | if (selection.size() == 0) {
53 | System.out.println("ERROR: selection is empty - cannot select");
54 | return false;
55 | } else {
56 | while (true) {
57 | displayChoices();
58 | issuePrompt();
59 | getChoice();
60 | if (indexSelected()) {
61 | return true;
62 | } else if (moveDownSelected()) {
63 | moveDown();
64 | } else if (moveUpSelected()) {
65 | moveUp();
66 | } else if (cancelSelected()) {
67 | return false;
68 | } else {
69 | System.out.println("Poor choice - try again.");
70 | }
71 | }
72 | }
73 | }
74 |
75 | // Get the selectes object from the chooser.
76 | // Should be called after chooseFromList returns true (otherwise returns null).
77 | public T getSelectedChoice() {
78 | return (hasInt) ? selection.get(intValue - 1) : null;
79 | }
80 |
81 | // Print a prompt that tells the user their options:
82 | // 1) the index of a list item that is currently on view. Return the object.
83 | // 2) '+' page down
84 | // 3) '-' page back up
85 | // 4) '*' cancel (return null)
86 | private void issuePrompt() {
87 | System.out.format("Enter index (%s)%s%s or '*' to cancel: ",
88 | (top != bottom) ? String.format("%d-%d", top, bottom) : String.format("%d", top),
89 | (canMoveUp()) ? " or '-' to back up" : "",
90 | (canMoveDown()) ? " or '+' to advance" : "");
91 | }
92 |
93 | // Display a set of entries with index numbers.
94 | // Number of entries displayed is determined by pageSize.
95 | private void displayChoices() {
96 | for (int i = top; i <= bottom; i++) {
97 | System.out.format(" [%d]: %s\n", i, selection.get(i - 1).listView());
98 | }
99 | }
100 |
101 | // Process user's response to issuePrompt() prompt.
102 | private void getChoice() {
103 | final Scanner tokenScanner = new Scanner(lineScanner.nextLine());
104 | choice = tokenScanner.nextLine().trim();
105 | try {
106 | intValue = Integer.parseInt(choice);
107 | hasInt = true;
108 | } catch (NumberFormatException ex) {
109 | hasInt = false;
110 | }
111 | tokenScanner.close();
112 | }
113 |
114 | // Unseen entries precede the current view.
115 | private boolean canMoveUp() {
116 | return top > 1;
117 | }
118 |
119 | // Unseen entries follow the current view.
120 | private boolean canMoveDown() {
121 | return bottom < selection.size();
122 | }
123 |
124 | private boolean indexSelected() {
125 | return hasInt && (intValue >= top) && (intValue <= bottom);
126 | }
127 |
128 | private final boolean moveDownSelected() {
129 | return choice.equals("+") && canMoveDown();
130 | }
131 |
132 | private final boolean moveUpSelected() {
133 | return choice.equals("-") && canMoveUp();
134 | }
135 |
136 | private final boolean cancelSelected() {
137 | return choice.equals("*");
138 | }
139 |
140 | // Move the view up one page (or to top of page).
141 | private void moveUp() {
142 | top = Math.max(top - pageSize, 1);
143 | bottom = Math.min(top + pageSize - 1, selection.size());
144 | }
145 |
146 | // Move the view down one page (or to bottom of page).
147 | private void moveDown() {
148 | if (selection.size() == 0) return;
149 | bottom = Math.min(bottom + pageSize, selection.size());
150 | top = bottom - pageSize + 1;
151 | }
152 | }
153 |
--------------------------------------------------------------------------------
/src/codeu/chat/client/simplegui/ChatSimpleGui.java:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package codeu.chat.client.simplegui;
16 |
17 | import java.awt.*;
18 | import javax.swing.*;
19 | import javax.swing.border.Border;
20 |
21 | import codeu.chat.client.ClientContext;
22 | import codeu.chat.client.Controller;
23 | import codeu.chat.client.View;
24 | import codeu.chat.util.Logger;
25 |
26 | // Chat - top-level client application - Java Simple GUI (using Java Swing)
27 | public final class ChatSimpleGui {
28 |
29 | private final static Logger.Log LOG = Logger.newLog(ChatSimpleGui.class);
30 |
31 | private JFrame mainFrame;
32 |
33 | private final ClientContext clientContext;
34 |
35 | // Constructor - sets up the Chat Application
36 | public ChatSimpleGui(Controller controller, View view) {
37 | clientContext = new ClientContext(controller, view);
38 | }
39 |
40 | // Run the GUI client
41 | public void run() {
42 |
43 | try {
44 |
45 | initialize();
46 | mainFrame.setVisible(true);
47 |
48 | } catch (Exception ex) {
49 | System.out.println("ERROR: Exception in ChatSimpleGui.run. Check log for details.");
50 | LOG.error(ex, "Exception in ChatSimpleGui.run");
51 | System.exit(1);
52 | }
53 | }
54 |
55 | private Border paneBorder() {
56 | Border outside = BorderFactory.createLineBorder(Color.LIGHT_GRAY);
57 | Border inside = BorderFactory.createEmptyBorder(8, 8, 8, 8);
58 | return BorderFactory.createCompoundBorder(outside, inside);
59 | }
60 |
61 | // Initialize the GUI
62 | private void initialize() {
63 |
64 | // Outermost frame.
65 | // NOTE: may have tweak size, or place in scrollable panel.
66 | mainFrame = new JFrame("Chat");
67 | mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
68 | mainFrame.setSize(790, 450);
69 |
70 | // Main View - outermost graphics panel.
71 | final JPanel mainViewPanel = new JPanel(new GridBagLayout());
72 | mainViewPanel.setBorder(paneBorder());
73 |
74 | // Build main panels - Users, Conversations, Messages.
75 | final JPanel usersViewPanel = new UserPanel(clientContext);
76 | usersViewPanel.setBorder(paneBorder());
77 | final GridBagConstraints usersViewC = new GridBagConstraints();
78 |
79 | final MessagePanel messagesViewPanel = new MessagePanel(clientContext);
80 | messagesViewPanel.setBorder(paneBorder());
81 | final GridBagConstraints messagesViewC = new GridBagConstraints();
82 |
83 | // ConversationsPanel gets access to MessagesPanel
84 | final JPanel conversationsViewPanel = new ConversationPanel(clientContext, messagesViewPanel);
85 | conversationsViewPanel.setBorder(paneBorder());
86 | final GridBagConstraints conversationViewC = new GridBagConstraints();
87 |
88 | // Placement of main panels.
89 | usersViewC.gridx = 0;
90 | usersViewC.gridy = 0;
91 | usersViewC.gridwidth = 1;
92 | usersViewC.gridheight = 1;
93 | usersViewC.fill = GridBagConstraints.BOTH;
94 | usersViewC.weightx = 0.3;
95 | usersViewC.weighty = 0.3;
96 |
97 | conversationViewC.gridx = 1;
98 | conversationViewC.gridy = 0;
99 | conversationViewC.gridwidth = 1;
100 | conversationViewC.gridheight = 1;
101 | conversationViewC.fill = GridBagConstraints.BOTH;
102 | conversationViewC.weightx = 0.7;
103 | conversationViewC.weighty = 0.3;
104 |
105 | messagesViewC.gridx = 0;
106 | messagesViewC.gridy = 1;
107 | messagesViewC.gridwidth = 2;
108 | messagesViewC.gridheight = 1;
109 | messagesViewC.fill = GridBagConstraints.BOTH;
110 | messagesViewC.weighty = 0.7;
111 |
112 | mainViewPanel.add(usersViewPanel, usersViewC);
113 | mainViewPanel.add(conversationsViewPanel, conversationViewC);
114 | mainViewPanel.add(messagesViewPanel, messagesViewC);
115 |
116 | mainFrame.add(mainViewPanel);
117 | mainFrame.pack();
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/src/codeu/chat/client/simplegui/ConversationPanel.java:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package codeu.chat.client.simplegui;
16 |
17 | import java.awt.*;
18 | import java.awt.event.ActionEvent;
19 | import java.awt.event.ActionListener;
20 | import javax.swing.*;
21 | import javax.swing.event.ListSelectionEvent;
22 | import javax.swing.event.ListSelectionListener;
23 |
24 | import codeu.chat.client.ClientContext;
25 | import codeu.chat.common.ConversationSummary;
26 |
27 | // NOTE: JPanel is serializable, but there is no need to serialize ConversationPanel
28 | // without the @SuppressWarnings, the compiler will complain of no override for serialVersionUID
29 | @SuppressWarnings("serial")
30 | public final class ConversationPanel extends JPanel {
31 |
32 | private final ClientContext clientContext;
33 | private final MessagePanel messagePanel;
34 |
35 | public ConversationPanel(ClientContext clientContext, MessagePanel messagePanel) {
36 | super(new GridBagLayout());
37 | this.clientContext = clientContext;
38 | this.messagePanel = messagePanel;
39 | initialize();
40 | }
41 |
42 | private void initialize() {
43 |
44 | // This panel contains from top to bottom: a title bar,
45 | // a list of conversations, and a button bar.
46 |
47 | // Title
48 | final JPanel titlePanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
49 | final GridBagConstraints titlePanelC = new GridBagConstraints();
50 | titlePanelC.gridx = 0;
51 | titlePanelC.gridy = 0;
52 | titlePanelC.anchor = GridBagConstraints.PAGE_START;
53 |
54 | final JLabel titleLabel = new JLabel("Conversations", JLabel.LEFT);
55 | titleLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
56 | titlePanel.add(titleLabel);
57 |
58 | // Conversation list
59 | final JPanel listShowPanel = new JPanel();
60 | final GridBagConstraints listPanelC = new GridBagConstraints();
61 |
62 | final DefaultListModel listModel = new DefaultListModel<>();
63 | final JList objectList = new JList<>(listModel);
64 | objectList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
65 | objectList.setVisibleRowCount(15);
66 | objectList.setSelectedIndex(-1);
67 |
68 | final JScrollPane listScrollPane = new JScrollPane(objectList);
69 | listShowPanel.add(listScrollPane);
70 | listScrollPane.setMinimumSize(new Dimension(250, 200));
71 |
72 | // Button bar
73 | final JPanel buttonPanel = new JPanel();
74 | final GridBagConstraints buttonPanelC = new GridBagConstraints();
75 |
76 | final JButton updateButton = new JButton("Update");
77 | final JButton addButton = new JButton("Add");
78 |
79 | updateButton.setAlignmentX(Component.LEFT_ALIGNMENT);
80 | buttonPanel.add(updateButton);
81 | buttonPanel.add(addButton);
82 |
83 | // Put panels together
84 | titlePanelC.gridx = 0;
85 | titlePanelC.gridy = 0;
86 | titlePanelC.gridwidth = 10;
87 | titlePanelC.gridheight = 4;
88 | titlePanelC.fill = GridBagConstraints.HORIZONTAL;
89 | titlePanelC.anchor = GridBagConstraints.FIRST_LINE_START;
90 |
91 | listPanelC.gridx = 0;
92 | listPanelC.gridy = 4;
93 | listPanelC.gridwidth = 10;
94 | listPanelC.gridheight = 4;
95 | listPanelC.fill = GridBagConstraints.BOTH;
96 | listPanelC.anchor = GridBagConstraints.FIRST_LINE_START;
97 | listPanelC.weightx = 0.8;
98 | listPanelC.weighty = 0.5;
99 |
100 | buttonPanelC.gridx = 0;
101 | buttonPanelC.gridy = 8;
102 | buttonPanelC.gridwidth = 10;
103 | buttonPanelC.gridheight = 4;
104 | buttonPanelC.fill = GridBagConstraints.HORIZONTAL;
105 | buttonPanelC.anchor = GridBagConstraints.FIRST_LINE_START;
106 |
107 | this.add(titlePanel, titlePanelC);
108 | this.add(listShowPanel, listPanelC);
109 | this.add(buttonPanel, buttonPanelC);
110 |
111 | // User clicks Conversations Update button.
112 | updateButton.addActionListener(new ActionListener() {
113 | @Override
114 | public void actionPerformed(ActionEvent e) {
115 | ConversationPanel.this.getAllConversations(listModel);
116 | }
117 | });
118 |
119 | // User clicks Conversations Add button.
120 | addButton.addActionListener(new ActionListener() {
121 | @Override
122 | public void actionPerformed(ActionEvent e) {
123 | if (clientContext.user.hasCurrent()) {
124 | final String s = (String) JOptionPane.showInputDialog(
125 | ConversationPanel.this, "Enter title:", "Add Conversation", JOptionPane.PLAIN_MESSAGE,
126 | null, null, "");
127 | if (s != null && s.length() > 0) {
128 | clientContext.conversation.startConversation(s, clientContext.user.getCurrent().id);
129 | ConversationPanel.this.getAllConversations(listModel);
130 | }
131 | } else {
132 | JOptionPane.showMessageDialog(ConversationPanel.this, "You are not signed in.");
133 | }
134 | }
135 | });
136 |
137 | // User clicks on Conversation - Set Conversation to current and fill in Messages panel.
138 | objectList.addListSelectionListener(new ListSelectionListener() {
139 | @Override
140 | public void valueChanged(ListSelectionEvent e) {
141 | if (objectList.getSelectedIndex() != -1) {
142 | final int index = objectList.getSelectedIndex();
143 | final String data = objectList.getSelectedValue();
144 | final ConversationSummary cs = ConversationPanel.this.lookupByTitle(data, index);
145 |
146 | clientContext.conversation.setCurrent(cs);
147 |
148 | messagePanel.update(cs);
149 | }
150 | }
151 | });
152 |
153 | getAllConversations(listModel);
154 | }
155 |
156 | // Populate ListModel - updates display objects.
157 | private void getAllConversations(DefaultListModel convDisplayList) {
158 |
159 | clientContext.conversation.updateAllConversations(false);
160 | convDisplayList.clear();
161 |
162 | for (final ConversationSummary conv : clientContext.conversation.getConversationSummaries()) {
163 | convDisplayList.addElement(conv.title);
164 | }
165 | }
166 |
167 | // Locate the Conversation object for a selected title string.
168 | // index handles possible duplicate titles.
169 | private ConversationSummary lookupByTitle(String title, int index) {
170 |
171 | int localIndex = 0;
172 | for (final ConversationSummary cs : clientContext.conversation.getConversationSummaries()) {
173 | if ((localIndex >= index) && cs.title.equals(title)) {
174 | return cs;
175 | }
176 | localIndex++;
177 | }
178 | return null;
179 | }
180 | }
181 |
--------------------------------------------------------------------------------
/src/codeu/chat/client/simplegui/MessagePanel.java:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package codeu.chat.client.simplegui;
16 |
17 | import java.awt.*;
18 | import java.awt.event.ActionEvent;
19 | import java.awt.event.ActionListener;
20 | import javax.swing.*;
21 |
22 | import codeu.chat.client.ClientContext;
23 | import codeu.chat.common.ConversationSummary;
24 | import codeu.chat.common.Message;
25 | import codeu.chat.common.User;
26 |
27 | // NOTE: JPanel is serializable, but there is no need to serialize MessagePanel
28 | // without the @SuppressWarnings, the compiler will complain of no override for serialVersionUID
29 | @SuppressWarnings("serial")
30 | public final class MessagePanel extends JPanel {
31 |
32 | // These objects are modified by the Conversation Panel.
33 | private final JLabel messageOwnerLabel = new JLabel("Owner:", JLabel.RIGHT);
34 | private final JLabel messageConversationLabel = new JLabel("Conversation:", JLabel.LEFT);
35 | private final DefaultListModel messageListModel = new DefaultListModel<>();
36 |
37 | private final ClientContext clientContext;
38 |
39 | public MessagePanel(ClientContext clientContext) {
40 | super(new GridBagLayout());
41 | this.clientContext = clientContext;
42 | initialize();
43 | }
44 |
45 | // External agent calls this to trigger an update of this panel's contents.
46 | public void update(ConversationSummary owningConversation) {
47 |
48 | final User u = (owningConversation == null) ?
49 | null :
50 | clientContext.user.lookup(owningConversation.owner);
51 |
52 | messageOwnerLabel.setText("Owner: " +
53 | ((u==null) ?
54 | ((owningConversation==null) ? "" : owningConversation.owner) :
55 | u.name));
56 |
57 | messageConversationLabel.setText("Conversation: " + owningConversation.title);
58 |
59 | getAllMessages(owningConversation);
60 | }
61 |
62 | private void initialize() {
63 |
64 | // This panel contains the messages in the current conversation.
65 | // It has a title bar with the current conversation and owner,
66 | // then a list panel with the messages, then a button bar.
67 |
68 | // Title bar - current conversation and owner
69 | final JPanel titlePanel = new JPanel(new GridBagLayout());
70 | final GridBagConstraints titlePanelC = new GridBagConstraints();
71 |
72 | final JPanel titleConvPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
73 | final GridBagConstraints titleConvPanelC = new GridBagConstraints();
74 | titleConvPanelC.gridx = 0;
75 | titleConvPanelC.gridy = 0;
76 | titleConvPanelC.anchor = GridBagConstraints.PAGE_START;
77 |
78 | final JPanel titleOwnerPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
79 | final GridBagConstraints titleOwnerPanelC = new GridBagConstraints();
80 | titleOwnerPanelC.gridx = 0;
81 | titleOwnerPanelC.gridy = 1;
82 | titleOwnerPanelC.anchor = GridBagConstraints.PAGE_START;
83 |
84 | // messageConversationLabel is an instance variable of Conversation panel
85 | // can update it.
86 | messageConversationLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
87 | titleConvPanel.add(messageConversationLabel);
88 |
89 | // messageOwnerLabel is an instance variable of Conversation panel
90 | // can update it.
91 | messageOwnerLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
92 | titleOwnerPanel.add(messageOwnerLabel);
93 |
94 | titlePanel.add(titleConvPanel, titleConvPanelC);
95 | titlePanel.add(titleOwnerPanel, titleOwnerPanelC);
96 | titlePanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
97 |
98 | // User List panel.
99 | final JPanel listShowPanel = new JPanel();
100 | final GridBagConstraints listPanelC = new GridBagConstraints();
101 |
102 | // messageListModel is an instance variable so Conversation panel
103 | // can update it.
104 | final JList userList = new JList<>(messageListModel);
105 | userList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
106 | userList.setVisibleRowCount(15);
107 | userList.setSelectedIndex(-1);
108 |
109 | final JScrollPane userListScrollPane = new JScrollPane(userList);
110 | listShowPanel.add(userListScrollPane);
111 | userListScrollPane.setMinimumSize(new Dimension(500, 200));
112 | userListScrollPane.setPreferredSize(new Dimension(500, 200));
113 |
114 | // Button panel
115 | final JPanel buttonPanel = new JPanel();
116 | final GridBagConstraints buttonPanelC = new GridBagConstraints();
117 |
118 | final JButton addButton = new JButton("Add");
119 | buttonPanel.add(addButton);
120 |
121 | // Placement of title, list panel, buttons, and current user panel.
122 | titlePanelC.gridx = 0;
123 | titlePanelC.gridy = 0;
124 | titlePanelC.gridwidth = 10;
125 | titlePanelC.gridheight = 1;
126 | titlePanelC.fill = GridBagConstraints.HORIZONTAL;
127 | titlePanelC.anchor = GridBagConstraints.FIRST_LINE_START;
128 |
129 | listPanelC.gridx = 0;
130 | listPanelC.gridy = 1;
131 | listPanelC.gridwidth = 10;
132 | listPanelC.gridheight = 8;
133 | listPanelC.fill = GridBagConstraints.BOTH;
134 | listPanelC.anchor = GridBagConstraints.FIRST_LINE_START;
135 | listPanelC.weighty = 0.8;
136 |
137 | buttonPanelC.gridx = 0;
138 | buttonPanelC.gridy = 11;
139 | buttonPanelC.gridwidth = 10;
140 | buttonPanelC.gridheight = 1;
141 | buttonPanelC.fill = GridBagConstraints.HORIZONTAL;
142 | buttonPanelC.anchor = GridBagConstraints.FIRST_LINE_START;
143 |
144 | this.add(titlePanel, titlePanelC);
145 | this.add(listShowPanel, listPanelC);
146 | this.add(buttonPanel, buttonPanelC);
147 |
148 | // User click Messages Add button - prompt for message body and add new Message to Conversation
149 | addButton.addActionListener(new ActionListener() {
150 | @Override
151 | public void actionPerformed(ActionEvent e) {
152 | if (!clientContext.user.hasCurrent()) {
153 | JOptionPane.showMessageDialog(MessagePanel.this, "You are not signed in.");
154 | } else if (!clientContext.conversation.hasCurrent()) {
155 | JOptionPane.showMessageDialog(MessagePanel.this, "You must select a conversation.");
156 | } else {
157 | final String messageText = (String) JOptionPane.showInputDialog(
158 | MessagePanel.this, "Enter message:", "Add Message", JOptionPane.PLAIN_MESSAGE,
159 | null, null, "");
160 | if (messageText != null && messageText.length() > 0) {
161 | clientContext.message.addMessage(
162 | clientContext.user.getCurrent().id,
163 | clientContext.conversation.getCurrentId(),
164 | messageText);
165 | MessagePanel.this.getAllMessages(clientContext.conversation.getCurrent());
166 | }
167 | }
168 | }
169 | });
170 |
171 | // Panel is set up. If there is a current conversation, Populate the conversation list.
172 | getAllMessages(clientContext.conversation.getCurrent());
173 | }
174 |
175 | // Populate ListModel
176 | // TODO: don't refetch messages if current conversation not changed
177 | private void getAllMessages(ConversationSummary conversation) {
178 | messageListModel.clear();
179 |
180 | for (final Message m : clientContext.message.getConversationContents(conversation)) {
181 | // Display author name if available. Otherwise display the author UUID.
182 | final String authorName = clientContext.user.getName(m.author);
183 |
184 | final String displayString = String.format("%s: [%s]: %s",
185 | ((authorName == null) ? m.author : authorName), m.creation, m.content);
186 |
187 | messageListModel.addElement(displayString);
188 | }
189 | }
190 | }
191 |
--------------------------------------------------------------------------------
/src/codeu/chat/client/simplegui/UserPanel.java:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package codeu.chat.client.simplegui;
16 |
17 | import java.awt.*;
18 | import java.awt.event.ActionEvent;
19 | import java.awt.event.ActionListener;
20 | import javax.swing.*;
21 | import javax.swing.event.ListSelectionEvent;
22 | import javax.swing.event.ListSelectionListener;
23 |
24 | import codeu.chat.client.ClientContext;
25 | import codeu.chat.common.User;
26 |
27 | // NOTE: JPanel is serializable, but there is no need to serialize UserPanel
28 | // without the @SuppressWarnings, the compiler will complain of no override for serialVersionUID
29 | @SuppressWarnings("serial")
30 | public final class UserPanel extends JPanel {
31 |
32 | private final ClientContext clientContext;
33 |
34 | public UserPanel(ClientContext clientContext) {
35 | super(new GridBagLayout());
36 | this.clientContext = clientContext;
37 | initialize();
38 | }
39 |
40 | private void initialize() {
41 |
42 | // This panel contains from top to bottom; a title bar, a list of users,
43 | // information about the current (selected) user, and a button bar.
44 |
45 | // Title bar - also includes name of currently signed-in user.
46 | final JPanel titlePanel = new JPanel(new GridBagLayout());
47 | final GridBagConstraints titlePanelC = new GridBagConstraints();
48 |
49 | final JLabel titleLabel = new JLabel("Users", JLabel.LEFT);
50 | final GridBagConstraints titleLabelC = new GridBagConstraints();
51 | titleLabelC.gridx = 0;
52 | titleLabelC.gridy = 0;
53 | titleLabelC.anchor = GridBagConstraints.PAGE_START;
54 |
55 | final GridBagConstraints titleGapC = new GridBagConstraints();
56 | titleGapC.gridx = 1;
57 | titleGapC.gridy = 0;
58 | titleGapC.fill = GridBagConstraints.HORIZONTAL;
59 | titleGapC.weightx = 0.9;
60 |
61 | final JLabel userSignedInLabel = new JLabel("not signed in", JLabel.RIGHT);
62 | final GridBagConstraints titleUserC = new GridBagConstraints();
63 | titleUserC.gridx = 2;
64 | titleUserC.gridy = 0;
65 | titleUserC.anchor = GridBagConstraints.LINE_END;
66 |
67 | titlePanel.add(titleLabel, titleLabelC);
68 | titlePanel.add(Box.createHorizontalGlue(), titleGapC);
69 | titlePanel.add(userSignedInLabel, titleUserC);
70 | titlePanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
71 |
72 | // User List panel.
73 | final JPanel listShowPanel = new JPanel();
74 | final GridBagConstraints listPanelC = new GridBagConstraints();
75 |
76 | final DefaultListModel listModel = new DefaultListModel<>();
77 | final JList userList = new JList<>(listModel);
78 | userList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
79 | userList.setVisibleRowCount(10);
80 | userList.setSelectedIndex(-1);
81 |
82 | final JScrollPane userListScrollPane = new JScrollPane(userList);
83 | listShowPanel.add(userListScrollPane);
84 | userListScrollPane.setPreferredSize(new Dimension(150, 150));
85 |
86 | // Current User panel
87 | final JPanel currentPanel = new JPanel();
88 | final GridBagConstraints currentPanelC = new GridBagConstraints();
89 |
90 | final JTextArea userInfoPanel = new JTextArea();
91 | final JScrollPane userInfoScrollPane = new JScrollPane(userInfoPanel);
92 | currentPanel.add(userInfoScrollPane);
93 | userInfoScrollPane.setPreferredSize(new Dimension(245, 85));
94 |
95 | // Button bar
96 | final JPanel buttonPanel = new JPanel();
97 | final GridBagConstraints buttonPanelC = new GridBagConstraints();
98 |
99 | final JButton userUpdateButton = new JButton("Update");
100 | final JButton userSignInButton = new JButton("Sign In");
101 | final JButton userAddButton = new JButton("Add");
102 |
103 | buttonPanel.add(userUpdateButton);
104 | buttonPanel.add(userSignInButton);
105 | buttonPanel.add(userAddButton);
106 |
107 | // Placement of title, list panel, buttons, and current user panel.
108 | titlePanelC.gridx = 0;
109 | titlePanelC.gridy = 0;
110 | titlePanelC.gridwidth = 10;
111 | titlePanelC.gridheight = 1;
112 | titlePanelC.fill = GridBagConstraints.HORIZONTAL;
113 | titlePanelC.anchor = GridBagConstraints.FIRST_LINE_START;
114 |
115 | listPanelC.gridx = 0;
116 | listPanelC.gridy = 1;
117 | listPanelC.gridwidth = 10;
118 | listPanelC.gridheight = 8;
119 | listPanelC.fill = GridBagConstraints.BOTH;
120 | listPanelC.anchor = GridBagConstraints.FIRST_LINE_START;
121 | listPanelC.weighty = 0.8;
122 |
123 | currentPanelC.gridx = 0;
124 | currentPanelC.gridy = 9;
125 | currentPanelC.gridwidth = 10;
126 | currentPanelC.gridheight = 3;
127 | currentPanelC.fill = GridBagConstraints.HORIZONTAL;
128 | currentPanelC.anchor = GridBagConstraints.FIRST_LINE_START;
129 |
130 | buttonPanelC.gridx = 0;
131 | buttonPanelC.gridy = 12;
132 | buttonPanelC.gridwidth = 10;
133 | buttonPanelC.gridheight = 1;
134 | buttonPanelC.fill = GridBagConstraints.HORIZONTAL;
135 | buttonPanelC.anchor = GridBagConstraints.FIRST_LINE_START;
136 |
137 | this.add(titlePanel, titlePanelC);
138 | this.add(listShowPanel, listPanelC);
139 | this.add(buttonPanel, buttonPanelC);
140 | this.add(currentPanel, currentPanelC);
141 |
142 | userUpdateButton.addActionListener(new ActionListener() {
143 | @Override
144 | public void actionPerformed(ActionEvent e) {
145 | UserPanel.this.getAllUsers(listModel);
146 | }
147 | });
148 |
149 | userSignInButton.addActionListener(new ActionListener() {
150 | @Override
151 | public void actionPerformed(ActionEvent e) {
152 | if (userList.getSelectedIndex() != -1) {
153 | final String data = userList.getSelectedValue();
154 | clientContext.user.signInUser(data);
155 | userSignedInLabel.setText("Hello " + data);
156 | }
157 | }
158 | });
159 |
160 | userAddButton.addActionListener(new ActionListener() {
161 | @Override
162 | public void actionPerformed(ActionEvent e) {
163 | final String s = (String) JOptionPane.showInputDialog(
164 | UserPanel.this, "Enter user name:", "Add User", JOptionPane.PLAIN_MESSAGE,
165 | null, null, "");
166 | if (s != null && s.length() > 0) {
167 | clientContext.user.addUser(s);
168 | UserPanel.this.getAllUsers(listModel);
169 | }
170 | }
171 | });
172 |
173 | userList.addListSelectionListener(new ListSelectionListener() {
174 | @Override
175 | public void valueChanged(ListSelectionEvent e) {
176 | if (userList.getSelectedIndex() != -1) {
177 | final String data = userList.getSelectedValue();
178 | userInfoPanel.setText(clientContext.user.showUserInfo(data));
179 | }
180 | }
181 | });
182 |
183 | getAllUsers(listModel);
184 | }
185 |
186 | // Swing UI: populate ListModel object - updates display objects.
187 | private void getAllUsers(DefaultListModel usersList) {
188 | clientContext.user.updateUsers();
189 | usersList.clear();
190 |
191 | for (final User u : clientContext.user.getUsers()) {
192 | usersList.addElement(u.name);
193 | }
194 | }
195 | }
196 |
--------------------------------------------------------------------------------
/src/codeu/chat/common/BasicController.java:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package codeu.chat.common;
16 |
17 | import codeu.chat.util.Uuid;
18 |
19 | // BASIC CONTROLLER
20 | //
21 | // The controller component in the Model-View-Controller pattern. This
22 | // component is used to write information to the model where the model
23 | // is the current state of the server. Data returned from the controller
24 | // should be treated as read only data as manipulating any data returned
25 | // from the controller may have no effect on the server's state.
26 | public interface BasicController {
27 |
28 | // NEW MESSAGE
29 | //
30 | // Create a new message on the server. All parameters must be provided
31 | // or else the server won't apply the change. If the operation is
32 | // successful, a Message object will be returned representing the full
33 | // state of the message on the server.
34 | Message newMessage(Uuid author, Uuid conversation, String body);
35 |
36 | // NEW USER
37 | //
38 | // Create a new user on the server. All parameters must be provided
39 | // or else the server won't apply the change. If the operation is
40 | // successful, a User object will be returned representing the full
41 | // state of the user on the server. Whether user names can be shared
42 | // is undefined.
43 | User newUser(String name);
44 |
45 | // NEW CONVERSATION
46 | //
47 | // Create a new conversation on the server. All parameters must be
48 | // provided or else the server won't apply the change. If the
49 | // operation is successful, a Conversation object will be returned
50 | // representing the full state of the conversation on the server.
51 | // Whether conversations can have the same title is undefined.
52 | Conversation newConversation(String title, Uuid owner);
53 |
54 | }
55 |
--------------------------------------------------------------------------------
/src/codeu/chat/common/BasicView.java:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package codeu.chat.common;
16 |
17 | import java.util.Collection;
18 |
19 | import codeu.chat.common.Conversation;
20 | import codeu.chat.common.ConversationSummary;
21 | import codeu.chat.common.Message;
22 | import codeu.chat.common.User;
23 | import codeu.chat.util.Uuid;
24 |
25 | // BASIC VIEW
26 | //
27 | // The view component in the Model-View-Controller pattern. This component
28 | // is used to read information from the model where the model is the current
29 | // state of the server. Data returned from the view should be treated as
30 | // read only data as manipulating any data returned from the view may
31 | // have no effect on the server's state.
32 |
33 | public interface BasicView {
34 |
35 | // GET USERS
36 | //
37 | // Return all users whose id is found in the given collection.
38 | Collection getUsers(Collection ids);
39 |
40 | // GET ALL CONVERSATIONS
41 | //
42 | // Return a summary of each converation.
43 | Collection getAllConversations();
44 |
45 | // GET CONVERSATIONS
46 | //
47 | // Return all conversations whose id is found in the given collection.
48 | Collection getConversations(Collection ids);
49 |
50 | // GET MESSAGES
51 | //
52 | // Return all messages whose id is found in the given collection.
53 | Collection getMessages(Collection ids);
54 |
55 | }
56 |
--------------------------------------------------------------------------------
/src/codeu/chat/common/Conversation.java:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package codeu.chat.common;
16 |
17 | import java.io.IOException;
18 | import java.io.InputStream;
19 | import java.io.OutputStream;
20 | import java.util.Collection;
21 | import java.util.HashSet;
22 |
23 | import codeu.chat.util.Serializer;
24 | import codeu.chat.util.Serializers;
25 | import codeu.chat.util.Time;
26 | import codeu.chat.util.Uuid;
27 |
28 | public final class Conversation {
29 |
30 | public static final Serializer SERIALIZER = new Serializer() {
31 |
32 | @Override
33 | public void write(OutputStream out, Conversation value) throws IOException {
34 |
35 | Uuid.SERIALIZER.write(out, value.id);
36 | Uuid.SERIALIZER.write(out, value.owner);
37 | Time.SERIALIZER.write(out, value.creation);
38 | Serializers.STRING.write(out, value.title);
39 | Serializers.collection(Uuid.SERIALIZER).write(out, value.users);
40 | Uuid.SERIALIZER.write(out, value.firstMessage);
41 | Uuid.SERIALIZER.write(out, value.lastMessage);
42 |
43 | }
44 |
45 | @Override
46 | public Conversation read(InputStream in) throws IOException {
47 |
48 | final Conversation value = new Conversation(
49 | Uuid.SERIALIZER.read(in),
50 | Uuid.SERIALIZER.read(in),
51 | Time.SERIALIZER.read(in),
52 | Serializers.STRING.read(in)
53 | );
54 |
55 | value.users.addAll(Serializers.collection(Uuid.SERIALIZER).read(in));
56 |
57 | value.firstMessage = Uuid.SERIALIZER.read(in);
58 | value.lastMessage = Uuid.SERIALIZER.read(in);
59 |
60 | return value;
61 |
62 | }
63 | };
64 |
65 | public final ConversationSummary summary;
66 |
67 | public final Uuid id;
68 | public final Uuid owner;
69 | public final Time creation;
70 | public final String title;
71 | public final Collection users = new HashSet<>();
72 | public Uuid firstMessage = Uuid.NULL;
73 | public Uuid lastMessage = Uuid.NULL;
74 |
75 | public Conversation(Uuid id, Uuid owner, Time creation, String title) {
76 |
77 | this.id = id;
78 | this.owner = owner;
79 | this.creation = creation;
80 | this.title = title;
81 |
82 | this.summary = new ConversationSummary(id, owner, creation, title);
83 |
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/src/codeu/chat/common/ConversationSummary.java:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package codeu.chat.common;
16 |
17 | import java.io.IOException;
18 | import java.io.InputStream;
19 | import java.io.OutputStream;
20 |
21 | import codeu.chat.util.Serializer;
22 | import codeu.chat.util.Serializers;
23 | import codeu.chat.util.Time;
24 | import codeu.chat.util.Uuid;
25 |
26 | public final class ConversationSummary implements ListViewable {
27 |
28 | public static final Serializer SERIALIZER = new Serializer() {
29 |
30 | @Override
31 | public void write(OutputStream out, ConversationSummary value) throws IOException {
32 |
33 | Uuid.SERIALIZER.write(out, value.id);
34 | Uuid.SERIALIZER.write(out, value.owner);
35 | Time.SERIALIZER.write(out, value.creation);
36 | Serializers.STRING.write(out, value.title);
37 |
38 | }
39 |
40 | @Override
41 | public ConversationSummary read(InputStream in) throws IOException {
42 |
43 | return new ConversationSummary(
44 | Uuid.SERIALIZER.read(in),
45 | Uuid.SERIALIZER.read(in),
46 | Time.SERIALIZER.read(in),
47 | Serializers.STRING.read(in)
48 | );
49 |
50 | }
51 | };
52 |
53 | public final Uuid id;
54 | public final Uuid owner;
55 | public final Time creation;
56 | public final String title;
57 |
58 | public ConversationSummary(Uuid id, Uuid owner, Time creation, String title) {
59 |
60 | this.id = id;
61 | this.owner = owner;
62 | this.creation = creation;
63 | this.title = title;
64 |
65 | }
66 |
67 | // How this object should appear in a user-viewable list
68 | @Override
69 | public String listView() {
70 | return title;
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/codeu/chat/common/LinearUuidGenerator.java:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package codeu.chat.common;
16 |
17 | import codeu.chat.util.Uuid;
18 |
19 | public final class LinearUuidGenerator implements Uuid.Generator {
20 |
21 | private final Uuid commonRoot;
22 | private final int start;
23 | private final int end;
24 |
25 | private int current;
26 |
27 | public LinearUuidGenerator(Uuid root, int start, int end) {
28 | this.commonRoot = root;
29 | this.start = start;
30 | this.end = end;
31 | this.current = start;
32 | }
33 |
34 | @Override
35 | public Uuid make() {
36 | return new Uuid(commonRoot, next());
37 | }
38 |
39 | private int next() {
40 | if (current == end) {
41 | throw new IllegalStateException("Uuid overflow");
42 | } else {
43 | current++;
44 | return current;
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/codeu/chat/common/ListViewable.java:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package codeu.chat.common;
16 |
17 | public interface ListViewable {
18 |
19 | // Produce a string for presenting this object within a list.
20 | String listView();
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/src/codeu/chat/common/LogicalView.java:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package codeu.chat.common;
16 |
17 | import java.util.Collection;
18 |
19 | import codeu.chat.util.Time;
20 | import codeu.chat.util.Uuid;
21 |
22 | // LOGICAL VIEW
23 | //
24 | // The logical view is another view for the Model-View-Control pattern. This view
25 | // focuses on providing more logical methods of accessing data. Each function is
26 | // based on a query rather than fetching specific objects.
27 | public interface LogicalView {
28 |
29 | // GET USER GENERATION
30 | //
31 | // Get an identifier that specifies the generation of all users. Storing and
32 | // tracking this number will allow checking if it is worth fetching all users.
33 | Uuid getUserGeneration();
34 |
35 | // GET USERS EXCLUDING
36 | //
37 | // Get all users whose ID are not found in the given set of ids.
38 | Collection getUsersExcluding(Collection ids);
39 |
40 | // GET CONVERSATIONS
41 | //
42 | // Get a collection of conversations given the start and end of a time series.
43 | // all conversations that are found to have been created between the start
44 | // and end time will be returned.
45 | Collection getConversations(Time start, Time end);
46 |
47 | // GET CONVERSATIONS
48 | //
49 | // Get a collection of conversations given a regex expression that will be
50 | // used against every conversation's title. All conversations whose title
51 | // matches the given regex expression will be returned.
52 | Collection getConversations(String filter);
53 |
54 | // GET MESSAGES
55 | //
56 | // Get all messages from a single conversation whose time value falls
57 | // between the start and end times. If the conversation is not found,
58 | // or the start time is invalid, or the end time is invalid, no
59 | // messages will be returned.
60 | Collection getMessages(Uuid conversation, Time start, Time end);
61 |
62 | // GET MESSAGES
63 | //
64 | // Get all messages within the specified range starting from the given
65 | // message id. If the range is greater than zero, all messages after the
66 | // given messages up to and including |range| will be returned. If the
67 | // range is negative, all messages before the given message up to and
68 | // including |range| will be returned. If the root message is not found
69 | // no messages will be returned.
70 | Collection getMessages(Uuid rootMessage, int range);
71 |
72 | }
73 |
--------------------------------------------------------------------------------
/src/codeu/chat/common/Message.java:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package codeu.chat.common;
16 |
17 | import java.io.IOException;
18 | import java.io.InputStream;
19 | import java.io.OutputStream;
20 |
21 | import codeu.chat.util.Serializer;
22 | import codeu.chat.util.Serializers;
23 | import codeu.chat.util.Time;
24 | import codeu.chat.util.Uuid;
25 |
26 | public final class Message {
27 |
28 | public static final Serializer SERIALIZER = new Serializer() {
29 |
30 | @Override
31 | public void write(OutputStream out, Message value) throws IOException {
32 |
33 | Uuid.SERIALIZER.write(out, value.id);
34 | Uuid.SERIALIZER.write(out, value.next);
35 | Uuid.SERIALIZER.write(out, value.previous);
36 | Time.SERIALIZER.write(out, value.creation);
37 | Uuid.SERIALIZER.write(out, value.author);
38 | Serializers.STRING.write(out, value.content);
39 |
40 | }
41 |
42 | @Override
43 | public Message read(InputStream in) throws IOException {
44 |
45 | return new Message(
46 | Uuid.SERIALIZER.read(in),
47 | Uuid.SERIALIZER.read(in),
48 | Uuid.SERIALIZER.read(in),
49 | Time.SERIALIZER.read(in),
50 | Uuid.SERIALIZER.read(in),
51 | Serializers.STRING.read(in)
52 | );
53 |
54 | }
55 | };
56 |
57 | public final Uuid id;
58 | public final Uuid previous;
59 | public final Time creation;
60 | public final Uuid author;
61 | public final String content;
62 | public Uuid next;
63 |
64 | public Message(Uuid id, Uuid next, Uuid previous, Time creation, Uuid author, String content) {
65 |
66 | this.id = id;
67 | this.next = next;
68 | this.previous = previous;
69 | this.creation = creation;
70 | this.author = author;
71 | this.content = content;
72 |
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/codeu/chat/common/NetworkCode.java:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package codeu.chat.common;
16 |
17 | public final class NetworkCode {
18 |
19 | public static final int
20 | NO_MESSAGE = 0,
21 | GET_USERS_BY_ID_REQUEST = 1,
22 | GET_USERS_BY_ID_RESPONSE = 2,
23 | GET_ALL_CONVERSATIONS_REQUEST = 3,
24 | GET_ALL_CONVERSATIONS_RESPONSE = 4,
25 | GET_CONVERSATIONS_BY_ID_RESPONSE = 5,
26 | GET_CONVERSATIONS_BY_ID_REQUEST = 6,
27 | GET_MESSAGES_BY_ID_REQUEST = 7,
28 | GET_MESSAGES_BY_ID_RESPONSE = 8,
29 | NEW_MESSAGE_REQUEST = 9,
30 | NEW_MESSAGE_RESPONSE = 10,
31 | NEW_USER_REQUEST = 11,
32 | NEW_USER_RESPONSE = 12,
33 | NEW_CONVERSATION_REQUEST = 13,
34 | NEW_CONVERSATION_RESPONSE = 14,
35 | GET_CONVERSATIONS_BY_TIME_REQUEST = 15,
36 | GET_CONVERSATIONS_BY_TIME_RESPONSE = 16,
37 | GET_CONVERSATIONS_BY_TITLE_REQUEST = 17,
38 | GET_CONVERSATIONS_BY_TITLE_RESPONSE = 18,
39 | GET_MESSAGES_BY_TIME_REQUEST = 19,
40 | GET_MESSAGES_BY_TIME_RESPONSE = 20,
41 | GET_MESSAGES_BY_RANGE_REQUEST = 21,
42 | GET_MESSAGES_BY_RANGE_RESPONSE = 22,
43 | GET_USER_GENERATION_REQUEST = 23,
44 | GET_USER_GENERATION_RESPONSE = 24,
45 | GET_USERS_EXCLUDING_REQUEST = 25,
46 | GET_USERS_EXCLUDING_RESPONSE = 26,
47 | RELAY_READ_REQUEST = 27,
48 | RELAY_READ_RESPONSE = 28,
49 | RELAY_WRITE_REQUEST = 29,
50 | RELAY_WRITE_RESPONSE = 30;
51 | }
52 |
--------------------------------------------------------------------------------
/src/codeu/chat/common/RandomUuidGenerator.java:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package codeu.chat.common;
16 |
17 | import java.util.Random;
18 |
19 | import codeu.chat.util.Uuid;
20 |
21 | // Create a new random uuid. Uuids from this generator are random
22 | // but are not guaranteed to be unique. Checking uniqueness is left
23 | // to the caller.
24 | public final class RandomUuidGenerator implements Uuid.Generator {
25 |
26 | private final Uuid commonRoot;
27 | private final Random random;
28 |
29 | public RandomUuidGenerator(Uuid root, long seed) {
30 | this.commonRoot = root;
31 | this.random = new Random(seed);
32 | }
33 |
34 | @Override
35 | public Uuid make() {
36 | return new Uuid(commonRoot, random.nextInt());
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/codeu/chat/common/RawController.java:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package codeu.chat.common;
16 |
17 | import codeu.chat.util.Time;
18 | import codeu.chat.util.Uuid;
19 |
20 | // RAW CONTROLLER
21 | //
22 | // A controller that grants a large amount of control over how data is inserted
23 | // into the model. If there is a conflict in data, the call will be rejected and
24 | // a null value returned.
25 | public interface RawController {
26 |
27 | // NEW MESSAGE
28 | //
29 | // Add a new message to the model with a specific id. If the id is already
30 | // in use, the call will fail and null will be returned.
31 | Message newMessage(Uuid id, Uuid author, Uuid conversation, String body, Time creationTime);
32 |
33 | // NEW USER
34 | //
35 | // Add a new user to the model with a specific id. If the id is already in
36 | // use, the call will fail and null will be returned.
37 | User newUser(Uuid id, String name, Time creationTime);
38 |
39 | // NEW CONVERSATION
40 | //
41 | // Add a new conversation to the model with a specific if. If the id is
42 | // already in use, the call will fail and null will be returned.
43 | Conversation newConversation(Uuid id, String title, Uuid owner, Time creationTime);
44 |
45 | }
46 |
--------------------------------------------------------------------------------
/src/codeu/chat/common/Relay.java:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package codeu.chat.common;
16 |
17 | import java.util.Collection;
18 |
19 | import codeu.chat.util.Time;
20 | import codeu.chat.util.Uuid;
21 |
22 | // RELAY
23 | //
24 | // This is the interface for communicating with the relay. Communication with the relay
25 | // is limited to two simple interfaces (writes and reads). As the relay is not expected
26 | // to track conversations or users, each bundle sent to the relay and received from the
27 | // the relay must be atomic and contain all information about the user, conversation, and
28 | // message.
29 | public interface Relay {
30 |
31 | // BUNDLE
32 | //
33 | // The relay's representation of what a message looks like. As the relay is not
34 | // going to track users and conversations so all information about a message must
35 | // be in the bundle. This means that each bundle will have a copy of the author's
36 | // id and name, a copy of the conversation's id and title. All information about
37 | // a message must be present for a server to recreate the series of events with
38 | // as little reasoning as possible.
39 | interface Bundle {
40 |
41 | // COMPONENT
42 | //
43 | // As there is a lot of similar information in a bundle. Component groups together
44 | // common fields to make the bundle interface easier to read. As a bundle is made-up
45 | // of three parts (user, conversation, and message) and each parts have a uuid,
46 | // string, and time field it cluttered the interface. A commonent is just a wrapper
47 | // to make the Bundle interface easier to read.
48 | interface Component {
49 |
50 | // ID
51 | //
52 | // The id for the component. As just about every piece of data has an ID, this
53 | // id is the id for the piece of data that the component is pointing to.
54 | Uuid id();
55 |
56 | // TEXT
57 | //
58 | // The text for the component. As just about every piece of data has a string value,
59 | // this string is used to represent that string value. For a user it will be their
60 | // name, for a conversation it is the title, and for a message it is the content.
61 | String text();
62 |
63 | // TIME
64 | //
65 | // The time for the component. As just about every piece of data has a time value,
66 | // this time is used to represent that time value. For messages, users, and conversations
67 | // this is the creation time.
68 | Time time();
69 |
70 | }
71 |
72 | // ID
73 | //
74 | // The id for the bundle. This is the id of the bundle which can be used with read as the root.
75 | Uuid id();
76 |
77 | // ID
78 | //
79 | // The time for when the bundle was created on the relay. This is the time that the bundle was
80 | // created on the relay and not the time the message was created on the server.
81 | Time time();
82 |
83 | // TEAM
84 | //
85 | // This id of the team that owns this message. This is the id of the team that sent the bundle
86 | // to the relay.
87 | Uuid team();
88 |
89 | // USER
90 | //
91 | // All the information about the user who authored the message.
92 | Component user();
93 |
94 | // CONVERSATION
95 | //
96 | // All the infromation about the conversation that the message is part of.
97 | Component conversation();
98 |
99 | // MESSAGE
100 | //
101 | // All the information about the message that was sent from the server to
102 | // the relay.
103 | Component message();
104 |
105 | }
106 |
107 | // PACK
108 | //
109 | // Pack together a uuid, string, and time into a component. This is to make
110 | // the signature for "write" to be shorter and easier to read.
111 | Bundle.Component pack(Uuid id, String text, Time time);
112 |
113 | // WRITE
114 | //
115 | // Write a single message and all its extra data to the relay server. A message
116 | // must have information about the user and conversation that the message is
117 | // part of as the relay does not track users or conversations. In order to write
118 | // a message to the relay, a team must write their team id and team secret or
119 | // else the relay will reject the message.
120 | boolean write(Uuid teamId,
121 | byte[] teamSecret,
122 | Bundle.Component user,
123 | Bundle.Component conversation,
124 | Bundle.Component message);
125 |
126 | // READ
127 | //
128 | // Read a series of bundles from the relay. Given a Uuid as the starting point
129 | // the relay will return up to but may return less than the range. The range must
130 | // be positive. Negative ranges are not allowed and will return an empty
131 | // collection. If the root is Uuids.NULL then the relay will start sending from
132 | // its earliest point. If the root is not found the relay will treat it as if it
133 | // was given Uuids.NULL.
134 | Collection read(Uuid teamId, byte[] teamSecret, Uuid root, int range);
135 |
136 | }
137 |
--------------------------------------------------------------------------------
/src/codeu/chat/common/Secret.java:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package codeu.chat.common;
16 |
17 | public final class Secret {
18 |
19 | // PARSE
20 | //
21 | // Take in the string representation of the secret and convert it into a byte
22 | // array. The string form of a secret should be a hex string that will be
23 | // converted into a byte array.
24 | //
25 | // For example: "ABABAB" becomes { 0xAB, 0xAB, 0xAB }
26 | public static byte[] parse(String stringSecret) {
27 |
28 | final byte[] expanded = new byte[stringSecret.length() + stringSecret.length() % 2];
29 |
30 | final int offset = stringSecret.length() % 2;
31 |
32 | for (int i = 0; i < stringSecret.length(); i++) {
33 | expanded[offset + i] = (byte) toHex(stringSecret.charAt(i));
34 | }
35 |
36 | final byte[] compressed = new byte[expanded.length / 2];
37 |
38 | for (int i = 0; i < compressed.length; i++) {
39 | compressed[i] = (byte) ((expanded[2 * i] << 4) | expanded[2 * i + 1]);
40 | }
41 |
42 | return compressed;
43 | }
44 |
45 | private static final int toHex(char c) {
46 |
47 | // If an invalid value was given, it will be treated as 0.
48 |
49 | switch (c) {
50 | case '0':
51 | case '1':
52 | case '2':
53 | case '3':
54 | case '4':
55 | case '5':
56 | case '6':
57 | case '7':
58 | case '8':
59 | case '9':
60 | return c - '0';
61 |
62 | case 'a':
63 | case 'b':
64 | case 'c':
65 | case 'd':
66 | case 'e':
67 | case 'f':
68 | return c - 'a' + 10;
69 |
70 | case 'A':
71 | case 'B':
72 | case 'C':
73 | case 'D':
74 | case 'E':
75 | case 'F':
76 | return c - 'A' + 10;
77 |
78 | default:
79 | return 0;
80 | }
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/src/codeu/chat/common/SinglesView.java:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package codeu.chat.common;
16 |
17 | import codeu.chat.common.Conversation;
18 | import codeu.chat.common.Message;
19 | import codeu.chat.common.User;
20 | import codeu.chat.util.Uuid;
21 |
22 | // SINGLES VIEW
23 | //
24 | // A view as part of the Model-View-Controller pattern. This view is
25 | // responsible for allowing single value reading from the model.
26 | public interface SinglesView {
27 |
28 | // FIND USER
29 | //
30 | // Find the user whose id matches the given id. If no user's id matches
31 | // the given id, null with be returned.
32 | User findUser(Uuid id);
33 |
34 | // FIND CONVERSATION
35 | //
36 | // Find the conversation whose id matches the given id. If no conversation's
37 | // matches the given id, null will be returned.
38 | Conversation findConversation(Uuid id);
39 |
40 | // FIND MESSAGE
41 | //
42 | // Find the message whose id matches the given id. if no message's id
43 | // matches the given id, null will be returned.
44 | Message findMessage(Uuid id);
45 |
46 | }
47 |
--------------------------------------------------------------------------------
/src/codeu/chat/common/User.java:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package codeu.chat.common;
16 |
17 | import java.io.IOException;
18 | import java.io.InputStream;
19 | import java.io.OutputStream;
20 |
21 | import codeu.chat.util.Serializer;
22 | import codeu.chat.util.Serializers;
23 | import codeu.chat.util.Time;
24 | import codeu.chat.util.Uuid;
25 |
26 | public final class User {
27 |
28 | public static final Serializer SERIALIZER = new Serializer() {
29 |
30 | @Override
31 | public void write(OutputStream out, User value) throws IOException {
32 |
33 | Uuid.SERIALIZER.write(out, value.id);
34 | Serializers.STRING.write(out, value.name);
35 | Time.SERIALIZER.write(out, value.creation);
36 |
37 | }
38 |
39 | @Override
40 | public User read(InputStream in) throws IOException {
41 |
42 | return new User(
43 | Uuid.SERIALIZER.read(in),
44 | Serializers.STRING.read(in),
45 | Time.SERIALIZER.read(in)
46 | );
47 |
48 | }
49 | };
50 |
51 | public final Uuid id;
52 | public final String name;
53 | public final Time creation;
54 |
55 | public User(Uuid id, String name, Time creation) {
56 |
57 | this.id = id;
58 | this.name = name;
59 | this.creation = creation;
60 |
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/codeu/chat/relay/Server.java:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package codeu.chat.relay;
16 |
17 | import java.util.ArrayList;
18 | import java.util.Arrays;
19 | import java.util.Collection;
20 | import java.util.HashMap;
21 | import java.util.LinkedList;
22 | import java.util.Map;
23 | import java.util.Queue;
24 |
25 | import codeu.chat.common.LinearUuidGenerator;
26 | import codeu.chat.common.Relay;
27 | import codeu.chat.util.Logger;
28 | import codeu.chat.util.Time;
29 | import codeu.chat.util.Logger;
30 | import codeu.chat.util.Uuid;
31 |
32 | public final class Server implements Relay {
33 |
34 | private final static Logger.Log LOG = Logger.newLog(Server.class);
35 |
36 | private static final class Component implements Relay.Bundle.Component {
37 |
38 | private final Uuid id;
39 | private final String text;
40 | private final Time time;
41 |
42 | public Component(Uuid id, String text, Time time) {
43 | this.id = id;
44 | this.text = text;
45 | this.time = time;
46 | }
47 |
48 | @Override
49 | public Uuid id() { return id; }
50 |
51 | @Override
52 | public String text() { return text; }
53 |
54 | @Override
55 | public Time time() { return time; }
56 |
57 | }
58 |
59 | private static final class Bundle implements Relay.Bundle {
60 |
61 | private final Uuid id;
62 | private final Time time;
63 | private final Uuid team;
64 | private final Component user;
65 | private final Component conversation;
66 | private final Component message;
67 |
68 | public Bundle(Uuid id,
69 | Time time,
70 | Uuid team,
71 | Component user,
72 | Component conversation,
73 | Component message) {
74 |
75 | this.id = id;
76 | this.time = time;
77 | this.team = team;
78 | this.user = user;
79 | this.conversation = conversation;
80 | this.message = message;
81 |
82 | }
83 |
84 | @Override
85 | public Uuid id() { return id; }
86 |
87 | @Override
88 | public Time time() { return time; }
89 |
90 | @Override
91 | public Uuid team() { return team; }
92 |
93 | @Override
94 | public Component user() { return user; }
95 |
96 | @Override
97 | public Component conversation() { return conversation; }
98 |
99 | @Override
100 | public Component message() { return message; }
101 |
102 | }
103 |
104 | private final Queue history = new LinkedList<>();
105 | private final Map teamSecrets = new HashMap<>();
106 |
107 | private final int maxHistory;
108 | private final int maxRead;
109 |
110 | // Okay, some reasoning behind why I'm using a statically initialized linear
111 | // generator for the ids for the relay server.
112 | //
113 | // Point A : The ids only need to be uniqiue for a single run time of the
114 | // relay. Ids from the relay are only used as a position into its
115 | // history. If it repeats an id its not a problem.
116 | //
117 | // Point B : The chance that the history would be so long that an id could
118 | // be reused and appear along side's twin is way too small. The
119 | // range for the ids is 1 to MAX INT (32 bit signed). This means
120 | // that there would need to be 2147483646 messages in memory. If
121 | // each message was 160 bytes long the relay server would need
122 | // over 319 GB of ram.
123 | //
124 | // As a side note, the ids start at 1 and not 0 to avoid the first id from
125 | // matching the NULL id which is defined as (null, 0);
126 |
127 | private final Uuid.Generator idGenerator = new LinearUuidGenerator(null, 1, Integer.MAX_VALUE);
128 |
129 | // SERVER
130 | //
131 | // When initializing the server keep the following in mind.
132 | // - Keep "maxHistory" small enough to avoid using too much memory.
133 | // - Keep "maxRead" small enough to avoid any one client from connecting to
134 | // the server for too long.
135 | public Server(int maxHistory, int maxRead) {
136 | this.maxHistory = Math.max(0, maxHistory);
137 | this.maxRead = Math.max(0, maxRead);
138 | }
139 |
140 | // ADD TEAM
141 | //
142 | // Let the relay know of a team's secret so that it will accept messages from that
143 | // team. If there is already a team entry, the secret will NOT be updated and the
144 | // call will return false.
145 | public boolean addTeam(Uuid id, byte[] secret) {
146 |
147 | LOG.info("Adding team to relay %s", id);
148 |
149 | final boolean open = teamSecrets.get(id) == null;
150 |
151 | if (open) {
152 | teamSecrets.put(id, secret);
153 | }
154 |
155 | LOG.info(open ?
156 | "Adding team was successful" :
157 | "Adding team failed - team id already exists");
158 |
159 | return open;
160 | }
161 |
162 | @Override
163 | public Relay.Bundle.Component pack(Uuid id, String text, Time time) {
164 | return new Component(id, text, time);
165 | }
166 |
167 | @Override
168 | public boolean write(Uuid teamId,
169 | byte[] teamSecret,
170 | Relay.Bundle.Component user,
171 | Relay.Bundle.Component conversation,
172 | Relay.Bundle.Component message) {
173 |
174 | if (authenticate(teamId, teamSecret)) {
175 |
176 | LOG.info(
177 | "Writing to server team=%s user=%s conversation=%s message=%s",
178 | teamId,
179 | user.id(),
180 | conversation.id(),
181 | message.id());
182 |
183 | if (history.size() >= maxHistory) {
184 | history.remove();
185 | }
186 |
187 | return history.offer(new Bundle(
188 | idGenerator.make(),
189 | Time.now(),
190 | teamId,
191 | user,
192 | conversation,
193 | message));
194 | } else {
195 |
196 | LOG.warning(
197 | "Unauthorized write attempt to server team=%s user=%s conversation=%s message=%s",
198 | teamId,
199 | user.id(),
200 | conversation.id(),
201 | message.id());
202 |
203 | return false;
204 | }
205 | }
206 |
207 | @Override
208 | public Collection read(Uuid teamId, byte[] teamSecret, Uuid root, int range) {
209 |
210 | final Collection found = new ArrayList<>();
211 |
212 | if (authenticate(teamId, teamSecret)) {
213 |
214 | LOG.info(
215 | "Request to read from server requested=%d allowed=%d",
216 | range,
217 | maxRead);
218 |
219 | for (final Relay.Bundle message : history) {
220 |
221 | // Only add a message if there is room. We cannot stop
222 | // searching in case we see the root later on.
223 | if (found.size() < Math.min(range, maxRead)) {
224 | found.add(message);
225 | }
226 |
227 | // If the start is found, drop all previous messages.
228 | if (message.id().equals(root)) {
229 | found.clear();
230 | }
231 | }
232 |
233 | LOG.info(
234 | "Read request complete requested=%d fullfilled=%d",
235 | range,
236 | found.size());
237 |
238 | } else {
239 |
240 | LOG.info(
241 | "Unauthroized attempt to read from server team=%s",
242 | teamId);
243 | }
244 |
245 | return found;
246 | }
247 |
248 | private boolean authenticate(Uuid id, byte[] secret) {
249 | return id != null && Arrays.equals(secret, teamSecrets.get(id));
250 | }
251 | }
252 |
--------------------------------------------------------------------------------
/src/codeu/chat/relay/ServerFrontEnd.java:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package codeu.chat.relay;
16 |
17 | import java.io.IOException;
18 | import java.io.InputStream;
19 | import java.io.OutputStream;
20 | import java.util.Collection;
21 |
22 | import codeu.chat.common.NetworkCode;
23 | import codeu.chat.common.Relay;
24 | import codeu.chat.util.Logger;
25 | import codeu.chat.util.Serializer;
26 | import codeu.chat.util.Serializers;
27 | import codeu.chat.util.Time;
28 | import codeu.chat.util.Uuid;
29 | import codeu.chat.util.connections.Connection;
30 |
31 | public final class ServerFrontEnd {
32 |
33 | private final static Logger.Log LOG = Logger.newLog(ServerFrontEnd.class);
34 |
35 | private static final Serializer COMPONENT_SERIALIZER =
36 | new Serializer() {
37 |
38 | @Override
39 | public Relay.Bundle.Component read(InputStream in) throws IOException {
40 |
41 | final Uuid id = Uuid.SERIALIZER.read(in);
42 | final String text = Serializers.STRING.read(in);
43 | final Time time = Time.SERIALIZER.read(in);
44 |
45 | // I could have passed the relay and use its "pack" method but that would
46 | // have been more work than just building an object here.
47 | return new Relay.Bundle.Component() {
48 | @Override
49 | public Uuid id() { return id; }
50 | @Override
51 | public String text() { return text; }
52 | @Override
53 | public Time time() { return time; }
54 | };
55 | }
56 |
57 | @Override
58 | public void write(OutputStream out, Relay.Bundle.Component value) throws IOException {
59 | Uuid.SERIALIZER.write(out, value.id());
60 | Serializers.STRING.write(out, value.text());
61 | Time.SERIALIZER.write(out, value.time());
62 | }
63 | };
64 |
65 | private static final Serializer BUNDLE_SERIALIZER =
66 | new Serializer() {
67 |
68 | @Override
69 | public Relay.Bundle read(InputStream in) throws IOException {
70 |
71 | final Uuid id = Uuid.SERIALIZER.read(in);
72 | final Time time = Time.SERIALIZER.read(in);
73 | final Uuid team = Uuid.SERIALIZER.read(in);
74 | final Relay.Bundle.Component user = COMPONENT_SERIALIZER.read(in);
75 | final Relay.Bundle.Component conversation = COMPONENT_SERIALIZER.read(in);
76 | final Relay.Bundle.Component message = COMPONENT_SERIALIZER.read(in);
77 |
78 | return new Relay.Bundle() {
79 | @Override
80 | public Uuid id() { return id; }
81 | @Override
82 | public Time time() { return time; }
83 | @Override
84 | public Uuid team() { return team; }
85 | @Override
86 | public Relay.Bundle.Component user() { return user; }
87 | @Override
88 | public Relay.Bundle.Component conversation() { return conversation; }
89 | @Override
90 | public Relay.Bundle.Component message() { return message; }
91 | };
92 | }
93 |
94 | @Override
95 | public void write(OutputStream out, Relay.Bundle value) throws IOException {
96 | Uuid.SERIALIZER.write(out, value.id());
97 | Time.SERIALIZER.write(out, value.time());
98 | Uuid.SERIALIZER.write(out, value.team());
99 | COMPONENT_SERIALIZER.write(out, value.user());
100 | COMPONENT_SERIALIZER.write(out, value.conversation());
101 | COMPONENT_SERIALIZER.write(out, value.message());
102 | }
103 | };
104 |
105 | private final Relay backEnd;
106 |
107 | public ServerFrontEnd(Relay backEnd) {
108 | this.backEnd = backEnd;
109 | }
110 |
111 | public void handleConnection(Connection connection) throws IOException {
112 |
113 | LOG.info("Handling Connection - start");
114 |
115 | switch (Serializers.INTEGER.read(connection.in())) {
116 | case NetworkCode.RELAY_READ_REQUEST: handleReadMessage(connection); break;
117 | case NetworkCode.RELAY_WRITE_REQUEST: handleWriteMessage(connection); break;
118 | }
119 |
120 | LOG.info("Handling Connection - end");
121 | }
122 |
123 | private void handleReadMessage(Connection connection) throws IOException {
124 |
125 | LOG.info("Handling Read Message - start");
126 |
127 | final Uuid teamId = Uuid.SERIALIZER.read(connection.in());
128 | final byte[] teamSecret = Serializers.BYTES.read(connection.in());
129 | final Uuid root = Uuid.SERIALIZER.read(connection.in());
130 | final int range = Serializers.INTEGER.read(connection.in());
131 |
132 | LOG.info(
133 | "Reading team=%s root=%s range=%d",
134 | teamId,
135 | root,
136 | range);
137 |
138 | final Collection result = backEnd.read(teamId, teamSecret, root, range);
139 |
140 | LOG.info("Reading result.size=%d", result.size());
141 |
142 | Serializers.INTEGER.write(connection.out(), NetworkCode.RELAY_READ_RESPONSE);
143 | Serializers.collection(BUNDLE_SERIALIZER).write(connection.out(), result);
144 |
145 | LOG.info("Handling Read Message - end");
146 | }
147 |
148 | private void handleWriteMessage(Connection connection) throws IOException {
149 |
150 | LOG.info("Handling Write Message - start");
151 |
152 | final Uuid teamId = Uuid.SERIALIZER.read(connection.in());
153 | final byte[] teamSecret = Serializers.BYTES.read(connection.in());
154 | final Relay.Bundle.Component user = COMPONENT_SERIALIZER.read(connection.in());
155 | final Relay.Bundle.Component conversation = COMPONENT_SERIALIZER.read(connection.in());
156 | final Relay.Bundle.Component message = COMPONENT_SERIALIZER.read(connection.in());
157 |
158 | LOG.info(
159 | "Writing team=%s user=%s conversation=%s message=%s",
160 | teamId,
161 | user.id(),
162 | conversation.id(),
163 | message.id());
164 |
165 | final boolean result = backEnd.write(teamId,
166 | teamSecret,
167 | user,
168 | conversation,
169 | message);
170 |
171 | LOG.info("Writing result=%s", result ? "success" : "fail");
172 |
173 | Serializers.INTEGER.write(connection.out(), NetworkCode.RELAY_WRITE_RESPONSE);
174 | Serializers.BOOLEAN.write(connection.out(), result);
175 |
176 | LOG.info("Handling Write Message - end");
177 | }
178 | }
179 |
--------------------------------------------------------------------------------
/src/codeu/chat/server/Controller.java:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package codeu.chat.server;
16 |
17 | import java.util.Collection;
18 |
19 | import codeu.chat.common.BasicController;
20 | import codeu.chat.common.Conversation;
21 | import codeu.chat.common.Message;
22 | import codeu.chat.common.RandomUuidGenerator;
23 | import codeu.chat.common.RawController;
24 | import codeu.chat.common.User;
25 | import codeu.chat.util.Logger;
26 | import codeu.chat.util.Time;
27 | import codeu.chat.util.Uuid;
28 |
29 | public final class Controller implements RawController, BasicController {
30 |
31 | private final static Logger.Log LOG = Logger.newLog(Controller.class);
32 |
33 | private final Model model;
34 | private final Uuid.Generator uuidGenerator;
35 |
36 | public Controller(Uuid serverId, Model model) {
37 | this.model = model;
38 | this.uuidGenerator = new RandomUuidGenerator(serverId, System.currentTimeMillis());
39 | }
40 |
41 | @Override
42 | public Message newMessage(Uuid author, Uuid conversation, String body) {
43 | return newMessage(createId(), author, conversation, body, Time.now());
44 | }
45 |
46 | @Override
47 | public User newUser(String name) {
48 | return newUser(createId(), name, Time.now());
49 | }
50 |
51 | @Override
52 | public Conversation newConversation(String title, Uuid owner) {
53 | return newConversation(createId(), title, owner, Time.now());
54 | }
55 |
56 | @Override
57 | public Message newMessage(Uuid id, Uuid author, Uuid conversation, String body, Time creationTime) {
58 |
59 | final User foundUser = model.userById().first(author);
60 | final Conversation foundConversation = model.conversationById().first(conversation);
61 |
62 | Message message = null;
63 |
64 | if (foundUser != null && foundConversation != null && isIdFree(id)) {
65 |
66 | message = new Message(id, Uuid.NULL, Uuid.NULL, creationTime, author, body);
67 | model.add(message);
68 | LOG.info("Message added: %s", message.id);
69 |
70 | // Find and update the previous "last" message so that it's "next" value
71 | // will point to the new message.
72 |
73 | if (Uuid.equals(foundConversation.lastMessage, Uuid.NULL)) {
74 |
75 | // The conversation has no messages in it, that's why the last message is NULL (the first
76 | // message should be NULL too. Since there is no last message, then it is not possible
77 | // to update the last message's "next" value.
78 |
79 | } else {
80 | final Message lastMessage = model.messageById().first(foundConversation.lastMessage);
81 | lastMessage.next = message.id;
82 | }
83 |
84 | // If the first message points to NULL it means that the conversation was empty and that
85 | // the first message should be set to the new message. Otherwise the message should
86 | // not change.
87 |
88 | foundConversation.firstMessage =
89 | Uuid.equals(foundConversation.firstMessage, Uuid.NULL) ?
90 | message.id :
91 | foundConversation.firstMessage;
92 |
93 | // Update the conversation to point to the new last message as it has changed.
94 |
95 | foundConversation.lastMessage = message.id;
96 |
97 | if (!foundConversation.users.contains(foundUser)) {
98 | foundConversation.users.add(foundUser.id);
99 | }
100 | }
101 |
102 | return message;
103 | }
104 |
105 | @Override
106 | public User newUser(Uuid id, String name, Time creationTime) {
107 |
108 | User user = null;
109 |
110 | if (isIdFree(id)) {
111 |
112 | user = new User(id, name, creationTime);
113 | model.add(user);
114 |
115 | LOG.info(
116 | "newUser success (user.id=%s user.name=%s user.time=%s)",
117 | id,
118 | name,
119 | creationTime);
120 |
121 | } else {
122 |
123 | LOG.info(
124 | "newUser fail - id in use (user.id=%s user.name=%s user.time=%s)",
125 | id,
126 | name,
127 | creationTime);
128 | }
129 |
130 | return user;
131 | }
132 |
133 | @Override
134 | public Conversation newConversation(Uuid id, String title, Uuid owner, Time creationTime) {
135 |
136 | final User foundOwner = model.userById().first(owner);
137 |
138 | Conversation conversation = null;
139 |
140 | if (foundOwner != null && isIdFree(id)) {
141 | conversation = new Conversation(id, owner, creationTime, title);
142 | model.add(conversation);
143 |
144 | LOG.info("Conversation added: " + conversation.id);
145 | }
146 |
147 | return conversation;
148 | }
149 |
150 | private Uuid createId() {
151 |
152 | Uuid candidate;
153 |
154 | for (candidate = uuidGenerator.make();
155 | isIdInUse(candidate);
156 | candidate = uuidGenerator.make()) {
157 |
158 | // Assuming that "randomUuid" is actually well implemented, this
159 | // loop should never be needed, but just incase make sure that the
160 | // Uuid is not actually in use before returning it.
161 |
162 | }
163 |
164 | return candidate;
165 | }
166 |
167 | private boolean isIdInUse(Uuid id) {
168 | return model.messageById().first(id) != null ||
169 | model.conversationById().first(id) != null ||
170 | model.userById().first(id) != null;
171 | }
172 |
173 | private boolean isIdFree(Uuid id) { return !isIdInUse(id); }
174 |
175 | }
176 |
--------------------------------------------------------------------------------
/src/codeu/chat/server/Model.java:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package codeu.chat.server;
16 |
17 | import java.util.Comparator;
18 |
19 | import codeu.chat.common.Conversation;
20 | import codeu.chat.common.ConversationSummary;
21 | import codeu.chat.common.LinearUuidGenerator;
22 | import codeu.chat.common.Message;
23 | import codeu.chat.common.User;
24 | import codeu.chat.util.Time;
25 | import codeu.chat.util.Uuid;
26 | import codeu.chat.util.store.Store;
27 | import codeu.chat.util.store.StoreAccessor;
28 |
29 | public final class Model {
30 |
31 | private static final Comparator UUID_COMPARE = new Comparator() {
32 |
33 | @Override
34 | public int compare(Uuid a, Uuid b) {
35 |
36 | if (a == b) { return 0; }
37 |
38 | if (a == null && b != null) { return -1; }
39 |
40 | if (a != null && b == null) { return 1; }
41 |
42 | final int order = Integer.compare(a.id(), b.id());
43 | return order == 0 ? compare(a.root(), b.root()) : order;
44 | }
45 | };
46 |
47 | private static final Comparator