feat(net): Modification méthode handleConnection, fin de ChatClient et de ClientTCP

This commit is contained in:
bouclyma 2024-12-28 01:54:36 +01:00
parent 0d13bc526b
commit 8ec377bec0
5 changed files with 269 additions and 11 deletions

View file

@ -24,10 +24,8 @@ import net.synedra.validatorfx.TooltipWrapper;
import net.synedra.validatorfx.Validator;
import rtgre.chat.graphisme.ContactListViewCell;
import rtgre.chat.graphisme.PostListViewCell;
import rtgre.modeles.Contact;
import rtgre.modeles.ContactMap;
import rtgre.modeles.Message;
import rtgre.modeles.Post;
import rtgre.chat.net.ChatClient;
import rtgre.modeles.*;
import java.awt.image.BufferedImage;
import java.io.File;
@ -38,6 +36,7 @@ import java.util.Date;
import java.util.Objects;
import java.util.ResourceBundle;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static rtgre.chat.ChatApplication.LOGGER;
@ -46,10 +45,11 @@ public class ChatController implements Initializable {
private static final Pattern LOGIN_PATTERN = Pattern.compile("^([a-z][a-z0-9]{2,7})$");
private static final Pattern HOST_PATTERN = Pattern.compile("/^[a-z]*((\\:?)\\d{1,5})?$/gm");
private final Pattern hostPortPattern = Pattern.compile("^([-.a-zA-Z0-9]+)(?::([0-9]{1,5}))?$");
public MenuItem hostAddMenuItem;
public MenuItem avatarMenuItem;
public MenuItem aboutMenuItem;
public ComboBox hostComboBox;
public ComboBox<String> hostComboBox;
public TextField loginTextField;
public ToggleButton connectionButton;
public ImageView avatarImageView;
@ -66,6 +66,8 @@ public class ChatController implements Initializable {
private ObservableList<Contact> contactObservableList = FXCollections.observableArrayList();
private ObservableList<Post> postsObservableList = FXCollections.observableArrayList();
Validator validatorLogin = new Validator();
private ChatClient client = null;
private PostVector postVector;
@Override
@ -147,7 +149,38 @@ public class ChatController implements Initializable {
contactMap.put(this.contact.getLogin(), this.contact);
LOGGER.info("Nouveau contact : " + contact);
LOGGER.info(contactMap.toString());
Matcher matcher = hostPortPattern.matcher("localhost:2024");
matcher.matches();
String host = matcher.group(1);
int port = (matcher.group(2) != null) ? Integer.parseInt(matcher.group(2)) : 2024;
try {
this.client = new ChatClient(host, port, this);
initContactListView();
initPostListView();
clearLists();
contactMap.add(this.contact);
this.contact.setConnected(true);
initContactListView();
initPostListView();
this.statusLabel.setText("Connected to %s@%s:%s".formatted(this.contact.getLogin(), host, port));
} catch (IOException e) {
new Alert(Alert.AlertType.ERROR, "Erreur de connexion").showAndWait();
connectionButton.setSelected(false);
}
} else if (!connectionButton.isSelected()) {
clearLists();
this.client.close();
this.contact.setConnected(false);
statusLabel.setText("not connected to " + hostComboBox.getValue());
}
}
private void clearLists() {
this.contactMap = new ContactMap();
this.postVector = new PostVector();
contactObservableList.clear();
postsObservableList.clear();
}
private void checkLogin(Check.Context context) {
@ -185,10 +218,7 @@ public class ChatController implements Initializable {
contactsListView.setCellFactory(contactListView -> new ContactListViewCell());
contactsListView.setItems(contactObservableList);
File avatars = new File(getClass().getResource("avatars.png").toURI());
Contact riri = new Contact("riri", false, avatars);
Contact fifi = new Contact("fifi", true, avatars);
contactObservableList.add(riri);
contactMap.add(riri);
contactObservableList.add(fifi);
contactMap.add(fifi);
} catch (Exception e) {

View file

@ -0,0 +1,53 @@
package rtgre.chat.net;
import rtgre.chat.ChatController;
import java.io.IOException;
import java.util.logging.Logger;
import static rtgre.chat.ChatApplication.LOGGER;
public class ChatClient extends ClientTCP {
private final ChatController listener;
/**
* Le constructeur ouvre la connexion TCP au serveur <code>host:port</code>
* et récupère les flux de caractères en entrée {@link #in} et sortie {@link #out}
* import static rtgre.chat.ChatApplication.LOGGER;
*
* @param host IP ou nom de domaine du serveur
* @param port port d'écoute du serveur
* @param listener instance de ChatController liée au client
* @throws IOException
*/
public ChatClient(String host, int port, ChatController listener) throws IOException {
super(host, port);
this.listener = listener;
}
@Override
public void receiveLoop() {
LOGGER.info(RED + "Boucle de réception de messages..." + RST);
try {
while (connected) {
String message = this.receive();
LOGGER.info(RED + "Réception: " + message + RST);
LOGGER.info(RED + message + RST);
}
} catch (IOException e) {
LOGGER.severe("[%s] %s".formatted(ipPort, e));
connected = false;
} finally {
close();
}
}
public Logger getLogger() {
return LOGGER;
}
public ChatController getListener() {
return listener;
}
}

View file

@ -1,5 +1,7 @@
package rtgre.chat.net;
import rtgre.chat.ChatController;
import java.io.*;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
@ -19,15 +21,16 @@ public class ClientTCP {
public static final String RST = "\u001b[0m";
public static final String END_MESSAGE = "fin";
/*
static {
try {
InputStream is = ClientTCP.class.getClassLoader()
.getResource("logging.properties").openStream();
.getResource("rtgre/chat/logging.properties").openStream();
LogManager.getLogManager().readConfiguration(is);
} catch (Exception e) {
LOGGER.log(Level.INFO, "Cannot read configuration file", e);
}
}
}*/
public static void main(String[] args) throws Exception {
/*
@ -94,6 +97,9 @@ public class ClientTCP {
LOGGER.fine("[%s] Conversion flux d'octets en flux de caractères UTF-8".formatted(ipPort));
out = new PrintStream(os, true, StandardCharsets.UTF_8);
in = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8), 2048);
Thread rcLoop = new Thread(this::receiveLoop);
rcLoop.setDaemon(true);
rcLoop.start();
}
/**

View file

@ -9,7 +9,7 @@ handlers=java.util.logging.ConsoleHandler, java.util.logging.FileHandler
java.util.logging.FileHandler.formatter=java.util.logging.SimpleFormatter
# Nom du fichier de logs
java.util.logging.FileHandler.pattern=../out/java%u.log
java.util.logging.FileHandler.pattern=../target/java%u.log
# Format de logs plus compact sur 1 seule ligne
java.util.logging.SimpleFormatter.format=%1$tF %1$tT.%1$tL | %4$-7s | %2$s | %5$s %6$s%n

View file

@ -0,0 +1,169 @@
package rtgre.chat.net;
import org.junit.jupiter.api.*;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.logging.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.junit.jupiter.params.provider.Arguments.arguments;
class ChatClientTest {
static Class classe = ChatClient.class;
static String module = "rtgre.chat.net";
@DisplayName("01-Structure de ChatClient")
@Nested
class StructureTest {
static List<String> methodesSignatures;
static List<String> constructeursSignatures;
@BeforeAll
static void init() {
// Les méthodes
Method[] methodes = classe.getDeclaredMethods();
methodesSignatures = Arrays.asList(methodes).stream().map(e -> e.toString()).collect(Collectors.toList());
Constructor<?>[] constructeurs = classe.getConstructors();
constructeursSignatures = Arrays.asList(constructeurs).stream().map(e -> e.toString()).collect(Collectors.toList());
}
static Stream<Arguments> attributsProvider() {
return Stream.of(
arguments("listener", "rtgre.chat.ChatController", Modifier.PRIVATE | Modifier.FINAL)
);
}
@DisplayName("Déclaration des attributs")
@ParameterizedTest
@MethodSource("attributsProvider")
void testDeclarationAttributs(String nom, String type, int modifier) throws NoSuchFieldException {
Field field = classe.getDeclaredField(nom);
Assertions.assertEquals(type, field.getType().getName(),
"Type " + nom + " erroné : doit être " + type);
Assertions.assertEquals(modifier, field.getModifiers(),
"Visibilité " + nom + " erronée : doit être " + modifier);
}
static Stream<Arguments> constructeursProvider() {
return Stream.of(
arguments("public %s.ChatClient(java.lang.String,int,rtgre.chat.ChatController) throws java.io.IOException")
);
}
@DisplayName("Déclaration des constructeurs")
@ParameterizedTest
@MethodSource("constructeursProvider")
void testConstructeurs1(String signature) {
Assertions.assertTrue(constructeursSignatures.contains(String.format(signature, module)),
String.format("Constructeur non déclaré : doit être %s\nalors que sont déclarés %s",
signature, constructeursSignatures));
}
static Stream<Arguments> methodesProvider() {
return Stream.of(
arguments("receiveLoop", "public void %s.ChatClient.receiveLoop()"),
arguments("getLogger", "public java.util.logging.Logger %s.ChatClient.getLogger()")
);
}
@DisplayName("Déclaration des méthodes")
@ParameterizedTest
@MethodSource("methodesProvider")
void testDeclarationMethodes1(String nom, String signature) {
Assertions.assertTrue(methodesSignatures.contains(String.format(signature, module, module)),
String.format("Méthode non déclarée : doit être %s\nalors que sont déclarés %s",
signature, methodesSignatures));
}
}
@DisplayName("02-Connexion/déconnexion (port=1810)")
@Nested
class ConnexionTest {
static int port = 1810;
static LocalServer server;
static ExecutorService executorService;
@BeforeAll
static void init() throws IOException {
server = new LocalServer(port);
executorService = new ThreadPoolExecutor(1, 1, 2,
TimeUnit.SECONDS, new LinkedBlockingDeque<Runnable>());
executorService.submit(server::acceptClients);
}
@AfterAll
static void close() throws IOException {
server.passiveSocket.close();
executorService.shutdown();
}
@DisplayName("Boucle de réception")
@Test
void testReceiveLoop() throws IOException, InterruptedException {
ChatClient client = new ChatClient("localhost", port, null);
Logger logger = client.getLogger();
ByteArrayOutputStream out = new ByteArrayOutputStream();
Handler handler = new StreamHandler(out, new SimpleFormatter());
logger.addHandler(handler);
server.clientOut.println("bonjour");
executorService.awaitTermination(1, TimeUnit.SECONDS);
handler.flush();
String logMsg = out.toString();
client.close();
Assertions.assertTrue(logMsg.contains("bonjour"),
"Le message reçu doit être loggué");
}
}
}
class LocalServer {
public ServerSocket passiveSocket;
public Socket clientSocket;
public PrintStream clientOut;
public BufferedReader clientIn;
public boolean stop = false;
public LocalServer(int port) throws IOException {
passiveSocket = new ServerSocket(port);
}
public void acceptClients() {
while (!stop) {
try {
clientSocket = passiveSocket.accept();
clientOut = new PrintStream(clientSocket.getOutputStream(), true, StandardCharsets.UTF_8);
clientIn = new BufferedReader(new InputStreamReader(clientSocket.getInputStream(), StandardCharsets.UTF_8), 2048);
} catch (IOException e) {
stop = true;
}
}
}
}