This commit is contained in:
2026-08-28 13:26:40 -04:00
parent a44ea00d3a
commit f310d39fc1
38 changed files with 2765 additions and 96 deletions
@@ -0,0 +1,263 @@
package haus.nightmare.lib3270j.charset;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
/**
* Unit tests for Single-Byte Character Set (SBCS) and Euro EBCDIC codepages.
*/
public class CodePageTest {
@Test
public void testCp037USCanada() {
CodePage cp = CodePageRegistry.getCodePage("037");
assertEquals("037", cp.getCodePageId());
assertEquals(37, cp.getCpgid());
assertEquals(697, cp.getCgcsgid());
assertFalse(cp.isDBCS());
// Basic ASCII roundtrip
assertEquals('A', cp.ebcdicToUnicode(0xC1));
assertEquals(0xC1, cp.unicodeToEbcdic('A'));
assertEquals('Z', cp.ebcdicToUnicode(0xE9));
assertEquals('0', cp.ebcdicToUnicode(0xF0));
assertEquals(' ', cp.ebcdicToUnicode(0x40));
// CP037 special punctuation
assertEquals('[', cp.ebcdicToUnicode(0xBA));
assertEquals(']', cp.ebcdicToUnicode(0xBB));
assertEquals('{', cp.ebcdicToUnicode(0xC0));
assertEquals('}', cp.ebcdicToUnicode(0xD0));
assertEquals('\\', cp.ebcdicToUnicode(0xE0));
assertEquals('~', cp.ebcdicToUnicode(0xA1));
assertEquals('^', cp.ebcdicToUnicode(0xB0));
}
@Test
public void testCp1047OpenSystems() {
CodePage cp = CodePageRegistry.getCodePage("1047");
assertEquals("1047", cp.getCodePageId());
assertEquals(1047, cp.getCpgid());
assertEquals(103, cp.getCgcsgid());
// CP1047 bracket differences
assertEquals('[', cp.ebcdicToUnicode(0xAD));
assertEquals(']', cp.ebcdicToUnicode(0xBD));
assertEquals(0xAD, cp.unicodeToEbcdic('['));
assertEquals(0xBD, cp.unicodeToEbcdic(']'));
assertEquals('^', cp.ebcdicToUnicode(0x5F));
assertEquals(0x5F, cp.unicodeToEbcdic('^'));
assertEquals('¬', cp.ebcdicToUnicode(0xB0));
}
@Test
public void testCp500International() {
CodePage cp = CodePageRegistry.getCodePage("500");
assertEquals("500", cp.getCodePageId());
assertEquals(500, cp.getCpgid());
assertEquals(697, cp.getCgcsgid());
// CP500 bracket & exclamation mappings
assertEquals('[', cp.ebcdicToUnicode(0x4A));
assertEquals(']', cp.ebcdicToUnicode(0x5A));
assertEquals(0x4A, cp.unicodeToEbcdic('['));
assertEquals(0x5A, cp.unicodeToEbcdic(']'));
assertEquals('!', cp.ebcdicToUnicode(0x4F));
assertEquals('$', cp.ebcdicToUnicode(0x5B));
}
@Test
public void testCp273GermanyAustria() {
CodePage cp = CodePageRegistry.getCodePage("273");
assertEquals("273", cp.getCodePageId());
assertEquals(273, cp.getCpgid());
// German umlauts and eszett roundtrip
int ebcAe = cp.unicodeToEbcdic('ä');
assertTrue(ebcAe >= 0);
assertEquals('ä', cp.ebcdicToUnicode(ebcAe));
int ebcOe = cp.unicodeToEbcdic('ö');
assertTrue(ebcOe >= 0);
assertEquals('ö', cp.ebcdicToUnicode(ebcOe));
int ebcUe = cp.unicodeToEbcdic('ü');
assertTrue(ebcUe >= 0);
assertEquals('ü', cp.ebcdicToUnicode(ebcUe));
int ebcSs = cp.unicodeToEbcdic('ß');
assertTrue(ebcSs >= 0);
assertEquals('ß', cp.ebcdicToUnicode(ebcSs));
}
@Test
public void testCp277DenmarkNorway() {
CodePage cp = CodePageRegistry.getCodePage("277");
assertEquals("277", cp.getCodePageId());
int ebcAe = cp.unicodeToEbcdic('æ');
assertTrue(ebcAe >= 0);
assertEquals('æ', cp.ebcdicToUnicode(ebcAe));
int ebcOe = cp.unicodeToEbcdic('ø');
assertTrue(ebcOe >= 0);
assertEquals('ø', cp.ebcdicToUnicode(ebcOe));
int ebcAa = cp.unicodeToEbcdic('å');
assertTrue(ebcAa >= 0);
assertEquals('å', cp.ebcdicToUnicode(ebcAa));
}
@Test
public void testCp278SwedenFinland() {
CodePage cp = CodePageRegistry.getCodePage("278");
assertEquals("278", cp.getCodePageId());
int ebcAe = cp.unicodeToEbcdic('ä');
assertTrue(ebcAe >= 0);
assertEquals('ä', cp.ebcdicToUnicode(ebcAe));
int ebcOe = cp.unicodeToEbcdic('ö');
assertTrue(ebcOe >= 0);
assertEquals('ö', cp.ebcdicToUnicode(ebcOe));
int ebcAa = cp.unicodeToEbcdic('å');
assertTrue(ebcAa >= 0);
assertEquals('å', cp.ebcdicToUnicode(ebcAa));
}
@Test
public void testCp280Italy() {
CodePage cp = CodePageRegistry.getCodePage("280");
assertEquals("280", cp.getCodePageId());
int ebcA = cp.unicodeToEbcdic('à');
assertTrue(ebcA >= 0);
assertEquals('à', cp.ebcdicToUnicode(ebcA));
int ebcE = cp.unicodeToEbcdic('é');
assertTrue(ebcE >= 0);
assertEquals('é', cp.ebcdicToUnicode(ebcE));
}
@Test
public void testCp284SpainLatinAmerica() {
CodePage cp = CodePageRegistry.getCodePage("284");
assertEquals("284", cp.getCodePageId());
int ebcN = cp.unicodeToEbcdic('ñ');
assertTrue(ebcN >= 0);
assertEquals('ñ', cp.ebcdicToUnicode(ebcN));
int ebcNu = cp.unicodeToEbcdic('Ñ');
assertTrue(ebcNu >= 0);
assertEquals('Ñ', cp.ebcdicToUnicode(ebcNu));
}
@Test
public void testCp285UK() {
CodePage cp = CodePageRegistry.getCodePage("285");
assertEquals("285", cp.getCodePageId());
int ebcPound = cp.unicodeToEbcdic('£');
assertTrue(ebcPound >= 0);
assertEquals('£', cp.ebcdicToUnicode(ebcPound));
}
@Test
public void testCp297France() {
CodePage cp = CodePageRegistry.getCodePage("297");
assertEquals("297", cp.getCodePageId());
int ebcA = cp.unicodeToEbcdic('à');
assertTrue(ebcA >= 0);
assertEquals('à', cp.ebcdicToUnicode(ebcA));
int ebcE = cp.unicodeToEbcdic('é');
assertTrue(ebcE >= 0);
assertEquals('é', cp.ebcdicToUnicode(ebcE));
}
@Test
public void testCp870Latin2() {
CodePage cp = CodePageRegistry.getCodePage("870");
assertEquals("870", cp.getCodePageId());
assertEquals(870, cp.getCpgid());
assertEquals(959, cp.getCgcsgid());
int ebcS = cp.unicodeToEbcdic('š');
assertTrue(ebcS >= 0);
assertEquals('š', cp.ebcdicToUnicode(ebcS));
}
@Test
public void testCp871Iceland() {
CodePage cp = CodePageRegistry.getCodePage("871");
assertEquals("871", cp.getCodePageId());
int ebcThorn = cp.unicodeToEbcdic('þ');
assertTrue(ebcThorn >= 0);
assertEquals('þ', cp.ebcdicToUnicode(ebcThorn));
int ebcEth = cp.unicodeToEbcdic('ð');
assertTrue(ebcEth >= 0);
assertEquals('ð', cp.ebcdicToUnicode(ebcEth));
}
@Test
public void testCp875Greece() {
CodePage cp = CodePageRegistry.getCodePage("875");
assertEquals("875", cp.getCodePageId());
int ebcAlpha = cp.unicodeToEbcdic('α');
assertTrue(ebcAlpha >= 0);
assertEquals('α', cp.ebcdicToUnicode(ebcAlpha));
int ebcOmega = cp.unicodeToEbcdic('Ω');
assertTrue(ebcOmega >= 0);
assertEquals('Ω', cp.ebcdicToUnicode(ebcOmega));
}
@Test
public void testCp1026Turkey() {
CodePage cp = CodePageRegistry.getCodePage("1026");
assertEquals("1026", cp.getCodePageId());
int ebcS = cp.unicodeToEbcdic('ş');
assertTrue(ebcS >= 0);
assertEquals('ş', cp.ebcdicToUnicode(ebcS));
}
@Test
public void testEuroVariantsCp1140To1149() {
CodePage cp1140 = CodePageRegistry.getCodePage("1140");
assertEquals(1140, cp1140.getCpgid());
assertEquals(695, cp1140.getCgcsgid());
assertEquals(0x9F, cp1140.unicodeToEbcdic('€'));
assertEquals('€', cp1140.ebcdicToUnicode(0x9F));
CodePage cp1141 = CodePageRegistry.getCodePage("1141");
assertEquals(0x9F, cp1141.unicodeToEbcdic('€'));
assertEquals('€', cp1141.ebcdicToUnicode(0x9F));
CodePage cp1148 = CodePageRegistry.getCodePage("1148");
assertEquals(1148, cp1148.getCpgid());
assertEquals(0x9F, cp1148.unicodeToEbcdic('€'));
assertEquals('€', cp1148.ebcdicToUnicode(0x9F));
}
@Test
public void testStringRoundTrips() {
CodePage cp273 = CodePageRegistry.getCodePage("273");
String german = "Grüße aus München: Äpfel & Öfen!";
byte[] ebcdic = cp273.stringToEbcdic(german);
String restored = cp273.ebcdicToString(ebcdic, 0, ebcdic.length);
assertEquals(german, restored);
CodePage cp284 = CodePageRegistry.getCodePage("284");
String spanish = "España y México: ¡Hola año nuevo!";
byte[] espEbcdic = cp284.stringToEbcdic(spanish);
String espRestored = cp284.ebcdicToString(espEbcdic, 0, espEbcdic.length);
assertEquals(spanish, espRestored);
}
}
@@ -0,0 +1,98 @@
package haus.nightmare.lib3270j.charset;
import haus.nightmare.lib3270j.TerminalModel;
import haus.nightmare.lib3270j.input.InputProcessor;
import haus.nightmare.lib3270j.screen.ScreenBuffer;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
/**
* Unit tests for Mixed Double-Byte Character Set (DBCS) codepages and SO/SI state processing.
*/
public class DBCSTranslationTest {
@Test
public void testDBCSProperties() {
CodePage cp930 = CodePageRegistry.getCodePage("930");
assertTrue(cp930.isDBCS());
assertEquals("930", cp930.getCodePageId());
assertEquals(930, cp930.getCpgid());
assertEquals(1172, cp930.getCgcsgid());
CodePage cp935 = CodePageRegistry.getCodePage("935");
assertTrue(cp935.isDBCS());
assertEquals(935, cp935.getCpgid());
CodePage cp937 = CodePageRegistry.getCodePage("937");
assertTrue(cp937.isDBCS());
assertEquals(937, cp937.getCpgid());
CodePage cp933 = CodePageRegistry.getCodePage("933");
assertTrue(cp933.isDBCS());
assertEquals(933, cp933.getCpgid());
}
@Test
public void testIdeographicSpace() {
CodePage cp930 = CodePageRegistry.getCodePage("930");
assertEquals('\u3000', cp930.dbcsToUnicode(0x40, 0x40));
assertEquals(0x4040, cp930.unicodeToDbcs('\u3000'));
}
@Test
public void testDBCSStreamSOSIProcessing() {
AbstractDBCSCodePage cp = (AbstractDBCSCodePage) CodePageRegistry.getCodePage("930");
cp.registerDbcsPair(0x4341, '\u6771'); // '東'
cp.registerDbcsPair(0x4342, '\u4EAC'); // '京'
// Mixed byte sequence: "IBM" + SO + 東 + 京 + SI + "123"
// 'I'=0xC9, 'B'=0xC2, 'M'=0xD4, SO=0x0E, 0x43, 0x41, 0x43, 0x42, SI=0x0F, '1'=0xF1, '2'=0xF2, '3'=0xF3
byte[] mixed = new byte[] {
(byte) 0xC9, (byte) 0xC2, (byte) 0xD4,
0x0E, (byte) 0x43, (byte) 0x41, (byte) 0x43, (byte) 0x42, 0x0F,
(byte) 0xF1, (byte) 0xF2, (byte) 0xF3
};
String translated = cp.ebcdicToString(mixed, 0, mixed.length);
assertEquals("IBM東京123", translated);
// Reverse conversion: stringToEbcdic
byte[] reEncoded = cp.stringToEbcdic("IBM東京123");
assertArrayEquals(mixed, reEncoded);
}
@Test
public void testInputProcessorDBCSInput() {
EbcdicTranslator translator = new EbcdicTranslator("930");
AbstractDBCSCodePage cp = (AbstractDBCSCodePage) translator.getCodePage();
cp.registerDbcsPair(0x4545, '\u6771');
ScreenBuffer screen = new ScreenBuffer(TerminalModel.IBM_3279_2, translator);
InputProcessor input = new InputProcessor(screen, translator, null);
// Enter DBCS character at pos 0
boolean ok = input.DBCSinputChar('\u6771', 0);
assertTrue(ok);
assertEquals(0x45, screen.getCell(0).ec & 0xFF);
assertEquals(0x45, screen.getCell(1).ec & 0xFF);
assertEquals('\u6771', (char) screen.getCell(0).ucs4);
assertEquals(2, screen.getCursorAddress());
}
@Test
public void testClearUnusedSISO() {
EbcdicTranslator translator = new EbcdicTranslator("930");
ScreenBuffer screen = new ScreenBuffer(TerminalModel.IBM_3279_2, translator);
InputProcessor input = new InputProcessor(screen, translator, null);
// Place orphaned SO (0x0E) followed immediately by SI (0x0F)
screen.setCell(5, 0x0E);
screen.setCell(6, 0x0F);
assertEquals(0x0E, screen.getCell(5).ec & 0xFF);
assertEquals(0x0F, screen.getCell(6).ec & 0xFF);
input.clearUnusedSISO(0);
assertEquals(0, screen.getCell(5).ec & 0xFF);
assertEquals(0, screen.getCell(6).ec & 0xFF);
}
}
@@ -0,0 +1,133 @@
package haus.nightmare.lib3270j.charset;
import haus.nightmare.lib3270j.ConnectionConfig;
import haus.nightmare.lib3270j.Telnet3270Client;
import haus.nightmare.lib3270j.TerminalModel;
import haus.nightmare.lib3270j.datastream.QueryReplyBuilder;
import haus.nightmare.lib3270j.screen.ScreenBuffer;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
/**
* High-level integration tests for Phase 4 Character Translation & Multi-Codepage subsystem.
*/
public class EbcdicTranslatorPhase4Test {
@Test
public void testRegistryAliases() {
assertEquals("037", CodePageRegistry.getCodePage("cp037").getCodePageId());
assertEquals("037", CodePageRegistry.getCodePage("IBM-037").getCodePageId());
assertEquals("037", CodePageRegistry.getCodePage("ebcdic-cp-us").getCodePageId());
assertEquals("1047", CodePageRegistry.getCodePage("posix").getCodePageId());
assertEquals("1047", CodePageRegistry.getCodePage("open-systems").getCodePageId());
assertEquals("1047", CodePageRegistry.getCodePage("IBM1047").getCodePageId());
assertEquals("273", CodePageRegistry.getCodePage("de").getCodePageId());
assertEquals("273", CodePageRegistry.getCodePage("germany").getCodePageId());
assertEquals("285", CodePageRegistry.getCodePage("uk").getCodePageId());
assertEquals("285", CodePageRegistry.getCodePage("gb").getCodePageId());
assertEquals("297", CodePageRegistry.getCodePage("fr").getCodePageId());
assertEquals("297", CodePageRegistry.getCodePage("france").getCodePageId());
assertEquals("930", CodePageRegistry.getCodePage("ja").getCodePageId());
assertEquals("935", CodePageRegistry.getCodePage("chinese-simplified").getCodePageId());
}
@Test
public void testDynamicCodePageSwitching() {
EbcdicTranslator translator = new EbcdicTranslator("037");
assertEquals("037", translator.getCodePageId());
assertEquals(37, translator.getCpgid());
assertEquals('[', translator.ebcdicToUnicode(0xBA));
// Switch to CP1047
translator.setCodePage("1047");
assertEquals("1047", translator.getCodePageId());
assertEquals(1047, translator.getCpgid());
assertEquals('[', translator.ebcdicToUnicode(0xAD));
// Switch to German CP273
translator.setCodePage("273");
assertEquals("273", translator.getCodePageId());
assertEquals(273, translator.getCpgid());
int ebcAe = translator.unicodeToEbcdic('ä');
assertTrue(ebcAe >= 0);
assertEquals('ä', translator.ebcdicToUnicode(ebcAe));
// Switch to Euro CP1140
translator.setCodePage("1140");
assertEquals("1140", translator.getCodePageId());
assertEquals(1140, translator.getCpgid());
assertEquals('€', translator.ebcdicToUnicode(0x9F));
}
@Test
public void testQueryReplyBuilderCharsetsSynchronization() {
ScreenBuffer screen = new ScreenBuffer(TerminalModel.IBM_3279_2, new EbcdicTranslator("273"));
QueryReplyBuilder builder = new QueryReplyBuilder(screen);
byte[] qrs = builder.buildAllQueryReplies(80, 24, 1920);
assertNotNull(qrs);
assertTrue(qrs.length > 0);
// Scan for QR_CHARSETS (0x85)
int idx = 1; // skip AID_SF
boolean foundCharsets = false;
while (idx < qrs.length) {
int len = ((qrs[idx] & 0xFF) << 8) | (qrs[idx + 1] & 0xFF);
int sfid = qrs[idx + 2] & 0xFF;
int qrCode = qrs[idx + 3] & 0xFF;
if (qrCode == 0x85) { // QR_CHARSETS
foundCharsets = true;
// Check Set 0 Descriptor (CPGID 273 = 0x0111)
// Descriptor 1 starts after 9-byte header
int descOffset = idx + 4 + 9;
int cpgidHigh = qrs[descOffset + 5] & 0xFF;
int cpgidLow = qrs[descOffset + 6] & 0xFF;
int cpgid = (cpgidHigh << 8) | cpgidLow;
assertEquals(273, cpgid, "Query Reply Charsets should advertise active CPGID 273");
break;
}
idx += len;
}
assertTrue(foundCharsets, "QR_CHARSETS must be present in Query Replies");
}
@Test
public void testScreenBufferTranslateWithActiveCodePage() {
EbcdicTranslator translator = new EbcdicTranslator("273");
ScreenBuffer screen = new ScreenBuffer(TerminalModel.IBM_3279_2, translator);
// Put German characters in buffer
screen.setCell(0, translator.unicodeToEbcdic('ä'));
screen.setCell(1, translator.unicodeToEbcdic('ö'));
screen.setCell(2, translator.unicodeToEbcdic('ü'));
screen.setCell(3, translator.unicodeToEbcdic('ß'));
screen.translateToUnicode();
assertEquals('ä', (char) screen.getCell(0).ucs4);
assertEquals('ö', (char) screen.getCell(1).ucs4);
assertEquals('ü', (char) screen.getCell(2).ucs4);
assertEquals('ß', (char) screen.getCell(3).ucs4);
}
@Test
public void testTelnet3270ClientWithConfiguredCodePage() {
ConnectionConfig config = new ConnectionConfig("mainframe.example.com", 23);
config.setCodePage("500");
Telnet3270Client client = new Telnet3270Client(config);
assertEquals("500", client.getCodePage());
assertEquals(500, client.getTranslator().getCpgid());
// Test changing at runtime
client.setCodePage("1047");
assertEquals("1047", client.getCodePage());
assertEquals(1047, client.getTranslator().getCpgid());
}
}