Connection storage
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
package haus.nightmare.j3270.storage;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
@@ -109,6 +110,267 @@ public class HostStorage {
|
||||
}
|
||||
}
|
||||
|
||||
// ================= INI File Export / Import =================
|
||||
|
||||
/**
|
||||
* Export all currently saved host connections to an INI file.
|
||||
*/
|
||||
public static synchronized void exportToIniFile(File file) throws IOException {
|
||||
List<SavedHost> hosts = getSavedHosts();
|
||||
exportToIniFile(file, hosts);
|
||||
}
|
||||
|
||||
public static synchronized void exportToIniFile(String path) throws IOException {
|
||||
exportToIniFile(new File(path));
|
||||
}
|
||||
|
||||
/**
|
||||
* Export a specified list of host connections to an INI file.
|
||||
*/
|
||||
public static void exportToIniFile(File file, List<SavedHost> hosts) throws IOException {
|
||||
if (file == null) {
|
||||
throw new IllegalArgumentException("Target file cannot be null");
|
||||
}
|
||||
File parent = file.getParentFile();
|
||||
if (parent != null && !parent.exists()) {
|
||||
parent.mkdirs();
|
||||
}
|
||||
try (PrintWriter w = new PrintWriter(new BufferedWriter(new FileWriter(file)))) {
|
||||
w.println("; j3270 Saved Connections Configuration File");
|
||||
w.println("; Exported on " + new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new java.util.Date()));
|
||||
w.println("; Connections count: " + (hosts != null ? hosts.size() : 0));
|
||||
w.println();
|
||||
|
||||
if (hosts != null) {
|
||||
for (SavedHost h : hosts) {
|
||||
String section = (h.getName() != null && !h.getName().trim().isEmpty())
|
||||
? h.getName().trim()
|
||||
: (h.getHost() != null && !h.getHost().trim().isEmpty() ? h.getHost().trim() : "Connection");
|
||||
w.println("[" + section + "]");
|
||||
if (h.getId() != null && !h.getId().trim().isEmpty()) {
|
||||
w.println("id = " + h.getId());
|
||||
}
|
||||
w.println("name = " + (h.getName() != null ? h.getName() : ""));
|
||||
w.println("host = " + (h.getHost() != null ? h.getHost() : ""));
|
||||
w.println("port = " + h.getPort());
|
||||
w.println("model = " + h.getModel());
|
||||
w.println("dynamicRows = " + h.getDynamicRows());
|
||||
w.println("dynamicCols = " + h.getDynamicCols());
|
||||
w.println("luName = " + (h.getLuName() != null ? h.getLuName() : ""));
|
||||
w.println("autoConnect = " + h.isAutoConnect());
|
||||
w.println("hostType = " + (h.getHostType() != null ? h.getHostType() : "TSO"));
|
||||
w.println("useTls = " + h.isUseTls());
|
||||
w.println("tlsVerifyCert = " + h.isTlsVerifyCert());
|
||||
w.println("tn3270e = " + h.isTn3270e());
|
||||
w.println("graphicsMode = " + (h.getGraphicsMode() != null ? h.getGraphicsMode() : "BOTH"));
|
||||
w.println("codePage = " + (h.getCodePage() != null ? h.getCodePage() : "037"));
|
||||
w.println("proxyType = " + (h.getProxyType() != null ? h.getProxyType() : "NONE"));
|
||||
w.println("proxyHost = " + (h.getProxyHost() != null ? h.getProxyHost() : ""));
|
||||
w.println("proxyPort = " + h.getProxyPort());
|
||||
w.println("proxyUsername = " + (h.getProxyUsername() != null ? h.getProxyUsername() : ""));
|
||||
w.println("proxyPassword = " + (h.getProxyPassword() != null ? h.getProxyPassword() : ""));
|
||||
w.println();
|
||||
}
|
||||
}
|
||||
}
|
||||
log.info("Saved connections exported to: " + file.getAbsolutePath());
|
||||
}
|
||||
|
||||
/**
|
||||
* Load connections from an INI file without saving them to persistent storage.
|
||||
*/
|
||||
public static List<SavedHost> loadFromIniFile(File file) throws IOException {
|
||||
if (file == null || !file.exists()) {
|
||||
throw new FileNotFoundException("Connections file not found: " + (file != null ? file.getAbsolutePath() : "null"));
|
||||
}
|
||||
List<SavedHost> hosts = new ArrayList<>();
|
||||
try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
|
||||
String line;
|
||||
SavedHost current = null;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
line = line.trim();
|
||||
if (line.isEmpty() || line.startsWith(";") || line.startsWith("#")) {
|
||||
continue;
|
||||
}
|
||||
if (line.startsWith("[") && line.endsWith("]")) {
|
||||
if (current != null) {
|
||||
hosts.add(current);
|
||||
}
|
||||
String section = line.substring(1, line.length() - 1).trim();
|
||||
if (section.toLowerCase().startsWith("connection:")) {
|
||||
section = section.substring("connection:".length()).trim();
|
||||
} else if (section.toLowerCase().startsWith("host:")) {
|
||||
section = section.substring("host:".length()).trim();
|
||||
}
|
||||
current = new SavedHost();
|
||||
current.setName(section);
|
||||
continue;
|
||||
}
|
||||
int eq = line.indexOf('=');
|
||||
if (eq < 0) {
|
||||
continue;
|
||||
}
|
||||
String key = line.substring(0, eq).trim();
|
||||
String value = line.substring(eq + 1).trim();
|
||||
if (current == null) {
|
||||
current = new SavedHost();
|
||||
current.setName("Default");
|
||||
}
|
||||
applyIniEntry(current, key, value);
|
||||
}
|
||||
if (current != null) {
|
||||
hosts.add(current);
|
||||
}
|
||||
}
|
||||
return hosts;
|
||||
}
|
||||
|
||||
public static List<SavedHost> loadFromIniFile(String path) throws IOException {
|
||||
return loadFromIniFile(new File(path));
|
||||
}
|
||||
|
||||
/**
|
||||
* Import connections from an INI file and merge them into persistent storage.
|
||||
* Updates existing profiles matching by ID or Name, and appends new profiles.
|
||||
*/
|
||||
public static synchronized List<SavedHost> importFromIniFile(File file) throws IOException {
|
||||
List<SavedHost> imported = loadFromIniFile(file);
|
||||
if (imported == null || imported.isEmpty()) {
|
||||
return getSavedHosts();
|
||||
}
|
||||
List<SavedHost> current = new ArrayList<>(getSavedHosts());
|
||||
for (SavedHost host : imported) {
|
||||
int index = -1;
|
||||
for (int i = 0; i < current.size(); i++) {
|
||||
SavedHost existing = current.get(i);
|
||||
if ((host.getId() != null && !host.getId().trim().isEmpty() && Objects.equals(existing.getId(), host.getId())) ||
|
||||
(host.getName() != null && !host.getName().trim().isEmpty() &&
|
||||
host.getName().equalsIgnoreCase(existing.getName()))) {
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (host.isAutoConnect()) {
|
||||
for (SavedHost c : current) {
|
||||
c.setAutoConnect(false);
|
||||
}
|
||||
}
|
||||
if (index >= 0) {
|
||||
if (host.getId() == null || host.getId().trim().isEmpty()) {
|
||||
host.setId(current.get(index).getId());
|
||||
}
|
||||
current.set(index, host);
|
||||
} else {
|
||||
current.add(host);
|
||||
}
|
||||
}
|
||||
saveAll(current);
|
||||
return current;
|
||||
}
|
||||
|
||||
public static synchronized List<SavedHost> importFromIniFile(String path) throws IOException {
|
||||
return importFromIniFile(new File(path));
|
||||
}
|
||||
|
||||
private static void applyIniEntry(SavedHost h, String key, String value) {
|
||||
String lowerKey = key.toLowerCase();
|
||||
switch (lowerKey) {
|
||||
case "id":
|
||||
if (!value.isEmpty()) h.setId(value);
|
||||
break;
|
||||
case "name":
|
||||
case "profilename":
|
||||
case "profile":
|
||||
if (!value.isEmpty()) h.setName(value);
|
||||
break;
|
||||
case "host":
|
||||
case "hostname":
|
||||
case "address":
|
||||
h.setHost(value);
|
||||
break;
|
||||
case "port":
|
||||
h.setPort(parseInt(value, 23));
|
||||
break;
|
||||
case "model":
|
||||
case "terminalmodel":
|
||||
h.setModel(parseInt(value, 4));
|
||||
break;
|
||||
case "dynamicrows":
|
||||
case "dynamic_rows":
|
||||
case "rows":
|
||||
h.setDynamicRows(parseInt(value, 62));
|
||||
break;
|
||||
case "dynamiccols":
|
||||
case "dynamic_cols":
|
||||
case "cols":
|
||||
h.setDynamicCols(parseInt(value, 160));
|
||||
break;
|
||||
case "luname":
|
||||
case "lu_name":
|
||||
case "lu":
|
||||
h.setLuName(value);
|
||||
break;
|
||||
case "autoconnect":
|
||||
case "auto_connect":
|
||||
h.setAutoConnect(Boolean.parseBoolean(value));
|
||||
break;
|
||||
case "hosttype":
|
||||
case "host_type":
|
||||
case "type":
|
||||
h.setHostType(value.toUpperCase());
|
||||
break;
|
||||
case "usetls":
|
||||
case "use_tls":
|
||||
case "tls":
|
||||
case "ssl":
|
||||
h.setUseTls(Boolean.parseBoolean(value));
|
||||
break;
|
||||
case "tlsverifycert":
|
||||
case "verifycert":
|
||||
case "tls_verify_cert":
|
||||
h.setTlsVerifyCert(Boolean.parseBoolean(value));
|
||||
break;
|
||||
case "tn3270e":
|
||||
case "enabletn3270e":
|
||||
h.setTn3270e(Boolean.parseBoolean(value));
|
||||
break;
|
||||
case "graphicsmode":
|
||||
case "graphics_mode":
|
||||
case "graphics":
|
||||
h.setGraphicsMode(value.toUpperCase());
|
||||
break;
|
||||
case "codepage":
|
||||
case "code_page":
|
||||
case "charset":
|
||||
h.setCodePage(value);
|
||||
break;
|
||||
case "proxytype":
|
||||
case "proxy_type":
|
||||
h.setProxyType(value.toUpperCase());
|
||||
break;
|
||||
case "proxyhost":
|
||||
case "proxy_host":
|
||||
h.setProxyHost(value);
|
||||
break;
|
||||
case "proxyport":
|
||||
case "proxy_port":
|
||||
h.setProxyPort(parseInt(value, 0));
|
||||
break;
|
||||
case "proxyusername":
|
||||
case "proxy_username":
|
||||
case "proxyuser":
|
||||
h.setProxyUsername(value);
|
||||
break;
|
||||
case "proxypassword":
|
||||
case "proxy_password":
|
||||
case "proxypass":
|
||||
h.setProxyPassword(value);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// ================= JSON Serialization =================
|
||||
|
||||
public static String serializeHostsJson(List<SavedHost> hosts) {
|
||||
|
||||
@@ -86,6 +86,8 @@ public class SettingsDialog extends JDialog {
|
||||
// Bottom action buttons
|
||||
private JButton btnImport;
|
||||
private JButton btnExport;
|
||||
private JButton btnImportConnections;
|
||||
private JButton btnExportConnections;
|
||||
|
||||
// Colors tab component tracking
|
||||
private final JPanel[] hostColorSwatches = new JPanel[16];
|
||||
@@ -97,7 +99,7 @@ public class SettingsDialog extends JDialog {
|
||||
AppIcon.applyTo(this);
|
||||
|
||||
initComponents();
|
||||
setSize(660, 540);
|
||||
setSize(680, 650);
|
||||
setLocationRelativeTo(parent);
|
||||
}
|
||||
|
||||
@@ -110,13 +112,18 @@ public class SettingsDialog extends JDialog {
|
||||
tabbedPane.addTab("Connection", createConnectionPanel());
|
||||
tabbedPane.addTab("Entry Assist", createEntryAssistPanel());
|
||||
|
||||
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 8, 8));
|
||||
btnImport = new JButton("Import Config...");
|
||||
ThemeManager.styleButton(btnImport, ThemeManager.ButtonVariant.DEFAULT);
|
||||
|
||||
btnExport = new JButton("Export Config...");
|
||||
ThemeManager.styleButton(btnExport, ThemeManager.ButtonVariant.DEFAULT);
|
||||
|
||||
btnImportConnections = new JButton("Import Connections...");
|
||||
ThemeManager.styleButton(btnImportConnections, ThemeManager.ButtonVariant.DEFAULT);
|
||||
|
||||
btnExportConnections = new JButton("Export Connections...");
|
||||
ThemeManager.styleButton(btnExportConnections, ThemeManager.ButtonVariant.DEFAULT);
|
||||
|
||||
JButton btnApply = new JButton("Apply");
|
||||
ThemeManager.styleButton(btnApply, ThemeManager.ButtonVariant.DEFAULT);
|
||||
|
||||
@@ -134,6 +141,14 @@ public class SettingsDialog extends JDialog {
|
||||
exportConfig();
|
||||
});
|
||||
|
||||
btnImportConnections.addActionListener((ActionEvent e) -> {
|
||||
importConnections();
|
||||
});
|
||||
|
||||
btnExportConnections.addActionListener((ActionEvent e) -> {
|
||||
exportConnections();
|
||||
});
|
||||
|
||||
btnOk.addActionListener((ActionEvent e) -> {
|
||||
boolean success = applySettings();
|
||||
if (success) {
|
||||
@@ -149,12 +164,28 @@ public class SettingsDialog extends JDialog {
|
||||
dispose();
|
||||
});
|
||||
|
||||
buttonPanel.add(btnImport);
|
||||
buttonPanel.add(btnExport);
|
||||
buttonPanel.add(Box.createHorizontalStrut(20));
|
||||
buttonPanel.add(btnApply);
|
||||
buttonPanel.add(btnCancel);
|
||||
buttonPanel.add(btnOk);
|
||||
JPanel buttonPanel = new JPanel(new GridLayout(3, 1, 0, 4));
|
||||
buttonPanel.setBorder(BorderFactory.createEmptyBorder(4, 8, 8, 8));
|
||||
|
||||
JPanel row1 = new JPanel(new FlowLayout(FlowLayout.RIGHT, 6, 2));
|
||||
row1.setOpaque(false);
|
||||
row1.add(btnImport);
|
||||
row1.add(btnExport);
|
||||
|
||||
JPanel row2 = new JPanel(new FlowLayout(FlowLayout.RIGHT, 6, 2));
|
||||
row2.setOpaque(false);
|
||||
row2.add(btnImportConnections);
|
||||
row2.add(btnExportConnections);
|
||||
|
||||
JPanel row3 = new JPanel(new FlowLayout(FlowLayout.RIGHT, 6, 2));
|
||||
row3.setOpaque(false);
|
||||
row3.add(btnApply);
|
||||
row3.add(btnCancel);
|
||||
row3.add(btnOk);
|
||||
|
||||
buttonPanel.add(row1);
|
||||
buttonPanel.add(row2);
|
||||
buttonPanel.add(row3);
|
||||
|
||||
getContentPane().add(tabbedPane, BorderLayout.CENTER);
|
||||
getContentPane().add(buttonPanel, BorderLayout.SOUTH);
|
||||
@@ -1510,7 +1541,7 @@ public class SettingsDialog extends JDialog {
|
||||
|
||||
// ========== Import Config ==========
|
||||
|
||||
void importConfigFile(File file) throws IOException {
|
||||
public void importConfigFile(File file) throws IOException {
|
||||
if (file == null || !file.exists()) {
|
||||
throw new FileNotFoundException("Config file not found: " + (file != null ? file.getAbsolutePath() : "null"));
|
||||
}
|
||||
@@ -1548,10 +1579,102 @@ public class SettingsDialog extends JDialog {
|
||||
return btnExport;
|
||||
}
|
||||
|
||||
public JButton getBtnImportConnections() {
|
||||
return btnImportConnections;
|
||||
}
|
||||
|
||||
public JButton getBtnExportConnections() {
|
||||
return btnExportConnections;
|
||||
}
|
||||
|
||||
public JComboBox<SavedHost> getSavedHostBox() {
|
||||
return savedHostBox;
|
||||
}
|
||||
|
||||
// ========== Import / Export Connections ==========
|
||||
|
||||
public void importConnectionsFile(File file) throws IOException {
|
||||
if (file == null || !file.exists()) {
|
||||
throw new FileNotFoundException("Connections file not found: " + (file != null ? file.getAbsolutePath() : "null"));
|
||||
}
|
||||
HostStorage.importFromIniFile(file);
|
||||
refreshSavedHostsBox();
|
||||
SavedHost autoHost = HostStorage.getAutoConnectHost();
|
||||
if (autoHost != null) {
|
||||
Settings.setAutoConnectHost(autoHost.getHost());
|
||||
Settings.setAutoConnectPort(autoHost.getPort());
|
||||
Settings.setAutoConnectTls(autoHost.isUseTls());
|
||||
Settings.setAutoConnectVerifyCert(autoHost.isTlsVerifyCert());
|
||||
Settings.setAutoConnectTn3270e(autoHost.isTn3270e());
|
||||
}
|
||||
refreshFromSettings();
|
||||
}
|
||||
|
||||
public void exportConnectionsFile(File file) throws IOException {
|
||||
if (file == null) {
|
||||
throw new IllegalArgumentException("Target file cannot be null");
|
||||
}
|
||||
HostStorage.exportToIniFile(file);
|
||||
}
|
||||
|
||||
private void importConnections() {
|
||||
JFileChooser fc = new JFileChooser();
|
||||
fc.setDialogTitle("Import Connections");
|
||||
fc.setSelectedFile(new java.io.File("connections.ini"));
|
||||
fc.setFileFilter(new javax.swing.filechooser.FileNameExtensionFilter("INI Files (*.ini)", "ini"));
|
||||
|
||||
int result = fc.showOpenDialog(this);
|
||||
if (result == JFileChooser.APPROVE_OPTION) {
|
||||
java.io.File file = fc.getSelectedFile();
|
||||
try {
|
||||
importConnectionsFile(file);
|
||||
int count = HostStorage.getSavedHosts().size();
|
||||
JOptionPane.showMessageDialog(this,
|
||||
"Connections imported successfully from:\n" + file.getAbsolutePath() + "\nTotal saved connections: " + count,
|
||||
"Import Successful", JOptionPane.INFORMATION_MESSAGE);
|
||||
} catch (Exception ex) {
|
||||
JOptionPane.showMessageDialog(this,
|
||||
"Import connections failed: " + ex.getMessage(),
|
||||
"Import Error", JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void exportConnections() {
|
||||
JFileChooser fc = new JFileChooser();
|
||||
fc.setDialogTitle("Export Connections");
|
||||
fc.setSelectedFile(new java.io.File("connections.ini"));
|
||||
fc.setFileFilter(new javax.swing.filechooser.FileNameExtensionFilter("INI Files (*.ini)", "ini"));
|
||||
|
||||
int result = fc.showSaveDialog(this);
|
||||
if (result == JFileChooser.APPROVE_OPTION) {
|
||||
java.io.File file = fc.getSelectedFile();
|
||||
// Ensure .ini extension
|
||||
if (!file.getName().toLowerCase().endsWith(".ini")) {
|
||||
file = new java.io.File(file.getAbsolutePath() + ".ini");
|
||||
}
|
||||
|
||||
// Confirm overwrite
|
||||
if (file.exists()) {
|
||||
int confirm = JOptionPane.showConfirmDialog(this,
|
||||
"File already exists. Overwrite?", "Confirm Overwrite",
|
||||
JOptionPane.YES_NO_OPTION);
|
||||
if (confirm != JOptionPane.YES_OPTION) return;
|
||||
}
|
||||
|
||||
try {
|
||||
exportConnectionsFile(file);
|
||||
JOptionPane.showMessageDialog(this,
|
||||
"Connections exported to:\n" + file.getAbsolutePath(),
|
||||
"Export Successful", JOptionPane.INFORMATION_MESSAGE);
|
||||
} catch (Exception ex) {
|
||||
JOptionPane.showMessageDialog(this,
|
||||
"Export connections failed: " + ex.getMessage(),
|
||||
"Export Error", JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ========== Export Config ==========
|
||||
|
||||
private void exportConfig() {
|
||||
|
||||
@@ -0,0 +1,342 @@
|
||||
package haus.nightmare.j3270.storage;
|
||||
|
||||
import haus.nightmare.j3270.J3270App;
|
||||
import haus.nightmare.j3270.config.Settings;
|
||||
import haus.nightmare.j3270.ui.SettingsDialog;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
public class ConnectionIniStorageTest {
|
||||
|
||||
private List<SavedHost> originalSavedHosts;
|
||||
private String origHost;
|
||||
private int origPort;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
originalSavedHosts = HostStorage.getSavedHosts();
|
||||
origHost = Settings.getAutoConnectHost();
|
||||
origPort = Settings.getAutoConnectPort();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void tearDown() {
|
||||
if (originalSavedHosts != null) {
|
||||
HostStorage.saveAll(originalSavedHosts);
|
||||
} else {
|
||||
HostStorage.clearAll();
|
||||
}
|
||||
Settings.setAutoConnectHost(origHost);
|
||||
Settings.setAutoConnectPort(origPort);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("HostStorage exports to and loads from connections.ini with full fidelity")
|
||||
public void testExportAndLoadConnectionsIniRoundtrip() throws IOException {
|
||||
List<SavedHost> hosts = new ArrayList<>();
|
||||
SavedHost h1 = new SavedHost("id-mainframe-1", "Production TSO", "mainframe.corp.com", 23, 4, 62, 160,
|
||||
"LU01", true, "TSO", false, true, true, "BOTH", "037", "NONE", "", 0, "", "");
|
||||
SavedHost h2 = new SavedHost("id-vm-2", "VM CMS Test", "vmcms.lab.net", 992, 2, 24, 80,
|
||||
"LUCMS", false, "CMS", true, false, true, "GDDM", "1047", "SOCKS5", "10.0.0.1", 1080, "proxyuser", "proxypass");
|
||||
hosts.add(h1);
|
||||
hosts.add(h2);
|
||||
|
||||
File tmpIni = File.createTempFile("connections_test", ".ini");
|
||||
tmpIni.deleteOnExit();
|
||||
|
||||
try {
|
||||
HostStorage.exportToIniFile(tmpIni, hosts);
|
||||
|
||||
assertTrue(tmpIni.exists());
|
||||
String content = new String(Files.readAllBytes(tmpIni.toPath()));
|
||||
assertTrue(content.contains("[Production TSO]"), "Must contain section header [Production TSO]");
|
||||
assertTrue(content.contains("[VM CMS Test]"), "Must contain section header [VM CMS Test]");
|
||||
assertTrue(content.contains("host = mainframe.corp.com"));
|
||||
assertTrue(content.contains("proxyHost = 10.0.0.1"));
|
||||
assertTrue(content.contains("proxyType = SOCKS5"));
|
||||
|
||||
List<SavedHost> loaded = HostStorage.loadFromIniFile(tmpIni);
|
||||
assertEquals(2, loaded.size(), "Should load 2 connections from INI");
|
||||
|
||||
SavedHost loaded1 = loaded.get(0);
|
||||
assertEquals("id-mainframe-1", loaded1.getId());
|
||||
assertEquals("Production TSO", loaded1.getName());
|
||||
assertEquals("mainframe.corp.com", loaded1.getHost());
|
||||
assertEquals(23, loaded1.getPort());
|
||||
assertEquals(4, loaded1.getModel());
|
||||
assertEquals(62, loaded1.getDynamicRows());
|
||||
assertEquals(160, loaded1.getDynamicCols());
|
||||
assertEquals("LU01", loaded1.getLuName());
|
||||
assertTrue(loaded1.isAutoConnect());
|
||||
assertEquals("TSO", loaded1.getHostType());
|
||||
assertFalse(loaded1.isUseTls());
|
||||
assertTrue(loaded1.isTlsVerifyCert());
|
||||
|
||||
SavedHost loaded2 = loaded.get(1);
|
||||
assertEquals("id-vm-2", loaded2.getId());
|
||||
assertEquals("VM CMS Test", loaded2.getName());
|
||||
assertEquals("vmcms.lab.net", loaded2.getHost());
|
||||
assertEquals(992, loaded2.getPort());
|
||||
assertEquals(2, loaded2.getModel());
|
||||
assertEquals(24, loaded2.getDynamicRows());
|
||||
assertEquals(80, loaded2.getDynamicCols());
|
||||
assertEquals("LUCMS", loaded2.getLuName());
|
||||
assertFalse(loaded2.isAutoConnect());
|
||||
assertEquals("CMS", loaded2.getHostType());
|
||||
assertTrue(loaded2.isUseTls());
|
||||
assertFalse(loaded2.isTlsVerifyCert());
|
||||
assertEquals("SOCKS5", loaded2.getProxyType());
|
||||
assertEquals("10.0.0.1", loaded2.getProxyHost());
|
||||
assertEquals(1080, loaded2.getProxyPort());
|
||||
assertEquals("proxyuser", loaded2.getProxyUsername());
|
||||
assertEquals("proxypass", loaded2.getProxyPassword());
|
||||
} finally {
|
||||
if (tmpIni.exists()) tmpIni.delete();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("HostStorage importFromIniFile merges and updates existing hosts by name or id")
|
||||
public void testImportFromIniFileMergesAndUpdates() throws IOException {
|
||||
SavedHost initial = new SavedHost("id-100", "Mainframe Prod", "old.host.org", 23, 4, 62, 160,
|
||||
"", false, "TSO", false, true, true, "BOTH", "037", "NONE", "", 0, "", "");
|
||||
List<SavedHost> initList = new ArrayList<>();
|
||||
initList.add(initial);
|
||||
HostStorage.saveAll(initList);
|
||||
|
||||
File tmpIni = File.createTempFile("connections_merge", ".ini");
|
||||
tmpIni.deleteOnExit();
|
||||
|
||||
try {
|
||||
try (FileWriter w = new FileWriter(tmpIni)) {
|
||||
// Update Mainframe Prod with new host address
|
||||
w.write("[Mainframe Prod]\n");
|
||||
w.write("id = id-100\n");
|
||||
w.write("host = new.host.org\n");
|
||||
w.write("port = 2323\n");
|
||||
w.write("\n");
|
||||
// Add a second connection
|
||||
w.write("[Development CICS]\n");
|
||||
w.write("host = cics.dev.org\n");
|
||||
w.write("port = 992\n");
|
||||
w.write("hostType = CICS\n");
|
||||
w.write("useTls = true\n");
|
||||
}
|
||||
|
||||
List<SavedHost> result = HostStorage.importFromIniFile(tmpIni);
|
||||
assertEquals(2, result.size(), "Import should result in 2 saved hosts (1 updated, 1 added)");
|
||||
|
||||
SavedHost updated = HostStorage.findById("id-100");
|
||||
assertNotNull(updated);
|
||||
assertEquals("new.host.org", updated.getHost());
|
||||
assertEquals(2323, updated.getPort());
|
||||
|
||||
SavedHost added = HostStorage.findByName("Development CICS");
|
||||
assertNotNull(added);
|
||||
assertEquals("cics.dev.org", added.getHost());
|
||||
assertEquals(992, added.getPort());
|
||||
assertEquals("CICS", added.getHostType());
|
||||
assertTrue(added.isUseTls());
|
||||
} finally {
|
||||
if (tmpIni.exists()) tmpIni.delete();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("HostStorage parses INI section prefixes and aliases properly")
|
||||
public void testImportHandlesMissingFieldsAndAliases() throws IOException {
|
||||
File tmpIni = File.createTempFile("connections_alias", ".ini");
|
||||
tmpIni.deleteOnExit();
|
||||
|
||||
try {
|
||||
try (FileWriter w = new FileWriter(tmpIni)) {
|
||||
w.write("[connection:Aliased Host]\n");
|
||||
w.write("hostname = test.alias.net\n");
|
||||
w.write("port = 1023\n");
|
||||
w.write("terminalModel = 2\n");
|
||||
w.write("type = CMS\n");
|
||||
w.write("tls = true\n");
|
||||
w.write("verifyCert = false\n");
|
||||
w.write("charset = 1047\n");
|
||||
w.write("rows = 24\n");
|
||||
w.write("cols = 80\n");
|
||||
w.write("lu = LUVM\n");
|
||||
}
|
||||
|
||||
List<SavedHost> parsed = HostStorage.loadFromIniFile(tmpIni);
|
||||
assertEquals(1, parsed.size());
|
||||
|
||||
SavedHost h = parsed.get(0);
|
||||
assertEquals("Aliased Host", h.getName());
|
||||
assertEquals("test.alias.net", h.getHost());
|
||||
assertEquals(1023, h.getPort());
|
||||
assertEquals(2, h.getModel());
|
||||
assertEquals("CMS", h.getHostType());
|
||||
assertTrue(h.isUseTls());
|
||||
assertFalse(h.isTlsVerifyCert());
|
||||
assertEquals("1047", h.getCodePage());
|
||||
assertEquals(24, h.getDynamicRows());
|
||||
assertEquals(80, h.getDynamicCols());
|
||||
assertEquals("LUVM", h.getLuName());
|
||||
} finally {
|
||||
if (tmpIni.exists()) tmpIni.delete();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("SettingsDialog contains Import Connections and Export Connections buttons and is taller")
|
||||
public void testSettingsDialogConnectionButtonsAndHeight() {
|
||||
J3270App app;
|
||||
try {
|
||||
app = new J3270App();
|
||||
} catch (HeadlessException e) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
SettingsDialog dialog = new SettingsDialog(app);
|
||||
|
||||
JButton btnImportConn = dialog.getBtnImportConnections();
|
||||
JButton btnExportConn = dialog.getBtnExportConnections();
|
||||
|
||||
assertNotNull(btnImportConn, "Import Connections button must exist in SettingsDialog");
|
||||
assertNotNull(btnExportConn, "Export Connections button must exist in SettingsDialog");
|
||||
|
||||
assertEquals("Import Connections...", btnImportConn.getText());
|
||||
assertEquals("Export Connections...", btnExportConn.getText());
|
||||
|
||||
// Check window height: must be taller than previous 540 to accommodate extra buttons
|
||||
assertTrue(dialog.getHeight() > 540,
|
||||
"Settings window height (" + dialog.getHeight() + ") must be taller than 540 to accommodate the extra buttons");
|
||||
|
||||
// Verify config buttons share container row1, and connection buttons share container row2
|
||||
JButton btnImport = dialog.getBtnImport();
|
||||
JButton btnExport = dialog.getBtnExport();
|
||||
assertNotNull(btnImport);
|
||||
assertNotNull(btnExport);
|
||||
|
||||
Container configRow = btnImport.getParent();
|
||||
assertNotNull(configRow);
|
||||
assertSame(configRow, btnExport.getParent(), "btnImport and btnExport must share the same parent row");
|
||||
|
||||
Container connRow = btnImportConn.getParent();
|
||||
assertNotNull(connRow);
|
||||
assertSame(connRow, btnExportConn.getParent(), "btnImportConn and btnExportConn must share the same parent row");
|
||||
|
||||
assertNotSame(configRow, connRow, "Connection buttons must be below and NOT in line with config buttons");
|
||||
|
||||
// Verify connRow is positioned below configRow in buttonPanel
|
||||
Container buttonPanel = configRow.getParent();
|
||||
assertNotNull(buttonPanel);
|
||||
assertSame(buttonPanel, connRow.getParent(), "Both rows must be in buttonPanel");
|
||||
|
||||
int configRowIdx = -1;
|
||||
int connRowIdx = -1;
|
||||
Component[] rows = buttonPanel.getComponents();
|
||||
for (int i = 0; i < rows.length; i++) {
|
||||
if (rows[i] == configRow) configRowIdx = i;
|
||||
if (rows[i] == connRow) connRowIdx = i;
|
||||
}
|
||||
assertTrue(configRowIdx >= 0, "configRow must be in buttonPanel");
|
||||
assertTrue(connRowIdx >= 0, "connRow must be in buttonPanel");
|
||||
assertTrue(connRowIdx > configRowIdx, "connRow must be below configRow in buttonPanel (" + connRowIdx + " > " + configRowIdx + ")");
|
||||
|
||||
dialog.dispose();
|
||||
app.dispose();
|
||||
} catch (HeadlessException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("SettingsDialog importConnectionsFile updates savedHostBox and Settings")
|
||||
public void testSettingsDialogImportConnectionsFileUpdatesDropdownAndSettings() throws IOException {
|
||||
J3270App app;
|
||||
try {
|
||||
app = new J3270App();
|
||||
} catch (HeadlessException e) {
|
||||
return;
|
||||
}
|
||||
|
||||
File tmpIni = File.createTempFile("settings_dialog_import_test", ".ini");
|
||||
tmpIni.deleteOnExit();
|
||||
|
||||
try {
|
||||
try (FileWriter w = new FileWriter(tmpIni)) {
|
||||
w.write("[Auto Mainframe]\n");
|
||||
w.write("host = auto.mainframe.org\n");
|
||||
w.write("port = 992\n");
|
||||
w.write("autoConnect = true\n");
|
||||
w.write("useTls = true\n");
|
||||
}
|
||||
|
||||
SettingsDialog dialog = new SettingsDialog(app);
|
||||
dialog.importConnectionsFile(tmpIni);
|
||||
|
||||
// Check savedHostBox updated
|
||||
JComboBox<SavedHost> box = dialog.getSavedHostBox();
|
||||
assertNotNull(box);
|
||||
assertTrue(box.getItemCount() >= 1);
|
||||
|
||||
SavedHost sel = (SavedHost) box.getSelectedItem();
|
||||
assertNotNull(sel);
|
||||
assertEquals("auto.mainframe.org", sel.getHost());
|
||||
assertTrue(sel.isAutoConnect());
|
||||
|
||||
// Check Settings updated for autoConnect host
|
||||
assertEquals("auto.mainframe.org", Settings.getAutoConnectHost());
|
||||
assertEquals(992, Settings.getAutoConnectPort());
|
||||
assertTrue(Settings.getAutoConnectTls());
|
||||
|
||||
// Test exportConnectionsFile
|
||||
File exportTarget = File.createTempFile("settings_dialog_export_test", ".ini");
|
||||
exportTarget.deleteOnExit();
|
||||
dialog.exportConnectionsFile(exportTarget);
|
||||
assertTrue(exportTarget.exists());
|
||||
String exported = new String(Files.readAllBytes(exportTarget.toPath()));
|
||||
assertTrue(exported.contains("auto.mainframe.org"));
|
||||
|
||||
dialog.dispose();
|
||||
app.dispose();
|
||||
} catch (HeadlessException ignored) {
|
||||
} finally {
|
||||
if (tmpIni.exists()) tmpIni.delete();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("SettingsDialog importConnectionsFile throws FileNotFoundException for nonexistent file")
|
||||
public void testImportNonExistentConnectionsFileThrows() {
|
||||
J3270App app;
|
||||
try {
|
||||
app = new J3270App();
|
||||
} catch (HeadlessException e) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
SettingsDialog dialog = new SettingsDialog(app);
|
||||
File nonExistent = new File("/path/to/nonexistent/connections_" + System.currentTimeMillis() + ".ini");
|
||||
assertThrows(FileNotFoundException.class, () -> {
|
||||
dialog.importConnectionsFile(nonExistent);
|
||||
});
|
||||
|
||||
dialog.dispose();
|
||||
app.dispose();
|
||||
} catch (HeadlessException ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user