diff --git a/j3270/src/main/java/haus/nightmare/j3270/J3270App.java b/j3270/src/main/java/haus/nightmare/j3270/J3270App.java index d500c89..30bf7af 100644 --- a/j3270/src/main/java/haus/nightmare/j3270/J3270App.java +++ b/j3270/src/main/java/haus/nightmare/j3270/J3270App.java @@ -945,8 +945,14 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate } public boolean isConnected() { - return client != null && (client.isConnected() || - (client.getConnectionState() != null && client.getConnectionState().isConnected())); + if (client == null) { + return false; + } + ConnectionState state = client.getConnectionState(); + if (state == null || state == ConnectionState.NOT_CONNECTED) { + return false; + } + return client.isConnected() || state.isConnected(); } public String getConnectedTarget() { @@ -1213,6 +1219,7 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate setTitle("j3270 — " + lastHost + ":" + lastPort + " [Reconnecting...]"); } else if (newState == ConnectionState.NOT_CONNECTED) { setTitle("j3270 — Java TN3270 Terminal Emulator"); + syncModeMenuItems(); } else if (newState.isFullSession() && !oldState.isFullSession()) { String tlsIndicator = (client != null && client.getConfig().isUseTls()) ? (client.getConfig().isTlsVerifyCert() ? " [TLS]" : " [TLS/Unverified]") : ""; diff --git a/j3270/src/test/java/haus/nightmare/j3270/ConnectionConflictTest.java b/j3270/src/test/java/haus/nightmare/j3270/ConnectionConflictTest.java index f773f06..485a871 100644 --- a/j3270/src/test/java/haus/nightmare/j3270/ConnectionConflictTest.java +++ b/j3270/src/test/java/haus/nightmare/j3270/ConnectionConflictTest.java @@ -294,4 +294,35 @@ public class ConnectionConflictTest { assertEquals(haus.nightmare.lib3270j.ConnectionState.NOT_CONNECTED, client.getConnectionState()); assertFalse(client.isConnected()); } + + @Test + @DisplayName("When not connected, connect does not trigger abort connection dialog for previously closed connection") + public void testNotConnectedDoesNotTriggerAbortDialog() throws Exception { + J3270App app = createApp(); + if (app == null) return; + + // Simulate a previously closed connection + ConnectionConfig config1 = new ConnectionConfig("host1.test", 23, TerminalModel.IBM_3278_2); + app.connect(config1, false); + + Telnet3270Client client = app.getClient(); + assertNotNull(client); + // Force the client's FSM state to NOT_CONNECTED, simulating connection closure + client.getTelnetFSM().setConnectionState(haus.nightmare.lib3270j.ConnectionState.NOT_CONNECTED); + + assertFalse(app.isConnected(), "app.isConnected() must return false when client is NOT_CONNECTED"); + + boolean[] handlerCalled = new boolean[]{false}; + app.setConnectChoiceHandler((owner, target) -> { + handlerCalled[0] = true; + return ConnectChoiceDialog.Choice.CANCEL; + }); + + // Initiate a new connection + ConnectionConfig config2 = new ConnectionConfig("host2.test", 23, TerminalModel.IBM_3278_2); + app.connect(config2, true); + + assertFalse(handlerCalled[0], "Connect choice handler must NOT be called when j3270 is not connected"); + assertEquals("host2.test", app.getClient().getConfig().getHost(), "Must proceed to connect to host2.test in same window"); + } } diff --git a/lib3270j/src/main/java/haus/nightmare/lib3270j/Telnet3270Client.java b/lib3270j/src/main/java/haus/nightmare/lib3270j/Telnet3270Client.java index 2e9a124..b9afbad 100644 --- a/lib3270j/src/main/java/haus/nightmare/lib3270j/Telnet3270Client.java +++ b/lib3270j/src/main/java/haus/nightmare/lib3270j/Telnet3270Client.java @@ -385,6 +385,9 @@ public class Telnet3270Client { * Check if connected (any state past TCP pending). */ public boolean isConnected() { + if (fsm != null && fsm.getConnectionState() == ConnectionState.NOT_CONNECTED) { + return false; + } return connection != null && connection.isConnected(); } diff --git a/lib3270j/src/main/java/haus/nightmare/lib3270j/telnet/TelnetConnection.java b/lib3270j/src/main/java/haus/nightmare/lib3270j/telnet/TelnetConnection.java index 971493e..021a348 100644 --- a/lib3270j/src/main/java/haus/nightmare/lib3270j/telnet/TelnetConnection.java +++ b/lib3270j/src/main/java/haus/nightmare/lib3270j/telnet/TelnetConnection.java @@ -599,7 +599,7 @@ public class TelnetConnection { * Check if the socket is connected. */ public boolean isConnected() { - return socket != null && socket.isConnected() && !socket.isClosed(); + return running && socket != null && socket.isConnected() && !socket.isClosed(); } /** @@ -654,7 +654,13 @@ public class TelnetConnection { fsm.onError("Network loop error: " + t.getMessage()); } } finally { + running = false; stopKeepAlive(); + try { + if (socket != null && !socket.isClosed()) { + socket.close(); + } + } catch (IOException ignored) {} } } diff --git a/lib3270j/src/test/java/haus/nightmare/lib3270j/testutil/Mock3270ServerIntegrationTest.java b/lib3270j/src/test/java/haus/nightmare/lib3270j/testutil/Mock3270ServerIntegrationTest.java index b1ce9b5..eaff8e4 100644 --- a/lib3270j/src/test/java/haus/nightmare/lib3270j/testutil/Mock3270ServerIntegrationTest.java +++ b/lib3270j/src/test/java/haus/nightmare/lib3270j/testutil/Mock3270ServerIntegrationTest.java @@ -335,4 +335,24 @@ public class Mock3270ServerIntegrationTest { } assertTrue(receivedWontOrDontTn3270e, "Server should have received Telnet de-negotiation for TN3270E"); } + + @Test + public void testServerSideDisconnectTransitionsClientToNotConnectedAndClosesSocket() throws Exception { + ConnectionConfig config = new ConnectionConfig("127.0.0.1", server.getPort(), TerminalModel.IBM_3279_2); + client = new Telnet3270Client(config); + assertTrue(client.connect(8000)); + assertTrue(server.waitForHandshake(5, TimeUnit.SECONDS)); + waitForState(client, ConnectionState.CONNECTED_TN3270E, 5000); + assertTrue(client.isConnected()); + + // Server drops connection (host disconnect) + server.disconnectClient(); + + // Wait for client to detect disconnect + waitForState(client, ConnectionState.NOT_CONNECTED, 5000); + assertEquals(ConnectionState.NOT_CONNECTED, client.getConnectionState()); + assertFalse(client.isConnected(), "Client must report isConnected() == false after host disconnect"); + assertNotNull(client.getConnection()); + assertFalse(client.getConnection().isConnected(), "TelnetConnection must report isConnected() == false after host disconnect"); + } }