diff --git a/.gitignore b/.gitignore index 0826a07..9899293 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,8 @@ gradle gradlew gradlew.bat build +.gradletasknamecache + +#TestNG +test-output + diff --git a/.travis.yml b/.travis.yml index 45e9d6b..302b508 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,21 @@ language: java +cache: gradle + jdk: - oraclejdk8 + +env: + global: + - GH_REPO_NAME: FastChat + - GH_REPO_REF: github.com/OHopiak/FastChat.git + +script: + - gradle assemble + - gradle check jacocoTestReport + - gradle build javadoc + +after_success: + - bash <(curl -s https://codecov.io/bash) + - cd $TRAVIS_BUILD_DIR + - ./scripts/deploy-travis diff --git a/FastChat.uml b/FastChat.uml index b793435..52cd288 100644 --- a/FastChat.uml +++ b/FastChat.uml @@ -3,79 +3,40 @@ JAVA - com.fastchat.FastChat.Loader - com.fastchat.FastChat.client.Login - com.fastchat.FastChat.client.Client - com.fastchat.FastChat.server.Server.Status - com.fastchat.FastChat.server.Server - com.fastchat.FastChat.client.OnlineUsers - com.fastchat.FastChat.server.ServerClient - com.fastchat.FastChat.client.ClientWindow + com.fastchat.FastChat.Loader + com.fastchat.FastChat.client + com.fastchat.FastChat.util + com.fastchat.FastChat.networking + com.fastchat.FastChat.server + com.fastchat.FastChat.beans - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - - + + + com.fastchat.FastChat.server + Methods - Inner Classes All private diff --git a/Jenkinsfile b/Jenkinsfile index ae87b51..3414a69 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -13,8 +13,9 @@ pipeline { } stage('test') { steps { - sh 'gradle test' + sh 'gradle check jacocoTestReport' junit(testResults: '**/build/test-results/**/*.xml', allowEmptyResults: true) + jacoco(execPattern: '**/build/**.exec') } } stage('deploy') { diff --git a/README.md b/README.md index 6785f21..e6de617 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,11 @@ FastChat ======== -Travis: [![Build Status](https://travis-ci.org/OHopiak/FastChat.svg?branch=master)](https://travis-ci.org/OHopiak/FastChat) - -Jenkins: -[![Build Status](http://18.194.57.194:8080/job/FastChat/job/master/4/badge/icon)](http://18.194.57.194:8080/job/FastChat/job/master/4/) +[![codecov](https://codecov.io/gh/OHopiak/FastChat/branch/dev/graph/badge.svg)](https://codecov.io/gh/OHopiak/FastChat) This is the simple chatting tool + Feel free to contribute to this project Requirements ------------ diff --git a/build.gradle b/build.gradle index c9c985a..4fad909 100644 --- a/build.gradle +++ b/build.gradle @@ -1,14 +1,19 @@ apply plugin: 'java' -apply plugin: 'maven' +apply plugin: 'jacoco' group = 'com.fastchat' -version = '0.0.1' +version = '0.0.3' description = """FastChat - a tool for instant chatting that not always works as expected - WIP""" sourceCompatibility = 1.8 targetCompatibility = 1.8 + +configurations { + libraries +} + jar { manifest { attributes( @@ -18,6 +23,9 @@ jar { ) } + from { + configurations.libraries.collect { it.isDirectory() ? it : zipTree(it) } + } } test { @@ -26,15 +34,34 @@ test { } repositories { - maven { url "http://jcenter.bintray.com" } - maven { url "http://repo.maven.apache.org/maven2" } + jcenter() } dependencies { - testCompile group: 'org.testng', name: 'testng', version: '6.9.12' + libraries 'com.jgoodies:jgoodies-forms:1.6.0' + libraries 'com.google.code.gson:gson:2.8.2' +// libraries 'com.intellij:forms_rt:7.0.3' + testCompile 'org.testng:testng:6.9.12' + testCompile 'com.google.guava:guava:19.0' + configurations.compile.extendsFrom(configurations.libraries) +} + +jacocoTestReport { + reports { + //noinspection GroovyAssignabilityCheck + html.enabled true + //noinspection GroovyAssignabilityCheck + xml.enabled true + //noinspection GroovyAssignabilityCheck + csv.enabled true + } } -task printJarName{ +javadoc { + failOnError = false +} + +task printJarName { dependsOn 'jar' doLast { println "${project.name}-${project.version}.jar" @@ -46,4 +73,4 @@ task printArtifactsDirectory { doLast { println "${project.name}/${project.version}" } -} \ No newline at end of file +} diff --git a/deploy/Dockerfile b/deploy/Dockerfile new file mode 100644 index 0000000..08f2bbf --- /dev/null +++ b/deploy/Dockerfile @@ -0,0 +1,6 @@ +FROM gradle:latest + +USER root +RUN mkdir /opt/artifacts && chown -R gradle:gradle /opt/artifacts +RUN apt update && apt install -y ssh +#USER gradle diff --git a/scripts/deploy b/scripts/deploy index 1647f4c..ef6e103 100755 --- a/scripts/deploy +++ b/scripts/deploy @@ -22,7 +22,7 @@ artifact_jar="build/libs/`gradle printJarName -q 2>/dev/null`" artifact_docs="build/docs/javadoc" artifact_tests="build/reports/tests/test" -mkdir -p $version_dir -cp -r $artifact_jar $version_dir -cp -r $artifact_docs $version_dir/docs -cp -r $artifact_tests $version_dir/tests \ No newline at end of file +mkdir -p "$version_dir" +cp -r "$artifact_jar" "$version_dir" +cp -r "$artifact_docs" "$version_dir/docs" +cp -r "$artifact_tests" "$version_dir/tests" diff --git a/scripts/deploy-travis b/scripts/deploy-travis new file mode 100755 index 0000000..56e2a1f --- /dev/null +++ b/scripts/deploy-travis @@ -0,0 +1,70 @@ +#!/usr/bin/env bash + +PROJECT_DIR=$(cd $(dirname "$0")/.. && echo "$PWD") +BUILD_DIR="$PROJECT_DIR/build" +GH_PAGES_DIR="$BUILD_DIR/gh-pages" +BRANCH=$(git branch | grep \* | cut -d ' ' -f2-) + +if [ "$BRANCH" == "master" ]; then + DEPLOY_DIR="$GH_PAGES_DIR" +else + DEPLOY_DIR="$GH_PAGES_DIR/$BRANCH" +fi + +set -ex + +function copy_markdown(){ + cp "$PROJECT_DIR"/*.md "$DEPLOY_DIR" +} + +function deploy_dir(){ + local dir=$1 + if [ -d "$BUILD_DIR/$dir" ]; then + if [ -d "$DEPLOY_DIR/$dir" ]; then + rm -fr "$DEPLOY_DIR/$dir" + fi + cp -r "$BUILD_DIR/$dir" "$DEPLOY_DIR/$dir" + else + echo "Directory $dir doesn't exist" + fi +} + +function set_up_git(){ + # delete directory with pages if it exists + if [ -d "$GH_PAGES_DIR" ]; then + rm -fr "$GH_PAGES_DIR" + fi + + git clone -b gh-pages "https://git@$GH_REPO_REF.git" "$GH_PAGES_DIR" + + cd "$GH_PAGES_DIR" + git config --global push.default simple + git config user.name "Travis CI" + git config user.email "travis@travis-ci.org" +} + +function push_changes(){ + if ! git diff-index --quiet HEAD --; then + git add --all + git commit -m "Deploy code docs to GitHub Pages Travis build: ${TRAVIS_BUILD_NUMBER}" -m "Commit: ${TRAVIS_COMMIT}" + git push --force "https://${GH_REPO_TOKEN}@${GH_REPO_REF}" > /dev/null 2>&1 + fi +} + +function main(){ + if [ ! -d "$PROJECT_DIR/build" ]; then + echo "You must build the project before deploying" + exit 1 + fi + + set_up_git + mkdir -p "$DEPLOY_DIR" + copy_markdown + deploy_dir libs + deploy_dir docs + deploy_dir reports + push_changes + echo "Deploy is finished successfully" +} + +main \ No newline at end of file diff --git a/src/main/java/com/fastchat/FastChat/Loader.java b/src/main/java/com/fastchat/FastChat/Loader.java index 057852b..b05ca6a 100644 --- a/src/main/java/com/fastchat/FastChat/Loader.java +++ b/src/main/java/com/fastchat/FastChat/Loader.java @@ -1,17 +1,19 @@ package com.fastchat.FastChat; -import com.fastchat.FastChat.client.Login; +import com.fastchat.FastChat.client.LoginInterface; +import com.fastchat.FastChat.client.gui.LoginGUI; import com.fastchat.FastChat.server.Server; +import com.fastchat.FastChat.util.Localization; -public class Loader { +import java.awt.*; + +class Loader { private static final String usageString = "Usage: java -jar FastChat.jar [ -s [PORT] [IP] ]"; private static final String description = "This is really cool tool for chatting\nrun without arguments to launch the client"; - private static void usage() { - System.out.println(usageString); - } +// private static Class clientClass; private static void help() { String helpString = usageString + "\n" + @@ -24,9 +26,44 @@ private static void help() { System.out.println(helpString); } + /** + *
+	 * Backend TODOs
+	 * TODO: Text Box for messages instead of Text Field
+	 * TODO: Limit width of message history Text Area
+	 * TODO: Show messages in separate boxes (or at least in different colors per user)
+	 * TODO: Group networking in a protocol
+	 * TODO: Add settings frame
+	 * TODO: Add sound to play when message comes
+	 * TODO: Make a single class (JavaBean) for User
+	 * TODO: Move networking from UDP to TCP
+	 * TODO: Make tests for existing classes
+	 * FIXME: Add cyrillic support in Windows
+	 * FIXME: Add either message limit, or enable sending messages of any length
+	 *
+	 * Frontend TODOs:
+	 * TODO: Pick main color scheme
+	 * TODO: Make a dark and light theme
+	 * TODO: (optional) use Material UI
+	 * TODO: Reshape all components
+	 * TODO: Add custom font (must support any languages)
+	 * 
+ * + * @param args arguments of application + */ public static void main(String[] args) { + + Localization.autoSetLocale(); //English +// Localization.setLocale(new Locale("uk")); //Ukrainian +// Localization.setLocale(new Locale("nb")); //Norwegian + if (args.length == 0) { - Login.main(args); +// EventQueue.invokeLater(Login::getCurrentLogin); + EventQueue.invokeLater(() -> { + System.out.println("[*] Starting Login"); + LoginInterface login = new LoginGUI(); + + }); } else if (args.length <= 3) { if (args[0].equals("-h") || args[0].equals("--help")) { help(); diff --git a/src/main/java/com/fastchat/FastChat/beans/User.java b/src/main/java/com/fastchat/FastChat/beans/User.java new file mode 100644 index 0000000..85e8165 --- /dev/null +++ b/src/main/java/com/fastchat/FastChat/beans/User.java @@ -0,0 +1,101 @@ +package com.fastchat.FastChat.beans; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.PrintWriter; +import java.net.InetAddress; +import java.net.Socket; + +public class User { + private String name; + private InetAddress ip; + private int port; + private int ID; + private Socket socket; + private BufferedReader reader; + private PrintWriter out; + + protected User() { + this.name = ""; + this.ip = null; + this.port = 0; + this.ID = 0; + this.socket = null; + this.reader = null; + this.out = null; + } + +// public User(String name, InetAddress ip, int port, int ID, Socket socket) throws IOException { +// this.name = name; +// this.ip = ip; +// this.port = port; +// this.ID = ID; +// this.socket = socket; +// this.reader = new BufferedReader(new InputStreamReader(socket.getInputStream())); +// this.out = new PrintWriter(socket.getOutputStream()); +// } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public InetAddress getIp() { + return ip; + } + + protected void setIp(InetAddress ip) { + this.ip = ip; + } + + public int getPort() { + return port; + } + + public void setPort(int port) { + this.port = port; + } + + public int getID() { + return ID; + } + + public void setID(int ID) { + this.ID = ID; + } + + public Socket getSocket() { + return socket; + } + + protected void setSocket(Socket socket) throws IOException { + this.socket = socket; + this.reader = new BufferedReader(new InputStreamReader(socket.getInputStream())); + this.out = new PrintWriter(socket.getOutputStream()); + + } + + public BufferedReader getReader() { + return reader; + } + + public void setReader(BufferedReader reader) { + this.reader = reader; + } + + public PrintWriter getOut() { + return out; + } + + public void setOut(PrintWriter out) { + this.out = out; + } + + public UserBean toBean() { + return new UserBean(name, ID); + } +} diff --git a/src/main/java/com/fastchat/FastChat/beans/UserBean.java b/src/main/java/com/fastchat/FastChat/beans/UserBean.java new file mode 100644 index 0000000..6b937ca --- /dev/null +++ b/src/main/java/com/fastchat/FastChat/beans/UserBean.java @@ -0,0 +1,30 @@ +package com.fastchat.FastChat.beans; + +public class UserBean { + private String name; + private int ID; + + public UserBean() { + } + + UserBean(String name, int ID) { + this.name = name; + this.ID = ID; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getID() { + return ID; + } + + public void setID(int ID) { + this.ID = ID; + } +} diff --git a/src/main/java/com/fastchat/FastChat/beans/package-info.java b/src/main/java/com/fastchat/FastChat/beans/package-info.java new file mode 100644 index 0000000..6c986fa --- /dev/null +++ b/src/main/java/com/fastchat/FastChat/beans/package-info.java @@ -0,0 +1 @@ +package com.fastchat.FastChat.beans; \ No newline at end of file diff --git a/src/main/java/com/fastchat/FastChat/client/Client.java b/src/main/java/com/fastchat/FastChat/client/Client.java index 89ac57f..25be7c7 100644 --- a/src/main/java/com/fastchat/FastChat/client/Client.java +++ b/src/main/java/com/fastchat/FastChat/client/Client.java @@ -1,134 +1,63 @@ package com.fastchat.FastChat.client; +import com.fastchat.FastChat.beans.User; +import com.fastchat.FastChat.networking.ProtocolCommands; + import java.io.IOException; -import java.net.DatagramPacket; -import java.net.DatagramSocket; import java.net.InetAddress; -import java.net.SocketException; -import java.net.UnknownHostException; - -import javax.swing.JOptionPane; +import java.net.Socket; -public class Client { - - private String name; - private int port; - private int ID; - private DatagramSocket socket; - private InetAddress ip; - private Thread send; - private boolean running, kicked = false, banned = false; - - protected boolean openConnection(String address) { - try { - socket = new DatagramSocket(); - ip = InetAddress.getByName(address); - } catch (UnknownHostException e) { - e.printStackTrace(); - return false; - } catch (SocketException e) { - e.printStackTrace(); - return false; - } - return true; - } +public class Client extends User { - protected String receive() { - byte[] data = new byte[1024]; - DatagramPacket packet = new DatagramPacket(data, data.length); - try { - socket.receive(packet); - } catch (SocketException e1) {} catch (IOException e) { - e.printStackTrace(); - } - String message = new String(packet.getData()); - message = message.split("/e/")[0]; - return message; + private boolean running = false, kicked = false, banned = false; + + Client(String name, int port) { + setName(name); + setPort(port); } - - protected void send(final byte[] data) { - send = new Thread("Send") { - public void run() { - DatagramPacket packet = new DatagramPacket(data, data.length, ip, port); - try { - socket.send(packet); - } catch (SocketException e1) {} catch (IOException e) { - e.printStackTrace(); - } - } - }; - send.start(); + + void openConnection(String address) throws IOException { + setIp(InetAddress.getByName(address)); + setSocket(new Socket(address, getPort())); + send(getName()); } - - protected void send(String message) { - message += "/e/"; - send(message.getBytes()); + + public void send(String message) { + getOut().println(message); + getOut().flush(); +// Protocol.send(socket, message, ip, port).start(); } - - protected void disconnect(int id) { + + public void disconnect(int id) { running = false; //socket.close(); if (kicked || banned) { - Login.getCurrentLogin().setVisible(true); - Login.getClientWindow().setVisible(false); - JOptionPane.showMessageDialog(null, - (kicked ? "You were kicked out of server\nTry to reconnect again" - : "You were banned on this server!\nYou can't reconnect unless the server restarts!"), - "Warning!", JOptionPane.WARNING_MESSAGE); +// ((LoginGUI)LoginGUI.getCurrentLogin()).reset(); +// ((ClientGUI)LoginGUI.getClientInterface()).getFrame().setVisible(false); +// JOptionPane.showMessageDialog(null, +// (kicked ? "You were kicked out of server\nTry to reconnect again" +// : "You were banned on this server!\nYou can't reconnect unless the server restarts!"), +// "Warning!", JOptionPane.WARNING_MESSAGE); kicked = false; banned = false; } else { - send("/d/" + id); + send(ProtocolCommands.Disconnect.PREFIX + id); System.exit(0); } } - - public String getName() { - return name; - } - - public void setID(int iD) { - ID = iD; - } - - public int getID() { - return ID; - } - - public int getPort() { - return port; - } - - public InetAddress getIp() { - return ip; - } - - public Client(String name, int port) { - super(); - this.name = name; - this.port = port; - } - + public boolean isRunning() { return running; } - + public void setRunning(boolean running) { this.running = running; } - - public boolean isKicked() { - return kicked; - } - + public void setKicked(boolean kicked) { this.kicked = kicked; } - - public boolean isBanned() { - return banned; - } - + public void setBanned(boolean banned) { this.banned = banned; } diff --git a/src/main/java/com/fastchat/FastChat/client/ClientInterface.java b/src/main/java/com/fastchat/FastChat/client/ClientInterface.java new file mode 100644 index 0000000..1038d5e --- /dev/null +++ b/src/main/java/com/fastchat/FastChat/client/ClientInterface.java @@ -0,0 +1,151 @@ +package com.fastchat.FastChat.client; + +import com.fastchat.FastChat.networking.NetworkCommandRegistry; +import com.fastchat.FastChat.networking.Protocol; +import com.fastchat.FastChat.networking.ProtocolCommands; +import com.fastchat.FastChat.util.Localization; + +import java.io.IOException; + +public abstract class ClientInterface implements Runnable { + + protected Client client; + protected OnlineUsersInterface users; + private String[] userArr = {"Name(id)"}; + private final NetworkCommandRegistry networkCommandRegistry = new NetworkCommandRegistry(); + private String name, address; + private int port; + @SuppressWarnings("FieldCanBeLocal") + private Thread run, listen; + + protected ClientInterface() { + this("Anon", "localhost", 1234); + } + + protected ClientInterface(String name, String address, int port) { + this.name = name; + this.address = address; + this.port = port; + + initCommands(); +// init(); + } + + protected abstract void onBreak(); + + public void init() { + client = new Client(name, port); + client.setRunning(true); + + console(String.format("%s %s:%d...", Localization.get("client_attempting_to_connect"), address, port)); + + boolean connected = true; + try { + client.openConnection(address); + } catch (IOException e) { + e.printStackTrace(); + connected = false; + } + + if (!connected) { + String client_connection_failed = Localization.get("client_connection_failed"); + System.err.println(client_connection_failed); + console(client_connection_failed); + onBreak(); + } else { + this.serve(); + } +// client.send((ProtocolCommands.Connect.PREFIX + name)); + } + + protected abstract void send(String message); + + protected abstract void console(String message); + + public void run() { + listen(); + } + + private void listen() { + listen = Protocol.listen(client::isRunning, () -> { + try { + String message = client.getReader().readLine(); + if (message == null) { + System.err.println("Server ended session"); + System.exit(0); + } + if (!message.isEmpty()) + Protocol.process(message, networkCommandRegistry, + Protocol.IGNORE, + () -> console("Error: " + message) + ); + } catch (IOException ignored) { + } + }); + listen.start(); + } + + private void serve() { + run = new Thread(this, "Run"); + run.start(); + } + + private void initCommands() { + networkCommandRegistry.add( +// new ProtocolCommands.Ping((args) -> client.send(ProtocolCommands.Ping.PREFIX + client.getID())), + new ProtocolCommands.Connect((args) -> { + if (args.length < 3) return; + String data = args[0]; + client.setID(Integer.parseInt(data)); + console("Connection is set up successfully... ID is " + client.getID()); + }), + new ProtocolCommands.Message((args) -> { + if (args.length <= 0) return; + String data = args[0]; + console(data); + }), + new ProtocolCommands.Disconnect((args) -> { + if (args.length <= 0) return; + String data = args[0]; + if (data.startsWith("0")) { + client.setKicked(true); + client.disconnect(client.getID()); + } else { + client.setBanned(true); + client.disconnect(client.getID()); + } + }), + new ProtocolCommands.Users((args) -> { + if (args.length <= 0) return; + String data = args[0]; + userArr = data.split("//"); + users.update(userArr); + users.show(); + }) + ); + } + + public Client getClient() { + return client; + } + + protected String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + protected String getAddress() { + return address; + } + + protected int getPort() { + return port; + } + + public void setPort(int port) { + this.port = port; + } +} diff --git a/src/main/java/com/fastchat/FastChat/client/ClientWindow.java b/src/main/java/com/fastchat/FastChat/client/ClientWindow.java deleted file mode 100644 index 9e2196e..0000000 --- a/src/main/java/com/fastchat/FastChat/client/ClientWindow.java +++ /dev/null @@ -1,192 +0,0 @@ -package com.fastchat.FastChat.client; - -import java.awt.GridBagConstraints; -import java.awt.GridBagLayout; -import java.awt.Insets; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.WindowAdapter; -import java.awt.event.WindowEvent; - -import javax.swing.JButton; -import javax.swing.JFrame; -import javax.swing.JMenu; -import javax.swing.JMenuBar; -import javax.swing.JMenuItem; -import javax.swing.JPanel; -import javax.swing.JScrollPane; -import javax.swing.JTextArea; -import javax.swing.JTextField; -import javax.swing.border.EmptyBorder; -import javax.swing.text.DefaultCaret; - -public class ClientWindow extends JFrame implements Runnable { - - private static final long serialVersionUID = 5015815262080715846L; - - private final static int WIDTH = 800; - private final static int HEIGHT = 600; - - private JPanel contentPane; - private JTextField txtMessage; - private JTextArea history; - private DefaultCaret caret; - - private Thread run, listen; - private Client client; - private JMenuBar menuBar; - private JMenu menu; - private JMenuItem onlineUsers; - private OnlineUsers users; - - private String[] userArr = { "Name(id)" }; - - public ClientWindow(String name, String address, int port) { - client = new Client(name, port); - client.setRunning(true); - createWindow(); - console("Attempting to conect to " + address + ":" + port + "..."); - boolean connected = client.openConnection(address); - if (!connected) { - System.err.println("Connection failed!"); - console("Connection failed!"); - txtMessage.setEditable(false); - } else { - run = new Thread(this, "Run"); - run.start(); - } - client.send(("/c/" + name)); - } - - private void console(String message) { - history.setCaretPosition(history.getDocument().getLength()); - history.append(message + "\r\n"); - } - - private void send(String message) { - if (message.equals("")) return; - message = client.getName() + ": " + message; - txtMessage.setText(""); - client.send("/m/" + message); - } - - public void run() { - listen(); - } - - private void listen() { - listen = new Thread("Listen") { - public void run() { - while (client.isRunning()) { - String message = client.receive(); - if (message.startsWith("/c/")) { - client.setID(Integer.parseInt(message.substring(3))); - console("Connection is setted up successfully... ID is " + client.getID()); - } else if (message.startsWith("/m/")) { - console(message.substring(3)); - } else if (message.startsWith("/i/")) { - client.send("/i/" + client.getID()); - } else if (message.startsWith("/d/")) { - message = message.substring(3); - if (message.startsWith("0")) { - client.setKicked(true); - client.disconnect(client.getID()); - } else { - client.setBanned(true); - client.disconnect(client.getID()); - } - } else if (message.startsWith("/u/")) { - message = message.substring(3); - userArr = message.split("//"); - users.updateList(userArr); - } else { - console("Server: " + message); - } - } - } - }; - listen.start(); - } - - private void createWindow() { - setDefaultCloseOperation(EXIT_ON_CLOSE); - setSize(WIDTH, HEIGHT); - setLocationRelativeTo(null); - setVisible(true); - - menuBar = new JMenuBar(); - setJMenuBar(menuBar); - - menu = new JMenu("Menu"); - menuBar.add(menu); - - users = new OnlineUsers(); - users.updateList(userArr); - onlineUsers = new JMenuItem("Online Users"); - onlineUsers.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent arg0) { - client.send("/u/"); - users.updateList(userArr); - users.setVisible(true); - } - }); - menu.add(onlineUsers); - contentPane = new JPanel(); - contentPane.setBorder(new EmptyBorder(5, 5, 5, 5)); - setContentPane(contentPane); - GridBagLayout gbl_contentPane = new GridBagLayout(); - gbl_contentPane.columnWidths = new int[] { 5, WIDTH - 30, 20, 0 }; - gbl_contentPane.rowHeights = new int[] { 5, HEIGHT - 30, 20, 5 }; - gbl_contentPane.columnWeights = new double[] { 0.0, 1.0, Double.MIN_VALUE }; - gbl_contentPane.rowWeights = new double[] { 0.0, 1.0, 0.0, Double.MIN_VALUE }; - contentPane.setLayout(gbl_contentPane); - - JScrollPane scrollPane = new JScrollPane(); - GridBagConstraints gbc_scrollPane = new GridBagConstraints(); - gbc_scrollPane.insets = new Insets(0, 0, 5, 5); - gbc_scrollPane.fill = GridBagConstraints.BOTH; - gbc_scrollPane.gridx = 1; - gbc_scrollPane.gridy = 1; - gbc_scrollPane.gridwidth = 2; - contentPane.add(scrollPane, gbc_scrollPane); - - history = new JTextArea(); - history.setEditable(false); - caret = (DefaultCaret) history.getCaret(); - caret.setUpdatePolicy(DefaultCaret.ALWAYS_UPDATE); - scrollPane.setViewportView(history); - - txtMessage = new JTextField(); - txtMessage.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - send(txtMessage.getText()); - } - }); - GridBagConstraints gbc_textField = new GridBagConstraints(); - gbc_textField.insets = new Insets(0, 0, 0, 5); - gbc_textField.fill = GridBagConstraints.HORIZONTAL; - gbc_textField.gridx = 1; - gbc_textField.gridy = 2; - contentPane.add(txtMessage, gbc_textField); - txtMessage.setColumns(10); - txtMessage.requestFocusInWindow(); - - JButton btnSend = new JButton("Send"); - btnSend.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - send(txtMessage.getText()); - } - }); - GridBagConstraints gbc_btnSend = new GridBagConstraints(); - gbc_btnSend.insets = new Insets(0, 0, 0, 5); - gbc_btnSend.gridx = 2; - gbc_btnSend.gridy = 2; - contentPane.add(btnSend, gbc_btnSend); - addWindowListener(new WindowAdapter() { - public void windowClosing(WindowEvent e) { - client.disconnect(client.getID()); - } - }); - setTitle("FastChat Client. User: " + client.getName()); - } -} diff --git a/src/main/java/com/fastchat/FastChat/client/Login.java b/src/main/java/com/fastchat/FastChat/client/Login.java deleted file mode 100644 index 1b8f410..0000000 --- a/src/main/java/com/fastchat/FastChat/client/Login.java +++ /dev/null @@ -1,137 +0,0 @@ -package com.fastchat.FastChat.client; - -import javax.swing.*; -import javax.swing.border.EmptyBorder; -import java.awt.*; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; - -public class Login extends JFrame { - - private static final long serialVersionUID = -6824082102990197571L; - - private static Login currentLogin; - private static ClientWindow clientWindow; - - private JPanel contentPane; - private JTextField txtName; - private JTextField txtAddress; - private JTextField txtPort; - - public Login() { - setResizable(false); - - setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); - setSize(205, 295); - setLocationRelativeTo(null); - - setVisible(true); - contentPane = new JPanel(); - contentPane.setBorder(new EmptyBorder(5, 5, 5, 5)); - setContentPane(contentPane); - contentPane.setLayout(null); - - txtName = new JTextField(); - txtName.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - txtAddress.requestFocusInWindow(); - } - }); - txtName.setToolTipText("Name"); - txtName.setBounds(25, 30, 140, 20); - contentPane.add(txtName); - txtName.setColumns(10); - txtName.requestFocusInWindow(); // requesting focus - - txtAddress = new JTextField(); - txtAddress.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - txtPort.requestFocusInWindow(); - } - }); - txtAddress.setToolTipText("Address"); - txtAddress.setColumns(10); - txtAddress.setBounds(25, 90, 140, 20); - contentPane.add(txtAddress); - - txtPort = new JTextField(); - txtPort.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent arg0) { - submit(); - } - }); - txtPort.setToolTipText("Port"); - txtPort.setColumns(10); - txtPort.setBounds(25, 154, 140, 20); - contentPane.add(txtPort); - - final JButton btnSubmit = new JButton("Submit"); - btnSubmit.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent arg0) { - submit(); - } - }); - btnSubmit.setBounds(53, 216, 89, 23); - contentPane.add(btnSubmit); - - JLabel lblName = new JLabel("Name:"); - lblName.setHorizontalAlignment(SwingConstants.CENTER); - lblName.setBounds(72, 11, 46, 14); - contentPane.add(lblName); - - JLabel lblAddress = new JLabel("Address:"); - lblAddress.setHorizontalAlignment(SwingConstants.CENTER); - lblAddress.setBounds(72, 72, 46, 14); - contentPane.add(lblAddress); - - JLabel lblPort = new JLabel("Port:"); - lblPort.setHorizontalAlignment(SwingConstants.CENTER); - lblPort.setBounds(72, 135, 46, 14); - contentPane.add(lblPort); - txtName.setText("Orest"); - txtAddress.setText("localhost"); - txtPort.setText("1234"); - } - - private void submit() { - String name = txtName.getText(); - String address = txtAddress.getText(); - int port = Integer.parseInt(txtPort.getText()); - clientWindow = new ClientWindow(name, address, port); - this.setVisible(false); - } - - public void reset() { - this.setVisible(true); - txtAddress.setText(""); - txtPort.setText(""); - txtAddress.requestFocusInWindow(); - } - - public static void main(String[] args) { - EventQueue.invokeLater(() -> { - try { - currentLogin = new Login(); - } catch (Exception e) { - e.printStackTrace(); - } - }); - } - - public static Login getCurrentLogin() { - return currentLogin; - } - - public static void setCurrentLogin(Login login) { - currentLogin = login; - } - - public static ClientWindow getClientWindow() { - return clientWindow; - } - - public static void setClientWindow(ClientWindow clientWindow) { - Login.clientWindow = clientWindow; - } - -} diff --git a/src/main/java/com/fastchat/FastChat/client/LoginInterface.java b/src/main/java/com/fastchat/FastChat/client/LoginInterface.java new file mode 100644 index 0000000..c66e7f1 --- /dev/null +++ b/src/main/java/com/fastchat/FastChat/client/LoginInterface.java @@ -0,0 +1,11 @@ +package com.fastchat.FastChat.client; + +public abstract class LoginInterface { + protected ClientInterface clientInterface; + + protected abstract void submit(String name, String address, int port); + + public ClientInterface getClientInterface() { + return clientInterface; + } +} diff --git a/src/main/java/com/fastchat/FastChat/client/OnlineUsers.java b/src/main/java/com/fastchat/FastChat/client/OnlineUsers.java deleted file mode 100644 index bdc8090..0000000 --- a/src/main/java/com/fastchat/FastChat/client/OnlineUsers.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.fastchat.FastChat.client; - -import java.awt.GridBagConstraints; -import java.awt.GridBagLayout; - -import javax.swing.JFrame; -import javax.swing.JList; -import javax.swing.JPanel; -import javax.swing.border.EmptyBorder; - -public class OnlineUsers extends JFrame { - - private static final long serialVersionUID = -3779778679932068169L; - private JPanel contentPane; - private JList list; - - public OnlineUsers() { - setType(Type.UTILITY); - setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); - setSize(278, 409); - setLocationRelativeTo(null); - setTitle("Users Online"); - contentPane = new JPanel(); - contentPane.setBorder(new EmptyBorder(5, 5, 5, 5)); - setContentPane(contentPane); - GridBagLayout gbl_contentPane = new GridBagLayout(); - gbl_contentPane.columnWidths = new int[] { 0, 0 }; - gbl_contentPane.rowHeights = new int[] { 0, 0 }; - gbl_contentPane.columnWeights = new double[] { 1.0, Double.MIN_VALUE }; - gbl_contentPane.rowWeights = new double[] { 1.0, Double.MIN_VALUE }; - contentPane.setLayout(gbl_contentPane); - - list = new JList(); - GridBagConstraints gbc_list = new GridBagConstraints(); - gbc_list.fill = GridBagConstraints.BOTH; - gbc_list.gridx = 0; - gbc_list.gridy = 0; - contentPane.add(list, gbc_list); - } - - public void updateList(String[] users) { - list.setListData(users); - } -} diff --git a/src/main/java/com/fastchat/FastChat/client/OnlineUsersInterface.java b/src/main/java/com/fastchat/FastChat/client/OnlineUsersInterface.java new file mode 100644 index 0000000..cbaba1c --- /dev/null +++ b/src/main/java/com/fastchat/FastChat/client/OnlineUsersInterface.java @@ -0,0 +1,11 @@ +package com.fastchat.FastChat.client; + +public abstract class OnlineUsersInterface { +// List users; + + protected abstract void init(); + + protected abstract void update(String[] users); + + protected abstract void show(); +} diff --git a/src/main/java/com/fastchat/FastChat/client/cli/OnlineUsersCLI.java b/src/main/java/com/fastchat/FastChat/client/cli/OnlineUsersCLI.java new file mode 100644 index 0000000..49ab297 --- /dev/null +++ b/src/main/java/com/fastchat/FastChat/client/cli/OnlineUsersCLI.java @@ -0,0 +1,33 @@ +package com.fastchat.FastChat.client.cli; + +import com.fastchat.FastChat.client.OnlineUsersInterface; + +import java.util.ArrayList; +import java.util.Arrays; + +public class OnlineUsersCLI extends OnlineUsersInterface { + + private ArrayList list; + + public OnlineUsersCLI() { + init(); + } + + @Override + public void init() { + list = new ArrayList<>(); + } + + @Override + public void update(String[] users) { + list.clear(); + list.addAll(Arrays.asList(users)); + } + + @Override + public void show() { + System.out.println("====================================="); + list.forEach(System.out::println); + System.out.println("====================================="); + } +} diff --git a/src/main/java/com/fastchat/FastChat/client/gui/ClientGUI.form b/src/main/java/com/fastchat/FastChat/client/gui/ClientGUI.form new file mode 100644 index 0000000..2702dab --- /dev/null +++ b/src/main/java/com/fastchat/FastChat/client/gui/ClientGUI.form @@ -0,0 +1,50 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/src/main/java/com/fastchat/FastChat/client/gui/ClientGUI.java b/src/main/java/com/fastchat/FastChat/client/gui/ClientGUI.java new file mode 100644 index 0000000..2a4ff6e --- /dev/null +++ b/src/main/java/com/fastchat/FastChat/client/gui/ClientGUI.java @@ -0,0 +1,217 @@ +package com.fastchat.FastChat.client.gui; + +import com.fastchat.FastChat.client.ClientInterface; +import com.fastchat.FastChat.networking.ProtocolCommands; +import com.fastchat.FastChat.util.Localization; + +import javax.swing.*; +import javax.swing.text.DefaultCaret; +import java.awt.*; +import java.awt.event.ActionListener; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.net.URL; + +/** + * The basic GUI class, + * has a generated part that can't be edited + */ +public class ClientGUI extends ClientInterface { + private final static int WIDTH = 400; + private final static int HEIGHT = 600; + private final static String TITLE = Localization.get("window_title"); + private static ClientGUI clientGUI; + + private JFrame frame; + private JTextField messagePrompt; + private JButton sendButton; + private JScrollPane messagePanel; + private JPanel clientPanel; + private JTextArea history; + + { +// GUI initializer generated by IntelliJ IDEA GUI Designer +// >>> IMPORTANT!! <<< +// DO NOT EDIT OR ADD ANY CODE HERE! + $$$setupUI$$$(); + } + + /** + * + */ + private ClientGUI() { + super(); + } + + private ClientGUI(String name, String address, int port) { + super(name, address, port); + } + + /** + * @return The singleton instance + */ + public static ClientGUI getInstance() { + if (clientGUI == null) + clientGUI = new ClientGUI(); + return clientGUI; + } + + /** + * @param name The username + * @param address The url (not tested) or an IP + * @param port The port to connect to + * @return The singleton instance + */ + public static ClientGUI getInstance(String name, String address, int port) { + if (clientGUI == null + || clientGUI.getName().equals(name) + || clientGUI.getAddress().equals(address) + || clientGUI.getPort() == port + ) + clientGUI = new ClientGUI(name, address, port); + return clientGUI; + } + + public JFrame getFrame() { + return frame; + } + + /** + * Creates a menu bar for the client window + * + * @return generated MenuBar + */ + private JMenuBar generateMenuBar() { + JMenuBar menuBar = new JMenuBar(); + + JMenu menu = new JMenu(Localization.get("menu_bar_menu")); + menuBar.add(menu); + JMenuItem onlineUsers = new JMenuItem(Localization.get("menu_bar_online_users")); + onlineUsers.addActionListener(event -> client.send(ProtocolCommands.Users.PREFIX)); + menu.add(onlineUsers); + + JMenuItem settings = new JMenuItem(Localization.get("menu_bar_settings")); + settings.addActionListener(event -> { + + }); + menu.add(settings); + + return menuBar; + } + + @Override + protected void console(String message) { + history.setCaretPosition(history.getDocument().getLength()); + history.append(message + "\r\n"); + } + + @Override + protected void send(String message) { + if (message.equals("")) return; + messagePrompt.setText(""); + client.send(ProtocolCommands.Message.PREFIX + message); + } + + @Override + protected void onBreak() { + history.setBackground(new Color(55, 55, 55)); + messagePrompt.setEditable(false); + System.exit(1); //FIXME + } + + /** + * + */ + @Override + public void init() { + ActionListener messageListener = actionEvent -> send(messagePrompt.getText()); + messagePrompt.addActionListener(messageListener); + sendButton.addActionListener(messageListener); + + sendButton.setText(Localization.get("send_btn_text")); + + ((DefaultCaret) history.getCaret()).setUpdatePolicy(DefaultCaret.ALWAYS_UPDATE); + + if (frame == null) + frame = new JFrame(TITLE + " (" + getName() + ")"); + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + + frame.setJMenuBar(generateMenuBar()); + + frame.setContentPane(this.clientPanel); + frame.pack(); + + frame.setSize(WIDTH, HEIGHT); + frame.setLocationRelativeTo(null); + + frame.addWindowListener(new WindowAdapter() { + public void windowClosing(WindowEvent e) { + client.disconnect(client.getID()); + } + }); + + // Set up window icon + URL iconUrl = getClass().getClassLoader().getResource("FastChatLogo_filled.png"); + if (iconUrl != null) { + ImageIcon icon; + icon = new ImageIcon(iconUrl); + frame.setIconImage(icon.getImage()); + } + + super.init(); + + frame.setVisible(true); + users = new OnlineUsersGUI(); + } + + /** + * Method generated by IntelliJ IDEA GUI Designer + * >>> IMPORTANT!! <<< + * DO NOT edit this method OR call it in your code! + * + * @noinspection ALL + */ + private void $$$setupUI$$$() { + clientPanel = new JPanel(); + clientPanel.setLayout(new GridBagLayout()); + clientPanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEmptyBorder(6, 6, 6, 6), null)); + messagePanel = new JScrollPane(); + messagePanel.setBackground(new Color(-1)); + GridBagConstraints gbc; + gbc = new GridBagConstraints(); + gbc.gridx = 0; + gbc.gridy = 0; + gbc.gridwidth = 2; + gbc.weightx = 1.0; + gbc.weighty = 10.0; + gbc.fill = GridBagConstraints.BOTH; + gbc.insets = new Insets(0, 0, 4, 0); + clientPanel.add(messagePanel, gbc); + history = new JTextArea(); + history.setEditable(false); + messagePanel.setViewportView(history); + messagePrompt = new JTextField(); + gbc = new GridBagConstraints(); + gbc.gridx = 0; + gbc.gridy = 1; + gbc.weightx = 1.0; + gbc.anchor = GridBagConstraints.WEST; + gbc.fill = GridBagConstraints.HORIZONTAL; + clientPanel.add(messagePrompt, gbc); + sendButton = new JButton(); + sendButton.setText("Submit"); + gbc = new GridBagConstraints(); + gbc.gridx = 1; + gbc.gridy = 1; + gbc.anchor = GridBagConstraints.EAST; + gbc.insets = new Insets(0, 3, 0, 0); + clientPanel.add(sendButton, gbc); + } + + /** + * @noinspection ALL + */ + public JComponent $$$getRootComponent$$$() { + return clientPanel; + } +} diff --git a/src/main/java/com/fastchat/FastChat/client/gui/LoginGUI.form b/src/main/java/com/fastchat/FastChat/client/gui/LoginGUI.form new file mode 100644 index 0000000..fbe138c --- /dev/null +++ b/src/main/java/com/fastchat/FastChat/client/gui/LoginGUI.form @@ -0,0 +1,157 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/src/main/java/com/fastchat/FastChat/client/gui/LoginGUI.java b/src/main/java/com/fastchat/FastChat/client/gui/LoginGUI.java new file mode 100644 index 0000000..b4e1bf8 --- /dev/null +++ b/src/main/java/com/fastchat/FastChat/client/gui/LoginGUI.java @@ -0,0 +1,153 @@ +package com.fastchat.FastChat.client.gui; + +import com.fastchat.FastChat.client.LoginInterface; +import com.fastchat.FastChat.util.Localization; +import com.jgoodies.forms.layout.CellConstraints; +import com.jgoodies.forms.layout.FormLayout; + +import javax.swing.*; +import java.awt.event.ActionListener; +import java.net.URL; + +public class LoginGUI extends LoginInterface { + + private static final String title = "Login"; + + private JFrame frame; + private JPanel loginPanel; + private JLabel lblName; + private JTextField txtName; + private JTextField txtAddress; + private JTextField txtPort; + private JButton submitButton; + private JLabel lblAddress; + private JLabel lblPort; + + { +// GUI initializer generated by IntelliJ IDEA GUI Designer +// >>> IMPORTANT!! <<< +// DO NOT EDIT OR ADD ANY CODE HERE! + $$$setupUI$$$(); + } + + public LoginGUI() { + init(); + } + + @Override + protected void submit(String name, String address, int port) { + clientInterface = ClientGUI.getInstance(name, address, port); + clientInterface.init(); + } + + public void reset() { + frame.setVisible(true); +// txtAddress.setText("localhost"); +// txtPort.setText("1234"); + txtAddress.requestFocusInWindow(); + } + + private void init() { + frame = new JFrame(title); + frame.setContentPane(loginPanel); + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + frame.pack(); + frame.setSize(205, 295); + frame.setResizable(false); + frame.setLocationRelativeTo(null); + + txtName.addActionListener(e -> txtAddress.requestFocusInWindow()); + txtAddress.addActionListener(e -> txtPort.requestFocusInWindow()); + ActionListener submitListener = e -> { + String name = txtName.getText(); + String address = txtAddress.getText(); + int port = Integer.parseInt(txtPort.getText()); + submit(name, address, port); + frame.setVisible(false); + }; + txtPort.addActionListener(submitListener); + submitButton.addActionListener(submitListener); + + lblName.setText(Localization.get("login_lbl_name") + ":"); + lblAddress.setText(Localization.get("login_lbl_address") + ":"); + lblPort.setText(Localization.get("login_lbl_port") + ":"); + submitButton.setText(Localization.get("login_submit")); + submitButton.setMnemonic(Localization.get("login_submit").charAt(0)); + + txtName.setText("Anon"); + txtAddress.setText("localhost"); + txtPort.setText("1234"); + + // Set up window icon + URL iconUrl = getClass().getClassLoader().getResource("FastChatLogo_filled.png"); + if (iconUrl != null) { + ImageIcon icon; + icon = new ImageIcon(iconUrl); + frame.setIconImage(icon.getImage()); + } + + frame.setVisible(true); + } + + /** + * Method generated by IntelliJ IDEA GUI Designer + * >>> IMPORTANT!! <<< + * DO NOT edit this method OR call it in your code! + * + * @noinspection ALL + */ + private void $$$setupUI$$$() { + loginPanel = new JPanel(); + loginPanel.setLayout(new FormLayout("fill:d:grow", "center:d:grow,top:4dlu:noGrow,center:d:noGrow,top:4dlu:noGrow,center:max(d;4px):noGrow,top:4dlu:noGrow,center:d:grow,top:4dlu:noGrow,center:max(d;4px):noGrow,top:4dlu:noGrow,center:max(d;4px):noGrow,top:4dlu:noGrow,center:d:grow,top:4dlu:noGrow,center:max(d;4px):noGrow,top:4dlu:noGrow,center:max(d;4px):noGrow,top:4dlu:noGrow,center:d:grow,top:4dlu:noGrow,center:max(d;4px):noGrow,top:4dlu:noGrow,center:d:grow")); + loginPanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5), null)); + lblName = new JLabel(); + lblName.setText("Name:"); + CellConstraints cc = new CellConstraints(); + loginPanel.add(lblName, cc.xy(1, 3, CellConstraints.CENTER, CellConstraints.DEFAULT)); + txtName = new JTextField(); + txtName.setColumns(15); + txtName.setText(""); + loginPanel.add(txtName, cc.xy(1, 5, CellConstraints.CENTER, CellConstraints.DEFAULT)); + lblAddress = new JLabel(); + lblAddress.setText("Address:"); + loginPanel.add(lblAddress, cc.xy(1, 9, CellConstraints.CENTER, CellConstraints.DEFAULT)); + txtAddress = new JTextField(); + txtAddress.setColumns(15); + loginPanel.add(txtAddress, cc.xy(1, 11, CellConstraints.CENTER, CellConstraints.DEFAULT)); + lblPort = new JLabel(); + lblPort.setText("Port:"); + loginPanel.add(lblPort, cc.xy(1, 15, CellConstraints.CENTER, CellConstraints.DEFAULT)); + txtPort = new JTextField(); + txtPort.setColumns(15); + loginPanel.add(txtPort, cc.xy(1, 17, CellConstraints.CENTER, CellConstraints.DEFAULT)); + submitButton = new JButton(); + submitButton.setText("Submit"); + loginPanel.add(submitButton, cc.xy(1, 21, CellConstraints.CENTER, CellConstraints.DEFAULT)); + final JSeparator separator1 = new JSeparator(); + separator1.setEnabled(true); + separator1.setVisible(false); + loginPanel.add(separator1, cc.xy(1, 1, CellConstraints.FILL, CellConstraints.FILL)); + final JSeparator separator2 = new JSeparator(); + separator2.setVisible(false); + loginPanel.add(separator2, cc.xy(1, 7, CellConstraints.FILL, CellConstraints.FILL)); + final JSeparator separator3 = new JSeparator(); + separator3.setVisible(false); + loginPanel.add(separator3, cc.xy(1, 13, CellConstraints.FILL, CellConstraints.FILL)); + final JSeparator separator4 = new JSeparator(); + separator4.setVisible(false); + loginPanel.add(separator4, cc.xy(1, 19, CellConstraints.FILL, CellConstraints.FILL)); + final JSeparator separator5 = new JSeparator(); + separator5.setVisible(false); + loginPanel.add(separator5, cc.xy(1, 23, CellConstraints.FILL, CellConstraints.FILL)); + lblName.setLabelFor(txtName); + lblAddress.setLabelFor(txtAddress); + lblPort.setLabelFor(txtPort); + } + + /** + * @noinspection ALL + */ + public JComponent $$$getRootComponent$$$() { + return loginPanel; + } +} diff --git a/src/main/java/com/fastchat/FastChat/client/gui/OnlineUsersGUI.form b/src/main/java/com/fastchat/FastChat/client/gui/OnlineUsersGUI.form new file mode 100644 index 0000000..5b463b6 --- /dev/null +++ b/src/main/java/com/fastchat/FastChat/client/gui/OnlineUsersGUI.form @@ -0,0 +1,26 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/src/main/java/com/fastchat/FastChat/client/gui/OnlineUsersGUI.java b/src/main/java/com/fastchat/FastChat/client/gui/OnlineUsersGUI.java new file mode 100644 index 0000000..a55d3fb --- /dev/null +++ b/src/main/java/com/fastchat/FastChat/client/gui/OnlineUsersGUI.java @@ -0,0 +1,77 @@ +package com.fastchat.FastChat.client.gui; + +import com.fastchat.FastChat.client.OnlineUsersInterface; +import com.fastchat.FastChat.util.Localization; + +import javax.swing.*; +import java.awt.*; + +public class OnlineUsersGUI extends OnlineUsersInterface { + private JFrame frame; + private JPanel panel; + private JList userList; + + { +// GUI initializer generated by IntelliJ IDEA GUI Designer +// >>> IMPORTANT!! <<< +// DO NOT EDIT OR ADD ANY CODE HERE! + $$$setupUI$$$(); + } + + OnlineUsersGUI() { + init(); + } + + @Override + protected void init() { + frame = new JFrame(); + frame.setType(JFrame.Type.UTILITY); + frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); + frame.setSize(278, 409); + frame.setLocationRelativeTo(null); + frame.setTitle(Localization.get("menu_bar_online_users")); + frame.setContentPane(panel); + } + + @Override + protected void update(String[] users) { + userList.setListData(users); + } + + @Override + protected void show() { + frame.setVisible(true); + } + + /** + * Method generated by IntelliJ IDEA GUI Designer + * >>> IMPORTANT!! <<< + * DO NOT edit this method OR call it in your code! + * + * @noinspection ALL + */ + private void $$$setupUI$$$() { + panel = new JPanel(); + panel.setLayout(new GridBagLayout()); + final JScrollPane scrollPane1 = new JScrollPane(); + GridBagConstraints gbc; + gbc = new GridBagConstraints(); + gbc.gridx = 0; + gbc.gridy = 0; + gbc.weightx = 1.0; + gbc.weighty = 1.0; + gbc.fill = GridBagConstraints.BOTH; + gbc.ipadx = 5; + gbc.ipady = 5; + panel.add(scrollPane1, gbc); + userList = new JList(); + scrollPane1.setViewportView(userList); + } + + /** + * @noinspection ALL + */ + public JComponent $$$getRootComponent$$$() { + return panel; + } +} diff --git a/src/main/java/com/fastchat/FastChat/client/package-info.java b/src/main/java/com/fastchat/FastChat/client/package-info.java new file mode 100644 index 0000000..7aa7cbb --- /dev/null +++ b/src/main/java/com/fastchat/FastChat/client/package-info.java @@ -0,0 +1 @@ +package com.fastchat.FastChat.client; \ No newline at end of file diff --git a/src/main/java/com/fastchat/FastChat/networking/NetworkCommand.java b/src/main/java/com/fastchat/FastChat/networking/NetworkCommand.java new file mode 100644 index 0000000..0da2023 --- /dev/null +++ b/src/main/java/com/fastchat/FastChat/networking/NetworkCommand.java @@ -0,0 +1,45 @@ +package com.fastchat.FastChat.networking; + +import com.fastchat.FastChat.util.EnhancedRunnable; + +/** + * A command that sends data through network + */ +public class NetworkCommand { + + private String name; + private String shortcut; + private EnhancedRunnable process; + + /** + * @param name to describe the command + * @param shortcut that is used in networking and as a key in registry + * @param process that is executed when command is executed + */ + NetworkCommand(String name, String shortcut, EnhancedRunnable process) { + this.name = name; + this.shortcut = shortcut; + this.process = process; + } + + /** + * Executes current network command with params + * @param args Arguments to pass to the command + * @throws RuntimeException if the command returned any errors + */ + public void exec(String... args) throws RuntimeException { + try { + process.run(args); + } catch (Exception e) { + throw new RuntimeException("Command '" + name + "' exited with error: " + e.getMessage()); + } + } + + public String getShortcut() { + return shortcut; + } + + public String getName() { + return name; + } +} diff --git a/src/main/java/com/fastchat/FastChat/networking/NetworkCommandRegistry.java b/src/main/java/com/fastchat/FastChat/networking/NetworkCommandRegistry.java new file mode 100644 index 0000000..1e7df06 --- /dev/null +++ b/src/main/java/com/fastchat/FastChat/networking/NetworkCommandRegistry.java @@ -0,0 +1,29 @@ +package com.fastchat.FastChat.networking; + +import java.util.HashMap; + +/** + * A registry to hold network commands + */ +public class NetworkCommandRegistry { + private HashMap registry = new HashMap<>(); + + /** + * adds commands to the registry, using command shortcut as a key + * @param commands Commands to be added to registry + */ + public void add(NetworkCommand... commands) { + for (NetworkCommand command : commands) { + registry.put(command.getShortcut(), command); + } + } + + /** + * Gets a network command based on the shortcut + * @param shortcut of a command in registry + * @return command with this shortcut or null if not found + */ + public NetworkCommand get(String shortcut) { + return registry.getOrDefault(shortcut, null); + } +} diff --git a/src/main/java/com/fastchat/FastChat/networking/Protocol.java b/src/main/java/com/fastchat/FastChat/networking/Protocol.java new file mode 100644 index 0000000..1e52679 --- /dev/null +++ b/src/main/java/com/fastchat/FastChat/networking/Protocol.java @@ -0,0 +1,40 @@ +package com.fastchat.FastChat.networking; + +import com.fastchat.FastChat.server.ServerClient; + +import java.util.function.Supplier; + +public class Protocol { + public static final Runnable IGNORE = () -> { + }; + + public static void process(ServerClient client, String message, NetworkCommandRegistry networkCommandRegistry, + Runnable onSuccess, Runnable onError) { + NetworkCommand cmd = message.length() >= 3 ? networkCommandRegistry.get(message.charAt(1) + "") : null; + //FIXME hardcoded crappy if statement should be replaced + if (cmd == null || message.charAt(0) != '/' || message.charAt(2) != '/') + onError.run(); + else { + onSuccess.run(); + if (client == null) + cmd.exec(message.substring(3)); + else + cmd.exec(message.substring(3), Integer.toString(client.getID())); + } + } + + public static void process(String message, NetworkCommandRegistry networkCommandRegistry, + Runnable onSuccess, Runnable onError) { + process(null, message, networkCommandRegistry, onSuccess, onError); + } + + public static Thread listen(Supplier isRunning, Runnable tick) { + return new Thread("Listen") { + public void run() { + while (isRunning.get()) { + tick.run(); + } + } + }; + } +} diff --git a/src/main/java/com/fastchat/FastChat/networking/ProtocolCommands.java b/src/main/java/com/fastchat/FastChat/networking/ProtocolCommands.java new file mode 100644 index 0000000..fe51dee --- /dev/null +++ b/src/main/java/com/fastchat/FastChat/networking/ProtocolCommands.java @@ -0,0 +1,59 @@ +package com.fastchat.FastChat.networking; + +import com.fastchat.FastChat.util.EnhancedRunnable; + +public class ProtocolCommands { + static String prefix(String shortcut) { + return "/" + shortcut + "/"; + } + + public static class Ping extends NetworkCommand { + static final String NAME = "ping"; + static final String SHORTCUT = "i"; + public static final String PREFIX = prefix(SHORTCUT); + + public Ping(EnhancedRunnable receive) { + super(NAME, SHORTCUT, receive); + } + } + + public static class Connect extends NetworkCommand { + static String NAME = "connect"; + static String SHORTCUT = "c"; + public static final String PREFIX = prefix(SHORTCUT); + + public Connect(EnhancedRunnable receive) { + super(NAME, SHORTCUT, receive); + } + } + + public static class Message extends NetworkCommand { + static String NAME = "message"; + static String SHORTCUT = "m"; + public static final String PREFIX = prefix(SHORTCUT); + + public Message(EnhancedRunnable receive) { + super(NAME, SHORTCUT, receive); + } + } + + public static class Disconnect extends NetworkCommand { + static String NAME = "disconnect"; + static String SHORTCUT = "d"; + public static final String PREFIX = prefix(SHORTCUT); + + public Disconnect(EnhancedRunnable receive) { + super(NAME, SHORTCUT, receive); + } + } + + public static class Users extends NetworkCommand { + static String NAME = "users"; + static String SHORTCUT = "u"; + public static final String PREFIX = prefix(SHORTCUT); + + public Users(EnhancedRunnable receive) { + super(NAME, SHORTCUT, receive); + } + } +} diff --git a/src/main/java/com/fastchat/FastChat/networking/package-info.java b/src/main/java/com/fastchat/FastChat/networking/package-info.java new file mode 100644 index 0000000..1a00977 --- /dev/null +++ b/src/main/java/com/fastchat/FastChat/networking/package-info.java @@ -0,0 +1,5 @@ +/** + * This package contains all functions that are used for network communication + * and a custom network protocol + */ +package com.fastchat.FastChat.networking; \ No newline at end of file diff --git a/src/main/java/com/fastchat/FastChat/package-info.java b/src/main/java/com/fastchat/FastChat/package-info.java new file mode 100644 index 0000000..3547d79 --- /dev/null +++ b/src/main/java/com/fastchat/FastChat/package-info.java @@ -0,0 +1 @@ +package com.fastchat.FastChat; \ No newline at end of file diff --git a/src/main/java/com/fastchat/FastChat/server/Server.java b/src/main/java/com/fastchat/FastChat/server/Server.java index 345c004..5ebf9f9 100644 --- a/src/main/java/com/fastchat/FastChat/server/Server.java +++ b/src/main/java/com/fastchat/FastChat/server/Server.java @@ -1,165 +1,99 @@ package com.fastchat.FastChat.server; +import com.fastchat.FastChat.networking.NetworkCommandRegistry; +import com.fastchat.FastChat.networking.Protocol; +import com.fastchat.FastChat.networking.ProtocolCommands; +import com.fastchat.FastChat.util.Command; +import com.fastchat.FastChat.util.CommandRegistry; + import java.io.IOException; -import java.net.*; -import java.util.ArrayList; -import java.util.List; +import java.net.InetAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.util.Arrays; +import java.util.HashMap; import java.util.NoSuchElementException; import java.util.Scanner; public class Server implements Runnable { - private final int MAX_ATTEMPTS = 4; - private static final String[] commands = {"clients", /*"cls",*/ "help", "kick", "raw", "exit"}; - private List clients = new ArrayList<>(); + private static final String CARET = "> "; - private DatagramSocket socket; + private final CommandRegistry commandRegistry = new CommandRegistry(); + private final NetworkCommandRegistry networkCommandRegistry = new NetworkCommandRegistry(); + private final HashMap clients = new HashMap<>(); - private int port; - private ArrayList response = new ArrayList<>(); + private final int port; private boolean running; private boolean raw; + private ServerSocket serverSocket; @SuppressWarnings("FieldCanBeLocal") - private Thread run, manage, send, receive; + private Thread run, manage, receive, console; + + private enum Status { + DISCONNECTED, TIMED_OUT, KICKED, BANNED + } + /** + * @param port + * @param address + */ public Server(int port, String address) { System.out.printf("Server started at %s:%d\n", address, port); this.port = port; try { - socket = new DatagramSocket(this.port, InetAddress.getByName(address)); - } catch (SocketException | UnknownHostException e) { + serverSocket = new ServerSocket(port, 100, InetAddress.getByName(address)); + } catch (IOException e) { e.printStackTrace(); return; } run = new Thread(this, "Server"); + initCommands(); run.start(); } + /** + * + */ public void run() { running = true; manageClients(); receive(); - Scanner in = new Scanner(System.in); - System.out.println("Type /help to get some information"); - while (running) { - System.out.print("> "); - String text; - - try { - text = in.nextLine(); - } catch (NoSuchElementException e) { - // Ctrl-D used - System.exit(0); - continue; - } - - if (!text.startsWith("/")) { - sendToAll("/m/Server: " + text + "/e/"); - System.out.println("/m/Server: " + text + "/e/"); - continue; - } - - text = text.substring(1); - command(text); - } - if (!running) in.close(); + startConsole(); + console("Type /help to get more information"); } + /** + * @param text command name with arguments + */ private void command(String text) { - switch (text.split(" ")[0]) { - case "raw": - raw = !raw; - System.out.println((raw ? "Raw mode enabled" : "Raw mode disabled")); - break; -// case "cls": -// System.out.flush(); -// Process p; -// try { -// p = Runtime.getRuntime().exec("cls"); -// p.waitFor(); -// } catch (IOException | InterruptedException e) { -// System.err.println("Cannot run this command"); -// } -// break; - case "clients": - System.out.println("Clients:"); - System.out.println("==================================="); - for (ServerClient serverClient : clients) { - System.out.println(serverClient); - } - System.out.println("==================================="); - break; - case "kick": - String name = text.split(" ")[1]; - int id = -1; - boolean number = true; - try { - id = Integer.parseInt(name); - } catch (NumberFormatException e) { - number = false; - } - if (number) { - boolean exists = false; - for (ServerClient client : clients) { - if (client.getID() == id) { - exists = true; - break; - } - } - if (exists) - disconnectClient(id, Status.KICKED); - else - System.out.println("Client with id " + id + " does not exist"); - } else { - for (ServerClient client : clients) { - if (name.equals(client.name)) { - disconnectClient(client.getID(), Status.KICKED); - break; - } - } - } - break; - case "help": - System.out.println("Available commands: "); - for (String command : commands) { - System.out.println("/" + command); - } - break; - case "exit": - System.out.print("Do you really want to exit? (y/n) "); - Scanner input = new Scanner(System.in); - char option = input.nextLine().toLowerCase().charAt(0); - if (option == 'y') { - System.exit(0); - } - break; - default: - System.out.println("Command does not exist!"); + String[] args = text.split(" "); + String commandName = args[0]; + args = Arrays.copyOfRange(args, 1, args.length); + try { + commandRegistry.run(commandName, args); + } catch (IllegalArgumentException e) { + System.out.println("Command \"" + commandName + "\" doesn't exist"); } - } + /** + * + */ private void manageClients() { manage = new Thread("Manage") { public void run() { while (running) { - sendToAll("/i/"); try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } - for (ServerClient c : clients) { - if (!response.contains(c.getID())) { - if (c.attempt >= MAX_ATTEMPTS) { + synchronized (clients) { + clients.forEach((id, c) -> { + if (c.getSocket().isClosed()) disconnectClient(c.getID(), Status.TIMED_OUT); - } else { - ++c.attempt; - } - } else { - response.remove(new Integer(c.getID())); - c.attempt = 0; - } + }); } } } @@ -167,112 +101,249 @@ public void run() { manage.start(); } - private void process(DatagramPacket packet) { - String string = new String(packet.getData()); - string = string.split("/e/")[0]; - if (raw) System.out.println(string); - if (string.startsWith("/c/")) { - String name = string.substring(3); - ServerClient client = new ServerClient(name, packet.getAddress(), packet.getPort()); - clients.add(client); - System.out.println(client + " has connected"); - sendToAll(client.name + "(" + client.getID() + ") has connected"); - send("/c/" + client.getID(), client.ip, client.port); - } else if (string.startsWith("/m/")) { - System.out.println(string); - sendToAll(string); - } else if (string.startsWith("/d/")) { - int id = Integer.parseInt(string.substring(3)); - disconnectClient(id, Status.DISCONNECTED); - } else if (string.startsWith("/i/")) { - response.add(Integer.parseInt(string.substring(3))); - } else if (string.startsWith("/u/")) { - StringBuilder message = new StringBuilder("/u/"); - for (ServerClient client : clients) { - message.append(client.name).append("(").append(client.getID()).append(")//"); - } - send(message.toString(), packet.getAddress(), packet.getPort()); - } else { - System.out.println(string); - } - } - private void receive() { - receive = new Thread("Receive") { - public void run() { - while (running) { - byte[] data = new byte[1024]; - DatagramPacket packet = new DatagramPacket(data, data.length); - try { - socket.receive(packet); - } catch (IOException e) { - e.printStackTrace(); - } - process(packet); - } - } - }; + receive = Protocol.listen(() -> running, this::tick); receive.start(); } - private void send(final byte[] data, final InetAddress address, final int port) { - send = new Thread("Send") { - public void run() { - DatagramPacket packet = new DatagramPacket(data, data.length, address, port); + private void tick() { + try { + Socket socket = serverSocket.accept(); + ServerClient client = new ServerClient(socket); + clients.put(client.getID(), client); + getClientThread(client).start(); + } catch (IOException e) { + System.err.println("I/O error: " + e); + } +// DatagramPacket packet = Protocol.receive(clientSocket); +// String string = Protocol.parsePacket(packet); +// +// Protocol.process(packet, networkCommandRegistry, +// () -> { +// if (raw) System.out.println(string); +// }, +// () -> System.out.println("[Err]: " + string) +// ); + } + + private void startConsole() { + console = new Thread(() -> { + Scanner in = new Scanner(System.in); + while (running) { + String text; + try { - socket.send(packet); - } catch (IOException e) { - e.printStackTrace(); + text = in.nextLine(); + } catch (NoSuchElementException e) { + // Ctrl-D used + System.exit(0); + continue; } + + if (!text.startsWith("/")) { + sendToAll("Server: " + text); + console("Server: " + text); + continue; + } + + text = text.substring(1); + command(text); } - }; - send.start(); + if (!running) in.close(); + }, "Console"); + console.start(); + } + + private void send(String message, ServerClient client) { + client.getOut().println(message); + client.getOut().flush(); } - private void send(String message, InetAddress ip, int port) { - message += "/e/"; - send(message.getBytes(), ip, port); + private void console(Object message) { + System.out.print("\r" + message.toString() + "\n" + CARET); } private void sendToAll(String string) { - for (ServerClient client : clients) { - send(string, client.ip, client.port); - } + clients.forEach((id, client) -> send(ProtocolCommands.Message.PREFIX + string, client)); } private void disconnectClient(int id, Status status) { - for (ServerClient client : clients) { - if (client.getID() == id) { - String disc = client.name + "(" + client.getID() + ") "; - switch (status) { - case DISCONNECTED: - disc += "disconnected."; - break; - case TIMED_OUT: - disc += "was timed out."; - break; - case KICKED: - send("/d/0", client.ip, client.port); - disc += "was kicked out of server."; - break; - case BANNED: - disc += "was banned!"; - break; - } - clients.remove(client); - ServerClient.removeId(id); - System.out.println(disc); - sendToAll(disc); + ServerClient client = clients.getOrDefault(id, null); + if (client == null) { + return; + } + String disc = client.getName() + "(" + client.getID() + ") "; + switch (status) { + case DISCONNECTED: + disc += "disconnected."; break; - } + case TIMED_OUT: + disc += "was timed out."; + break; + case KICKED: + send(ProtocolCommands.Disconnect.PREFIX + "0", client); + disc += "was kicked out of server."; + break; + case BANNED: + disc += "was banned!"; + break; + } + try { + client.getSocket().close(); + } catch (IOException e) { + e.printStackTrace(); } + synchronized (clients) { + clients.remove(id); + } + ServerClient.removeId(id); + console(disc); + sendToAll(disc); + } public int getPort() { return port; } - private enum Status { - DISCONNECTED, TIMED_OUT, KICKED, BANNED + private void initCommands() { + + commandRegistry.add( + new Command("exit", (argv) -> { + System.out.print("Do you really want to exit? (y/n) "); + Scanner input = new Scanner(System.in); + char option = input.nextLine().toLowerCase().charAt(0); + if (option == 'y') { + System.exit(0); + } + return null; + }), + new Command("help", (argv) -> { + console("Available commands: "); + commandRegistry.getRegistry().forEach((name, c) -> + console("/" + name + (c.getHelp().isEmpty() ? "" : " - " + c.getHelp())) + ); + return null; + }), + new Command("clients", "prints the list of clients", (argv) -> { + console("Clients:"); + console("==================================="); + clients.forEach((id, client) -> console(client)); + console("==================================="); + return null; + }), + new Command("kick", "kick the list of clients", (argv) -> { + for (String name : argv) { + + int id = -1; + try { + id = Integer.parseInt(name); + } catch (NumberFormatException ignored) { + } + if (clients.containsKey(id)) + disconnectClient(id, Status.KICKED); + else + console("Client with id " + id + " does not exist"); + } + return null; + }), + new Command("raw", "", (argv) -> { + raw = !raw; + console((raw ? "Raw mode enabled" : "Raw mode disabled")); + return null; + }) + ); + + networkCommandRegistry.add( +// new ProtocolCommands.Connect((args) -> { +// if (args.length < 3) return; +// String data = args[0]; +// InetAddress address; +// try { +// address = InetAddress.getByName(args[1]); +// } catch (UnknownHostException e) { +// System.err.println("Couldn't send connection confirm to " + args[1]); +// return; +// } +// int port = Integer.parseInt(args[2]); +// String name = data.substring(3); +// ServerClient client = null; +// try { +// client = new ServerClient(name, address, port, null); +// } catch (IOException e) { +// e.printStackTrace(); +// return; +// } +// synchronized (clients) { +// clients.add(client); +// } +// }), + new ProtocolCommands.Message((args) -> { + if (args.length <= 0) return; + String data = args[0]; + int id = Integer.parseInt(args[1]); + if (clients.containsKey(id)) { + data = clients.get(id) + ":" + data; + console(data); + sendToAll(data); + } + }), + new ProtocolCommands.Disconnect((args) -> { + if (args.length < 2) return; + int id = Integer.parseInt(args[1]); + disconnectClient(id, Status.DISCONNECTED); + }), + new ProtocolCommands.Users((args) -> { + if (args.length < 2) return; + int id = Integer.parseInt(args[1]); + ServerClient current = clients.getOrDefault(id, null); + if (current == null) return; + StringBuilder message = new StringBuilder(ProtocolCommands.Users.PREFIX); + clients.forEach((key, client) -> + message.append(client.getName()) + .append("(") + .append(client.getID()) + .append(")//")); + send(message.toString(), current); + }) + ); + + } + + /** + * Creates a thread for connection with client + * + * @param client that has connected + * @return thread that keeps the connection with client + */ + private Thread getClientThread(ServerClient client) { + return new Thread(() -> { + try { + client.setName(client.getReader().readLine()); + } catch (IOException e) { + e.printStackTrace(); + return; + } + + console(client + " has connected"); + sendToAll(client.getName() + "(" + client.getID() + ") has connected"); + send(ProtocolCommands.Connect.PREFIX + client.getID(), client); + + while (!client.getSocket().isClosed()) { + try { + String line = client.getReader().readLine(); + if (line == null) return; + Protocol.process(client, line, networkCommandRegistry, + () -> { + if (raw) console(line); + }, + () -> console("[Err]: " + line) + ); + } catch (IOException e) { + e.printStackTrace(); + return; + } + } + }); } -} +} \ No newline at end of file diff --git a/src/main/java/com/fastchat/FastChat/server/ServerClient.java b/src/main/java/com/fastchat/FastChat/server/ServerClient.java index e67fcf7..99b9c0b 100644 --- a/src/main/java/com/fastchat/FastChat/server/ServerClient.java +++ b/src/main/java/com/fastchat/FastChat/server/ServerClient.java @@ -1,30 +1,33 @@ package com.fastchat.FastChat.server; +import com.fastchat.FastChat.beans.User; + +import java.io.IOException; import java.net.InetAddress; +import java.net.Socket; import java.util.ArrayList; import java.util.Random; -public class ServerClient { - - public String name; - public InetAddress ip; - public int port; - private final int ID; - public int attempt = 0; - private static ArrayList identifiers = new ArrayList(); - - public int getID() { - return ID; +public class ServerClient extends User { + + private static final ArrayList identifiers = new ArrayList<>(); + + private ServerClient(String name, InetAddress ip, int port, Socket socket) throws IOException { + setName(name); + setPort(port); + setIp(ip); + setSocket(socket); + setID(createId()); } - - public ServerClient(String name, InetAddress ip, int port) { - super(); - this.name = name; - this.ip = ip; - this.port = port; - this.ID = createId(); + + ServerClient(Socket socket) throws IOException { + this(socket.getInetAddress().getHostAddress() + ":" + socket.getPort(), socket); } - + + private ServerClient(String name, Socket socket) throws IOException { + this(name, socket.getInetAddress(), socket.getPort(), socket); + } + private static int createId() { int id = new Random().nextInt(10000); if (!(identifiers.contains(id) || id <= 0)) { @@ -34,18 +37,19 @@ private static int createId() { return createId(); } } - + public static void removeId(int id) { for (Integer integer : identifiers) { if (integer.equals(id)) { identifiers.remove(integer); - return; + break; } } } - + @Override public String toString() { - return (name != null ? name : "NONAME") + "(" + ID + ")" + (ip != null ? ip + ":" : "NOADDRESS") + port; + return (getName() != null ? getName() : "NONAME") + "(" + getID() + ")"; +// + (ip != null ? ip + ":" : "NOADDRESS") + port; } } diff --git a/src/main/java/com/fastchat/FastChat/server/package-info.java b/src/main/java/com/fastchat/FastChat/server/package-info.java new file mode 100644 index 0000000..29fb349 --- /dev/null +++ b/src/main/java/com/fastchat/FastChat/server/package-info.java @@ -0,0 +1 @@ +package com.fastchat.FastChat.server; \ No newline at end of file diff --git a/src/main/java/com/fastchat/FastChat/util/Command.java b/src/main/java/com/fastchat/FastChat/util/Command.java new file mode 100644 index 0000000..58535da --- /dev/null +++ b/src/main/java/com/fastchat/FastChat/util/Command.java @@ -0,0 +1,54 @@ +package com.fastchat.FastChat.util; + + +public class Command { + private final String help; + private final String name; + + //it's Interface created for body + private final EnhancedCallable body; + + + public EnhancedCallable getBody() { + return body; + } + + + /** + * @param name a name of the command + * @param body body of the command + */ + public Command(String name, EnhancedCallable body) { + this(name, "", body); + } + + public Command(String name, String help, EnhancedCallable body) { + this.name = name; + this.help = help; + this.body = body; + } + + /** + * @param args command arguments + */ + public void exec(String... args) { + try { + body.call(args); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public String getHelp() { + return help; + } + + public String getName() { + return name; + } + + +} + + + diff --git a/src/main/java/com/fastchat/FastChat/util/CommandRegistry.java b/src/main/java/com/fastchat/FastChat/util/CommandRegistry.java new file mode 100644 index 0000000..ad10b9a --- /dev/null +++ b/src/main/java/com/fastchat/FastChat/util/CommandRegistry.java @@ -0,0 +1,26 @@ +package com.fastchat.FastChat.util; + +import java.util.HashMap; + +public class CommandRegistry { + private final HashMap registry = new HashMap<>(); + + public void add(Command... commands) { + for (Command command : commands) + registry.put(command.getName(), command); + } + + public void run(String name, String... args) throws IllegalArgumentException { + Command c = registry.get(name); + + if (c != null) { + c.exec(args); + } else { + throw new IllegalArgumentException("Command does not exist!"); + } + } + + public HashMap getRegistry() { + return registry; + } +} diff --git a/src/main/java/com/fastchat/FastChat/util/EnhancedCallable.java b/src/main/java/com/fastchat/FastChat/util/EnhancedCallable.java new file mode 100644 index 0000000..ed006c2 --- /dev/null +++ b/src/main/java/com/fastchat/FastChat/util/EnhancedCallable.java @@ -0,0 +1,11 @@ +package com.fastchat.FastChat.util; + +/** + * + * @param return type + * @param arguments to be put as varargs + */ +@FunctionalInterface +public interface EnhancedCallable { + R call(A... args); +} diff --git a/src/main/java/com/fastchat/FastChat/util/EnhancedRunnable.java b/src/main/java/com/fastchat/FastChat/util/EnhancedRunnable.java new file mode 100644 index 0000000..a486337 --- /dev/null +++ b/src/main/java/com/fastchat/FastChat/util/EnhancedRunnable.java @@ -0,0 +1,10 @@ +package com.fastchat.FastChat.util; + +/** + * @param arguments to be put as varargs + */ +@FunctionalInterface +public interface EnhancedRunnable { + void run(A... args); +} + diff --git a/src/main/java/com/fastchat/FastChat/util/Localization.java b/src/main/java/com/fastchat/FastChat/util/Localization.java new file mode 100644 index 0000000..cb16730 --- /dev/null +++ b/src/main/java/com/fastchat/FastChat/util/Localization.java @@ -0,0 +1,56 @@ +package com.fastchat.FastChat.util; + +import java.util.Locale; +import java.util.MissingResourceException; +import java.util.ResourceBundle; + +public class Localization { + + private static final ResourceBundle DEFAULT_BUNDLE = ResourceBundle.getBundle("default", new UTF8Control()); + private static String bundleName = "default"; + private static ResourceBundle bundle = ResourceBundle.getBundle(bundleName, new UTF8Control()); + + public static String get(String key) { + String result = ""; + try { + result = bundle.getString(key); + } catch (MissingResourceException ex) { + try { + result = DEFAULT_BUNDLE.getString(key); + } catch (MissingResourceException ignore) { + } + } + return result; + } + + private static void setBundle(String bundleNewName) { + if (!bundleNewName.equals(bundleName)) { + + if (Localization.class.getResource(String.format("/%s.properties", bundleNewName)) != null) { + bundleName = bundleNewName; + bundle = ResourceBundle.getBundle(bundleName, new UTF8Control()); + } else { + bundleName = "default"; + bundle = DEFAULT_BUNDLE; + } + } + } + + public static void autoSetLocale() { + String language = System.getProperty("user.language"); + setLocale(new Locale(language)); + } + + private static void setLocale(Locale locale) { + switch (locale.getLanguage()) { + case "uk": + setBundle("ukrainian"); + break; + case "nb": + setBundle("norwegian"); + break; + default: + setBundle("default"); + } + } +} diff --git a/src/main/java/com/fastchat/FastChat/util/UTF8Control.java b/src/main/java/com/fastchat/FastChat/util/UTF8Control.java new file mode 100644 index 0000000..14fa159 --- /dev/null +++ b/src/main/java/com/fastchat/FastChat/util/UTF8Control.java @@ -0,0 +1,43 @@ +package com.fastchat.FastChat.util; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.URL; +import java.net.URLConnection; +import java.util.Locale; +import java.util.PropertyResourceBundle; +import java.util.ResourceBundle; + +class UTF8Control extends ResourceBundle.Control { + public ResourceBundle newBundle(String baseName, Locale locale, String format, + ClassLoader loader, boolean reload) throws IOException { + + // The below is a copy of the default implementation. + String bundleName = toBundleName(baseName, locale); + String resourceName = toResourceName(bundleName, "properties"); + ResourceBundle bundle = null; + InputStream stream = null; + if (reload) { + URL url = loader.getResource(resourceName); + if (url != null) { + URLConnection connection = url.openConnection(); + if (connection != null) { + connection.setUseCaches(false); + stream = connection.getInputStream(); + } + } + } else { + stream = loader.getResourceAsStream(resourceName); + } + if (stream != null) { + try { + // Only this line is changed to make it to read properties files as UTF-8. + bundle = new PropertyResourceBundle(new InputStreamReader(stream, "UTF-8")); + } finally { + stream.close(); + } + } + return bundle; + } +} \ No newline at end of file diff --git a/src/main/java/com/fastchat/FastChat/util/package-info.java b/src/main/java/com/fastchat/FastChat/util/package-info.java new file mode 100644 index 0000000..7117c08 --- /dev/null +++ b/src/main/java/com/fastchat/FastChat/util/package-info.java @@ -0,0 +1 @@ +package com.fastchat.FastChat.util; \ No newline at end of file diff --git a/src/main/resources/FastChatLogo.png b/src/main/resources/FastChatLogo.png new file mode 100644 index 0000000..d329695 Binary files /dev/null and b/src/main/resources/FastChatLogo.png differ diff --git a/src/main/resources/FastChatLogo_filled.png b/src/main/resources/FastChatLogo_filled.png new file mode 100644 index 0000000..65243c7 Binary files /dev/null and b/src/main/resources/FastChatLogo_filled.png differ diff --git a/src/main/resources/default.properties b/src/main/resources/default.properties new file mode 100644 index 0000000..335c963 --- /dev/null +++ b/src/main/resources/default.properties @@ -0,0 +1,11 @@ +send_btn_text=Send +window_title=FastChat +menu_bar_menu=Menu +menu_bar_online_users=Online Users +menu_bar_settings=Settings +client_attempting_to_connect=Attempting to conect to +client_connection_failed=Connection failed! +login_lbl_name=Name +login_lbl_address=Address (IP) +login_lbl_port=Port +login_submit=Submit diff --git a/src/main/resources/norwegian.properties b/src/main/resources/norwegian.properties new file mode 100644 index 0000000..56aef24 --- /dev/null +++ b/src/main/resources/norwegian.properties @@ -0,0 +1,11 @@ +#send_btn_text=Send +window_title=FastChat (RaskChat) +menu_bar_menu=Meny +#menu_bar_online_users=Online Users +menu_bar_settings=Instillinger +#client_attempting_to_connect=Attempting to conect to +#client_connection_failed=Connection failed! +#login_lbl_name=Name +#login_lbl_address=Address (IP) +#login_lbl_port=Port +#login_submit=Submit \ No newline at end of file diff --git a/src/main/resources/ukrainian.properties b/src/main/resources/ukrainian.properties new file mode 100644 index 0000000..2a8d8aa --- /dev/null +++ b/src/main/resources/ukrainian.properties @@ -0,0 +1,11 @@ +send_btn_text=Надіслати +window_title=FastChat (ШвидкоЧат) +menu_bar_menu=Меню +menu_bar_online_users=Користувачі онлайн +menu_bar_settings=Налаштування +client_attempting_to_connect=Спроба під'єднання до +client_connection_failed=Не вдалося під'єднатися! +login_lbl_name=Ім'я +login_lbl_address=Адреса (IP) +login_lbl_port=Порт +login_submit=Відправити diff --git a/src/test/java/util/CommandRegistryTest.java b/src/test/java/util/CommandRegistryTest.java new file mode 100644 index 0000000..f18e2c1 --- /dev/null +++ b/src/test/java/util/CommandRegistryTest.java @@ -0,0 +1,4 @@ +package util; + +class CommandRegistryTest { +} diff --git a/src/test/java/util/CommandTest.java b/src/test/java/util/CommandTest.java new file mode 100644 index 0000000..2b50dc2 --- /dev/null +++ b/src/test/java/util/CommandTest.java @@ -0,0 +1,65 @@ +package util; + +import com.fastchat.FastChat.util.Command; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +import java.util.concurrent.atomic.AtomicBoolean; + +import static org.testng.Assert.*; + + +public class CommandTest { + + private final Command command = new Command("help", "", (argv) -> { + System.out.print("Do you really need help? "); + return "Do you really need help? "; + }); + + @Test + public void CommandNotNull() { + assertNotNull(command.getName()); + assertNotNull(command.getHelp()); + assertNotNull(command.getBody()); + } + + @Test(dataProvider = "names", dataProviderClass = CommandTestData.class) + public void TestCommandName(String name, Command command) { + assertEquals(name, command.getName()); + } + + @Test + public void CommandExecutedCorrectly() { + AtomicBoolean variable = new AtomicBoolean(false); + Command changesTheVariable = new Command("change_var", args -> { + variable.set(true); + return null; + }); + + assertFalse(variable.get()); + changesTheVariable.exec(); + assertTrue(variable.get()); + } + +/* + //Runs all tests in this class + public static void main (String [] arg) { + ITestNGListener tla = new TestListenerAdapter(); + TestNG testng = new TestNG(); + testng.setTestClasses(new Class[] { CommandTest.class }); + testng.addListener(tla); + testng.setVerbose(2); + testng.run(); + } +*/ +} + +class CommandTestData { + @DataProvider(name = "names") + public static Object[][] testCmdName() { + return new Object[][]{ + {"help", new Command("help", "", null)}, + {"", new Command("", "", null)} + }; + } +}