feat: F4.6g4 the Gnutella window, measured against Java's

GnutellaWindowRun uses Java's Gnutella window on terminal-net beside the
line of GnutellaRun's story "search". The core cancels a search as Java
does (every connection of the program's own requests closed, passed-on
queries kept), lets the number of peers change and lists /peer2peer's
files; the room takes the window's actions and sends its view; the editor
shows Java's four tabs with Java's symbols. Not adopted from Java: a number
of peers of one digit only, a results table that keeps the last search's
rows, a files table that lags behind the folder.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-03 21:39:29 +00:00
co-authored by Claude Opus 5.5
parent 2b85e7432c
commit 5224e1a28b
24 changed files with 2211 additions and 17 deletions
@@ -0,0 +1,299 @@
// Uses FILIUS' own Gnutella window (GUIApplicationPeerToPeerAnwendungWindow
// on a GUIDesktopPanel) on terminal-net's first computer as a person does:
// the address field (also with what is no address) and "Netz beitreten",
// the search field with "Suchen", "Stopp!", a result chosen and
// "Herunterladen", "Liste leeren", the field of the number of peers with
// "Speichern" (also with what is no number), "Netz verlassen". The other
// computers form the line of GnutellaRun's story "search" through their
// programs. Java's message dialogs are answered and written down. After each
// step it writes what the window shows — the address field (and whether it
// is red), the three tables, the field of the number of peers — and what the
// program keeps: the number, its peers, its results, the files of
// /peer2peer. The reference for the Gnutella window
// (packages/core/test/gnutellaWindow.test.ts).
//
// Usage: java -cp filius.jar:lib/*:classes GnutellaWindowRun <scenario.json> <output json>
import filius.gui.anwendungssicht.GUIApplicationPeerToPeerAnwendungWindow;
import filius.gui.anwendungssicht.GUIDesktopPanel;
import filius.rahmenprogramm.EingabenUeberpruefung;
import filius.software.dateiaustausch.PeerToPeerAnwendung;
import filius.software.system.Betriebssystem;
import filius.software.system.Datei;
import filius.software.system.Dateisystem;
import java.awt.Component;
import java.awt.Container;
import java.awt.Window;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
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.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JTabbedPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.table.TableModel;
public class GnutellaWindowRun {
static final String CLIENT = "00000000-0000-0000-0000-000000000401";
static final String WEB = "00000000-0000-0000-0000-000000000403";
static final String ECHO = "00000000-0000-0000-0000-000000000404";
static final String FAR = "00000000-0000-0000-0000-000000000406";
static final String[] PEERS = { CLIENT, WEB, ECHO, FAR };
static final String CLASS = "filius.software.dateiaustausch.PeerToPeerAnwendung";
static final List<Object> steps = new ArrayList<>();
static final List<String> dialogs = new ArrayList<>();
static GUIApplicationPeerToPeerAnwendungWindow window;
static PeerToPeerAnwendung program;
static Object field(String name) throws Exception {
Field field = GUIApplicationPeerToPeerAnwendungWindow.class.getDeclaredField(name);
field.setAccessible(true);
return field.get(window);
}
@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 void watchDialogs() {
Thread watcher = new Thread(() -> {
while (true) {
for (Window each : Window.getWindows()) {
if (!(each instanceof JDialog) || !each.isVisible()) continue;
JOptionPane pane = find((Container) each, JOptionPane.class);
if (pane == null) continue;
synchronized (dialogs) {
dialogs.add(String.valueOf(pane.getMessage()));
}
pane.setValue(JOptionPane.OK_OPTION);
each.setVisible(false);
}
try {
Thread.sleep(50);
} catch (InterruptedException error) {
return;
}
}
});
watcher.setDaemon(true);
watcher.start();
}
static void onEdt(Runnable action) throws Exception {
SwingUtilities.invokeLater(action);
Thread.sleep(500);
}
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 Map<String, Object> textField(String name) throws Exception {
JTextField each = (JTextField) field(name);
Map<String, Object> shown = new LinkedHashMap<>();
shown.put("text", each.getText());
shown.put("red", each.getForeground().equals(EingabenUeberpruefung.farbeFalsch));
return shown;
}
static void step(String name) throws Exception {
Map<String, Object> result = new LinkedHashMap<>();
result.put("step", name);
result.put("address", textField("networkIpField"));
result.put("peers", rows((JTable) field("netzwerkTabelle")));
result.put("search", ((JTextField) field("searchField")).getText());
result.put("results", rows((JTable) field("ergebnisTabelle")));
result.put("files", rows((JTable) field("dateiTabelle")));
result.put("maximum", textField("maxClientsField"));
result.put("programMaximum", program.getMaxTeilnehmerZahl());
result.put("programPeers", new ArrayList<>(program.holeBekanntePeerToPeerTeilnehmer()));
result.put("programResults", new ArrayList<>(program.holeErgebnisse()));
List<List<String>> folder = new ArrayList<>();
for (Datei each : program.getSystemSoftware().getDateisystem().holeDateien(program.holeVerzeichnis())) {
List<String> file = new ArrayList<>();
file.add(each.getName());
file.add(each.getDateiTyp());
file.add(each.getDateiInhalt());
folder.add(file);
}
result.put("programFiles", folder);
synchronized (dialogs) {
result.put("dialogs", new ArrayList<>(dialogs));
dialogs.clear();
}
steps.add(result);
}
/** Typed as a person types: the text, then a key released (the window checks the field then). */
static void type(String name, String text) throws Exception {
JTextField each = (JTextField) field(name);
onEdt(() -> {
each.setText(text);
KeyEvent event = new KeyEvent(each, KeyEvent.KEY_RELEASED, System.currentTimeMillis(), 0, KeyEvent.VK_A, 'a');
for (KeyListener listener : each.getKeyListeners()) listener.keyReleased(event);
});
}
/** Java's buttons here listen for the mouse pressed, not an action. */
static void press(String text, long wait) throws Exception {
List<JButton> found = new ArrayList<>();
collect(window, text, found);
JButton button = found.get(0);
onEdt(() -> {
MouseEvent event = new MouseEvent(button, MouseEvent.MOUSE_PRESSED, System.currentTimeMillis(), 0, 5, 5, 1, false);
for (MouseListener listener : button.getMouseListeners()) listener.mousePressed(event);
});
Thread.sleep(wait);
}
static void collect(Container container, String text, List<JButton> found) {
for (Component component : container.getComponents()) {
if (component instanceof JButton && text.equals(((JButton) component).getText())) found.add((JButton) component);
if (component instanceof Container) collect((Container) component, text, found);
}
}
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]);
Map<String, PeerToPeerAnwendung> programs = new LinkedHashMap<>();
for (String host : PEERS) {
network.system(host).installApp(CLASS, false);
PeerToPeerAnwendung each = (PeerToPeerAnwendung) network.system(host).holeSoftware(CLASS);
each.setMaxTeilnehmerZahl(host.equals(CLIENT) || host.equals(FAR) ? 1 : 2);
programs.put(host, each);
}
program = programs.get(CLIENT);
network.system(ECHO).getDateisystem().speicherDatei("/peer2peer", new Datei("notizen.txt", "txt", "Notiz"));
network.system(FAR).getDateisystem().speicherDatei("/peer2peer", new Datei("Lied der Woche.txt", "txt", "La la la"));
network.start();
Thread.sleep(500);
watchDialogs();
// The line .20 - .30 - .31 - 10.0.0.2: the others join first, as in GnutellaRun's story.
programs.get(WEB).beitretenNetzwerk("192.168.0.31");
Thread.sleep(2500);
programs.get(FAR).beitretenNetzwerk("192.168.0.31");
Thread.sleep(2500);
JFrame frame = new JFrame();
GUIDesktopPanel desktop = new GUIDesktopPanel((Betriebssystem) network.system(CLIENT));
frame.getContentPane().add(desktop);
window = (GUIApplicationPeerToPeerAnwendungWindow) desktop.starteAnwendung(CLASS);
SwingUtilities.invokeAndWait(() -> {
frame.pack();
frame.setVisible(true);
});
Thread.sleep(500);
step("open");
type("networkIpField", "192.168.0.300");
step("type an address that is none");
press("Netz beitreten", 1000);
step("join with an address that is none");
type("networkIpField", "192.168.0.20");
press("Netz beitreten", 2500);
step("join itself");
type("networkIpField", "192.168.0.30");
press("Netz beitreten", 3000);
step("join 192.168.0.30");
type("searchField", "woche");
press("Suchen", 3000);
step("search woche");
JTable results = (JTable) field("ergebnisTabelle");
onEdt(() -> results.setRowSelectionInterval(0, 0));
press("Herunterladen", 3000);
step("download row 0");
press("Herunterladen", 3000);
step("download row 0 again");
type("searchField", "xyz");
press("Suchen", 3000);
step("search xyz");
press("Liste leeren", 500);
step("empty the list");
type("searchField", "notiz");
press("Suchen", 0);
press("Stopp!", 3000);
step("search notiz and abort at once");
press("Suchen", 3000);
step("search notiz again");
onEdt(() -> results.clearSelection());
press("Herunterladen", 2000);
step("download with no row chosen");
// Another tab, as a person looks at the files: Java fills its tables anew.
JTabbedPane tabs = find(window, JTabbedPane.class);
onEdt(() -> tabs.setSelectedIndex(2));
step("files tab");
type("maxClientsField", "abc");
step("type a number of peers that is none");
press("Speichern", 500);
step("take a number of peers that is none");
type("maxClientsField", "0");
press("Speichern", 500);
step("take 0 peers");
type("maxClientsField", "99999999999");
press("Speichern", 500);
step("take a number beyond Java's int");
type("maxClientsField", "12");
press("Speichern", 500);
step("take 12 peers");
type("maxClientsField", "4");
press("Speichern", 500);
step("take 4 peers");
press("Netz verlassen", 1000);
step("leave the network");
// A file added to the folder beside the window: the table only shows it when it is filled anew.
Dateisystem files = network.system(CLIENT).getDateisystem();
onEdt(() -> files.speicherDatei("/peer2peer", new Datei("neu.txt", "txt", "neu")));
step("a file added beside the window");
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("gnutella window steps: " + steps.size());
}
}