From 2fe6235869217855174c2ab01c018e977aa62ddf Mon Sep 17 00:00:00 2001 From: Mysaa Date: Wed, 30 Jun 2021 22:38:01 +0200 Subject: [PATCH] La sauvegarde automatique de tous les miniels, notament le ServerMiniel. Le programme demande aussi avant de se fermer et supprime alors les sauvegardes automatiques maintenant inutiles. --- .../java/com/bernard/murder/ParseUtils.java | 39 ++++- .../java/com/bernard/murder/YamlUtils.java | 29 ++++ .../com/bernard/murder/audio/AudioServer.java | 137 +++++++++++++++++- .../com/bernard/murder/game/GameManager.java | 6 + .../bernard/murder/view/MinelsCreator.java | 5 + .../murder/view/MurderatorGameFrame.java | 31 +++- .../com/bernard/murder/view/minel/Minel.java | 2 + .../murder/view/minel/ServeurMinel.java | 99 +++++++++---- .../murder/view/minel/StatusMinel.java | 29 ---- .../murder/view/minel/TextPanMinel.java | 9 +- 10 files changed, 321 insertions(+), 65 deletions(-) delete mode 100755 src/main/java/com/bernard/murder/view/minel/StatusMinel.java diff --git a/src/main/java/com/bernard/murder/ParseUtils.java b/src/main/java/com/bernard/murder/ParseUtils.java index d4af21a..f1e9dc5 100644 --- a/src/main/java/com/bernard/murder/ParseUtils.java +++ b/src/main/java/com/bernard/murder/ParseUtils.java @@ -1,11 +1,17 @@ package com.bernard.murder; import java.awt.Color; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; import java.time.Instant; import java.time.LocalTime; import java.time.ZoneId; import java.time.format.DateTimeFormatter; +import java.util.Base64; import java.util.Collection; import java.util.Set; import java.util.Spliterator; @@ -137,11 +143,42 @@ public class ParseUtils { builder = builder.add(n); return builder.build(); } + public static final YamlSequence setToSeqS(Set nodes) { YamlSequenceBuilder builder = Yaml.createYamlSequenceBuilder(); - for(String n : nodes) + for (String n : nodes) builder = builder.add(n); return builder.build(); } + + public static final Object fromBase64(String s) throws ClassNotFoundException { + try { + byte[] data = Base64.getDecoder().decode(s); + ObjectInputStream ois; + ois = new ObjectInputStream(new ByteArrayInputStream(data)); + Object o = ois.readObject(); + ois.close(); + return o; + } catch (IOException e) { + System.err.println("Erreur dans la lecture de la chaine de caractères"); + e.printStackTrace(); + } + return null; + } + + public static final String toBase64(Serializable o) { + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ObjectOutputStream oos; + oos = new ObjectOutputStream(baos); + oos.writeObject(o); + oos.close(); + return Base64.getEncoder().encodeToString(baos.toByteArray()); + } catch (IOException e) { + System.err.println("Erreur dans l'écriture de la chaine de caractères"); + e.printStackTrace(); + } + return null; + } } diff --git a/src/main/java/com/bernard/murder/YamlUtils.java b/src/main/java/com/bernard/murder/YamlUtils.java index 773dbd0..f6fdd95 100755 --- a/src/main/java/com/bernard/murder/YamlUtils.java +++ b/src/main/java/com/bernard/murder/YamlUtils.java @@ -21,6 +21,9 @@ import com.amihaiemil.eoyaml.YamlSequenceBuilder; public class YamlUtils { + public static final Function compose(Function f,Function g) { + return f.compose(g); + } public static final YamlSequence listToSeq(List nodes) { YamlSequenceBuilder ysb = Yaml.createYamlSequenceBuilder(); @@ -43,6 +46,13 @@ public class YamlUtils { return ysb.build(); } + public static final YamlMapping mapToMapping(Map map,Function fKey,Function fVal) { + YamlMappingBuilder ysb = Yaml.createYamlMappingBuilder(); + for(Entry e : map.entrySet()) + ysb.add(fKey.apply(e.getKey()), fVal.apply(e.getValue())); + return ysb.build(); + } + public static final YamlSequence getSequence(YamlNode node) { if(node.type()==Node.SEQUENCE)return node.asSequence(); if(node.type()==Node.SCALAR && node.asScalar().value().contentEquals("[]"))return Yaml.createYamlSequenceBuilder().build(); @@ -63,7 +73,26 @@ public class YamlUtils { return (node.type()==Node.SEQUENCE) || (node.type()==Node.SCALAR && node.asScalar().value().contentEquals("[]")); } + public static final YamlNode scalar(Object in) { + return Yaml.createYamlScalarBuilder().addLine(in.toString()).buildPlainScalar(); + } + public static final YamlNode foldedScalar(String in) { + return Yaml.createYamlScalarBuilder().addLine(in).buildFoldedBlockScalar(); + } + public static final YamlSequenceBuilder seqMerge(YamlSequenceBuilder ysba,YamlSequenceBuilder ysbb) { + for(YamlNode na : ysba.build().values()) + ysbb = ysbb.add(na); + return ysbb; + } + + public static final CollectorImpl sequenceCollector = + new CollectorImpl<>( + Yaml::createYamlSequenceBuilder, + YamlSequenceBuilder::add, + YamlUtils::seqMerge, + YamlSequenceBuilder::build, + Set.of(Collector.Characteristics.UNORDERED)); /** * (Copied from Collectors class) diff --git a/src/main/java/com/bernard/murder/audio/AudioServer.java b/src/main/java/com/bernard/murder/audio/AudioServer.java index 1c810ea..75b582a 100755 --- a/src/main/java/com/bernard/murder/audio/AudioServer.java +++ b/src/main/java/com/bernard/murder/audio/AudioServer.java @@ -16,11 +16,18 @@ import java.util.Map.Entry; import java.util.Set; import java.util.UUID; import java.util.function.Consumer; +import java.util.stream.Collectors; import javax.sound.sampled.AudioFormat; import javax.swing.JOptionPane; +import com.amihaiemil.eoyaml.Yaml; +import com.amihaiemil.eoyaml.YamlMapping; +import com.amihaiemil.eoyaml.YamlNode; +import com.amihaiemil.eoyaml.YamlSequenceBuilder; import com.bernard.murder.BytesUtils; +import com.bernard.murder.ParseUtils; +import com.bernard.murder.YamlUtils; public class AudioServer { @@ -88,6 +95,53 @@ public class AudioServer { initServer(); } + public AudioServer(YamlMapping data) throws SocketException, UnknownHostException { + this(); + for(YamlNode spkn : data.yamlMapping("enceintes").values()) { + try { + YamlMapping spk = spkn.asMapping(); + int id = spk.integer("id"); + String name = spk.string("name"); + SocketAddress address = (SocketAddress) ParseUtils.fromBase64(spk.string("address")); + speakers.put(id, name); + speakersAddr.put(id, address); + } catch (ClassNotFoundException e) { + System.err.println("Impossible de récupérer l'adresse de cette enceinte"); + e.printStackTrace(); + } + } + for(YamlNode micn : data.yamlMapping("micros").values()) { + try { + YamlMapping mic = micn.asMapping(); + int id = mic.integer("id"); + String name = mic.string("name"); + SocketAddress address = (SocketAddress) ParseUtils.fromBase64(mic.string("address")); + mics.put(id, name); + micsAddr.put(id, address); + listening.put(id, new ArrayList<>()); + + for(YamlNode ecId : mic.yamlSequence("listening").values()) { + listening.get(id).add(Integer.parseInt(ecId.asScalar().value())); + } + + } catch (ClassNotFoundException e) { + System.err.println("Impossible de récupérer l'adresse de ce micro"); + e.printStackTrace(); + } + } + + micId = mics.keySet().stream().collect(Collectors.maxBy(Integer::compare)).orElse(-1)+1; + speakerId = speakers.keySet().stream().collect(Collectors.maxBy(Integer::compare)).orElse(-1)+1; + + + publishAudioList(speakersAddr.values()); + changeListeners.forEach(Runnable::run); + + //XXX: Ajouter des tests demandant si toutes les enceintes sont toujours connéctées + // et savoir quel micro est connécté. + + } + public void initServer() throws SocketException, UnknownHostException { serveur = new Serveur(this::receiveCommand, AudioServer.communicationPort); } @@ -335,7 +389,7 @@ public class AudioServer { } } - public void disconnectEnceinte(int id) { +public void disconnectEnceinte(int id) { if(!speakers.containsKey(id)) return; @@ -373,4 +427,85 @@ public class AudioServer { } } + + public void disconnectMic(int id) { + + if(!mics.containsKey(id)) + return; + // On lui annonce notre déconnection, puis on la supprime des données + ByteBuffer decoBuf = ByteBuffer.allocate(AudioServer.packetMaxSize); + decoBuf.put(AudioServer.DISCONNECTING); + decoBuf.put(AudioServer.MASTER_DEVICE); + decoBuf.putInt(0); + try { + serveur.sendData(decoBuf, micsAddr.get(id)); + } catch (Exception e) { + serverErrorException.forEach(c -> c.accept(e)); + } + + mics.remove(id); + micsAddr.remove(id); + changeListeners.forEach(Runnable::run); + + for(int lster : listening.get(id)) { + ByteBuffer out4 = ByteBuffer.allocate(AudioServer.packetMaxSize); + out4.put(AudioServer.STOP_STREAMING); + out4.putInt(id); + try { + serveur.sendData(out4, speakersAddr.get(lster)); + } catch (Exception e1) { + serverErrorException.forEach(c -> c.accept(e1)); + } + } + publishAudioList(listening.get(id).stream().map(speakersAddr::get).collect(Collectors.toSet())); + listening.remove(id); + } + + public void dispose() { + speakers.keySet().forEach(this::disconnectEnceinte); + mics.keySet().forEach(this::disconnectMic); + + mics.clear(); + speakers.clear(); + micsAddr.clear(); + speakersAddr.clear(); + listening.clear(); + + changeListeners.forEach(Runnable::run); + + serveur.dispose(); + } + + public YamlMapping saveToYaml() { + YamlSequenceBuilder ymbMics = Yaml.createYamlSequenceBuilder(); + for(int i : mics.keySet()) { + ymbMics = ymbMics.add( + Yaml.createYamlMappingBuilder() + .add("id", Integer.toString(i)) + .add("name", mics.get(i)) + .add("address", ParseUtils.toBase64(micsAddr.get(i))) + .add("listening", listening.get(i) + .stream() + .map(YamlUtils::scalar) + .collect(YamlUtils.sequenceCollector)) + .build()); + } + + YamlSequenceBuilder ymbSpks = Yaml.createYamlSequenceBuilder(); + for(int i : speakers.keySet()) { + ymbSpks = ymbSpks.add( + Yaml.createYamlMappingBuilder() + .add("id", Integer.toString(i)) + .add("name", speakers.get(i)) + .add("address", ParseUtils.toBase64(speakersAddr.get(i))) + .build()); + } + + return Yaml.createYamlMappingBuilder() + .add("micros", ymbMics.build()) + .add("enceintes", ymbSpks.build()) + .build(); + + + } } diff --git a/src/main/java/com/bernard/murder/game/GameManager.java b/src/main/java/com/bernard/murder/game/GameManager.java index 1a91921..d8a234b 100644 --- a/src/main/java/com/bernard/murder/game/GameManager.java +++ b/src/main/java/com/bernard/murder/game/GameManager.java @@ -87,6 +87,12 @@ public class GameManager { public String quickSaveFilename() { return "murder-"+DateTimeFormatter.ofPattern("uu-MM-dd_HH'h'mm").withZone(ZoneId.systemDefault()).withLocale(Locale.getDefault()).format(Instant.ofEpochMilli(startTime))+".bernard.quickmurder"; } + + public void removeQuickSaves() { + for(File f : (new File(".")).listFiles((f,n) -> n.startsWith("murder-") && n.endsWith(".bernard.quickmurder"))) { + f.delete(); + } + } public Set getEveryInventaire() { Set inventaires = new HashSet(); diff --git a/src/main/java/com/bernard/murder/view/MinelsCreator.java b/src/main/java/com/bernard/murder/view/MinelsCreator.java index 9e3d21a..6cfc761 100644 --- a/src/main/java/com/bernard/murder/view/MinelsCreator.java +++ b/src/main/java/com/bernard/murder/view/MinelsCreator.java @@ -142,4 +142,9 @@ public class MinelsCreator { .build(); } + public static void closeMiniels(Map> persoMinels, Map> supMinels) { + persoMinels.values().stream().forEach(l -> l.forEach(Minel::onFrameClose)); + supMinels .values().stream().forEach(l -> l.forEach(Minel::onFrameClose)); + } + } diff --git a/src/main/java/com/bernard/murder/view/MurderatorGameFrame.java b/src/main/java/com/bernard/murder/view/MurderatorGameFrame.java index 6bc2f20..9eaff55 100755 --- a/src/main/java/com/bernard/murder/view/MurderatorGameFrame.java +++ b/src/main/java/com/bernard/murder/view/MurderatorGameFrame.java @@ -3,11 +3,14 @@ package com.bernard.murder.view; import java.awt.BorderLayout; import java.awt.Dimension; import java.awt.GridLayout; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; import java.util.List; import java.util.Map; import javax.swing.BorderFactory; import javax.swing.JFrame; +import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTabbedPane; @@ -28,6 +31,8 @@ public class MurderatorGameFrame extends JFrame{ Map> minelsSup; Map> minels; + GameManager manager; + public MurderatorGameFrame(String frameName, Partie partie, GameManager manager,Map> minelsSup,Map> minels) { this.setSize(700, 500); this.setMinimumSize(new Dimension(200, 100)); @@ -35,9 +40,11 @@ public class MurderatorGameFrame extends JFrame{ this.setTitle(frameName); this.minelsSup = minelsSup; this.minels = minels; + this.manager = manager; manager.bindMinelQuicksaver(() -> MinelsCreator.createMinelQuicksave(minels, minelsSup)); this.setContentPane(genGamePane(partie,manager,minelsSup,minels)); + this.addWindowListener(new WListener()); this.pack(); this.setLocationRelativeTo(null); this.setVisible(true); @@ -115,8 +122,30 @@ public class MurderatorGameFrame extends JFrame{ } - public String personnageIdentifier(Personnage personnage) { + public static final String personnageIdentifier(Personnage personnage) { return String.format("%08X",System.identityHashCode(personnage))+personnage.getNom(); } + public class WListener extends WindowAdapter{ + + @Override + public void windowClosing(WindowEvent e) { + super.windowClosing(e); + int res = JOptionPane.showConfirmDialog(MurderatorGameFrame.this, + "En fermant cette fenêtre, vous allez mettre fin à la murder et supprimer les sauvegardes automatiques ?", + "Fermeture", + JOptionPane.OK_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE); + + if(res==JOptionPane.OK_OPTION) { + + manager.removeQuickSaves(); + MinelsCreator.closeMiniels(minels, minelsSup); + + MurderatorGameFrame.this.setVisible(false); + MurderatorGameFrame.this.dispose(); + } + } + + } + } diff --git a/src/main/java/com/bernard/murder/view/minel/Minel.java b/src/main/java/com/bernard/murder/view/minel/Minel.java index 331d034..3cb22a4 100644 --- a/src/main/java/com/bernard/murder/view/minel/Minel.java +++ b/src/main/java/com/bernard/murder/view/minel/Minel.java @@ -24,4 +24,6 @@ public abstract class Minel{ public abstract YamlMappingBuilder saveToYaml(); + public void onFrameClose() {}; + } diff --git a/src/main/java/com/bernard/murder/view/minel/ServeurMinel.java b/src/main/java/com/bernard/murder/view/minel/ServeurMinel.java index 2db713d..92b953a 100755 --- a/src/main/java/com/bernard/murder/view/minel/ServeurMinel.java +++ b/src/main/java/com/bernard/murder/view/minel/ServeurMinel.java @@ -29,11 +29,11 @@ public class ServeurMinel extends Minel { AudioServer serveur; - JList microList; - JList enceinteListe; + JList micListe; + JList spkListe; - JPopupMenu microPopup; - JPopupMenu enceintePopup; + JPopupMenu micPopup; + JPopupMenu spkPopup; public ServeurMinel(GameManager manager) { super(manager); @@ -45,12 +45,8 @@ public class ServeurMinel extends Minel { } public ServeurMinel(GameManager manager,YamlMapping ym) { - super(manager); - try { - serveur = new AudioServer(); - }catch(SocketException | UnknownHostException ex) { - JOptionPane.showMessageDialog(null, "Lancement du serveur audio impossible !\n"+ex.getMessage(), "Impossible de lancer le serveur audio", JOptionPane.ERROR_MESSAGE, null); - } + this(manager); + // On se fiche de la sauvegarde automatique. } @Override @@ -61,24 +57,28 @@ public class ServeurMinel extends Minel { titre.setFont(Parametres.minielTitleFont); titre.setBorder(BorderFactory.createEmptyBorder(5,5,5,5)); - microList = new JList<>(); - microList.setOpaque(false); - enceinteListe = new JList<>(); - enceinteListe.setOpaque(false); + micListe = new JList<>(); + micListe.setOpaque(false); + spkListe = new JList<>(); + spkListe.setOpaque(false); EnceintePopupListener epl = new EnceintePopupListener(); - enceintePopup = epl.makePopup(); - enceinteListe.addMouseListener(epl); + spkPopup = epl.makePopup(); + spkListe.addMouseListener(epl); + + MicsPopupListener mpl = new MicsPopupListener(); + micPopup = mpl.makePopup(); + micListe.addMouseListener(mpl); serveur.addChangeListener(() -> { // Updating lists content - microList.setListData(new Vector(serveur.getMics().keySet())); - enceinteListe.setListData(new Vector(serveur.getSpeakers().keySet())); + micListe.setListData(new Vector(serveur.getMics().keySet())); + spkListe.setListData(new Vector(serveur.getSpeakers().keySet())); }); - JScrollPane jE = new JScrollPane(enceinteListe); + JScrollPane jE = new JScrollPane(spkListe); jE.setBorder(BorderFactory.createTitledBorder("Enceintes connéctées")); - JScrollPane jM = new JScrollPane(microList); + JScrollPane jM = new JScrollPane(micListe); jM.setBorder(BorderFactory.createTitledBorder("Microphones connéctés")); JPanel centerP = new JPanel(new GridLayout(2, 1)); @@ -92,7 +92,7 @@ public class ServeurMinel extends Minel { return panel; } - public class EnceintePopupListener extends MouseAdapter{ +public class EnceintePopupListener extends MouseAdapter{ public final JPopupMenu makePopup() { JPopupMenu popup = new JPopupMenu(); @@ -100,18 +100,18 @@ public class ServeurMinel extends Minel { JLabel idText = new JLabel(); idText.setFont(idText.getFont().deriveFont(Font.ITALIC)); idText.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); - enceinteListe.getSelectionModel().addListSelectionListener(e -> - idText.setText("Id: "+ (enceinteListe.getSelectedValue()!=null?enceinteListe.getSelectedValue():-1)) + spkListe.getSelectionModel().addListSelectionListener(e -> + idText.setText("Id: "+ (spkListe.getSelectedValue()!=null?spkListe.getSelectedValue():-1)) ); popup.add(idText); JMenuItem jmi = new JMenuItem("Silence"); jmi.addActionListener(e -> { - serveur.forceSilence(enceinteListe.getSelectedValue()); + serveur.forceSilence(spkListe.getSelectedValue()); }); popup.add(jmi); jmi = new JMenuItem("Déconnecter"); jmi.addActionListener(e -> { - serveur.disconnectEnceinte(enceinteListe.getSelectedValue()); + serveur.disconnectEnceinte(spkListe.getSelectedValue()); }); popup.add(jmi); return popup; @@ -127,9 +127,46 @@ public class ServeurMinel extends Minel { private void maybeShowPopup(MouseEvent e) { if (e.isPopupTrigger()) { - enceinteListe.setSelectedIndex(enceinteListe.locationToIndex(e.getPoint())); - if(!enceinteListe.isSelectionEmpty()) - enceintePopup.show(e.getComponent(), e.getX(), e.getY()); + spkListe.setSelectedIndex(spkListe.locationToIndex(e.getPoint())); + if(!spkListe.isSelectionEmpty()) + spkPopup.show(e.getComponent(), e.getX(), e.getY()); + } + } + } + + public class MicsPopupListener extends MouseAdapter{ + + public final JPopupMenu makePopup() { + JPopupMenu popup = new JPopupMenu(); + + JLabel idText = new JLabel(); + idText.setFont(idText.getFont().deriveFont(Font.ITALIC)); + idText.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); + micListe.getSelectionModel().addListSelectionListener(e -> + idText.setText("Id: "+ (micListe.getSelectedValue()!=null?micListe.getSelectedValue():-1)) + ); + popup.add(idText); + JMenuItem jmi = new JMenuItem("Déconnecter"); + jmi.addActionListener(e -> { + serveur.disconnectMic(micListe.getSelectedValue()); + }); + popup.add(jmi); + return popup; + } + + public void mousePressed(MouseEvent e) { + maybeShowPopup(e); + } + + public void mouseReleased(MouseEvent e) { + maybeShowPopup(e); + } + + private void maybeShowPopup(MouseEvent e) { + if (e.isPopupTrigger()) { + micListe.setSelectedIndex(micListe.locationToIndex(e.getPoint())); + if(!micListe.isSelectionEmpty()) + micPopup.show(e.getComponent(), e.getX(), e.getY()); } } } @@ -137,7 +174,13 @@ public class ServeurMinel extends Minel { @Override public YamlMappingBuilder saveToYaml() { + // Ce miniel représente le serveur audio, indépendant du relancement return Yaml.createYamlMappingBuilder(); } + + @Override + public void onFrameClose() { + serveur.dispose(); + } } diff --git a/src/main/java/com/bernard/murder/view/minel/StatusMinel.java b/src/main/java/com/bernard/murder/view/minel/StatusMinel.java deleted file mode 100755 index 7c07809..0000000 --- a/src/main/java/com/bernard/murder/view/minel/StatusMinel.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.bernard.murder.view.minel; - -import javax.swing.JPanel; - -import com.amihaiemil.eoyaml.Yaml; -import com.amihaiemil.eoyaml.YamlMappingBuilder; -import com.bernard.murder.game.GameManager; -import com.bernard.murder.model.Personnage; - -public class StatusMinel extends Minel { - - public StatusMinel(GameManager manager, Personnage personnage) { - super(manager); - // TODO Auto-generated constructor stub - } - - @Override - public JPanel genContentPane() { - // TODO Auto-generated method stub - return null; - } - - @Override - public YamlMappingBuilder saveToYaml() { - //TODO auto-generated thingy - return Yaml.createYamlMappingBuilder(); - } - -} diff --git a/src/main/java/com/bernard/murder/view/minel/TextPanMinel.java b/src/main/java/com/bernard/murder/view/minel/TextPanMinel.java index 56068f1..89a92e0 100644 --- a/src/main/java/com/bernard/murder/view/minel/TextPanMinel.java +++ b/src/main/java/com/bernard/murder/view/minel/TextPanMinel.java @@ -17,7 +17,6 @@ import com.bernard.murder.game.GameManager; public class TextPanMinel extends Minel { - String initTexte = ""; JTextArea textArea; public TextPanMinel(GameManager manager) { @@ -25,8 +24,8 @@ public class TextPanMinel extends Minel { } public TextPanMinel(GameManager gm, YamlMapping ym) { - super(gm); - initTexte = ym.string("texte"); + this(gm); + textArea.setText(ym.string("texte")); } @Override @@ -40,7 +39,7 @@ public class TextPanMinel extends Minel { textArea = new JTextArea(); textArea.setBorder(new EmptyBorder(23,23,23,23)); - textArea.setText(initTexte); + textArea.setText(""); textArea.setBackground(Parametres.textPanMinielBackgroundColor); textArea.setForeground(Parametres.textPanMinielTextColor); @@ -52,7 +51,7 @@ public class TextPanMinel extends Minel { @Override public YamlMappingBuilder saveToYaml() { - return Yaml.createYamlMappingBuilder().add("texte", textArea!=null?textArea.getText():initTexte); + return Yaml.createYamlMappingBuilder().add("texte", textArea!=null?textArea.getText():""); } } \ No newline at end of file