feat(editor): F4.6e the e-mail program and e-mail server on a computer's desktop

The oracle MailWindowRun uses Java's e-mail windows as a person does and
answers their dialogs. The core sets and saves the program's account,
deletes a mail, checks a form as Java's window does, and gives the server
its domain, new and removed accounts and the window's own line; the room
sends both programs' views and takes their actions; the editor has both
windows. Better than Java: a name field for a new account, a user name the
server has is refused, deleting hits the folder shown, a new domain goes
into the accounts file at once.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-03 16:44:36 +00:00
co-authored by Claude Opus 5.5
parent aa54e5400e
commit 11f13bca84
33 changed files with 2841 additions and 52 deletions
+356
View File
@@ -0,0 +1,356 @@
// Uses FILIUS' own e-mail server and e-mail program windows
// (GUIApplicationEmailServerWindow, GUIApplicationEmailAnwendungWindow on
// their GUIDesktopPanels) on mail-lan's network as a person does: the
// server's start button, its domain field, its form for a new account (the
// button's mouse listener, as Java's window listens), the account list and
// its delete button; the program's account dialog, its compose window (the
// fields and buttons of the window it shows on the desktop), fetching, a mail
// chosen, answered and deleted. Java's message dialogs are answered as a
// person would (OK, "yes" to a question) and their text is written down.
// After each step it writes what the windows show: the server's button,
// domain field, account rows, log and its accounts file; the program's
// folders' rows, preview, the compose window's fields and its accounts file.
// The reference for the editor's e-mail windows
// (packages/sim/test/mailWindow.test.ts).
//
// Usage: java -cp filius.jar:lib/*:classes MailWindowRun <scenario.json> <output json>
import filius.gui.anwendungssicht.GUIApplicationEmailAnwendungWindow;
import filius.gui.anwendungssicht.GUIApplicationEmailServerWindow;
import filius.gui.anwendungssicht.GUIDesktopPanel;
import filius.software.email.EmailServer;
import filius.software.system.Betriebssystem;
import filius.software.system.Datei;
import java.awt.Component;
import java.awt.Container;
import java.awt.Window;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.io.FileOutputStream;
import java.lang.reflect.Field;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.swing.AbstractButton;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JDialog;
import javax.swing.JEditorPane;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPasswordField;
import javax.swing.JTabbedPane;
import javax.swing.JTable;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.table.TableModel;
public class MailWindowRun {
static final String CLIENT = "00000000-0000-0000-0000-000000000201";
static final String SERVER = "00000000-0000-0000-0000-000000000203";
static final List<Object> steps = new ArrayList<>();
/** The texts of Java's message dialogs since the last step. */
static final List<String> dialogs = new ArrayList<>();
static Object field(Class<?> owner, Object object, String name) throws Exception {
Field field = owner.getDeclaredField(name);
field.setAccessible(true);
return field.get(object);
}
static List<List<String>> rows(JTable table) {
List<List<String>> rows = new ArrayList<>();
TableModel model = table.getModel();
for (int row = 0; row < model.getRowCount(); row++) {
List<String> cells = new ArrayList<>();
for (int column = 0; column < model.getColumnCount(); column++) cells.add(String.valueOf(model.getValueAt(row, column)));
rows.add(cells);
}
return rows;
}
static List<String> takeDialogs() {
synchronized (dialogs) {
List<String> taken = new ArrayList<>(dialogs);
dialogs.clear();
return taken;
}
}
/** Answers each message dialog as a person would: OK, or "yes" to a question; writes down its text. */
static void watchDialogs() {
Thread watcher = new Thread(() -> {
while (true) {
for (Window window : Window.getWindows()) {
if (!(window instanceof JDialog) || !window.isVisible()) continue;
JOptionPane pane = find((Container) window, JOptionPane.class);
if (pane == null) continue;
synchronized (dialogs) {
dialogs.add(String.valueOf(pane.getMessage()));
}
pane.setValue(pane.getOptionType() == JOptionPane.DEFAULT_OPTION ? JOptionPane.OK_OPTION : JOptionPane.YES_OPTION);
window.setVisible(false);
}
try {
Thread.sleep(50);
} catch (InterruptedException error) {
return;
}
}
});
watcher.setDaemon(true);
watcher.start();
}
@SuppressWarnings("unchecked")
static <T> T find(Container container, Class<T> kind) {
for (Component component : container.getComponents()) {
if (kind.isInstance(component)) return (T) component;
if (component instanceof Container) {
T found = find((Container) component, kind);
if (found != null) return found;
}
}
return null;
}
static <T> List<T> all(Container container, Class<T> kind, List<T> found) {
for (Component component : container.getComponents()) {
if (kind.isInstance(component)) found.add(kind.cast(component));
if (component instanceof Container) all((Container) component, kind, found);
}
return found;
}
static AbstractButton button(Container container, String text) {
for (AbstractButton each : all(container, AbstractButton.class, new ArrayList<>())) if (text.equals(each.getText())) return each;
throw new IllegalStateException("no button " + text);
}
static void onEdt(Runnable action) throws Exception {
SwingUtilities.invokeLater(action);
Thread.sleep(400);
}
/** A press with the mouse, as Java's server window listens (mousePressed, not an action). */
static void press(AbstractButton button) throws Exception {
onEdt(() -> {
MouseEvent event = new MouseEvent(button, MouseEvent.MOUSE_PRESSED, System.currentTimeMillis(), 0, 5, 5, 1, false);
for (MouseListener listener : button.getMouseListeners()) listener.mousePressed(event);
});
}
static String file(Betriebssystem system, String path) {
Datei datei = system.getDateisystem().holeDatei(path);
return datei == null ? null : datei.getDateiInhalt();
}
static void serverStep(GUIApplicationEmailServerWindow window, Betriebssystem system, String step) throws Exception {
Class<?> type = GUIApplicationEmailServerWindow.class;
Map<String, Object> result = new LinkedHashMap<>();
result.put("step", step);
result.put("button", ((JButton) field(type, window, "startStopButton")).getText());
JTextField domain = (JTextField) field(type, window, "domainField");
result.put("domain", domain.getText());
result.put("domainEnabled", domain.isEnabled());
result.put("accounts", rows((JTable) field(type, window, "kontenListenTabelle")));
result.put("username", ((JTextField) field(type, window, "benutzernameField")).getText());
result.put("log", ((JTextArea) field(type, window, "logArea")).getText());
result.put("accountsFile", file(system, "/mailserver/konten.txt"));
result.put("dialogs", takeDialogs());
steps.add(result);
}
static void clientStep(GUIApplicationEmailAnwendungWindow window, GUIDesktopPanel desktop, Betriebssystem system, String step) throws Exception {
Class<?> type = GUIApplicationEmailAnwendungWindow.class;
Map<String, Object> result = new LinkedHashMap<>();
result.put("step", step);
result.put("inbox", rows((JTable) field(type, window, "posteingangTable")));
result.put("sent", rows((JTable) field(type, window, "gesendeteTable")));
result.put("tab", ((JTabbedPane) field(type, window, "tabbedPane")).getSelectedIndex());
JEditorPane preview = (JEditorPane) field(type, window, "emailVorschau");
result.put("preview", preview.getContentType().equals("text/plain") ? preview.getText() : "html");
JPanel compose = (JPanel) field(type, window, "verfassenPanel");
boolean composing = compose != null && compose.getParent() != null;
result.put("composing", composing);
if (composing) {
List<String> fields = new ArrayList<>();
for (JTextField each : all(compose, JTextField.class, new ArrayList<>())) fields.add(each.getText());
result.put("composeFields", fields);
result.put("composeText", find(compose, JTextArea.class).getText());
JComboBox<?> sender = find(compose, JComboBox.class);
result.put("composeSender", sender.getSelectedItem() == null ? null : sender.getSelectedItem().toString());
}
result.put("accountFile", file(system, "/konten.txt"));
result.put("dialogs", takeDialogs());
steps.add(result);
}
static void waitFor(java.util.function.BooleanSupplier condition, long milliseconds) throws Exception {
long until = System.currentTimeMillis() + milliseconds;
while (System.currentTimeMillis() < until && !condition.getAsBoolean()) Thread.sleep(100);
Thread.sleep(1500);
}
public static void main(String[] args) throws Exception {
try {
run(args);
} catch (Throwable error) {
error.printStackTrace();
System.exit(1);
}
System.exit(0);
}
static void run(String[] args) throws Exception {
ScenarioNetwork network = ScenarioNetwork.read(args[0]);
Betriebssystem serverSystem = (Betriebssystem) network.nodes.get(SERVER).getSystemSoftware();
Betriebssystem clientSystem = (Betriebssystem) network.nodes.get(CLIENT).getSystemSoftware();
serverSystem.installApp("filius.software.email.EmailServer", false);
clientSystem.installApp("filius.software.email.EmailAnwendung", false);
EmailServer server = (EmailServer) serverSystem.holeSoftware("filius.software.email.EmailServer");
network.start();
Thread.sleep(500);
watchDialogs();
JFrame frame = new JFrame();
GUIDesktopPanel serverDesktop = new GUIDesktopPanel(serverSystem);
GUIDesktopPanel clientDesktop = new GUIDesktopPanel(clientSystem);
frame.getContentPane().setLayout(new java.awt.FlowLayout());
frame.getContentPane().add(serverDesktop);
frame.getContentPane().add(clientDesktop);
GUIApplicationEmailServerWindow serverWindow = (GUIApplicationEmailServerWindow) serverDesktop.starteAnwendung("filius.software.email.EmailServer");
GUIApplicationEmailAnwendungWindow window = (GUIApplicationEmailAnwendungWindow) clientDesktop.starteAnwendung("filius.software.email.EmailAnwendung");
SwingUtilities.invokeAndWait(() -> {
frame.pack();
frame.setVisible(true);
});
Thread.sleep(500);
Class<?> serverType = GUIApplicationEmailServerWindow.class;
JTextField domain = (JTextField) field(serverType, serverWindow, "domainField");
JTextField username = (JTextField) field(serverType, serverWindow, "benutzernameField");
JPasswordField password = (JPasswordField) field(serverType, serverWindow, "passwortField");
JButton startStop = (JButton) field(serverType, serverWindow, "startStopButton");
AbstractButton addAccount = (AbstractButton) field(serverType, serverWindow, "addButton");
serverStep(serverWindow, serverSystem, "server:open");
for (String typed : new String[] { "ungültig domain!", "schule.de" }) {
onEdt(() -> {
domain.setText(typed);
domain.postActionEvent();
});
serverStep(serverWindow, serverSystem, "server:domain " + typed);
}
onEdt(startStop::doClick);
serverStep(serverWindow, serverSystem, "server:start");
for (String[] account : new String[][] { { "anna", "geheim" }, { "", "x" }, { "carl os", "x" }, { "ANNA", "anders" }, { "bob", "" }, { "bob", "pw" } }) {
onEdt(() -> {
username.setText(account[0]);
password.setText(account[1]);
});
press(addAccount);
serverStep(serverWindow, serverSystem, "server:add " + account[0] + "/" + account[1]);
}
// The program: its account, first with a user name Java does not take, then as it should be.
Class<?> clientType = GUIApplicationEmailAnwendungWindow.class;
clientStep(window, clientDesktop, clientSystem, "client:open");
String[][] accounts = { { "Anna Maria Muster", "anna@schule.de", "192.168.0.30", "110", "192.168.0.30", "25", "an na", "geheim" },
{ "Anna Maria Muster", "anna@schule.de", "192.168.0.30", "110x", "192.168.0.30", "25", "anna", "geheim" },
{ "Anna Maria Muster", "anna@schule.de", "192.168.0.30", "110", "192.168.0.30", "25", "anna", "geheim" } };
for (String[] account : accounts) {
onEdt(((AbstractButton) field(clientType, window, "buttonKonten"))::doClick);
String[] names = { "tfName", "tfEmailAdresse", "tfPOP3Server", "tfPOP3Port", "tfSMTPServer", "tfSMTPPort", "tfBenutzername", "tfPasswort" };
onEdt(() -> {
try {
for (int index = 0; index < names.length; index++) ((JTextField) field(clientType, window, names[index])).setText(account[index]);
} catch (Exception error) {
throw new RuntimeException(error);
}
});
onEdt(button(clientDesktop, "Speichern")::doClick);
clientStep(window, clientDesktop, clientSystem, "client:account " + account[6] + " " + account[3]);
// A dialog Java leaves open after a failure: closed as a person would.
for (AbstractButton cancel : all(clientDesktop, AbstractButton.class, new ArrayList<>())) {
if (cancel.getText() != null && cancel.getText().equals("Abbrechen") && cancel.isShowing()) onEdt(cancel::doClick);
}
}
// Opened again, the dialog shows the account.
onEdt(((AbstractButton) field(clientType, window, "buttonKonten"))::doClick);
Map<String, Object> shown = new LinkedHashMap<>();
shown.put("step", "client:account shown");
List<String> fields = new ArrayList<>();
for (String name : new String[] { "tfName", "tfEmailAdresse", "tfPOP3Server", "tfPOP3Port", "tfSMTPServer", "tfSMTPPort", "tfBenutzername", "tfPasswort" }) {
fields.add(((JTextField) field(clientType, window, name)).getText());
}
shown.put("fields", fields);
steps.add(shown);
onEdt(button(clientDesktop, "Abbrechen")::doClick);
// Mails: written in the compose window, with addresses Java does not take, none, then to bob and to anna herself.
String[][] mails = { { "bob@schule.de, kein", "", "", "Falsch", "x" }, { "", "", "", "Leer", "x" }, { "bob@schule.de", "", "", "Hallo", "Zeile 1\nZeile 2" },
{ "anna@schule.de", "", "", "Selbst", "Erste Zeile\nZweite Zeile" } };
for (String[] mail : mails) {
JPanel open = (JPanel) field(clientType, window, "verfassenPanel");
if (open == null || open.getParent() == null) onEdt(((AbstractButton) field(clientType, window, "buttonMailVerfassen"))::doClick);
JPanel compose = (JPanel) field(clientType, window, "verfassenPanel");
onEdt(() -> {
List<JTextField> boxes = all(compose, JTextField.class, new ArrayList<>());
for (int index = 0; index < 4; index++) boxes.get(index).setText(mail[index]);
find(compose, JTextArea.class).setText(mail[4]);
});
int before = ((JTable) field(clientType, window, "gesendeteTable")).getRowCount();
onEdt(button(compose, "Senden")::doClick);
JTable sent = (JTable) field(clientType, window, "gesendeteTable");
if (!mail[0].isEmpty() && !mail[0].contains("kein")) waitFor(() -> sent.getRowCount() > before, 20000);
clientStep(window, clientDesktop, clientSystem, "client:send " + mail[3]);
}
serverStep(serverWindow, serverSystem, "server:after mails");
onEdt(((AbstractButton) field(clientType, window, "buttonMailsAbholen"))::doClick);
JTable inbox = (JTable) field(clientType, window, "posteingangTable");
waitFor(() -> inbox.getRowCount() > 0, 20000);
clientStep(window, clientDesktop, clientSystem, "client:fetch");
onEdt(() -> inbox.setRowSelectionInterval(0, 0));
clientStep(window, clientDesktop, clientSystem, "client:choose inbox 0");
onEdt(((AbstractButton) field(clientType, window, "buttonMailAntworten"))::doClick);
clientStep(window, clientDesktop, clientSystem, "client:answer");
onEdt(button((JPanel) field(clientType, window, "verfassenPanel"), "Abbrechen")::doClick);
JTable sent = (JTable) field(clientType, window, "gesendeteTable");
onEdt(() -> sent.setRowSelectionInterval(0, 0));
clientStep(window, clientDesktop, clientSystem, "client:choose sent 0");
onEdt(() -> inbox.setRowSelectionInterval(0, 0));
onEdt(((AbstractButton) field(clientType, window, "buttonEmailLoeschen"))::doClick);
clientStep(window, clientDesktop, clientSystem, "client:delete inbox 0");
serverStep(serverWindow, serverSystem, "server:after fetch");
// The server: an account chosen and deleted (Java asks), then stopped.
JTable list = (JTable) field(serverType, serverWindow, "kontenListenTabelle");
onEdt(() -> list.setRowSelectionInterval(1, 1));
press((AbstractButton) field(serverType, serverWindow, "deleteButton"));
serverStep(serverWindow, serverSystem, "server:delete row 1");
onEdt(startStop::doClick);
serverStep(serverWindow, serverSystem, "server:stop");
onEdt(() -> {
domain.setText("neu.de");
domain.postActionEvent();
});
serverStep(serverWindow, serverSystem, "server:domain neu.de");
steps.add(Map.of("step", "server:accounts", "accounts", String.valueOf(server.getListeBenutzerkonten().size())));
Map<String, Object> output = new LinkedHashMap<>();
output.put("steps", steps);
try (FileOutputStream out = new FileOutputStream(args[1])) {
out.write((FlsOracle.Json.write(output, "") + "\n").getBytes(StandardCharsets.UTF_8));
}
network.stop();
System.out.println("mail window steps: " + steps.size());
}
}