Cleaning and some optimizations

This commit is contained in:
2026-09-28 01:56:13 +00:00
parent 0201c7c277
commit 9918c823c2
19 changed files with 518 additions and 155 deletions
+42
View File
@@ -1,5 +1,11 @@
plugins {
id 'java-library'
id 'maven-publish'
}
java {
withSourcesJar()
withJavadocJar()
}
dependencies {
@@ -17,3 +23,39 @@ jar {
'Implementation-Version': archiveVersion
}
}
publishing {
publications {
mavenJava(MavenPublication) {
from components.java
groupId = 'haus.nightmare.j3270'
artifactId = 'lib3270j'
version = project.version
pom {
name = 'lib3270j'
description = 'Pure Java IBM 3270 terminal emulation and protocol library with Host On-Demand (HOD) parity'
url = 'https://github.com/rudi/j3270'
licenses {
license {
name = 'The Unlicense'
url = 'https://unlicense.org/'
distribution = 'repo'
}
}
developers {
developer {
id = 'rudi'
name = 'Rudi'
}
}
scm {
connection = 'scm:git:git://github.com/rudi/j3270.git'
developerConnection = 'scm:git:ssh://github.com/rudi/j3270.git'
url = 'https://github.com/rudi/j3270'
}
}
}
}
}
@@ -8,6 +8,12 @@ import java.util.Arrays;
* Reusable, resizable byte buffer that provides zero-copy access to its internal
* array and supports ByteBuffer slicing to minimize heap allocations during
* network stream processing.
* <p>
* <b>Threading Contract:</b> This class is NOT thread-safe and is designed for
* high-throughput single-threaded or thread-confined stream accumulation (such as
* Telnet network receiver loops matching IBM Host On-Demand {@code sArray} and
* {@code sb_buffer}). External synchronization must be used if instances are shared
* across threads.
*/
public class FastByteBuffer extends OutputStream {
@@ -31,13 +37,13 @@ public class FastByteBuffer extends OutputStream {
}
@Override
public synchronized void write(int b) {
public void write(int b) {
ensureCapacity(count + 1);
buf[count++] = (byte) b;
}
@Override
public synchronized void write(byte[] b, int off, int len) {
public void write(byte[] b, int off, int len) {
if (b == null || len <= 0) return;
ensureCapacity(count + len);
System.arraycopy(b, off, buf, count, len);
@@ -48,29 +54,29 @@ public class FastByteBuffer extends OutputStream {
* Direct reference to internal buffer array.
* Use {@link #size()} to determine active length.
*/
public synchronized byte[] buffer() {
public byte[] buffer() {
return buf;
}
public synchronized int size() {
public int size() {
return count;
}
public synchronized void reset() {
public void reset() {
count = 0;
}
/**
* Creates a read-only ByteBuffer view wrapping active bytes without copying.
*/
public synchronized ByteBuffer asByteBuffer() {
public ByteBuffer asByteBuffer() {
return ByteBuffer.wrap(buf, 0, count).asReadOnlyBuffer();
}
/**
* Creates a read-only ByteBuffer slice for a sub-range without copying.
*/
public synchronized ByteBuffer slice(int offset, int length) {
public ByteBuffer slice(int offset, int length) {
if (offset < 0 || length < 0 || offset + length > count) {
throw new IndexOutOfBoundsException("offset=" + offset + " length=" + length + " size=" + count);
}
@@ -80,7 +86,7 @@ public class FastByteBuffer extends OutputStream {
/**
* Produces a copied byte array if an isolated copy is explicitly required.
*/
public synchronized byte[] toByteArray() {
public byte[] toByteArray() {
return Arrays.copyOf(buf, count);
}
}
@@ -110,6 +110,14 @@ public class ExtendedAttribute {
*/
public char ucs4;
public ExtendedAttribute() {}
public ExtendedAttribute(ExtendedAttribute other) {
if (other != null) {
copyFrom(other);
}
}
/** Clear all attributes. */
public void clear() {
ec = 0;
@@ -197,48 +197,99 @@ public class ScreenBuffer {
return buffer[addr];
}
/**
* Immutable snapshot container for tear-free, lock-free screen display.
* Published atomically to UI thread / EDT via volatile reference release.
*/
public static final class DisplaySnapshot {
public final ExtendedAttribute[] cells;
public final int rows;
public final int cols;
public final int cursorAddress;
public final long version;
public DisplaySnapshot(ExtendedAttribute[] cells, int rows, int cols, int cursorAddress) {
this(cells, rows, cols, cursorAddress, 0L);
}
public DisplaySnapshot(ExtendedAttribute[] cells, int rows, int cols, int cursorAddress, long version) {
this.cells = cells;
this.rows = rows;
this.cols = cols;
this.cursorAddress = cursorAddress;
this.version = version;
}
public ExtendedAttribute getCell(int addr) {
if (cells == null || addr < 0 || addr >= cells.length) {
return null;
}
return cells[addr];
}
public int getRows() { return rows; }
public int getCols() { return cols; }
public int getCursorAddress() { return cursorAddress; }
public long getVersion() { return version; }
}
private volatile DisplaySnapshot currentSnapshot;
private ExtendedAttribute[] displaySnapshot;
private int displayRows;
private int displayCols;
private int displayCursorAddress;
private long snapshotVersion = 0;
/**
* Atomically creates a snapshot of the current presentation buffer for tear-free rendering.
* Takes ~2 microseconds and eliminates mutual thread contention with the UI thread.
* Atomically creates an immutable snapshot of the current presentation buffer for tear-free rendering.
* Published via a volatile release fence, eliminating mutual monitor contention with the UI paint loop.
*/
public synchronized void updateDisplaySnapshot() {
int size = rows * cols;
if (displaySnapshot == null || displaySnapshot.length < size) {
displaySnapshot = new ExtendedAttribute[size];
for (int i = 0; i < size; i++) {
displaySnapshot[i] = new ExtendedAttribute();
}
}
ExtendedAttribute[] snapshotCells = new ExtendedAttribute[size];
for (int i = 0; i < size; i++) {
displaySnapshot[i].copyFrom(buffer[i]);
ExtendedAttribute ea = new ExtendedAttribute();
ea.copyFrom(buffer[i]);
snapshotCells[i] = ea;
}
this.displayRows = rows;
this.displayCols = cols;
this.displayCursorAddress = cursorAddress;
this.snapshotVersion++;
this.displaySnapshot = snapshotCells;
this.currentSnapshot = new DisplaySnapshot(snapshotCells, rows, cols, cursorAddress, snapshotVersion);
}
public synchronized ExtendedAttribute getDisplayCell(int addr) {
if (displaySnapshot == null || addr < 0 || addr >= displayRows * displayCols) {
public DisplaySnapshot getDisplaySnapshot() {
return currentSnapshot;
}
public long getSnapshotVersion() {
DisplaySnapshot snap = this.currentSnapshot;
return snap != null ? snap.version : snapshotVersion;
}
public ExtendedAttribute getDisplayCell(int addr) {
DisplaySnapshot snap = this.currentSnapshot;
if (snap == null || addr < 0 || addr >= snap.cells.length) {
return getCell(addr);
}
return displaySnapshot[addr];
return snap.cells[addr];
}
public synchronized int getDisplayRows() {
return displayRows > 0 ? displayRows : rows;
public int getDisplayRows() {
DisplaySnapshot snap = this.currentSnapshot;
return (snap != null && snap.rows > 0) ? snap.rows : (displayRows > 0 ? displayRows : rows);
}
public synchronized int getDisplayCols() {
return displayCols > 0 ? displayCols : cols;
public int getDisplayCols() {
DisplaySnapshot snap = this.currentSnapshot;
return (snap != null && snap.cols > 0) ? snap.cols : (displayCols > 0 ? displayCols : cols);
}
public synchronized int getDisplayCursorAddress() {
return displayRows > 0 ? displayCursorAddress : cursorAddress;
public int getDisplayCursorAddress() {
DisplaySnapshot snap = this.currentSnapshot;
return (snap != null && snap.rows > 0) ? snap.cursorAddress : (displayRows > 0 ? displayCursorAddress : cursorAddress);
}
// ========== Dimension accessors ==========
@@ -346,6 +397,10 @@ public class ScreenBuffer {
oldAddr = this.cursorAddress;
this.cursorAddress = addr;
this.displayCursorAddress = addr;
DisplaySnapshot snap = this.currentSnapshot;
if (snap != null) {
this.currentSnapshot = new DisplaySnapshot(snap.cells, snap.rows, snap.cols, addr, snap.version);
}
}
if (oldAddr != addr) {
notifyCursorMoved(oldAddr, addr);
@@ -49,6 +49,9 @@ public class TelnetFSM {
// 3270 input buffer (accumulated between telnet framing)
private final haus.nightmare.lib3270j.datastream.FastByteBuffer ibuf = new haus.nightmare.lib3270j.datastream.FastByteBuffer(32768);
// Reusable single-byte scratch buffer for plain NVT data dispatch (Phase 27.5 / T1-C)
private final byte[] nvtSingleByteScratch = new byte[1];
// Sub-negotiation buffer
private final ByteArrayOutputStream sbbuf = new ByteArrayOutputStream(4096);
@@ -324,7 +327,8 @@ public class TelnetFSM {
boolean isPlainNvt = (connectionState == ConnectionState.CONNECTED_NVT || connectionState == ConnectionState.CONNECTED_NVT_CHAR)
&& !(hisOpts[TELOPT_BINARY] && hisOpts[TELOPT_EOR]);
if (isPlainNvt) {
nvtProcessor.processNVTData(new byte[] { (byte) c }, 0, 1);
nvtSingleByteScratch[0] = (byte) c;
nvtProcessor.processNVTData(nvtSingleByteScratch, 0, 1);
return;
}