From 0dcfca4937234150467a9c5f8bfb3b25d5bbf4b1 Mon Sep 17 00:00:00 2001 From: Cavailles Kevin Date: Sun, 31 Jan 2021 16:33:59 +0100 Subject: [PATCH] source files for the broadcast version --- .../filetransfer/FileTransferClient.java | 41 ++ .../FileTransferReceivingThread.java | 82 +++ .../FileTransferSendingThread.java | 75 +++ .../filetransfer/FileTransferServer.java | 46 ++ .../filetransfer/FileTransferUtils.java | 120 ++++ src_reseau/communication/tcp/TCPClient.java | 73 +++ .../communication/tcp/TCPInputThread.java | 72 +++ src_reseau/communication/tcp/TCPServer.java | 42 ++ .../communication/udp/CommunicationUDP.java | 176 ++++++ src_reseau/communication/udp/UDPClient.java | 46 ++ src_reseau/communication/udp/UDPServer.java | 87 +++ src_reseau/connexion/ControleurConnexion.java | 142 +++++ src_reseau/connexion/VueConnexion.java | 123 ++++ src_reseau/database/SQLiteCreateTables.java | 73 +++ src_reseau/database/SQLiteEncprytion.java | 84 +++ src_reseau/database/SQLiteManager.java | 562 ++++++++++++++++++ src_reseau/main/Main.java | 37 ++ src_reseau/main/Utilisateur.java | 53 ++ src_reseau/main/Vue.java | 19 + .../messages/MauvaisTypeMessageException.java | 8 + src_reseau/messages/Message.java | 96 +++ src_reseau/messages/MessageFichier.java | 46 ++ src_reseau/messages/MessageSysteme.java | 39 ++ src_reseau/messages/MessageTexte.java | 33 + .../observers/ObserverInputMessage.java | 5 + src_reseau/observers/ObserverSocketState.java | 7 + src_reseau/observers/ObserverUserList.java | 11 + src_reseau/session/ControleurSession.java | 279 +++++++++ src_reseau/session/VueSession.java | 264 ++++++++ src_reseau/standard/ControleurStandard.java | 318 ++++++++++ src_reseau/standard/VueStandard.java | 373 ++++++++++++ 31 files changed, 3432 insertions(+) create mode 100644 src_reseau/communication/filetransfer/FileTransferClient.java create mode 100644 src_reseau/communication/filetransfer/FileTransferReceivingThread.java create mode 100644 src_reseau/communication/filetransfer/FileTransferSendingThread.java create mode 100644 src_reseau/communication/filetransfer/FileTransferServer.java create mode 100644 src_reseau/communication/filetransfer/FileTransferUtils.java create mode 100644 src_reseau/communication/tcp/TCPClient.java create mode 100644 src_reseau/communication/tcp/TCPInputThread.java create mode 100644 src_reseau/communication/tcp/TCPServer.java create mode 100644 src_reseau/communication/udp/CommunicationUDP.java create mode 100644 src_reseau/communication/udp/UDPClient.java create mode 100644 src_reseau/communication/udp/UDPServer.java create mode 100644 src_reseau/connexion/ControleurConnexion.java create mode 100644 src_reseau/connexion/VueConnexion.java create mode 100644 src_reseau/database/SQLiteCreateTables.java create mode 100644 src_reseau/database/SQLiteEncprytion.java create mode 100644 src_reseau/database/SQLiteManager.java create mode 100644 src_reseau/main/Main.java create mode 100644 src_reseau/main/Utilisateur.java create mode 100644 src_reseau/main/Vue.java create mode 100644 src_reseau/messages/MauvaisTypeMessageException.java create mode 100644 src_reseau/messages/Message.java create mode 100644 src_reseau/messages/MessageFichier.java create mode 100644 src_reseau/messages/MessageSysteme.java create mode 100644 src_reseau/messages/MessageTexte.java create mode 100644 src_reseau/observers/ObserverInputMessage.java create mode 100644 src_reseau/observers/ObserverSocketState.java create mode 100644 src_reseau/observers/ObserverUserList.java create mode 100644 src_reseau/session/ControleurSession.java create mode 100644 src_reseau/session/VueSession.java create mode 100644 src_reseau/standard/ControleurStandard.java create mode 100644 src_reseau/standard/VueStandard.java diff --git a/src_reseau/communication/filetransfer/FileTransferClient.java b/src_reseau/communication/filetransfer/FileTransferClient.java new file mode 100644 index 0000000..ec87b4a --- /dev/null +++ b/src_reseau/communication/filetransfer/FileTransferClient.java @@ -0,0 +1,41 @@ +package communication.filetransfer; + + +import java.io.File; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.UnknownHostException; + +import java.util.ArrayList; + +import observers.ObserverInputMessage; + + + + +public class FileTransferClient { + + private int portOther; + private InetAddress addrOther; + private ArrayList files = null; + private ObserverInputMessage obsInput; + + + public FileTransferClient(InetAddress addrOther, int portOther, ArrayList filesToSend, ObserverInputMessage obs) throws UnknownHostException, IOException { + this.addrOther = addrOther; + this.portOther = portOther; + this.files = filesToSend; + this.obsInput = obs; + } + + public void sendFiles() throws IOException, InterruptedException { + for(File f: this.files) { + FileTransferSendingThread ftc = new FileTransferSendingThread(this.addrOther, this.portOther, f,this.obsInput); + ftc.start(); + ftc.join(); + } + + } + +} diff --git a/src_reseau/communication/filetransfer/FileTransferReceivingThread.java b/src_reseau/communication/filetransfer/FileTransferReceivingThread.java new file mode 100644 index 0000000..75f3062 --- /dev/null +++ b/src_reseau/communication/filetransfer/FileTransferReceivingThread.java @@ -0,0 +1,82 @@ +package communication.filetransfer; + + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; + +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.channels.SocketChannel; + +import messages.Message; +import messages.MessageFichier; +import observers.ObserverInputMessage; + +public class FileTransferReceivingThread extends Thread { + + private SocketChannel sockTransfert; + private ObserverInputMessage obsInput; + + public FileTransferReceivingThread(SocketChannel sock, ObserverInputMessage obs) { + this.sockTransfert = sock; + this.obsInput = obs; + } + + public void run() { + try { + int nbByteRead = 0; + ByteBuffer fileData = ByteBuffer.allocate(4 * FileTransferUtils.KB_SIZE); + + ObjectInputStream inputFileInformation = new ObjectInputStream( + this.sockTransfert.socket().getInputStream()); + + int nbTotalBytesRead; + + nbTotalBytesRead = 0; + + Object o = inputFileInformation.readObject(); + MessageFichier m = (MessageFichier) o; + String[] fileInfo = this.processFileInformation(m); + String filePath = FileTransferUtils.DOWNLOADS_RELATIVE_PATH + fileInfo[0]; + long fileSize = Long.parseLong(fileInfo[1]); + + + FileOutputStream fOutStream = new FileOutputStream(filePath); + + FileChannel fileWriter = fOutStream.getChannel(); + + while (nbTotalBytesRead < fileSize && (nbByteRead = this.sockTransfert.read(fileData)) > 0) { + fileData.flip(); + fileWriter.write(fileData); + fileData.clear(); + + nbTotalBytesRead += nbByteRead; + + } + + fileWriter.close(); + fOutStream.close(); + + Message mUpdate = FileTransferUtils.processMessageToDisplay(new File(filePath)); + mUpdate.setSender("other"); + this.obsInput.update(this, mUpdate); + + + } catch (IOException | ClassNotFoundException e) { + e.printStackTrace(); + } finally { + try { + this.sockTransfert.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + private String[] processFileInformation(MessageFichier m) { + return m.getContenu().split(";"); + } + +} diff --git a/src_reseau/communication/filetransfer/FileTransferSendingThread.java b/src_reseau/communication/filetransfer/FileTransferSendingThread.java new file mode 100644 index 0000000..aaddc24 --- /dev/null +++ b/src_reseau/communication/filetransfer/FileTransferSendingThread.java @@ -0,0 +1,75 @@ +package communication.filetransfer; + +import java.io.File; +import java.io.IOException; + +import java.io.ObjectOutputStream; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.channels.SocketChannel; +import java.nio.file.Paths; + +import messages.MauvaisTypeMessageException; +import messages.Message; +import messages.MessageFichier; +import messages.Message.TypeMessage; +import observers.ObserverInputMessage; + +public class FileTransferSendingThread extends Thread{ + + private SocketChannel sockTransfert; + private File file; + private ObserverInputMessage obsInput; + + public FileTransferSendingThread(InetAddress addrOther, int port, File fileToSend, ObserverInputMessage obs) throws IOException { + SocketChannel sock = SocketChannel.open(); + SocketAddress addr = new InetSocketAddress(addrOther, port); + sock.connect(addr); + this.sockTransfert = sock; + this.file = fileToSend; + this.obsInput = obs; + } + + public void run() { + try { + + ByteBuffer fileData = ByteBuffer.allocate(4 * FileTransferUtils.KB_SIZE); + + ObjectOutputStream outputFileInformation = new ObjectOutputStream( + this.sockTransfert.socket().getOutputStream()); + + FileChannel fileReader = FileChannel.open(Paths.get(file.getPath())); + String str = file.getName() + ";" + file.getTotalSpace(); + + // Send file datas (name + size); + outputFileInformation.writeObject(new MessageFichier(TypeMessage.FICHIER, str, "")); + + while (fileReader.read(fileData) > 0) { + fileData.flip(); + this.sockTransfert.write(fileData); + fileData.clear(); + } + + fileReader.close(); + + Message mUpdate = FileTransferUtils.processMessageToDisplay(this.file); + mUpdate.setSender("Moi"); + this.obsInput.update(this, mUpdate); + + } catch (IOException | MauvaisTypeMessageException e) { + e.printStackTrace(); + } finally { + try { + this.sockTransfert.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + + +} diff --git a/src_reseau/communication/filetransfer/FileTransferServer.java b/src_reseau/communication/filetransfer/FileTransferServer.java new file mode 100644 index 0000000..e16db67 --- /dev/null +++ b/src_reseau/communication/filetransfer/FileTransferServer.java @@ -0,0 +1,46 @@ +package communication.filetransfer; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.UnknownHostException; +import java.nio.channels.ServerSocketChannel; +import java.nio.channels.SocketChannel; + +import observers.ObserverInputMessage; + + +public class FileTransferServer extends Thread { + + private ServerSocketChannel sockFTListen; + private int nbFile; + private ObserverInputMessage obsInput; + + + public FileTransferServer(int nbFile, ObserverInputMessage obs) throws UnknownHostException, IOException { + this.sockFTListen = ServerSocketChannel.open(); + this.sockFTListen.socket().bind(new InetSocketAddress(0)); + this.nbFile = nbFile; + this.obsInput = obs; + } + + public int getPort() { + return this.sockFTListen.socket().getLocalPort(); + } + + @Override + public void run() { + try { + for (int i = 0; i < this.nbFile; i++) { + SocketChannel sock = this.sockFTListen.accept(); + + Thread ft = new FileTransferReceivingThread(sock, this.obsInput); + ft.start(); + ft.join(); + } + + this.sockFTListen.close(); + } catch (IOException | InterruptedException e) { + e.printStackTrace(); + } + } +} \ No newline at end of file diff --git a/src_reseau/communication/filetransfer/FileTransferUtils.java b/src_reseau/communication/filetransfer/FileTransferUtils.java new file mode 100644 index 0000000..cd561ac --- /dev/null +++ b/src_reseau/communication/filetransfer/FileTransferUtils.java @@ -0,0 +1,120 @@ +package communication.filetransfer; + +import java.awt.Graphics2D; +import java.awt.GraphicsConfiguration; +import java.awt.GraphicsDevice; +import java.awt.GraphicsEnvironment; +import java.awt.geom.AffineTransform; +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Base64; +import java.util.List; + +import javax.imageio.ImageIO; + +import messages.MessageFichier; +import messages.MauvaisTypeMessageException; +import messages.Message.TypeMessage; + +public class FileTransferUtils { + + protected static final String DOWNLOADS_RELATIVE_PATH = "../downloads/"; + protected static final ArrayList IMAGE_EXTENSIONS = new ArrayList(List.of("tif","tiff","bmp","jpg","jpeg","gif", "png", "eps", "svg")); + protected static final int KB_SIZE = 1024; + + + protected static MessageFichier processMessageToDisplay(File file) throws IOException { + String nameFile = file.getName(); + String extension = processFileExtension(nameFile); + TypeMessage type; + String contenu; + + if(IMAGE_EXTENSIONS.contains(extension)) { + type = TypeMessage.IMAGE; + BufferedImage img = ImageIO.read(file); + contenu = encodeImage(createThumbnail(img), extension) ; + + }else { + type = TypeMessage.FICHIER; + contenu = nameFile; + } + + try { + //return new MessageFichier(type, contenu, extension); + return new MessageFichier(type, contenu, extension); + } catch (MauvaisTypeMessageException e) { + System.out.println("Should never go in"); + e.printStackTrace(); + } + return null; + } + + protected static String processFileExtension(String fileName) { + String extension = ""; + + int i = fileName.indexOf('.'); + if (i >= 0 || i != -1) { + extension = fileName.substring(i+1).toLowerCase(); + } + return extension; + } + + + private static BufferedImage createThumbnail(BufferedImage image){ + float w = image.getWidth(); + float ratio = (w > 150) ? (150F/w) : 1; + BufferedImage scaled = scale(image, ratio); + return scaled; + } + + private static String encodeImage(BufferedImage img, String extension) throws IOException { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + ImageIO.write(img, extension, bos); + String imgString = Base64.getEncoder().encodeToString(bos.toByteArray()); + bos.close(); + return imgString; + } + + + public static BufferedImage decodeImage(String imageString) throws IOException { + byte[] imgData = Base64.getDecoder().decode(imageString); + InputStream is = new ByteArrayInputStream(imgData); + BufferedImage img = ImageIO.read(is); + is.close(); + return img; + } + + private static BufferedImage scale(BufferedImage source, double ratio) { + int w = (int) (source.getWidth() * ratio); + int h = (int) (source.getHeight() * ratio); + BufferedImage bi = getCompatibleImage(w, h); + Graphics2D g2d = bi.createGraphics(); + double xScale = (double) w / source.getWidth(); + double yScale = (double) h / source.getHeight(); + AffineTransform at = AffineTransform.getScaleInstance(xScale, yScale); + g2d.drawRenderedImage(source, at); + g2d.dispose(); + return bi; + } + + private static BufferedImage getCompatibleImage(int w, int h) { + GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); + GraphicsDevice gd = ge.getDefaultScreenDevice(); + GraphicsConfiguration gc = gd.getDefaultConfiguration(); + BufferedImage image = gc.createCompatibleImage(w, h); + return image; + } + + public static void createDownloads() { + File downloads = new File(FileTransferUtils.DOWNLOADS_RELATIVE_PATH); + + if(!downloads.exists()) { + downloads.mkdir(); + } + } +} diff --git a/src_reseau/communication/tcp/TCPClient.java b/src_reseau/communication/tcp/TCPClient.java new file mode 100644 index 0000000..0a56841 --- /dev/null +++ b/src_reseau/communication/tcp/TCPClient.java @@ -0,0 +1,73 @@ +package communication.tcp; + +import java.io.IOException; + +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; + +import java.net.InetAddress; +import java.net.Socket; + +import observers.ObserverInputMessage; +import observers.ObserverSocketState; + +import messages.MauvaisTypeMessageException; +import messages.Message; + +public class TCPClient { + + private Socket sockTCP; + private ObjectOutputStream output; + private TCPInputThread inputThread; + + + public TCPClient(Socket sockTCP) throws IOException { + this.sockTCP = sockTCP; + + this.output = new ObjectOutputStream(sockTCP.getOutputStream()); + ObjectInputStream input = new ObjectInputStream(sockTCP.getInputStream()); + this.inputThread = new TCPInputThread(input); + } + + public TCPClient(InetAddress addr) throws IOException { + this(new Socket(addr, TCPServer.PORT_SERVER)); + + } + + public void startInputThread() { + this.inputThread.start(); + } + + + public void sendMessage(Message message) throws IOException, MauvaisTypeMessageException { + System.out.println("dans write"); + this.output.writeObject(message); + } + + + public void setObserverInputThread(ObserverInputMessage o) { + this.inputThread.setObserverInputMessage(o); + } + + + public void setObserverSocketState(ObserverSocketState o) { + this.inputThread.setObserverSocketState(o); + } + + + public void destroyAll() { + try { + if (!this.sockTCP.isClosed()) { + this.output.close(); + this.sockTCP.close(); + this.inputThread.setObserverSocketState(null); + } + this.inputThread = null; + this.sockTCP = null; + this.output = null; + + } catch (Exception e) { + e.printStackTrace(); + } + } +} diff --git a/src_reseau/communication/tcp/TCPInputThread.java b/src_reseau/communication/tcp/TCPInputThread.java new file mode 100644 index 0000000..6f2f437 --- /dev/null +++ b/src_reseau/communication/tcp/TCPInputThread.java @@ -0,0 +1,72 @@ +package communication.tcp; + +import java.io.IOException; +import java.io.ObjectInputStream; +import observers.ObserverInputMessage; +import observers.ObserverSocketState; + +public class TCPInputThread extends Thread { + + private ObjectInputStream input; + private boolean running; + private ObserverInputMessage obsInput; + private ObserverSocketState obsState; + + public TCPInputThread(ObjectInputStream input) { + this.input = input; + this.running = true; + } + + @Override + public void run() { + + while (this.running) { + try { + +// System.out.println("dans read"); + Object o = this.input.readObject(); + this.obsInput.update(this, o); + + } catch (IOException | ClassNotFoundException e) { + this.interrupt(); + + } + + } + } + + @Override + public void interrupt() { + + try { + //Stop the thread + this.running = false; + //Close the stream and the socket + this.input.close(); + + if(this.obsState != null) { + //Send an update to the controller + this.obsState.updateSocketState(this, true); + } + + + //Set every attribute to null so they're collected by the GC + this.obsInput = null; + this.obsState = null; + this.input = null; + + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + protected void setObserverInputMessage(ObserverInputMessage o) { + this.obsInput = o; + } + + protected void setObserverSocketState(ObserverSocketState o) { + this.obsState = o; + } + +} diff --git a/src_reseau/communication/tcp/TCPServer.java b/src_reseau/communication/tcp/TCPServer.java new file mode 100644 index 0000000..7230589 --- /dev/null +++ b/src_reseau/communication/tcp/TCPServer.java @@ -0,0 +1,42 @@ +package communication.tcp; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.UnknownHostException; + +import observers.ObserverInputMessage; + + +public class TCPServer extends Thread { + + public static int PORT_SERVER = 7000; + + private ServerSocket sockListenTCP; + private ObserverInputMessage obs; + + public TCPServer() throws UnknownHostException, IOException { + this.sockListenTCP = new ServerSocket(PORT_SERVER, 50, InetAddress.getLocalHost()); + } + + @Override + public void run() { + System.out.println("TCP running"); + Socket sockAccept; + while(true) { + try { + sockAccept = this.sockListenTCP.accept(); + this.obs.update(this, sockAccept); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + } + } + + public void addObserver(ObserverInputMessage obs) { + this.obs = obs; + } +} diff --git a/src_reseau/communication/udp/CommunicationUDP.java b/src_reseau/communication/udp/CommunicationUDP.java new file mode 100644 index 0000000..599136a --- /dev/null +++ b/src_reseau/communication/udp/CommunicationUDP.java @@ -0,0 +1,176 @@ +package communication.udp; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.SocketException; +import java.net.UnknownHostException; +import java.util.ArrayList; + +import main.Utilisateur; + +import messages.*; +import observers.ObserverUserList; + +public class CommunicationUDP extends Thread { + + protected static int PORT_SERVEUR = 3000; + protected static int PORT_CLIENT = 2000; + + private UDPClient client; + private UDPServer server; + private int portServer; + + private ArrayList users = new ArrayList(); + private ObserverUserList observer; + + public CommunicationUDP() throws SocketException, UnknownHostException { + this.portServer = PORT_SERVEUR; + this.server = new UDPServer(portServer, this); + this.server.start(); + this.client = new UDPClient(PORT_CLIENT); + } + + public void setObserver(ObserverUserList obs) { + this.observer = obs; + } + + // -------------- USER LIST UPDATE FUNCTION --------------// + + protected synchronized void addUser(String idClient, String pseudoClient, InetAddress ipClient) + throws IOException { + users.add(new Utilisateur(idClient, pseudoClient, ipClient)); + + this.sendUpdate(); + + } + + protected synchronized void changePseudoUser(String idClient, String pseudoClient, InetAddress ipClient) { + int index = getIndexFromID(idClient); + users.get(index).setPseudo(pseudoClient); + this.sendUpdate(); + } + + protected synchronized void removeUser(String idClient, String pseudoClient, InetAddress ipClient) { + int index = getIndexFromIP(ipClient); + if (index != -1) { + users.remove(index); + } + this.sendUpdate(); + } + + public void removeAll() { + int oSize = users.size(); + for (int i = 0; i < oSize; i++) { + users.remove(0); + } + } + + // -------------- CHECKERS --------------// + + protected boolean containsUserFromID(String id) { + for (Utilisateur u : users) { + if (u.getId().equals(id)) { + return true; + } + } + return false; + } + + public boolean containsUserFromPseudo(String pseudo) { + for (Utilisateur u : users) { + if (u.getPseudo().toLowerCase().equals(pseudo)) { + return true; + } + } + + return false; + } + + // -------------- GETTERS --------------// + + public Utilisateur getUserFromPseudo(String pseudo) { + for (int i = 0; i < users.size(); i++) { + if (users.get(i).getPseudo().equals(pseudo)) { + return users.get(i); + } + } + return null; + } + + private int getIndexFromID(String id) { + for (int i = 0; i < users.size(); i++) { + if (users.get(i).getId().equals(id)) { + return i; + } + } + return -1; + } + + private int getIndexFromIP(InetAddress ip) { + for (int i = 0; i < users.size(); i++) { + if (users.get(i).getIp().equals(ip)) { + return i; + } + } + return -1; + } + + // -------------- SEND MESSAGES --------------// + + + public void sendMessageConnecte() throws UnknownHostException, IOException { + try { + MessageSysteme m = new MessageSysteme(Message.TypeMessage.JE_SUIS_CONNECTE); + this.client.sendMessageUDP_broadcast(m); + } catch (MauvaisTypeMessageException | IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + + public void sendMessageInfoPseudo(InetAddress addrOther) { + Utilisateur self = Utilisateur.getSelf(); + try { + MessageSysteme m = new MessageSysteme(Message.TypeMessage.INFO_PSEUDO, self.getPseudo(), self.getId()); + this.client.sendMessageUDP_local(m, addrOther); + } catch (MauvaisTypeMessageException | IOException e) { + e.printStackTrace(); + } + } + + public void sendMessageInfoPseudo() { + Utilisateur self = Utilisateur.getSelf(); + try { + MessageSysteme m = new MessageSysteme(Message.TypeMessage.INFO_PSEUDO, self.getPseudo(), self.getId()); + this.client.sendMessageUDP_broadcast(m); + } catch (MauvaisTypeMessageException | IOException e) { + e.printStackTrace(); + } + } + + public void sendMessageDelete() { + + try { + MessageSysteme m = new MessageSysteme(Message.TypeMessage.JE_SUIS_DECONNECTE); + this.client.sendMessageUDP_broadcast(m); + } catch (MauvaisTypeMessageException | IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + } + + + private void sendUpdate() { + if(this.observer != null) { + this.observer.updateList(this, users); + } + } + + public void destroyAll() { + this.client.destroyAll(); + this.server.interrupt(); + } + +} diff --git a/src_reseau/communication/udp/UDPClient.java b/src_reseau/communication/udp/UDPClient.java new file mode 100644 index 0000000..e084fa6 --- /dev/null +++ b/src_reseau/communication/udp/UDPClient.java @@ -0,0 +1,46 @@ +package communication.udp; + +import java.io.IOException; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.net.UnknownHostException; + +import messages.*; + +public class UDPClient { + + private DatagramSocket sockUDP; + private InetAddress broadcast; + + public UDPClient(int port) throws SocketException, UnknownHostException { + this.sockUDP = new DatagramSocket(port); + + InetAddress localHost = InetAddress.getLocalHost(); + NetworkInterface networkInterface = NetworkInterface.getByInetAddress(localHost); + this.broadcast = networkInterface.getInterfaceAddresses().get(0).getBroadcast(); + System.out.println(this.broadcast); + System.out.println(InetAddress.getLocalHost()); + } + + protected void sendMessageUDP_local(Message message, InetAddress addrOther) throws IOException { + String messageString= message.toString(); + DatagramPacket outpacket = new DatagramPacket(messageString.getBytes(), messageString.length(), addrOther, CommunicationUDP.PORT_SERVEUR); + this.sockUDP.send(outpacket); + } + + protected void sendMessageUDP_broadcast(Message message) throws IOException{ + String messageString = message.toString(); + DatagramPacket outpacket = new DatagramPacket(messageString.getBytes(), messageString.length(), this.broadcast, CommunicationUDP.PORT_SERVEUR); + this.sockUDP.send(outpacket); + } + + protected void destroyAll() { + this.sockUDP.close(); + this.sockUDP = null; + this.broadcast = null; + } + +} \ No newline at end of file diff --git a/src_reseau/communication/udp/UDPServer.java b/src_reseau/communication/udp/UDPServer.java new file mode 100644 index 0000000..cdd2043 --- /dev/null +++ b/src_reseau/communication/udp/UDPServer.java @@ -0,0 +1,87 @@ +package communication.udp; + +import java.io.IOException; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.InetAddress; +import java.net.SocketException; +import main.Utilisateur; +import messages.*; + +public class UDPServer extends Thread { + + private DatagramSocket sockUDP; + private CommunicationUDP commUDP; + private byte[] buffer; + private boolean running; + + public UDPServer(int port, CommunicationUDP commUDP) throws SocketException { + this.running = true; + this.commUDP = commUDP; + this.sockUDP = new DatagramSocket(port); + this.buffer = new byte[256]; + } + + @Override + public void run() { + while (this.running) { + + try { + + DatagramPacket inPacket = new DatagramPacket(buffer, buffer.length); + this.sockUDP.receive(inPacket); + String msgString = new String(inPacket.getData(), 0, inPacket.getLength()); + Message msg = Message.stringToMessage(msgString); + + if (inPacket.getAddress().equals(InetAddress.getLocalHost())) { + continue; + } + + switch (msg.getTypeMessage()) { + case JE_SUIS_CONNECTE: + + if (Utilisateur.getSelf() != null) { + + this.commUDP.sendMessageInfoPseudo(inPacket.getAddress()); + } + + break; + + case INFO_PSEUDO: + MessageSysteme m = (MessageSysteme) msg; + + if (this.commUDP.containsUserFromID(m.getId())) { + this.commUDP.changePseudoUser(m.getId(), m.getPseudo(), inPacket.getAddress()); + } else { + + this.commUDP.addUser(m.getId(), m.getPseudo(), inPacket.getAddress()); + System.out.println(m.getId() + ", " + m.getPseudo()); + } + break; + + case JE_SUIS_DECONNECTE: + MessageSysteme m2 = (MessageSysteme) msg; + this.commUDP.removeUser(m2.getId(), m2.getPseudo(), inPacket.getAddress()); + break; + + default: // Others types of messages are ignored because they are supposed to be + // transmitted by TCP and not UDP + } + + } catch (IOException e) { + System.out.println("receive exception"); + } + + } + } + + @Override + public void interrupt() { + // Stop the thread + this.running = false; + // Close the stream and the socket + this.sockUDP.close(); + this.buffer = null; + this.commUDP = null; + } +} diff --git a/src_reseau/connexion/ControleurConnexion.java b/src_reseau/connexion/ControleurConnexion.java new file mode 100644 index 0000000..c31e335 --- /dev/null +++ b/src_reseau/connexion/ControleurConnexion.java @@ -0,0 +1,142 @@ +package connexion; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.IOException; +import java.net.UnknownHostException; +import java.sql.SQLException; + +import communication.udp.CommunicationUDP; +import database.SQLiteManager; +import main.Utilisateur; +import standard.VueStandard; + +public class ControleurConnexion implements ActionListener{ + + private enum Etat {DEBUT, ID_OK}; + + private VueConnexion vue; + private Etat etat; + private CommunicationUDP comUDP; + private String username; + private SQLiteManager sqlManager; + private VueStandard vueStd; + + public ControleurConnexion(VueConnexion vue) { + this.vue = vue; + this.etat = Etat.DEBUT; + this.username = ""; + this.sqlManager = new SQLiteManager(0); + this.vueStd = null; + //Pour les tests, changer pour un truc plus general quand on change CommunicationUDP + + try { + this.comUDP = new CommunicationUDP(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + @Override + public void actionPerformed(ActionEvent e) { + String pseudo; + boolean inputOK = false; + if (this.etat == Etat.DEBUT) { + + this.username = this.vue.getUsernameValue(); + char[] password = this.vue.getPasswordValue(); + + try { + int res = this.sqlManager.checkPwd(this.username, password); + inputOK = (res == 1); + + } catch (SQLException e2) { + e2.printStackTrace(); + } + + + if (inputOK) { + this.etat=Etat.ID_OK; + + //Envoi broadcast du message "JeSuisActif" et, attente du retour de la liste des utilisateurs actifs + try { + comUDP.sendMessageConnecte(); + } catch (UnknownHostException e1) { + // TODO Auto-generated catch block + e1.printStackTrace(); + } catch (IOException e1) { + // TODO Auto-generated catch block + e1.printStackTrace(); + } + + try { + Thread.sleep(2); + } catch (InterruptedException e1) { + // TODO Auto-generated catch block + e1.printStackTrace(); + } + + //Mise en place de la demande du pseudo + this.vue.setConnexionInfo(""); + this.vue.removePasswordPanel(); + + this.vue.setTextUsernameField("Veuillez entrer votre pseudonyme"); + this.vue.resetUsernameField(); + inputOK=false; + } + else { + this.vue.setConnexionInfo("Identifiant ou mot de passe invalide, veuillez réessayer"); + this.vue.resetPasswordField(); + } + + } + else { + pseudo = vue.getUsernameValue(); + + //Recherche dans la liste locale des utilisateurs connectes, report sur inputOK + inputOK = !this.comUDP.containsUserFromPseudo(pseudo); + if(pseudo.equals("")) { + this.vue.setConnexionInfo("Votre pseudonyme doit contenir au moins 1 caratère"); + }else if (inputOK) { + //Reglage de l'utilisateur + try { + Utilisateur.setSelf(this.username, pseudo, "localhost"); + } catch (UnknownHostException e2) { + // TODO Auto-generated catch block + e2.printStackTrace(); + } + this.comUDP.sendMessageInfoPseudo(); + + try { + this.resetView(); + this.vue.setVisible(false); + this.setVueStandard(); + } catch (IOException e1) { + e1.printStackTrace(); + } + } + else this.vue.setConnexionInfo("Ce nom est déjà utilisé, veuillez en choisir un autre"); + } + } + + private void setVueStandard() throws IOException { + if(this.vueStd == null) { + this.vueStd = new VueStandard("Standard", this.comUDP, this.sqlManager, this.vue); + + }else { + this.vueStd.initControleur(); + this.vueStd.setPseudoSelf(); + this.vueStd.setVisible(true); + } + } + + private void resetView() { + this.etat = Etat.DEBUT; + this.vue.addPasswordPanel(); + this.vue.resetPasswordField(); + this.vue.resetUsernameField(); + this.vue.setTextUsernameField("Nom d'utilisateur"); + this.vue.setConnexionInfo(""); + + } +} diff --git a/src_reseau/connexion/VueConnexion.java b/src_reseau/connexion/VueConnexion.java new file mode 100644 index 0000000..efd3e84 --- /dev/null +++ b/src_reseau/connexion/VueConnexion.java @@ -0,0 +1,123 @@ +package connexion; + +//Importe les librairies +import java.awt.*; +import javax.swing.*; + +import main.Vue; + +public class VueConnexion extends Vue { + + private static final long serialVersionUID = 1L; + //Elements vue + private JButton boutonValider; + private JTextField inputUsername; + private JPasswordField inputPassword; + private JLabel labelUsername; + private JLabel labelPassword; + private JLabel connexionInfo; + private JPanel main; + private JPanel panelPassword; + + //Controleur + private ControleurConnexion controle; + + //penser à enlever le numtest + public VueConnexion() { + super("Connexion"); + controle = new ControleurConnexion(this); + + //Creation fenetre + this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + this.setSize(400, 300); + this.setLocationRelativeTo(null); + + //Ajout elements + ajouterElements(); + + //Regle le bouton par défaut + this.getRootPane().setDefaultButton(boutonValider); + + //Affiche la fenetre + this.setVisible(true); + } + + private void ajouterElements() { + + //Creation panel + main = new JPanel(new GridLayout(4,1)); + JPanel panelUsername = new JPanel(new GridLayout(1, 2)); + this.panelPassword = new JPanel(new GridLayout(1, 2)); + + //Cree les elements + this.connexionInfo = new JLabel(""); + + this.inputUsername = new JTextField(); + this.inputUsername.setPreferredSize(new Dimension(100, 50)); + + this.labelUsername = new JLabel("Nom d'utilisateur"); + this.labelUsername.setBorder(BorderFactory.createEmptyBorder(5,5,5,5)); + + this.inputPassword = new JPasswordField(); + this.inputPassword.setPreferredSize(new Dimension(100, 50)); + + this.labelPassword = new JLabel("Mot de passe :"); + this.labelPassword.setBorder(BorderFactory.createEmptyBorder(5,5,5,5)); + + boutonValider = new JButton("Valider"); + + //Le controleur guette les evenements du bouton + boutonValider.addActionListener(controle); + + //Ajoute les elements + panelUsername.add(this.inputUsername); + panelUsername.add(this.labelUsername); + + this.panelPassword.add(this.inputPassword); + this.panelPassword.add(this.labelPassword); + + + main.add(connexionInfo); + main.add(panelUsername); + main.add(this.panelPassword); + main.add(boutonValider); + + this.add(main); + } + + + //Getters et setters + protected void setConnexionInfo(String text) { + this.connexionInfo.setText(text); + } + + protected void setTextUsernameField(String text) { + this.labelUsername.setText(text); + } + + protected String getUsernameValue() { + return this.inputUsername.getText(); + } + + protected char[] getPasswordValue() { + return this.inputPassword.getPassword(); + } + + + protected void resetUsernameField() { + this.inputUsername.setText(""); + } + + protected void removePasswordPanel() { + this.main.remove(2); + } + + protected void addPasswordPanel() { + this.main.add(this.panelPassword, 2); + } + + protected void resetPasswordField() { + this.inputPassword.setText(""); + } + +} diff --git a/src_reseau/database/SQLiteCreateTables.java b/src_reseau/database/SQLiteCreateTables.java new file mode 100644 index 0000000..45aa061 --- /dev/null +++ b/src_reseau/database/SQLiteCreateTables.java @@ -0,0 +1,73 @@ +package database; + +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Statement; + +class SQLiteCreateTables { + + + protected static void createTableUser(Connection connec) throws SQLException { + String createTableUser = "CREATE TABLE IF NOT EXISTS user (\r\n" + + " id INTEGER PRIMARY KEY AUTOINCREMENT,\r\n" + + " username VARCHAR (50) NOT NULL\r\n" + + " UNIQUE ON CONFLICT ROLLBACK,\r\n" + + " pwd_salt BLOB,\r\n" + + " db_datakey_salt BLOB,\r\n" + + " encrypted_pwd_hashsalt BLOB,\r\n" + + " encrypted_db_datakey BLOB,\r\n" + + " iv_datakey BLOB\r\n" + + ");"; + + Statement stmt = connec.createStatement(); + stmt.execute(createTableUser); + + } + + protected static void createTableConversation(Connection connec) throws SQLException { + String createTableConversation = "CREATE TABLE IF NOT EXISTS conversation (\r\n" + + " id_conversation INTEGER PRIMARY KEY AUTOINCREMENT,\r\n" + + " id_emetteur INTEGER REFERENCES user (id) \r\n" + + " NOT NULL,\r\n" + + " id_recepteur INTEGER REFERENCES user (id) \r\n" + + " NOT NULL,\r\n" + +" iv_conversation BLOB NOT NULL" + + ");"; + + Statement stmt = connec.createStatement(); + stmt.execute(createTableConversation); + + } + + protected static void createTableMessage(Connection connec) throws SQLException { + String createTableMessage = "CREATE TABLE IF NOT EXISTS message (\r\n" + + " id_message INTEGER PRIMARY KEY AUTOINCREMENT,\r\n" + + " id_conversation INTEGER REFERENCES conversation (id_conversation) \r\n" + + " NOT NULL,\r\n" + + " id_type INTEGER REFERENCES type (id_type) \r\n" + + " NOT NULL,\r\n" + + " content BLOB,\r\n" + + " date INTEGER NOT NULL,\r\n" + + " extension VARCHAR (20) \r\n" + + ");\r\n"; + + Statement stmt = connec.createStatement(); + stmt.execute(createTableMessage); + } + + protected static void createTableType(Connection connec) throws SQLException { + String createTableType = "CREATE TABLE IF NOT EXISTS type (\r\n" + " id_type INTEGER PRIMARY KEY,\r\n" + + " label VARCHAR (20) NOT NULL\r\n" + ");"; + + Statement stmt = connec.createStatement(); + stmt.execute(createTableType); + + String typeText = "INSERT OR IGNORE INTO type (id_type, label) " + "VALUES (0, 'text');"; + String typeFile = "INSERT OR IGNORE INTO type (id_type, label) " + "VALUES (1, 'file');"; + String typeImage = "INSERT OR IGNORE INTO type (id_type, label) " + "VALUES (2, 'image');"; + + stmt.execute(typeText); + stmt.execute(typeFile); + stmt.execute(typeImage); + } +} diff --git a/src_reseau/database/SQLiteEncprytion.java b/src_reseau/database/SQLiteEncprytion.java new file mode 100644 index 0000000..38c0916 --- /dev/null +++ b/src_reseau/database/SQLiteEncprytion.java @@ -0,0 +1,84 @@ +package database; + + +import java.util.Base64; +import java.util.Random; + +import javax.crypto.*; +import javax.crypto.spec.*; +import java.security.*; +import java.security.spec.*; + +class SQLiteEncprytion { + + private static final Random RANDOM = new SecureRandom(); + private static final int ITERATIONS = 10000; + private static final int KEY_LENGTH = 256; + protected static final String encryptAlgorithm = "AES/CBC/PKCS5Padding"; + + protected static byte[] getNextSalt() { + byte[] salt = new byte[24]; + RANDOM.nextBytes(salt); + return salt; + } + + + protected static byte[] hash(char[] password, byte[] salt) { + return SQLiteEncprytion.getKey(password, salt).getEncoded(); + } + + + protected static SecretKey getKey(char[] password, byte[] salt) { + PBEKeySpec saltpwd = new PBEKeySpec(password, salt, ITERATIONS, KEY_LENGTH); + + try { + SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1"); + SecretKey tmp = skf.generateSecret(saltpwd); + SecretKey key = new SecretKeySpec(tmp.getEncoded(), "AES"); + return key; + } catch (InvalidKeySpecException | NoSuchAlgorithmException e) { + e.printStackTrace(); + } finally { + saltpwd.clearPassword(); + } + return null; + } + + + public static IvParameterSpec generateIv() { + byte[] iv = new byte[16]; + new SecureRandom().nextBytes(iv); + return new IvParameterSpec(iv); + } + + + protected static byte[] encrypt(String algorithm, byte[] input, SecretKey key, IvParameterSpec iv) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException{ + Cipher cipher = Cipher.getInstance(algorithm); + cipher.init(Cipher.ENCRYPT_MODE, key, iv); + byte[] cipherText = cipher.doFinal(input); + return Base64.getEncoder().encode(cipherText); + } + + + protected static byte[] decryptByte(String algorithm, byte[] cipherText, SecretKey key, IvParameterSpec iv) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException{ + Cipher cipher = Cipher.getInstance(algorithm); + cipher.init(Cipher.DECRYPT_MODE, key, iv); + byte[] plainText = cipher.doFinal(Base64.getDecoder().decode(cipherText)); + return plainText; + } + + protected static String decryptString(String algorithm, byte[] cipherText, SecretKey key, IvParameterSpec iv) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException { + return new String(SQLiteEncprytion.decryptByte(algorithm, cipherText, key, iv) ); + } + + + public static byte[] keyToByte(SecretKey key) { + return Base64.getEncoder().encode(key.getEncoded()); + } + + + public static SecretKey byteToKey(byte[] encodedKey) { + byte[] decodedKey = Base64.getDecoder().decode(encodedKey); + return new SecretKeySpec(decodedKey, 0, decodedKey.length, "AES"); + } +} diff --git a/src_reseau/database/SQLiteManager.java b/src_reseau/database/SQLiteManager.java new file mode 100644 index 0000000..707a4ba --- /dev/null +++ b/src_reseau/database/SQLiteManager.java @@ -0,0 +1,562 @@ +package database; + +import java.io.File; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; + +import javax.crypto.BadPaddingException; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.KeyGenerator; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.SecretKey; +import javax.crypto.spec.IvParameterSpec; + + +import main.Utilisateur; +import messages.MauvaisTypeMessageException; +import messages.Message; +import messages.MessageTexte; +import messages.Message.TypeMessage; +import messages.MessageFichier; + +public class SQLiteManager { + + private static final String DATABASE_RELATIVE_PATH = "../database"; + + public static String[] hardcodedNames = {"Olivia","Liam","Benjamin","Sophia","Charlotte","Noah","Elijah","Isabella", + "Oliver","Emma","William","Amelia","Evelyn","James","Mia","Ava","Lucas","Mason","Ethan","Harper"}; + + private Connection connec; + private int numDatabase; + private SecretKey dbDataKey; + + public SQLiteManager(int numDatabase) { + this.numDatabase = numDatabase; + + this.openConnection(); + + try { + + SQLiteCreateTables.createTableUser(this.connec); + SQLiteCreateTables.createTableConversation(this.connec); + SQLiteCreateTables.createTableType(this.connec); + SQLiteCreateTables.createTableMessage(this.connec); + + } catch (SQLException e) { + this.closeConnection(); + File db = new File("../database"+this.numDatabase+".db"); + if(db.delete()) { + System.out.println("supp"); + }else { + System.out.println("no supp"); + } + e.printStackTrace(); + } + + this.closeConnection(); + } + + private void openConnection() { + String url = "jdbc:sqlite:"+ DATABASE_RELATIVE_PATH + this.numDatabase + ".db"; + try { + this.connec = DriverManager.getConnection(url); +// System.out.println("Connection to bdd established"); + } catch (SQLException e) { + System.out.println(e.getMessage()); + } + } + + private void closeConnection() { + try { + if (this.connec != null) { + this.connec.close(); + } + } catch (SQLException e) { + System.out.println(e.getMessage()); + } + + } + + public int insertAllMessages(ArrayList messages, String usernameSender, String usernameReceiver) throws SQLException{ + int nbRows = 0; + this.openConnection(); + + int idSender = this.getIDUser(usernameSender); + int idReceiver = this.getIDUser(usernameReceiver); + + if(idSender == -1) { + this.insertUser(usernameSender); + idSender = this.getIDUser(usernameSender); + } + + if(idReceiver == -1) { + this.insertUser(usernameReceiver); + idReceiver = this.getIDUser(usernameReceiver); + } + + int idConversation = getIDConversation(idSender, idReceiver); + + if(idConversation == -1) { + this.insertConversation(idSender, idReceiver); + idConversation = getIDConversation(idSender, idReceiver); + } + + IvParameterSpec ivConversation = this.getIvConversation(idConversation); + + this.connec.setAutoCommit(false); + + for(Message m : messages) { + try { + nbRows += this.insertMessage(idConversation, m, ivConversation); + } catch (SQLException e) { + e.printStackTrace(); + this.connec.rollback(); + } + } + + this.connec.commit(); + + this.closeConnection(); + + //System.out.println("Nombre de message(s) insérée(s) : " + nbRows); + + return nbRows; + } + + + public ArrayList getHistoriquesMessages(String usernameOther, String pseudoOther) throws SQLException { + + this.openConnection(); + + + ArrayList messages = new ArrayList(); + + String usernameSelf = Utilisateur.getSelf().getId(); + + int idSelf = this.getIDUser(usernameSelf); + int idOther = this.getIDUser(usernameOther); + + int idConversationSelf = this.getIDConversation(idSelf, idOther); + int idConversationOther = this.getIDConversation(idOther, idSelf); + IvParameterSpec ivConversation = this.getIvConversation(idConversationSelf); +// String str = "datetime(d1,'unixepoch','localtime')"; + + + String getHistoriqueRequest = "SELECT id_conversation, id_type, content, date, extension " + + "FROM message " + + "WHERE id_conversation IN (?,?) " + + "ORDER by date"; + + PreparedStatement prepStmt = this.connec.prepareStatement(getHistoriqueRequest); + prepStmt.setInt(1, idConversationSelf); + prepStmt.setInt(2, idConversationOther); + ResultSet res = prepStmt.executeQuery(); + + //Retrieve the messages one by one + //Create the appropriate message object depending on the type and sender/receiver + //and add the message in the list + while(res.next()) { + int idType = res.getInt("id_type"); + String type = this.getType(idType); + + String content = null; + try { + content = this.bytesToStringContent(res.getBytes("content"), ivConversation); + } catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException + | InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException + | SQLException e1) { + //System.out.println("erreur déchiffrement"); + } + + Message message = null; + String extension; + + if(!type.equals("")) { + try { + switch(type) { + case "text": + message = new MessageTexte(TypeMessage.TEXTE, content); + break; + case "file": + extension = res.getString("extension"); + message = new MessageFichier(TypeMessage.FICHIER, content, extension); + break; + default: + extension = res.getString("extension"); + message = new MessageFichier(TypeMessage.IMAGE, content, extension); + } + + } catch (MauvaisTypeMessageException e) { + e.printStackTrace(); + } + } + + if(res.getInt("id_conversation") == idConversationSelf) { + message.setSender("Moi"); + }else{ + message.setSender(pseudoOther); + } + message.setDateMessage(res.getString("date")); + if(content != null) { + messages.add(message); + } + + } + + + this.closeConnection(); + + return messages; + } + + + private void insertUser(String username) throws SQLException { + String insertUserRequest = "INSERT INTO user (username) " + "VALUES (?);"; + + PreparedStatement prepStmt = this.connec.prepareStatement(insertUserRequest); + prepStmt.setString(1, username); + prepStmt.executeUpdate(); + } + + + private int getIDUser(String username) throws SQLException { + String getIDRequest = "SELECT id " + " FROM user" + " WHERE username = ? ;"; + + PreparedStatement prepStmt = this.connec.prepareStatement(getIDRequest); + prepStmt.setString(1, username); + ResultSet res = prepStmt.executeQuery(); + + if (res.next()) { + return res.getInt("id"); + } + return -1; + + } + + + private void insertConversation(int idSender, int idReceiver) throws SQLException { + String insertConversationRequest = "INSERT INTO conversation (id_emetteur, id_recepteur, iv_conversation) " + "VALUES " + + "(?, ?, ?)," + + "(?, ?, ?);"; + + byte[] ivConversation = SQLiteEncprytion.generateIv().getIV(); + + PreparedStatement prepStmt = this.connec.prepareStatement(insertConversationRequest); + prepStmt.setInt(1, idSender); + prepStmt.setInt(2, idReceiver); + prepStmt.setBytes(3, ivConversation); + prepStmt.setInt(4, idReceiver); + prepStmt.setInt(5, idSender); + prepStmt.setBytes(6, ivConversation); + + prepStmt.executeUpdate(); + } + + + private int getIDConversation(int idSender, int idReceiver) throws SQLException { + String getIDRequest = "SELECT id_conversation " + "FROM conversation " + "WHERE id_emetteur = ? " + + "AND id_recepteur = ? ;"; + + PreparedStatement prepStmt = this.connec.prepareStatement(getIDRequest); + prepStmt.setInt(1, idSender); + prepStmt.setInt(2, idReceiver); + ResultSet res = prepStmt.executeQuery(); + + if (res.next()) { + return res.getInt("id_conversation"); + } + return -1; + } + + private IvParameterSpec getIvConversation(int idConversation) throws SQLException { + String getIvRequest = "SELECT iv_conversation " + "FROM conversation " + "WHERE id_conversation = ?;"; + + PreparedStatement prepStmt = this.connec.prepareStatement(getIvRequest); + prepStmt.setInt(1, idConversation); + ResultSet res = prepStmt.executeQuery(); + + if (res.next()) { + return new IvParameterSpec(res.getBytes("iv_conversation")); + } + + return null; + } + + + + + private int getIDType(String label) throws SQLException { + String getIDRequest = "SELECT id_type FROM type WHERE label = ?;"; + + PreparedStatement prepStmt = this.connec.prepareStatement(getIDRequest); + prepStmt.setString(1, label); + + ResultSet res = prepStmt.executeQuery(); + + if (res.next()) { + return res.getInt("id_type"); + } + return -1; + } + + + private String getType(int idType) throws SQLException { + String getTypeRequest = "SELECT label FROM type WHERE id_type = ?;"; + + PreparedStatement prepStmt = this.connec.prepareStatement(getTypeRequest); + prepStmt.setInt(1, idType); + + ResultSet res = prepStmt.executeQuery(); + + if(res.next()) { + return res.getString("label"); + } + + return ""; + + } + + + private byte[] stringToBytesContent(Message m, IvParameterSpec iv) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException { + String content; + if (m.getTypeMessage() == TypeMessage.TEXTE) { + MessageTexte messageTxt = (MessageTexte) m; + content = messageTxt.getContenu(); + }else { + MessageFichier messageFichier = (MessageFichier) m; + content = messageFichier.getContenu(); + } + byte[] encryptedContent = SQLiteEncprytion.encrypt(SQLiteEncprytion.encryptAlgorithm, content.getBytes(), this.dbDataKey, iv); + return encryptedContent; + + } + + private String bytesToStringContent(byte[] encryptedContent, IvParameterSpec iv) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException { + return SQLiteEncprytion.decryptString(SQLiteEncprytion.encryptAlgorithm, encryptedContent, this.dbDataKey, iv); + } + + + private String processMessageType(Message m) { + switch (m.getTypeMessage()) { + case TEXTE: return "text"; + case FICHIER: return "file"; + case IMAGE: return "image"; + default: return ""; + } + } + + private String processExtension(Message m) { + if(m.getTypeMessage() == TypeMessage.TEXTE) { + return null; + }else { + MessageFichier mFile = (MessageFichier) m; + return mFile.getExtension(); + } + } + + + private int insertMessage(int idConversation, Message m, IvParameterSpec iv) throws SQLException { + + + String dateMessage = m.getDateMessage(); + String extension = this.processExtension(m); + String type = this.processMessageType(m); + int idType = this.getIDType(type); + + byte[] content = null; + + try { + content = this.stringToBytesContent(m, iv); + } catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException + | InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) { + + e.printStackTrace(); + } + + String insertMessageRequest = "INSERT INTO message(id_conversation, id_type, content, date, extension) " + + "VALUES (?, ?, ?, ?, ?);"; + + PreparedStatement prepStmt = this.connec.prepareStatement(insertMessageRequest); + prepStmt.setInt(1, idConversation); + prepStmt.setInt(2, idType); + prepStmt.setBytes(3, content); + prepStmt.setString(4, dateMessage); + prepStmt.setString(5, extension); + + int nbRows = prepStmt.executeUpdate(); + + return nbRows; + } + + public void createNewUserEncrypt(String username, String password) { + + + String algo = SQLiteEncprytion.encryptAlgorithm; + + KeyGenerator keyGen = null; + try { + keyGen = KeyGenerator.getInstance("AES"); + keyGen.init(256); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + } + + byte[] passwordSalt = SQLiteEncprytion.getNextSalt(); + byte[] dbDataKeySalt = SQLiteEncprytion.getNextSalt(); + + SecretKey dbDataKey = keyGen.generateKey(); + + SecretKey dbDataEncryptKey = SQLiteEncprytion.getKey(password.toCharArray(), dbDataKeySalt); + IvParameterSpec ivDbDataKey = SQLiteEncprytion.generateIv(); + + byte[] passwordHash = SQLiteEncprytion.hash(password.toCharArray(), passwordSalt); + + byte[] dbDataKeyEncrypted = null; + byte[] encryptedPasswordHash = null; + + try { + dbDataKeyEncrypted = SQLiteEncprytion.encrypt( + algo, SQLiteEncprytion.keyToByte(dbDataKey), dbDataEncryptKey, ivDbDataKey); + encryptedPasswordHash = SQLiteEncprytion.encrypt( + algo, passwordHash , dbDataKey, ivDbDataKey); + + } catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException + | InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + this.openConnection(); + + String createUserRequest = "INSERT INTO user(username, pwd_salt, db_datakey_salt, encrypted_pwd_hashsalt, encrypted_db_datakey, iv_datakey) " + + "VALUES (?, ?, ?, ?, ?, ?); "; + + PreparedStatement prepStmt = null; + try { + prepStmt = this.connec.prepareStatement(createUserRequest); + prepStmt.setString(1, username); + prepStmt.setBytes(2, passwordSalt); + prepStmt.setBytes(3, dbDataKeySalt); + prepStmt.setBytes(4, encryptedPasswordHash); + prepStmt.setBytes(5, dbDataKeyEncrypted); + prepStmt.setBytes(6, ivDbDataKey.getIV()); + + } catch (SQLException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + try { + prepStmt.executeUpdate(); + System.out.println("Utilisateur crée"); + } catch (SQLException e) { + System.out.println("Nom d'utilisateur déjà pris"); + } + + this.closeConnection(); + + } + + /** + * + * @param username + * @param password + * @return -1 if user do not exists, + * 0 if password incorrect, + * 1 if password correct + * @throws SQLException + */ + public int checkPwd(String username, char[] password) throws SQLException { + + this.openConnection(); + + String selectUserDataRequest = "SELECT pwd_salt, db_datakey_salt, encrypted_pwd_hashsalt, encrypted_db_datakey, iv_datakey " + + "FROM user " + + "WHERE username = ?;"; + PreparedStatement prepStmt; + prepStmt = this.connec.prepareStatement(selectUserDataRequest); + prepStmt.setString(1, username); + + ResultSet res = prepStmt.executeQuery(); + if(!res.next()) { + return -1; + } + + byte[] passwordSalt = res.getBytes("pwd_salt"); + byte[] dbDataKeySalt = res.getBytes("db_datakey_salt"); + + SecretKey dbDataEncryptKey = SQLiteEncprytion.getKey(password, dbDataKeySalt); + IvParameterSpec iv = new IvParameterSpec(res.getBytes("iv_datakey")); + + byte[] encryptedDbDataKey = res.getBytes("encrypted_db_datakey"); + + + SecretKey dbDataKey = null; + try { + dbDataKey = SQLiteEncprytion.byteToKey( + SQLiteEncprytion.decryptByte(SQLiteEncprytion.encryptAlgorithm, encryptedDbDataKey, dbDataEncryptKey, iv) + ); + } catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException + | InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) { + //System.out.println("Problème déchiffrement clé db"); + } + + this.dbDataKey = dbDataKey; + + byte[] encryptedPasswordHash = res.getBytes("encrypted_pwd_hashsalt"); + + + byte[] passwordHash = SQLiteEncprytion.hash(password, passwordSalt); + + this.closeConnection(); + + boolean checkHash = this.checkHashPwd(passwordHash ,encryptedPasswordHash, dbDataKey, iv); + if(checkHash) { + return 1; + } + return 0; + } + + + private boolean checkHashPwd(byte[] passwordHash, byte[] encryptedPasswordHash, SecretKey dbDataKey, IvParameterSpec iv) { + + byte[] expectedHash = "".getBytes(); + try { + expectedHash = SQLiteEncprytion.decryptByte(SQLiteEncprytion.encryptAlgorithm, encryptedPasswordHash, dbDataKey, iv); + } catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException + | InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) { + } + + + + if (passwordHash.length != expectedHash.length) return false; + for (int i = 0; i < passwordHash.length; i++) { + if (passwordHash[i] != expectedHash[i]) return false; + } + return true; + } + + + public static void main(String[] args) { + String[] hardcodedNames = {"Olivia","Liam","Benjamin","Sophia","Charlotte","Noah","Elijah","Isabella", + "Oliver","Emma","William","Amelia","Evelyn","James","Mia","Ava","Lucas","Mason","Ethan","Harper"}; + + String pwdPrefix = "aze1$"; + + SQLiteManager sqlManager = new SQLiteManager(1); + + for(int i=0; i : "+this.contenu+" "+suffixe; + } + } +} \ No newline at end of file diff --git a/src_reseau/messages/MessageSysteme.java b/src_reseau/messages/MessageSysteme.java new file mode 100644 index 0000000..a4d880a --- /dev/null +++ b/src_reseau/messages/MessageSysteme.java @@ -0,0 +1,39 @@ +package messages; + +public class MessageSysteme extends Message { + + private static final long serialVersionUID = 1L; + private String pseudo; + private String id; + + public MessageSysteme(TypeMessage type) throws MauvaisTypeMessageException{ + if ((type==TypeMessage.JE_SUIS_CONNECTE)||(type==TypeMessage.JE_SUIS_DECONNECTE)||(type==TypeMessage.MESSAGE_NUL)) { + this.type=type; + this.pseudo=""; + this.id=""; + } + else throw new MauvaisTypeMessageException(); + } + + public MessageSysteme(TypeMessage type, String pseudo, String id) throws MauvaisTypeMessageException { + if (type==TypeMessage.INFO_PSEUDO) { + this.type=type; + this.pseudo=pseudo; + this.id=id; + } + else throw new MauvaisTypeMessageException(); + } + + public String getPseudo() { + return this.pseudo; + } + + public String getId() { + return this.id; + } + + @Override + protected String attributsToString() { + return this.pseudo+"###"+this.id; + } +} diff --git a/src_reseau/messages/MessageTexte.java b/src_reseau/messages/MessageTexte.java new file mode 100644 index 0000000..434598b --- /dev/null +++ b/src_reseau/messages/MessageTexte.java @@ -0,0 +1,33 @@ +package messages; + +public class MessageTexte extends Message { + + + private static final long serialVersionUID = 1L; + private String contenu; + + + public MessageTexte(TypeMessage type, String contenu) throws MauvaisTypeMessageException{ + if (type==TypeMessage.TEXTE) { + this.type=type; + this.contenu=contenu; + this.setDateMessage(Message.getDateAndTime()); + } + else throw new MauvaisTypeMessageException(); + } + + public String getContenu() { + return this.contenu; + } + + + @Override + protected String attributsToString() { + return this.contenu; + } + + @Override + public String toString() { + return "<"+this.getDateMessage()+"> "+this.getSender()+" : "+this.contenu+"\n"; + } +} \ No newline at end of file diff --git a/src_reseau/observers/ObserverInputMessage.java b/src_reseau/observers/ObserverInputMessage.java new file mode 100644 index 0000000..415c869 --- /dev/null +++ b/src_reseau/observers/ObserverInputMessage.java @@ -0,0 +1,5 @@ +package observers; + +public interface ObserverInputMessage { + public void update(Object o, Object arg); +} diff --git a/src_reseau/observers/ObserverSocketState.java b/src_reseau/observers/ObserverSocketState.java new file mode 100644 index 0000000..58325df --- /dev/null +++ b/src_reseau/observers/ObserverSocketState.java @@ -0,0 +1,7 @@ +package observers; + +public interface ObserverSocketState { + + public void updateSocketState(Object o, Object arg); + +} diff --git a/src_reseau/observers/ObserverUserList.java b/src_reseau/observers/ObserverUserList.java new file mode 100644 index 0000000..1dc483a --- /dev/null +++ b/src_reseau/observers/ObserverUserList.java @@ -0,0 +1,11 @@ +package observers; + +import java.util.ArrayList; + +import main.Utilisateur; + +public interface ObserverUserList { + + public void updateList(Object o, ArrayList userList); + +} diff --git a/src_reseau/session/ControleurSession.java b/src_reseau/session/ControleurSession.java new file mode 100644 index 0000000..852f179 --- /dev/null +++ b/src_reseau/session/ControleurSession.java @@ -0,0 +1,279 @@ +package session; + +import java.awt.event.ActionEvent; + +import java.awt.event.ActionListener; +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; +import java.io.File; +import java.io.IOException; +import java.net.InetAddress; +import java.net.Socket; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collections; + +import javax.swing.JButton; +import javax.swing.JFileChooser; + +import communication.filetransfer.FileTransferClient; +import communication.filetransfer.FileTransferServer; +import communication.tcp.TCPClient; +import database.SQLiteManager; +import main.Utilisateur; +import messages.MauvaisTypeMessageException; +import messages.Message; +import messages.MessageFichier; +import messages.MessageTexte; +import messages.Message.TypeMessage; +import observers.ObserverInputMessage; +import observers.ObserverSocketState; + +public class ControleurSession implements ActionListener, ObserverInputMessage, ObserverSocketState, KeyListener { + + private VueSession vue; + private String idOther; + private String pseudoOther; + private InetAddress ipOther; + private TCPClient tcpClient; + private ArrayList messagesIn; + private ArrayList messagesOut; + private SQLiteManager sqlManager; + private ArrayList files; + + protected ControleurSession(VueSession vue, Socket socketComm, String idOther, String pseudoOther, InetAddress ipOther, SQLiteManager sqlManager) throws IOException { + this.vue = vue; + this.tcpClient = new TCPClient(socketComm); + this.tcpClient.setObserverInputThread(this); + this.tcpClient.setObserverSocketState(this); + this.tcpClient.startInputThread(); + this.messagesIn = new ArrayList(); + this.messagesOut = new ArrayList(); + + this.idOther = idOther; + this.pseudoOther = pseudoOther; + this.ipOther = ipOther; + + this.sqlManager = sqlManager; + + this.files = new ArrayList(); + } + + // ---------- ACTION LISTENER OPERATIONS ----------// + @Override + public void actionPerformed(ActionEvent e) { + + //Quand le bouton envoyer est presse + if ((JButton) e.getSource() == this.vue.getButtonEnvoyer()) { + String messageContent = this.vue.getInputedText(); + System.out.println(messageContent); + + if(!this.files.isEmpty()) { + this.processSelectedFiles(messageContent); + if(!this.files.isEmpty()) { + this.askFileTransfer(); + + this.vue.resetZoneSaisie(); + messageContent = ""; + } + } + + + //If the text field is not empty + if (!messageContent.equals("")) { + + //Retrieve the date and prepare the messages to send/display + MessageTexte messageOut = null; + + try { + messageOut = new MessageTexte(TypeMessage.TEXTE, messageContent); + messageOut.setSender(Utilisateur.getSelf().getPseudo()); + } catch (MauvaisTypeMessageException e2) { + e2.printStackTrace(); + } + + + try { + this.tcpClient.sendMessage(messageOut); + } catch (MauvaisTypeMessageException | IOException e1) { + // TODO Auto-generated catch block + e1.printStackTrace(); + } + + messageOut.setSender("Moi"); + this.vue.appendMessage(messageOut); + this.vue.resetZoneSaisie(); + + this.messagesOut.add(messageOut); + } + } + + if((JButton) e.getSource() == this.vue.getButtonImportFile()) { + JFileChooser fc = new JFileChooser(); + fc.setMultiSelectionEnabled(true); + int returVal = fc.showDialog(this.vue, "Importer"); + + + if(returVal == JFileChooser.APPROVE_OPTION) { + File[] files = fc.getSelectedFiles(); + Collections.addAll(this.files, files); + for(File file : files) { + this.vue.appendInputedText(file.getName()); + this.vue.appendInputedText(";"); + } + } + + } + + } + + + @Override + public void keyTyped(KeyEvent e) { + // TODO Auto-generated method stub + + } + + @Override + public void keyPressed(KeyEvent e) { + if(e.getKeyCode() == KeyEvent.VK_ENTER) { + if(!e.isShiftDown()) { + this.vue.getButtonEnvoyer().doClick(); + } + + } + } + + @Override + public void keyReleased(KeyEvent e) { + // TODO Auto-generated method stub + + } + + + protected ArrayList getHistorique(){ + try { + ArrayList historique = this.sqlManager.getHistoriquesMessages(idOther, pseudoOther); + return historique; + } catch (SQLException e) { + e.printStackTrace(); + return new ArrayList(); + + } + } + + private void processSelectedFiles(String input) { + String[] tmp = input.split(";"); + ArrayList potentialFiles = new ArrayList(); + Collections.addAll(potentialFiles, tmp); + + for(File file: this.files) { + if(!potentialFiles.contains(file.getName()) ) { + this.files.remove(file); + } + } + } + + private void askFileTransfer() { + try { + MessageFichier messageOut = new MessageFichier(TypeMessage.FICHIER_INIT, ""+this.files.size(), ""); + this.tcpClient.sendMessage(messageOut); + } catch (MauvaisTypeMessageException | IOException e1) { + e1.printStackTrace(); + } + } + + private void answerFileTransfer(int port) { + try { + MessageFichier messageOut = new MessageFichier(TypeMessage.FICHIER_ANSWER, ""+port, ""); + this.tcpClient.sendMessage(messageOut); + } catch (MauvaisTypeMessageException | IOException e1) { + e1.printStackTrace(); + } + } + + //Methode appelee quand l'inputStream de la socket de communication recoit des donnees + @Override + public void update(Object o, Object arg) { + Message message = (Message) arg; + switch(message.getTypeMessage()) { + case TEXTE: + System.out.println(message.toString()); + this.vue.appendMessage(message); + this.messagesIn.add(message); + break; + case IMAGE: + this.vue.appendImage(message); + + if(message.getSender().equals("Moi")) { + this.messagesOut.add(message); + }else { + this.messagesIn.add(message); + } + break; + case FICHIER: + this.vue.appendMessage(message); + + if(message.getSender().equals("Moi")) { + this.messagesOut.add(message); + }else { + this.messagesIn.add(message); + } + break; + case FICHIER_INIT: + try { + MessageFichier mFichier = (MessageFichier) arg; + int nbFile = Integer.parseInt(mFichier.getContenu()); + FileTransferServer fts = new FileTransferServer(nbFile, this); + int port = fts.getPort(); + fts.start(); + this.answerFileTransfer(port); + + } catch (IOException e) { + e.printStackTrace(); + } + break; + case FICHIER_ANSWER: + try { + MessageFichier mFichier = (MessageFichier) arg; + int port = Integer.parseInt(mFichier.getContenu()); + + @SuppressWarnings("unchecked") + FileTransferClient ftc = new FileTransferClient(this.ipOther, port ,(ArrayList) this.files.clone(), this); + + ftc.sendFiles(); + this.files.clear(); + } catch (IOException | InterruptedException e) { + e.printStackTrace(); + } + + break; + default: + } + + } + + @Override + public void updateSocketState(Object o, Object arg) { + this.vue.endSession(this.pseudoOther); + } + + protected void destroyAll() { + String idSelf = Utilisateur.getSelf().getId(); + String idOther = this.idOther; + + try { + this.sqlManager.insertAllMessages(messagesOut, idSelf, idOther); + this.sqlManager.insertAllMessages(messagesIn, idOther, idSelf); + } catch (SQLException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + + this.vue = null; + this.tcpClient.destroyAll(); + this.tcpClient = null; + } + +} \ No newline at end of file diff --git a/src_reseau/session/VueSession.java b/src_reseau/session/VueSession.java new file mode 100644 index 0000000..340b412 --- /dev/null +++ b/src_reseau/session/VueSession.java @@ -0,0 +1,264 @@ +package session; + +import java.awt.*; +import java.awt.event.KeyEvent; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.net.InetAddress; +import java.net.Socket; +import java.util.ArrayList; + +import javax.swing.Icon; +import javax.swing.ImageIcon; +import javax.swing.JButton; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTextArea; +import javax.swing.JTextPane; +import javax.swing.KeyStroke; +import javax.swing.ScrollPaneConstants; +import javax.swing.border.EmptyBorder; +import javax.swing.text.AbstractDocument; +import javax.swing.text.BadLocationException; +import javax.swing.text.BoxView; +import javax.swing.text.ComponentView; +import javax.swing.text.Document; +import javax.swing.text.Element; +import javax.swing.text.IconView; +import javax.swing.text.LabelView; +import javax.swing.text.ParagraphView; +import javax.swing.text.StyleConstants; +import javax.swing.text.StyledDocument; +import javax.swing.text.StyledEditorKit; +import javax.swing.text.View; +import javax.swing.text.ViewFactory; + +import communication.filetransfer.FileTransferUtils; +import database.SQLiteManager; +import messages.Message; +import messages.Message.TypeMessage; + +public class VueSession extends JPanel { + + /** + * + */ + private static final long serialVersionUID = 1L; + + private JButton sendMessage; + private JButton importFile; + private JTextPane chatWindow; + private JTextArea chatInput; + private ControleurSession c; + + public VueSession(Socket socketComm, String idOther, String pseudoOther, InetAddress ipOther, SQLiteManager sqlManager) + throws IOException { + + this.c = new ControleurSession(this, socketComm, idOther, pseudoOther, ipOther, sqlManager); + + this.setBorder(new EmptyBorder(5, 5, 5, 5)); + this.setLayout(new BorderLayout(0, 0)); + + this.chatInput = new JTextArea(); + this.chatInput.setColumns(10); + this.chatInput.setLineWrap(true); + this.chatInput.setWrapStyleWord(true); + this.chatInput.addKeyListener(this.c); + + this.chatWindow = new JTextPane(); + this.chatWindow.setEditable(false); + this.chatWindow.setEditorKit(new WrapEditorKit()); + + JScrollPane chatScroll = new JScrollPane(this.chatWindow); + chatScroll.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED); + + JPanel bottom = new JPanel(); + bottom.setLayout(new BorderLayout(0, 0)); + + // remap "ENTER" to "none" to avoid "\n" in the input area after sending message + KeyStroke enter = KeyStroke.getKeyStroke("ENTER"); + this.chatInput.getInputMap().put(enter, "none"); + KeyStroke shiftEnter = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, KeyEvent.SHIFT_DOWN_MASK); + this.chatInput.getInputMap().put(shiftEnter, "insert-break"); + + this.importFile = new JButton("Importer.."); + this.importFile.addActionListener(this.c); + + JScrollPane inputScroll = new JScrollPane(this.chatInput); + inputScroll.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); + + this.sendMessage = new JButton("Envoyer"); + this.sendMessage.addActionListener(this.c); + + bottom.add(this.importFile, BorderLayout.WEST); + bottom.add(inputScroll, BorderLayout.CENTER); + bottom.add(this.sendMessage, BorderLayout.EAST); + + this.add(chatScroll, BorderLayout.CENTER); + this.add(bottom, BorderLayout.SOUTH); + + this.setPreferredSize(new Dimension(500, 500)); + + this.displayHistorique(); + + } + + protected JButton getButtonEnvoyer() { + return this.sendMessage; + } + + protected JButton getButtonImportFile() { + return this.importFile; + } + + protected String getInputedText() { + return this.chatInput.getText(); + } + + protected void appendInputedText(String str) { + this.chatInput.append(str); + } + + + protected void resetZoneSaisie() { + this.chatInput.setText(""); + + } + + private void setCaretToEnd() { + this.chatWindow.setCaretPosition(this.chatWindow.getDocument().getLength()); + } + + protected void appendString(String str) { + try { + Document doc = this.chatWindow.getDocument(); + doc.insertString(doc.getLength(), str, null); + } catch (BadLocationException e) { + e.printStackTrace(); + } + } + + protected void appendMessage(Message message) { + + try { + StyledDocument sdoc = this.chatWindow.getStyledDocument(); + // sdoc.setParagraphAttributes(sdoc.getLength(), message.toString().length()-1, + // style, false); + sdoc.insertString(sdoc.getLength(), message.toString(), null); + + } catch (BadLocationException e) { + e.printStackTrace(); + } + } + + protected void appendImage(Message message) { + this.setCaretToEnd(); + + String imgString = message.toString(); + Icon ic; + try { + BufferedImage img = FileTransferUtils.decodeImage(imgString); + ic = new ImageIcon(img); + this.chatWindow.insertIcon(ic); + this.appendString("\n"); + + } catch (IOException e) { + e.printStackTrace(); + } + + + } + + private void printLineSeparator() { + this.appendString("------------------------------------------\n"); + } + + protected void endSession(String pseudoOther) { + this.printLineSeparator(); + this.appendString(pseudoOther + " a mis fin à la session."); + this.chatInput.setEnabled(false); + this.chatInput.setFocusable(false); + this.sendMessage.setEnabled(false); + this.importFile.setEnabled(false); + } + + private void displayHistorique() { + ArrayList historique = this.c.getHistorique(); + + for (Message m : historique) { + if(m.getTypeMessage() == TypeMessage.IMAGE) { + this.appendImage(m); + }else { + this.appendMessage(m); + } + } + + if (historique.size() > 0) { + this.printLineSeparator(); + } + } + + public void destroyAll() { + if (this.c != null) { + this.c.destroyAll(); + } + this.c = null; + this.chatInput = null; + this.chatWindow = null; + this.sendMessage = null; + } + + // ------------- PRIVATE CLASS TO WRAP TEXT -------------// + + class WrapEditorKit extends StyledEditorKit { + + private static final long serialVersionUID = 1L; + + ViewFactory defaultFactory = new WrapColumnFactory(); + + public ViewFactory getViewFactory() { + return defaultFactory; + } + + } + + class WrapColumnFactory implements ViewFactory { + public View create(Element elem) { + String kind = elem.getName(); + if (kind != null) { + if (kind.equals(AbstractDocument.ContentElementName)) { + return new WrapLabelView(elem); + } else if (kind.equals(AbstractDocument.ParagraphElementName)) { + return new ParagraphView(elem); + } else if (kind.equals(AbstractDocument.SectionElementName)) { + return new BoxView(elem, View.Y_AXIS); + } else if (kind.equals(StyleConstants.ComponentElementName)) { + return new ComponentView(elem); + } else if (kind.equals(StyleConstants.IconElementName)) { + return new IconView(elem); + } + } + + // default to text display + return new LabelView(elem); + } + } + + class WrapLabelView extends LabelView { + public WrapLabelView(Element elem) { + super(elem); + } + + public float getMinimumSpan(int axis) { + switch (axis) { + case View.X_AXIS: + return 0; + case View.Y_AXIS: + return super.getMinimumSpan(axis); + default: + throw new IllegalArgumentException("Invalid axis: " + axis); + } + } + + } +} \ No newline at end of file diff --git a/src_reseau/standard/ControleurStandard.java b/src_reseau/standard/ControleurStandard.java new file mode 100644 index 0000000..ef1570c --- /dev/null +++ b/src_reseau/standard/ControleurStandard.java @@ -0,0 +1,318 @@ +package standard; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.WindowEvent; +import java.awt.event.WindowListener; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.PrintWriter; +import java.net.InetAddress; +import java.net.Socket; +import java.net.UnknownHostException; +import java.util.ArrayList; + +import javax.swing.JButton; +import javax.swing.JList; +import javax.swing.WindowConstants; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; + +import communication.tcp.TCPServer; +import communication.udp.CommunicationUDP; +import connexion.VueConnexion; +import database.SQLiteManager; +import main.Utilisateur; +import observers.ObserverInputMessage; +import observers.ObserverSocketState; +import observers.ObserverUserList; +import session.VueSession; + +public class ControleurStandard implements ActionListener, ListSelectionListener, WindowListener, ObserverInputMessage, + ObserverUserList, ObserverSocketState { + + private enum ModifPseudo { + TERMINE, EN_COURS + } + + private ModifPseudo modifPseudo; + private VueStandard vue; + private CommunicationUDP commUDP; + private String lastPseudo; + private TCPServer tcpServ; + private ArrayList idsSessionEnCours; + private SQLiteManager sqlManager; + private VueConnexion vueConnexion; + + public ControleurStandard(VueStandard vue, CommunicationUDP commUDP, SQLiteManager sqlManager, VueConnexion vueConnexion) throws IOException { + this.vue = vue; + this.vue.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); + + this.vueConnexion = vueConnexion; + this.tcpServ = new TCPServer(); + + this.tcpServ.addObserver(this); + this.tcpServ.start(); + + this.commUDP = commUDP; + + this.idsSessionEnCours = new ArrayList(); + + this.sqlManager = sqlManager; + } + + // ---------- LISTSELECTION LISTENER OPERATIONS ----------// + @Override + public void valueChanged(ListSelectionEvent e) { + + // Case when a list element is selected + if (this.vue.getActiveUsersList().isFocusOwner() && !e.getValueIsAdjusting() + && this.vue.getActiveUsersList().getSelectedValue() != null) { + + JList list = this.vue.getActiveUsersList(); + String pseudoOther = list.getSelectedValue(); + Utilisateur other = this.commUDP.getUserFromPseudo(pseudoOther); + String idOther = other.getId(); + + // Check if we are already asking for a session/chatting with the person + // selected + // null condition because the list.clearSelection() generates an event + if (!this.idsSessionEnCours.contains(idOther)) { + + int choix = this.vue.displayJOptionSessionCreation(pseudoOther); + System.out.println("choix : " + choix); + + if (choix == 0) { + + InetAddress ipOther = other.getIp(); + + try { + + Socket socketComm = new Socket(ipOther, TCPServer.PORT_SERVER); + + this.sendMessage(socketComm, Utilisateur.getSelf().getPseudo()); + String reponse = this.readMessage(socketComm); + + System.out.println("reponse : " + reponse); + + if (reponse.equals("accepted")) { + this.idsSessionEnCours.add(idOther); + + VueSession session = new VueSession(socketComm, idOther, pseudoOther, ipOther, this.sqlManager); + this.vue.addSession(pseudoOther, session); + + this.vue.displayJOptionResponse("acceptee"); + + } else { + this.vue.displayJOptionResponse("refusee"); + socketComm.close(); + System.out.println("refused"); + } + + } catch (IOException e1) { + e1.printStackTrace(); + } + } + + } + + list.clearSelection(); + System.out.println("pseudo de la personne a atteindre : " + pseudoOther); + + } + } + + // ---------- ACTION LISTENER OPERATIONS ----------// + @Override + public void actionPerformed(ActionEvent e) { + + // Cas Modifier Pseudo + if ((JButton) e.getSource() == this.vue.getButtonModifierPseudo()) { + JButton modifierPseudo = (JButton) e.getSource(); + + if (this.modifPseudo == ModifPseudo.TERMINE) { + this.lastPseudo = Utilisateur.getSelf().getPseudo(); + modifierPseudo.setText("OK"); + this.modifPseudo = ModifPseudo.EN_COURS; + } else { + + if (this.vue.getDisplayedPseudo().length() >= 1 + && !this.commUDP.containsUserFromPseudo(this.vue.getDisplayedPseudo().toLowerCase())) { + + Utilisateur.getSelf().setPseudo(this.vue.getDisplayedPseudo()); + this.commUDP.sendMessageInfoPseudo(); + + } else { + this.vue.setDisplayedPseudo(this.lastPseudo); + } + + modifierPseudo.setText("Modifier"); + this.modifPseudo = ModifPseudo.TERMINE; + } + + this.vue.toggleEditPseudo(); + } + + // Cas deconnexion + else if ((JButton) e.getSource() == this.vue.getButtonDeconnexion()) { + try { + this.setVueConnexion(); + } catch (IOException e1) { + + e1.printStackTrace(); + } + } + + else if (this.vue.isButtonTab(e.getSource())) { + JButton button = (JButton) e.getSource(); + int index = this.vue.removeSession(button); + this.idsSessionEnCours.remove(index); + } + } + + // ---------- WINDOW LISTENER OPERATIONS ----------// + + @Override + public void windowClosing(WindowEvent e) { + + try { + this.setVueConnexion(); + + } catch (IOException e1) { + e1.printStackTrace(); + } + + } + + @Override + public void windowOpened(WindowEvent e) { + // TODO Auto-generated method stub + + } + + @Override + public void windowClosed(WindowEvent e) { + + } + + @Override + public void windowIconified(WindowEvent e) { + // TODO Auto-generated method stub + + } + + @Override + public void windowDeiconified(WindowEvent e) { + // TODO Auto-generated method stub + + } + + @Override + public void windowActivated(WindowEvent e) { + // TODO Auto-generated method stub + + } + + @Override + public void windowDeactivated(WindowEvent e) { + // TODO Auto-generated method stub + + } + + // ------------SOCKET-------------// + + private void sendMessage(Socket sock, String message) throws IOException { + PrintWriter output = new PrintWriter(sock.getOutputStream(), true); + output.println(message); + } + + private String readMessage(Socket sock) throws IOException { + + BufferedReader input = new BufferedReader(new InputStreamReader(sock.getInputStream())); + return input.readLine(); + } + + // ------------OBSERVERS-------------// + + @Override + public void update(Object o, Object arg) { + + if (o == this.tcpServ) { + + Socket sockAccept = (Socket) arg; + + try { + + String pseudoOther = this.readMessage(sockAccept); + Utilisateur other = this.commUDP.getUserFromPseudo(pseudoOther); + String idOther = other.getId(); + InetAddress ipOther = other.getIp(); + + int reponse; + + if (!this.idsSessionEnCours.contains(idOther)) { + reponse = this.vue.displayJOptionAskForSession(pseudoOther); + System.out.println("reponse : " + reponse); + } else { + reponse = 1; + } + + if (reponse == 0) { + + this.idsSessionEnCours.add(idOther); + this.sendMessage(sockAccept, "accepted"); + + VueSession session = new VueSession(sockAccept, idOther, pseudoOther,ipOther, this.sqlManager); + this.vue.addSession(pseudoOther, session); + } else { + this.sendMessage(sockAccept, "refused"); + sockAccept.close(); + } + + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + @Override + public void updateList(Object o, ArrayList userList) { + + if (o == this.commUDP) { + ArrayList pseudos = new ArrayList(); + for (Utilisateur u : userList) { + pseudos.add(u.getPseudo()); + } + this.vue.setActiveUsersList(pseudos); + } + } + + @Override + public void updateSocketState(Object o, Object arg) { + VueSession session = (VueSession) arg; + int index = this.vue.removeSession(session); + this.idsSessionEnCours.remove(index); + } + + private void setVueConnexion() throws UnknownHostException, IOException { + this.commUDP.sendMessageDelete(); + this.vue.removeAllUsers(); + this.vue.closeAllSession(); + this.idsSessionEnCours.clear(); + Utilisateur.resetSelf(); + this.commUDP.setObserver(null); + + this.vue.setVisible(false); + this.vueConnexion.setVisible(true); + } + + protected void init() throws UnknownHostException, IOException { + this.commUDP.setObserver(this); + this.commUDP.sendMessageConnecte(); + this.commUDP.sendMessageInfoPseudo(); + this.modifPseudo = ModifPseudo.TERMINE; + } + + +} \ No newline at end of file diff --git a/src_reseau/standard/VueStandard.java b/src_reseau/standard/VueStandard.java new file mode 100644 index 0000000..c8c8157 --- /dev/null +++ b/src_reseau/standard/VueStandard.java @@ -0,0 +1,373 @@ +package standard; + +import java.awt.*; +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.io.IOException; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.Iterator; + +import javax.swing.AbstractButton; +import javax.swing.BorderFactory; +import javax.swing.DefaultListModel; +import javax.swing.JButton; +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTabbedPane; +import javax.swing.JTextField; +import javax.swing.ListSelectionModel; +import javax.swing.ScrollPaneConstants; +import javax.swing.plaf.basic.BasicButtonUI; + +import communication.udp.CommunicationUDP; +import connexion.VueConnexion; +import database.SQLiteManager; +import main.Utilisateur; +import main.Vue; +import session.VueSession; + +public class VueStandard extends Vue { + + /** + * + */ + private static final long serialVersionUID = 1L; + + private JList activeUsersList; + private JTextField pseudoSelf; + private JTabbedPane zoneSessions; + private JButton modifierPseudo; + private JButton seConnecter; + private JButton seDeconnecter; + private ControleurStandard c; + private ArrayList tabButtons; + private ArrayList sessions; + private DefaultListModel userList = new DefaultListModel(); + + public VueStandard(String title, CommunicationUDP commUDP, SQLiteManager sqlManager, VueConnexion vueConnexion) throws IOException { + super(title); + + this.tabButtons = new ArrayList(); + this.sessions = new ArrayList(); + this.c = new ControleurStandard(this, commUDP, sqlManager, vueConnexion); + this.c.init(); + + getContentPane().setLayout(new GridBagLayout()); + + JPanel left = new JPanel(new BorderLayout()); + + this.zoneSessions = new JTabbedPane(); + this.zoneSessions.setTabPlacement(JTabbedPane.BOTTOM); + + this.zoneSessions.setPreferredSize(new Dimension(600, 600)); + + // --------Panel haut pseudo--------// + JPanel self = new JPanel(new FlowLayout()); + + this.pseudoSelf = new JTextField(Utilisateur.getSelf().getPseudo()); + this.pseudoSelf.setPreferredSize(new Dimension(100, 30)); + this.pseudoSelf.setEditable(false); + this.pseudoSelf.setFocusable(false); + this.pseudoSelf.setEnabled(false); + + this.modifierPseudo = new JButton("Modifier"); + this.modifierPseudo.addActionListener(this.c); + + self.add(new JLabel("Moi : ")); + self.add(this.pseudoSelf); + self.add(this.modifierPseudo); + this.pseudoSelf.addKeyListener(new KeyListener() { + + @Override + public void keyTyped(KeyEvent e) { + } + + @Override + public void keyReleased(KeyEvent e) { + } + + @Override + public void keyPressed(KeyEvent e) { + if (e.getKeyCode() == KeyEvent.VK_ENTER) { + modifierPseudo.doClick(); + } + } + }); + + // --------Panel milieu liste utilisateurs--------// + this.activeUsersList = new JList(this.userList); + this.activeUsersList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + this.activeUsersList.setLayoutOrientation(JList.VERTICAL); + this.activeUsersList.addListSelectionListener(this.c); + + System.out.println("listener ajouté"); + + JScrollPane listScroller = new JScrollPane(this.activeUsersList); + listScroller.setPreferredSize(new Dimension(50, 50)); + listScroller.setAlignmentX(LEFT_ALIGNMENT); + listScroller.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS); + listScroller.setBorder(BorderFactory.createCompoundBorder( + BorderFactory.createTitledBorder("Utilisateurs Actifs"), BorderFactory.createEmptyBorder(5, 2, 2, 2))); + + // --------Panel bas deconnexion--------// + JPanel deconnexion = new JPanel(new GridLayout(1, 2)); + + this.seDeconnecter = new JButton("Se Déconnecter"); + this.seDeconnecter.addActionListener(this.c); + deconnexion.add(this.seDeconnecter); + + if(Utilisateur.getSelf().getId() == "admin") { + JButton addNewUser = new JButton("Ajouter un nouvel utilisateur"); + deconnexion.add(addNewUser); + } + + // --------Ajout � la vue--------// + left.add(self, BorderLayout.PAGE_START); + left.add(listScroller, BorderLayout.CENTER); + left.add(deconnexion, BorderLayout.PAGE_END); + + GridBagConstraints gridBagConstraintLeft = new GridBagConstraints(); + GridBagConstraints gridBagConstraintSessions = new GridBagConstraints(); + + gridBagConstraintLeft.fill = GridBagConstraints.BOTH; + gridBagConstraintLeft.gridx = 0; + gridBagConstraintLeft.gridy = 0; + gridBagConstraintLeft.gridwidth = 1; + gridBagConstraintLeft.gridheight = 4; + gridBagConstraintLeft.weightx = 0.33; + gridBagConstraintLeft.weighty = 1; + + getContentPane().add(left, gridBagConstraintLeft); + + gridBagConstraintSessions.fill = GridBagConstraints.BOTH; + gridBagConstraintSessions.gridx = 1; + gridBagConstraintSessions.gridy = 0; + gridBagConstraintSessions.gridwidth = 2; + gridBagConstraintSessions.gridheight = 4; + gridBagConstraintSessions.weightx = 0.66; + gridBagConstraintSessions.weighty = 1; + + getContentPane().add(this.zoneSessions, gridBagConstraintSessions); + + this.pack(); + this.setVisible(true); + + this.addWindowListener(c); + } + + // ------------ GETTERS -------------// + + protected JList getActiveUsersList() { + return this.activeUsersList; + } + + protected JButton getButtonModifierPseudo() { + return this.modifierPseudo; + } + + protected JButton getButtonDeconnexion() { + return this.seDeconnecter; + } + + protected JButton getButtonConnexion() { + return this.seConnecter; + } + + protected String getDisplayedPseudo() { + return this.pseudoSelf.getText(); + } + + // ------------ SETTERS -------------// + + protected void setActiveUsersList(ArrayList users) { + this.removeAllUsers(); + this.userList.addAll(users); + } + + protected void setDisplayedPseudo(String pseudo) { + this.pseudoSelf.setText(pseudo); + } + + public void setPseudoSelf() { + this.setDisplayedPseudo(Utilisateur.getSelf().getPseudo()); + } + + // ------------ JOPTIONS -------------// + + protected int displayJOptionSessionCreation(String pseudo) { + return JOptionPane.showConfirmDialog(this, "Voulez vous créer une session avec " + pseudo + " ?", + "Confirmation session", JOptionPane.YES_NO_OPTION); + } + + protected int displayJOptionAskForSession(String pseudo) { + return JOptionPane.showConfirmDialog(this, pseudo + " souhaite créer une session avec vous.", + "Accepter demande", JOptionPane.YES_NO_OPTION); + } + + protected void displayJOptionResponse(String reponse) { + JOptionPane.showMessageDialog(this, "Demande de session " + reponse); + } + + // ------------ TOGGLEBUTTONS -------------// + + protected void toggleEditPseudo() { + this.pseudoSelf.setEditable(!this.pseudoSelf.isEditable()); + this.pseudoSelf.setFocusable(!this.pseudoSelf.isFocusable()); + this.pseudoSelf.setEnabled(!this.pseudoSelf.isEnabled()); + } + + protected void toggleEnableButtonDeconnexion() { + this.seDeconnecter.setEnabled(!this.seDeconnecter.isEnabled()); + } + + protected void toggleEnableButtonConnexion() { + this.seConnecter.setEnabled(!this.seConnecter.isEnabled()); + } + + // ------------SESSION-------------// + + protected boolean isButtonTab(Object o) { + return this.tabButtons.contains(o); + } + + protected int removeSession(JButton button) { + int index = this.tabButtons.indexOf(button); + + VueSession vue = this.sessions.get(index); + vue.destroyAll(); + + this.zoneSessions.remove(vue); + this.sessions.remove(index); + this.tabButtons.remove(index); + + return index; + } + + protected void addSession(String pseudo, VueSession session) { + JPanel tabTitle = new JPanel(); + + TabButton closeTab = new TabButton(); + + tabTitle.add(new JLabel(pseudo)); + tabTitle.add(closeTab); + + this.zoneSessions.addTab(pseudo, session); + this.zoneSessions.setTabComponentAt(this.zoneSessions.getTabCount() - 1, tabTitle); + + this.tabButtons.add(closeTab); + this.sessions.add(session); + + session.requestFocus(); + + } + + protected synchronized int removeSession(VueSession vue) { + int index = this.sessions.indexOf(vue); + + vue.destroyAll(); + + this.zoneSessions.remove(vue); + this.sessions.remove(index); + this.tabButtons.remove(index); + + return index; + } + + protected void closeAllSession() { + Iterator it = this.sessions.iterator(); + while(it.hasNext()) { + VueSession session = it.next(); + this.zoneSessions.remove(session); + session.destroyAll(); + it.remove(); + } + + this.tabButtons.clear(); + } + + + // ------------ OTHERS -------------// + + protected void removeAllUsers() { + this.userList.removeAllElements(); + } + + public void initControleur() throws UnknownHostException, IOException { + this.c.init(); + } + + // ------------- PRIVATE CLASSES FOR THE TABS BUTTON -------------// + private class TabButton extends JButton { + + private static final long serialVersionUID = 1L; + + public TabButton() { + int size = 17; + setPreferredSize(new Dimension(size, size)); + setToolTipText("close this tab"); + // Make the button looks the same for all Laf's + setUI(new BasicButtonUI()); + // Make it transparent + setContentAreaFilled(false); + // No need to be focusable + setFocusable(false); + setBorder(BorderFactory.createEtchedBorder()); + setBorderPainted(false); + + addMouseListener(VueStandard.buttonMouseListener); + // Making nice rollover effect + // we use the same listener for all buttons + addActionListener(c); + setRolloverEnabled(true); + } + + // we don't want to update UI for this button + public void updateUI() { + } + + // paint the cross + protected void paintComponent(Graphics g) { + super.paintComponent(g); + Graphics2D g2 = (Graphics2D) g.create(); + // shift the image for pressed buttons + if (getModel().isPressed()) { + g2.translate(1, 1); + } + g2.setStroke(new BasicStroke(2)); + g2.setColor(Color.BLACK); + if (getModel().isRollover()) { + g2.setColor(Color.MAGENTA); + } + int delta = 6; + g2.drawLine(delta, delta, getWidth() - delta - 1, getHeight() - delta - 1); + g2.drawLine(getWidth() - delta - 1, delta, delta, getHeight() - delta - 1); + g2.dispose(); + } + } + + private final static MouseListener buttonMouseListener = new MouseAdapter() { + public void mouseEntered(MouseEvent e) { + Component component = e.getComponent(); + if (component instanceof AbstractButton) { + AbstractButton button = (AbstractButton) component; + button.setBorderPainted(true); + } + } + + public void mouseExited(MouseEvent e) { + Component component = e.getComponent(); + if (component instanceof AbstractButton) { + AbstractButton button = (AbstractButton) component; + button.setBorderPainted(false); + } + } + }; + +} \ No newline at end of file