This commit is contained in:
@@ -662,8 +662,15 @@ public class DataStreamProcessor {
|
||||
|
||||
int size = screen.getRows() * screen.getCols();
|
||||
|
||||
int aid = (inputProcessor != null && inputProcessor.getLastAid() != 0)
|
||||
? inputProcessor.getLastAid()
|
||||
: AID_NO;
|
||||
if (inputProcessor != null) {
|
||||
inputProcessor.setLastAid(AID_NO);
|
||||
}
|
||||
|
||||
// AID byte
|
||||
outputWrite(AID_NO);
|
||||
outputWrite(aid);
|
||||
// Cursor address
|
||||
byte[] caddr = encodeAddress(screen.getCursorAddress(), screen.getRows(), screen.getCols());
|
||||
outputWrite(caddr[0] & 0xFF);
|
||||
@@ -710,56 +717,36 @@ public class DataStreamProcessor {
|
||||
outputWrite(caddr[1] & 0xFF);
|
||||
|
||||
if (!screen.isFormatted()) {
|
||||
// Unformatted screen: send data up to last non-null
|
||||
int lastNonNull = -1;
|
||||
for (int i = size - 1; i >= 0; i--) {
|
||||
if (screen.getCell(i).ec != 0) {
|
||||
lastNonNull = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (lastNonNull >= 0) {
|
||||
for (int i = 0; i <= lastNonNull; i++) {
|
||||
int b = screen.getCell(i).ec & 0xFF;
|
||||
outputWrite(b != 0 ? b : 0x40);
|
||||
// Unformatted screen: send all non-null characters (null suppression)
|
||||
for (int i = 0; i < size; i++) {
|
||||
int b = screen.getCell(i).ec & 0xFF;
|
||||
if (b != 0x00) {
|
||||
outputWrite(b);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Formatted screen: send modified fields with trailing NULLs stripped
|
||||
// Formatted screen: send modified fields with null suppression per 3270 spec
|
||||
for (int i = 0; i < size; i++) {
|
||||
ExtendedAttribute ea = screen.getCell(i);
|
||||
if (ea.isFieldAttribute() && (all || faIsModified(ea.fa & 0xFF))) {
|
||||
int fieldStart = (i + 1) % size;
|
||||
|
||||
// Collect field data and find last non-null byte
|
||||
ByteArrayOutputStream fieldData = new ByteArrayOutputStream();
|
||||
int pos = fieldStart;
|
||||
int lastNonNull = -1;
|
||||
int fieldLen = 0;
|
||||
while (!screen.getCell(pos).isFieldAttribute()) {
|
||||
int b = screen.getCell(pos).ec & 0xFF;
|
||||
fieldData.write(b);
|
||||
if (b != 0x00) {
|
||||
lastNonNull = fieldLen;
|
||||
}
|
||||
fieldLen++;
|
||||
pos = (pos + 1) % size;
|
||||
if (pos == fieldStart)
|
||||
break;
|
||||
}
|
||||
|
||||
// Always send SBA and address
|
||||
// Always send SBA and address of first character position in field
|
||||
outputWrite(ORDER_SBA);
|
||||
byte[] addr = encodeAddress(fieldStart, screen.getRows(), screen.getCols());
|
||||
outputWrite(addr[0] & 0xFF);
|
||||
outputWrite(addr[1] & 0xFF);
|
||||
|
||||
// Send field data (strip trailing nulls)
|
||||
if (lastNonNull >= 0) {
|
||||
byte[] allData = fieldData.toByteArray();
|
||||
for (int k = 0; k <= lastNonNull; k++) {
|
||||
outputWrite(allData[k] & 0xFF);
|
||||
// Send all non-null characters in field (suppressing 0x00)
|
||||
int pos = fieldStart;
|
||||
while (!screen.getCell(pos).isFieldAttribute()) {
|
||||
int b = screen.getCell(pos).ec & 0xFF;
|
||||
if (b != 0x00) {
|
||||
outputWrite(b);
|
||||
}
|
||||
pos = (pos + 1) % size;
|
||||
if (pos == fieldStart)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,8 +95,10 @@ public class InputProcessor {
|
||||
public void typeCharacter(char ch) {
|
||||
if (keyboardLocked) return;
|
||||
|
||||
int baddr = screen.getCursorAddress();
|
||||
int size = screen.getRows() * screen.getCols();
|
||||
if (size <= 0) return;
|
||||
int baddr = screen.getCursorAddress();
|
||||
baddr = ((baddr % size) + size) % size;
|
||||
|
||||
// Check if cursor is at a field attribute or in a protected field
|
||||
ExtendedAttribute ea = screen.getCell(baddr);
|
||||
@@ -120,8 +122,10 @@ public class InputProcessor {
|
||||
// Insert mode: shift characters right from cursor to end of field
|
||||
// Find end of field
|
||||
int endAddr = baddr;
|
||||
while (!screen.getCell(screen.incrementAddress(endAddr)).isFieldAttribute()) {
|
||||
int count = 0;
|
||||
while (!screen.getCell(screen.incrementAddress(endAddr)).isFieldAttribute() && count < size) {
|
||||
endAddr = screen.incrementAddress(endAddr);
|
||||
count++;
|
||||
if (endAddr == baddr) break; // wrapped around (unformatted)
|
||||
}
|
||||
// Check if last position is non-null (field overflow)
|
||||
@@ -131,11 +135,13 @@ public class InputProcessor {
|
||||
}
|
||||
// Shift right from endAddr-1 down to baddr
|
||||
int dst = endAddr;
|
||||
while (dst != baddr) {
|
||||
int shiftCount = 0;
|
||||
while (dst != baddr && shiftCount < size) {
|
||||
int src = screen.decrementAddress(dst);
|
||||
screen.getCell(dst).ec = screen.getCell(src).ec;
|
||||
screen.getCell(dst).ucs4 = screen.getCell(src).ucs4;
|
||||
dst = src;
|
||||
shiftCount++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,9 +161,11 @@ public class InputProcessor {
|
||||
// Advance cursor
|
||||
int startAdvance = baddr;
|
||||
baddr = (baddr + 1) % size;
|
||||
int advCount = 0;
|
||||
// Skip over field attributes safely
|
||||
while (screen.getCell(baddr).isFieldAttribute() && baddr != startAdvance) {
|
||||
while (screen.getCell(baddr).isFieldAttribute() && baddr != startAdvance && advCount < size) {
|
||||
baddr = (baddr + 1) % size;
|
||||
advCount++;
|
||||
}
|
||||
screen.setCursorAddress(baddr);
|
||||
screen.markAllChanged();
|
||||
@@ -247,39 +255,28 @@ public class InputProcessor {
|
||||
|
||||
if (screen.isFormatted()) {
|
||||
// Send modified fields with SBA
|
||||
// Per 3270 Data Stream Architecture: strip trailing NULLs (0x00) from field data
|
||||
// Per 3270 Data Stream Architecture: suppress NULLs (0x00) from field data
|
||||
int size = screen.getRows() * screen.getCols();
|
||||
for (int i = 0; i < size; i++) {
|
||||
ExtendedAttribute ea = screen.getCell(i);
|
||||
if (ea.isFieldAttribute() && faIsModified(ea.fa & 0xFF)) {
|
||||
int fieldStart = (i + 1) % size;
|
||||
|
||||
// First, collect field data and find last non-null byte
|
||||
ByteArrayOutputStream fieldData = new ByteArrayOutputStream();
|
||||
int pos = fieldStart;
|
||||
int lastNonNull = -1;
|
||||
int fieldLen = 0;
|
||||
while (!screen.getCell(pos).isFieldAttribute()) {
|
||||
int b = screen.getCell(pos).ec & 0xFF;
|
||||
fieldData.write(b);
|
||||
if (b != 0x00) {
|
||||
lastNonNull = fieldLen;
|
||||
}
|
||||
fieldLen++;
|
||||
pos = (pos + 1) % size;
|
||||
if (pos == fieldStart) break;
|
||||
}
|
||||
|
||||
// Always send SBA and address
|
||||
// Always send SBA and address of first character in field
|
||||
out.write(ORDER_SBA);
|
||||
byte[] addr = encodeAddress(fieldStart, screen.getRows(), screen.getCols());
|
||||
out.write(addr[0] & 0xFF);
|
||||
out.write(addr[1] & 0xFF);
|
||||
|
||||
// Only send data if there is any (strip trailing nulls)
|
||||
if (lastNonNull >= 0) {
|
||||
byte[] allData = fieldData.toByteArray();
|
||||
out.write(allData, 0, lastNonNull + 1);
|
||||
// Send all non-null characters in field (suppressing 0x00)
|
||||
int pos = fieldStart;
|
||||
while (!screen.getCell(pos).isFieldAttribute()) {
|
||||
int b = screen.getCell(pos).ec & 0xFF;
|
||||
if (b != 0x00) {
|
||||
out.write(b);
|
||||
}
|
||||
pos = (pos + 1) % size;
|
||||
if (pos == fieldStart) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -359,6 +356,8 @@ public class InputProcessor {
|
||||
return false;
|
||||
}
|
||||
int size = screen.getRows() * screen.getCols();
|
||||
if (size <= 0) return false;
|
||||
address = ((address % size) + size) % size;
|
||||
int faPos = screen.findFieldAttribute(address);
|
||||
if (faPos < 0) {
|
||||
System.out.println("LP: no FA found for addr=" + address);
|
||||
@@ -461,11 +460,14 @@ public class InputProcessor {
|
||||
}
|
||||
|
||||
public void backTab() {
|
||||
// Find previous unprotected field
|
||||
int addr = screen.getCursorAddress();
|
||||
if (!screen.isFormatted()) return;
|
||||
int size = screen.getRows() * screen.getCols();
|
||||
if (size <= 0) return;
|
||||
int addr = screen.getCursorAddress();
|
||||
addr = ((addr % size) + size) % size;
|
||||
int start = screen.decrementAddress(addr);
|
||||
addr = start;
|
||||
int count = 0;
|
||||
do {
|
||||
addr = screen.decrementAddress(addr);
|
||||
if (screen.getCell(addr).isFieldAttribute()) {
|
||||
@@ -475,7 +477,8 @@ public class InputProcessor {
|
||||
return;
|
||||
}
|
||||
}
|
||||
} while (addr != start);
|
||||
count++;
|
||||
} while (addr != start && count < size);
|
||||
}
|
||||
|
||||
public void eraseEof() {
|
||||
@@ -493,15 +496,19 @@ public class InputProcessor {
|
||||
}
|
||||
int addr = screen.getCursorAddress();
|
||||
int size = screen.getRows() * screen.getCols();
|
||||
if (size <= 0) return;
|
||||
addr = ((addr % size) + size) % size;
|
||||
byte faVal = screen.getFieldAttributeAt(addr);
|
||||
if (faIsProtected(faVal & 0xFF)) return;
|
||||
|
||||
// Erase from cursor to end of field
|
||||
while (!screen.getCell(addr).isFieldAttribute()) {
|
||||
int count = 0;
|
||||
while (!screen.getCell(addr).isFieldAttribute() && count < size) {
|
||||
ExtendedAttribute ea = screen.getCell(addr);
|
||||
ea.ec = 0;
|
||||
ea.ucs4 = 0;
|
||||
addr = screen.incrementAddress(addr);
|
||||
count++;
|
||||
}
|
||||
|
||||
// Set MDT
|
||||
@@ -516,13 +523,20 @@ public class InputProcessor {
|
||||
public void deleteChar() {
|
||||
if (!screen.isFormatted()) return;
|
||||
int addr = screen.getCursorAddress();
|
||||
int size = screen.getRows() * screen.getCols();
|
||||
if (size <= 0) return;
|
||||
addr = ((addr % size) + size) % size;
|
||||
|
||||
// Cannot delete a field attribute
|
||||
if (screen.getCell(addr).isFieldAttribute()) return;
|
||||
|
||||
byte faVal = screen.getFieldAttributeAt(addr);
|
||||
if (faIsProtected(faVal & 0xFF)) return;
|
||||
|
||||
// Shift characters left within the field
|
||||
int shiftAddr = addr;
|
||||
int size = screen.getRows() * screen.getCols();
|
||||
while (true) {
|
||||
int count = 0;
|
||||
while (count < size) {
|
||||
int next = screen.incrementAddress(shiftAddr);
|
||||
if (screen.getCell(next).isFieldAttribute()) {
|
||||
screen.getCell(shiftAddr).ec = 0;
|
||||
@@ -532,6 +546,7 @@ public class InputProcessor {
|
||||
screen.getCell(shiftAddr).ec = screen.getCell(next).ec;
|
||||
screen.getCell(shiftAddr).ucs4 = screen.getCell(next).ucs4;
|
||||
shiftAddr = next;
|
||||
count++;
|
||||
}
|
||||
|
||||
int faAddr = screen.findFieldAttribute(addr);
|
||||
@@ -543,8 +558,19 @@ public class InputProcessor {
|
||||
}
|
||||
|
||||
public void backspace() {
|
||||
if (screen.getCursorAddress() == 0) return;
|
||||
cursorLeft();
|
||||
int addr = screen.getCursorAddress();
|
||||
int prev = screen.decrementAddress(addr);
|
||||
if (screen.isFormatted()) {
|
||||
// If previous position is a field attribute or in protected field, do not back up over it
|
||||
if (screen.getCell(prev).isFieldAttribute()) {
|
||||
return;
|
||||
}
|
||||
byte faVal = screen.getFieldAttributeAt(prev);
|
||||
if (faIsProtected(faVal & 0xFF)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
screen.setCursorAddress(prev);
|
||||
deleteChar();
|
||||
}
|
||||
|
||||
@@ -715,11 +741,13 @@ public class InputProcessor {
|
||||
int fieldLen = 0;
|
||||
int pos = fieldStart;
|
||||
int size = screen.getRows() * screen.getCols();
|
||||
while (!screen.getCell(pos).isFieldAttribute()) {
|
||||
int count = 0;
|
||||
while (!screen.getCell(pos).isFieldAttribute() && count < size) {
|
||||
screen.getCell(pos).ec = 0;
|
||||
screen.getCell(pos).ucs4 = 0;
|
||||
fieldLen++;
|
||||
pos = screen.incrementAddress(pos);
|
||||
count++;
|
||||
if (pos == fieldStart) break;
|
||||
}
|
||||
|
||||
|
||||
@@ -272,13 +272,17 @@ public class ScreenBuffer {
|
||||
public int findFieldAttribute(int baddr) {
|
||||
if (!formatted) return -1;
|
||||
int size = rows * cols;
|
||||
if (size <= 0) return -1;
|
||||
baddr = ((baddr % size) + size) % size;
|
||||
int start = baddr;
|
||||
int count = 0;
|
||||
do {
|
||||
if (buffer[baddr].isFieldAttribute()) {
|
||||
return baddr;
|
||||
}
|
||||
baddr = (baddr > 0) ? baddr - 1 : size - 1;
|
||||
} while (baddr != start);
|
||||
count++;
|
||||
} while (baddr != start && count < size);
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -296,8 +300,12 @@ public class ScreenBuffer {
|
||||
* Returns 0 if none found.
|
||||
*/
|
||||
public int findNextUnprotected(int baddr) {
|
||||
if (!formatted) return 0;
|
||||
int size = rows * cols;
|
||||
if (size <= 0) return 0;
|
||||
baddr = ((baddr % size) + size) % size;
|
||||
int start = baddr;
|
||||
int count = 0;
|
||||
do {
|
||||
int next = (baddr + 1) % size;
|
||||
if (buffer[baddr].isFieldAttribute()
|
||||
@@ -306,7 +314,8 @@ public class ScreenBuffer {
|
||||
return next;
|
||||
}
|
||||
baddr = next;
|
||||
} while (baddr != start);
|
||||
count++;
|
||||
} while (baddr != start && count < size);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -329,12 +338,17 @@ public class ScreenBuffer {
|
||||
|
||||
/** Increment buffer address (wrapping). */
|
||||
public int incrementAddress(int addr) {
|
||||
return (addr + 1) % (rows * cols);
|
||||
int size = rows * cols;
|
||||
if (size <= 0) return 0;
|
||||
return ((addr % size) + size + 1) % size;
|
||||
}
|
||||
|
||||
/** Decrement buffer address (wrapping). */
|
||||
public int decrementAddress(int addr) {
|
||||
return (addr > 0) ? addr - 1 : (rows * cols) - 1;
|
||||
int size = rows * cols;
|
||||
if (size <= 0) return 0;
|
||||
int norm = ((addr % size) + size) % size;
|
||||
return (norm > 0) ? norm - 1 : size - 1;
|
||||
}
|
||||
|
||||
/** Convert buffer address to row. */
|
||||
|
||||
@@ -66,4 +66,20 @@ public class DataStreamProcessorTest {
|
||||
assertEquals('A', screen.getCell(i).ucs4, "Cell " + i + " should be 'A'");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadBufferPropagatesLastAid() {
|
||||
org.lib3270j.input.InputProcessor input = new org.lib3270j.input.InputProcessor(screen, translator, null);
|
||||
processor.setInputProcessor(input);
|
||||
input.setLastAid(AID_ENTER);
|
||||
|
||||
java.util.concurrent.atomic.AtomicReference<byte[]> sentData = new java.util.concurrent.atomic.AtomicReference<>();
|
||||
processor.setOutputCallback(sentData::set);
|
||||
|
||||
byte[] rbRecord = new byte[] { (byte) CMD_RB };
|
||||
processor.processRecord(rbRecord, 0, rbRecord.length, true);
|
||||
|
||||
assertNotNull(sentData.get());
|
||||
assertEquals((byte) AID_ENTER, sentData.get()[0], "ReadBuffer must transmit operator's stored AID");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,4 +153,50 @@ public class InputProcessorTest {
|
||||
assertEquals((byte) 0xFF, sf[34]);
|
||||
assertEquals((byte) AID_ENTER, sf[35]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSendAidSuppressesNullsInModifiedField() {
|
||||
java.util.concurrent.atomic.AtomicReference<byte[]> sent = new java.util.concurrent.atomic.AtomicReference<>();
|
||||
InputProcessor input = new InputProcessor(screen, translator, null) {
|
||||
@Override
|
||||
public void sendAid(int aidCode) {
|
||||
// Call super logic by hooking via fake FSM or overriding sendAid
|
||||
super.sendAid(aidCode);
|
||||
}
|
||||
};
|
||||
// Setup screen: field attribute at 0 (unprotected, modified)
|
||||
screen.erase(false);
|
||||
screen.setCellFA(0, (byte) (FA_PRINTABLE | FA_MODIFY));
|
||||
// Pos 1 = null (0x00)
|
||||
screen.getCell(1).ec = 0x00;
|
||||
// Pos 2 = 'Q' (0xD8)
|
||||
screen.getCell(2).ec = (byte) 0xD8;
|
||||
// Pos 3 = 'U' (0xE4)
|
||||
screen.getCell(3).ec = (byte) 0xE4;
|
||||
// Pos 4 = 'I' (0xC9)
|
||||
screen.getCell(4).ec = (byte) 0xC9;
|
||||
// Pos 5 = 'T' (0xE3)
|
||||
screen.getCell(5).ec = (byte) 0xE3;
|
||||
// Next field at 10 (protected)
|
||||
screen.setCellFA(10, (byte) (FA_PRINTABLE | FA_PROTECT));
|
||||
|
||||
// Use custom callback or inspection
|
||||
// Let's verify DataStreamProcessor ReadModified behavior with the same buffer
|
||||
DataStreamProcessor dsp = new DataStreamProcessor(screen, translator);
|
||||
dsp.setInputProcessor(input);
|
||||
dsp.setOutputCallback(sent::set);
|
||||
|
||||
byte[] rmRecord = new byte[] { (byte) CMD_RM };
|
||||
dsp.processRecord(rmRecord, 0, rmRecord.length, true);
|
||||
|
||||
byte[] result = sent.get();
|
||||
assertNotNull(result);
|
||||
// Result format: AID (1 byte), Cursor (2 bytes), SBA (1 byte), Addr (2 bytes), Data ('Q', 'U', 'I', 'T' - 4 bytes)
|
||||
assertEquals(10, result.length, "Result should be AID(1) + Cursor(2) + SBA(1) + Addr(2) + Data(4)");
|
||||
assertEquals((byte) ORDER_SBA, result[3]);
|
||||
assertEquals((byte) 0xD8, result[6]); // 'Q'
|
||||
assertEquals((byte) 0xE4, result[7]); // 'U'
|
||||
assertEquals((byte) 0xC9, result[8]); // 'I'
|
||||
assertEquals((byte) 0xE3, result[9]); // 'T'
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user