Bugfixes from zOS testing
This commit is contained in:
@@ -456,11 +456,26 @@ public class ECLXfer implements FTCut.FTCutListener, FTDft.FTDftListener {
|
||||
this.bytesTransferred = 0;
|
||||
this.totalBytes = (config.isSend() && localFile.exists()) ? localFile.length() : 0;
|
||||
|
||||
// Auto-detect DBCS from translator codepage if not explicitly set
|
||||
if (!config.isDbcs() && translator != null && translator.getCodePage() != null && translator.getCodePage().isDBCS()) {
|
||||
config.setDbcs(true);
|
||||
}
|
||||
|
||||
// Wait for keyboard unlock before typing command
|
||||
waitForKeyboardUnlock(2000);
|
||||
|
||||
// If clear is requested, send Clear AID first to wipe intermediate screen status
|
||||
if (config.isClear() && input != null && !input.isNvtMode()) {
|
||||
input.sendAid(haus.nightmare.lib3270j.protocol.DS3270Constants.AID_CLEAR);
|
||||
try { Thread.sleep(150); } catch (InterruptedException ignored) {}
|
||||
waitForKeyboardUnlock(2000);
|
||||
}
|
||||
|
||||
String command = config.buildCommand();
|
||||
log.info("ECLXfer: initiating transfer: " + command);
|
||||
|
||||
int capacity = input.kybdPrime();
|
||||
if (capacity < command.length()) {
|
||||
int capacity = input != null ? input.kybdPrime() : 0;
|
||||
if (input != null && capacity < command.length()) {
|
||||
String err = "Input field capacity insufficient for command (" + capacity + " chars)";
|
||||
fireEvent(ECLXferEvent.XFER_ABORTED, FTConstants.ECL_ERR_XFER_ABORT, err);
|
||||
return FTConstants.ECL_ERR_XFER_ABORT;
|
||||
@@ -469,10 +484,25 @@ public class ECLXfer implements FTCut.FTCutListener, FTDft.FTDftListener {
|
||||
setState(FTState.AWAIT_ACK);
|
||||
fireEvent(ECLXferEvent.XFER_STARTED, 0, "Transfer initiated");
|
||||
|
||||
input.emulateInput(command + "\n");
|
||||
if (input != null) {
|
||||
input.emulateInput(command + "\n");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private void waitForKeyboardUnlock(long timeoutMs) {
|
||||
if (input == null) return;
|
||||
long start = System.currentTimeMillis();
|
||||
while (input.isKeyboardLocked() && (System.currentTimeMillis() - start) < timeoutMs) {
|
||||
try {
|
||||
Thread.sleep(20);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void parseOptionsIntoConfig(FTConfig config, String options) {
|
||||
if (options == null || options.trim().isEmpty()) return;
|
||||
config.parseOptions(options);
|
||||
|
||||
@@ -74,6 +74,11 @@ public class FTConfig {
|
||||
private String codePage = null;
|
||||
private boolean unicode = false;
|
||||
private int unicodeType = 0; // 0 = UCS2, 1 = UTF8
|
||||
private String programName = null;
|
||||
private boolean dbcs = false;
|
||||
private boolean optionNew = false;
|
||||
private boolean clear = true;
|
||||
private boolean optionNoso = false;
|
||||
|
||||
// ========== Derived convenience getters ==========
|
||||
|
||||
@@ -91,6 +96,37 @@ public class FTConfig {
|
||||
}
|
||||
public boolean isAppend() { return existAction == ExistAction.APPEND; }
|
||||
public boolean isOverwrite() { return existAction == ExistAction.REPLACE; }
|
||||
public boolean isOptionNew() { return optionNew; }
|
||||
public void setOptionNew(boolean optionNew) { this.optionNew = optionNew; }
|
||||
public boolean isOptionNoso() { return optionNoso; }
|
||||
public void setOptionNoso(boolean optionNoso) { this.optionNoso = optionNoso; }
|
||||
public boolean isDbcs() { return dbcs; }
|
||||
public void setDbcs(boolean dbcs) { this.dbcs = dbcs; }
|
||||
public boolean isClear() { return clear; }
|
||||
public void setClear(boolean clear) { this.clear = clear; }
|
||||
|
||||
public String getProgramName() {
|
||||
if (programName != null && !programName.trim().isEmpty()) {
|
||||
return programName.trim();
|
||||
}
|
||||
if (dbcs || isDbcsCodePage(codePage)) {
|
||||
return "APVUFILE";
|
||||
}
|
||||
return "IND$FILE";
|
||||
}
|
||||
|
||||
public void setProgramName(String programName) {
|
||||
this.programName = programName;
|
||||
}
|
||||
|
||||
private static boolean isDbcsCodePage(String cp) {
|
||||
if (cp == null || cp.trim().isEmpty()) return false;
|
||||
String clean = cp.trim().toUpperCase();
|
||||
if (clean.startsWith("CP")) clean = clean.substring(2);
|
||||
return clean.equals("930") || clean.equals("939") || clean.equals("935") ||
|
||||
clean.equals("1388") || clean.equals("933") || clean.equals("937") ||
|
||||
clean.equals("1371");
|
||||
}
|
||||
|
||||
// ========== Standard getters and setters ==========
|
||||
|
||||
@@ -303,10 +339,22 @@ public class FTConfig {
|
||||
|
||||
if (upper.contains("APPEND")) {
|
||||
this.existAction = ExistAction.APPEND;
|
||||
} else if (upper.contains("NEW") && !upper.contains("NONEW")) {
|
||||
this.optionNew = true;
|
||||
} else if (upper.contains("REPLACE") || upper.contains("OVERWRITE")) {
|
||||
this.existAction = ExistAction.REPLACE;
|
||||
}
|
||||
|
||||
if (upper.contains("NOSO")) {
|
||||
this.optionNoso = true;
|
||||
}
|
||||
|
||||
if (upper.contains("APVUFILE")) {
|
||||
this.programName = "APVUFILE";
|
||||
} else if (upper.contains("IND$FILE")) {
|
||||
this.programName = "IND$FILE";
|
||||
}
|
||||
|
||||
if (upper.contains("CICS")) {
|
||||
this.hostType = HostType.CICS;
|
||||
} else if (upper.contains("CMS") || upper.contains("VM")) {
|
||||
@@ -332,6 +380,10 @@ public class FTConfig {
|
||||
|
||||
String hostTrimmed = hostFilename.trim();
|
||||
|
||||
if (hostType == HostType.TSO && hostTrimmed.contains(" ") && validateCmsFilename(hostTrimmed) == null) {
|
||||
hostType = HostType.CMS;
|
||||
}
|
||||
|
||||
if (hostType == HostType.TSO) {
|
||||
String tsoError = validateTsoDatasetName(hostTrimmed);
|
||||
if (tsoError != null) return tsoError;
|
||||
@@ -448,8 +500,8 @@ public class FTConfig {
|
||||
public String buildCommand() {
|
||||
StringBuilder cmd = new StringBuilder();
|
||||
|
||||
// IND$FILE GET/PUT hostfile
|
||||
cmd.append("IND$FILE ");
|
||||
// Host program selection: IND$FILE or APVUFILE
|
||||
cmd.append(getProgramName()).append(" ");
|
||||
cmd.append(isReceive() ? "GET " : "PUT ");
|
||||
cmd.append(hostFilename != null ? hostFilename.trim() : "");
|
||||
|
||||
@@ -477,16 +529,23 @@ public class FTConfig {
|
||||
opts.append("NOCRLF");
|
||||
}
|
||||
|
||||
// Append (send only)
|
||||
if (isAppend() && isSend()) {
|
||||
if (isOptionNoso()) {
|
||||
if (opts.length() > 0) opts.append(" ");
|
||||
opts.append("APPEND");
|
||||
opts.append("NOSO");
|
||||
}
|
||||
|
||||
// Overwrite / Replace (if send or explicitly requested)
|
||||
if (isOverwrite() && isSend()) {
|
||||
if (opts.length() > 0) opts.append(" ");
|
||||
opts.append("REPLACE");
|
||||
// Append / New / Replace (send only)
|
||||
if (isSend()) {
|
||||
if (isAppend()) {
|
||||
if (opts.length() > 0) opts.append(" ");
|
||||
opts.append("APPEND");
|
||||
} else if (isOptionNew()) {
|
||||
if (opts.length() > 0) opts.append(" ");
|
||||
opts.append("NEW");
|
||||
} else if (isOverwrite()) {
|
||||
if (opts.length() > 0) opts.append(" ");
|
||||
opts.append("REPLACE");
|
||||
}
|
||||
}
|
||||
|
||||
// Host-specific send options
|
||||
|
||||
@@ -123,15 +123,69 @@ public final class FTConstants {
|
||||
// Error codes
|
||||
public static final int TR_ERR_EOF = 0x2200;
|
||||
public static final int TR_ERR_CMDFAIL = 0x0100;
|
||||
public static final int TR_ERR_OPEN_FAILED = 0x0100;
|
||||
public static final int TR_ERR_SEQ_NOT_ALLOWED = 0x0200;
|
||||
public static final int TR_ERR_NOT_AUTHORIZED = 0x3E00;
|
||||
public static final int TR_ERR_SYNTAX_ERROR = 0x6000;
|
||||
public static final int TR_ERR_GET_PAST_EOF = 0x2200;
|
||||
|
||||
// DFT buffer size limits
|
||||
// DFT buffer size limits (Default matching IBM HoD bmtuSize = 2500)
|
||||
public static final int DFT_MIN_BUF = 256;
|
||||
public static final int DFT_MAX_BUF = 32768;
|
||||
public static final int DFT_BUF = 4096;
|
||||
public static final int DFT_BUF = 2500;
|
||||
|
||||
// AID for structured field response
|
||||
public static final int AID_SF = 0x88;
|
||||
|
||||
// DDM Stream Names (ASCII and EBCDIC)
|
||||
public static final String STREAM_NAME_FT_DATA = "FT:DATA";
|
||||
public static final String STREAM_NAME_FT_MSG = "FT:MSG";
|
||||
public static final byte[] STREAM_NAME_FT_DATA_BYTES_ASCII = {
|
||||
0x46, 0x54, 0x3A, 0x44, 0x41, 0x54, 0x41 // 'F','T',':','D','A','T','A'
|
||||
};
|
||||
public static final byte[] STREAM_NAME_FT_DATA_BYTES_EBCDIC = {
|
||||
(byte) 0xC6, (byte) 0xE3, 0x7A, (byte) 0xC4, (byte) 0xC1, (byte) 0xE3, (byte) 0xC1
|
||||
};
|
||||
public static final byte[] STREAM_NAME_FT_MSG_BYTES_ASCII = {
|
||||
0x46, 0x54, 0x3A, 0x4D, 0x53, 0x47, 0x20 // 'F','T',':','M','S','G',' '
|
||||
};
|
||||
public static final byte[] STREAM_NAME_FT_MSG_BYTES_EBCDIC = {
|
||||
(byte) 0xC6, (byte) 0xE3, 0x7A, (byte) 0xD4, (byte) 0xE2, (byte) 0xC7, 0x40
|
||||
};
|
||||
|
||||
// HoD Binary Packet Templates (Xfer3270.java)
|
||||
public static final byte[] RB_OPEN_ACK = {
|
||||
(byte) 0x88, 0x00, 0x05, (byte) 0xD0, 0x00, 0x09, (byte) 0xFF, (byte) 0xEF
|
||||
};
|
||||
public static final byte[] RB_CLOSE_ACK = {
|
||||
(byte) 0x88, 0x00, 0x05, (byte) 0xD0, 0x41, 0x09, (byte) 0xFF, (byte) 0xEF
|
||||
};
|
||||
public static final byte[] RB_OPEN_FAILED = {
|
||||
0x00, 0x09, (byte) 0xD0, 0x00, 0x08, 0x69, 0x04, 0x01, 0x00, (byte) 0xFF, (byte) 0xEF
|
||||
};
|
||||
public static final byte[] RB_GET_PAST_EOF = {
|
||||
(byte) 0x88, 0x00, 0x09, (byte) 0xD0, 0x46, 0x08, 0x69, 0x04, 0x22, 0x00, (byte) 0xFF, (byte) 0xEF
|
||||
};
|
||||
public static final byte[] RB_UPLOAD_DATA_HEADER = {
|
||||
0x11, 0x11, (byte) 0xD0, 0x46, 0x05, 0x63, 0x06, 0x00, 0x00, 0x00, 0x00,
|
||||
(byte) 0xC0, (byte) 0x80, 0x61, 0x00, 0x00
|
||||
};
|
||||
|
||||
/**
|
||||
* Builds exact 14-byte Data Acknowledgement matching HoD Xfer3270:
|
||||
* [0x88, 0x00, 0x0B, 0xD0, 0x47, 0x05, 0x63, 0x06, rec_b0, rec_b1, rec_b2, rec_b3, 0xFF, 0xEF]
|
||||
*/
|
||||
public static byte[] createDataAck(long recnum) {
|
||||
return new byte[] {
|
||||
(byte) 0x88, 0x00, 0x0B, (byte) 0xD0, 0x47, 0x05, 0x63, 0x06,
|
||||
(byte) ((recnum >> 24) & 0xFF),
|
||||
(byte) ((recnum >> 16) & 0xFF),
|
||||
(byte) ((recnum >> 8) & 0xFF),
|
||||
(byte) (recnum & 0xFF),
|
||||
(byte) 0xFF, (byte) 0xEF
|
||||
};
|
||||
}
|
||||
|
||||
// ========== Transfer State ==========
|
||||
|
||||
public enum FTState {
|
||||
|
||||
@@ -215,11 +215,13 @@ public class FTCut {
|
||||
// CUT frames MUST have a skip field attribute at O_SF (1919)
|
||||
byte sfAttr = screen.getCellFAByte(O_SF);
|
||||
if (sfAttr == 0 || !isSkip(sfAttr)) {
|
||||
// Not a CUT frame (likely local echo, menu return, or intermediate screen)
|
||||
// Not a CUT frame (likely local echo, menu return, or host error screen)
|
||||
if (xferInProgress) {
|
||||
log.warning("CUT: Received non-CUT frame while transfer in progress. Host aborted.");
|
||||
xferInProgress = false;
|
||||
listener.onTransferAborted("Host aborted transfer (returned to menu)");
|
||||
String hostErr = scanScreenForErrors();
|
||||
String abortMsg = hostErr != null ? hostErr : "Host aborted transfer (returned to menu)";
|
||||
listener.onTransferAborted(abortMsg);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -245,6 +247,38 @@ public class FTCut {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scans rows 1-24 of the 3270 screen buffer for host error indicators.
|
||||
* Checks for TSO (IKJ*), CMS (DMS*), CICS (DFH*), and generic transfer abort messages.
|
||||
*/
|
||||
public String scanScreenForErrors() {
|
||||
if (screen == null) return null;
|
||||
int rows = screen.getRows();
|
||||
int cols = screen.getCols();
|
||||
String[] errorPrefixes = {
|
||||
"IKJ", "DMS", "DFH", "TRANS", "NOT FOUND", "ERROR", "REJECTED", "INVALID", "COMMAND NOT FOUND", "FILE NOT FOUND"
|
||||
};
|
||||
for (int r = 0; r < rows; r++) {
|
||||
StringBuilder lineSb = new StringBuilder();
|
||||
for (int c = 0; c < cols; c++) {
|
||||
int pos = r * cols + c;
|
||||
int ec = screen.getCellEC(pos);
|
||||
if (ec != 0) {
|
||||
lineSb.append(translator.ebcdicToUnicode(ec));
|
||||
}
|
||||
}
|
||||
String line = lineSb.toString().trim();
|
||||
if (line.isEmpty()) continue;
|
||||
String lineUpper = line.toUpperCase();
|
||||
for (String prefix : errorPrefixes) {
|
||||
if (lineUpper.contains(prefix)) {
|
||||
return line;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void checkForHostPrompts() {
|
||||
if (screen == null) return;
|
||||
int size = screen.getRows() * screen.getCols();
|
||||
@@ -299,6 +333,10 @@ public class FTCut {
|
||||
cutAck();
|
||||
|
||||
String msg = extractHostMessage();
|
||||
if ("Host cancelled transfer".equals(msg)) {
|
||||
String hostErr = scanScreenForErrors();
|
||||
if (hostErr != null) msg = hostErr;
|
||||
}
|
||||
listener.onTransferAborted(msg);
|
||||
break;
|
||||
|
||||
@@ -388,7 +426,7 @@ public class FTCut {
|
||||
log.fine("CUT: > DATA seq=" + seq + " len=" + count);
|
||||
expandedLength += count;
|
||||
listener.onBytesTransferred(expandedLength);
|
||||
input.sendAidForFT(AID_ENTER);
|
||||
paceAndSendAid(AID_ENTER);
|
||||
}
|
||||
|
||||
// ========== Data (Download) ==========
|
||||
@@ -467,11 +505,11 @@ public class FTCut {
|
||||
screen.setCellFA(O_DR_SF, attr);
|
||||
|
||||
log.fine("CUT: Retransmitting last upload frame (len=" + lastUploadCount + ")");
|
||||
input.sendAidForFT(AID_ENTER);
|
||||
paceAndSendAid(AID_ENTER);
|
||||
} else {
|
||||
// In download mode, send ACK_RETRANSMIT (PF1)
|
||||
log.fine("CUT: Requesting frame retransmit from host via PF1");
|
||||
input.sendAidForFT(ACK_RETRANSMIT);
|
||||
paceAndSendAid(ACK_RETRANSMIT);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -479,7 +517,7 @@ public class FTCut {
|
||||
|
||||
private void cutAck() {
|
||||
log.fine("CUT: > ACK (Enter)");
|
||||
input.sendAidForFT(AID_ENTER);
|
||||
paceAndSendAid(AID_ENTER);
|
||||
}
|
||||
|
||||
// ========== Abort ==========
|
||||
@@ -492,10 +530,24 @@ public class FTCut {
|
||||
screen.setCell(RO_REASON_CODE, (reason >> 8) & 0xFF);
|
||||
screen.setCell(RO_REASON_CODE + 1, reason & 0xFF);
|
||||
|
||||
input.sendAidForFT(AID_PF2);
|
||||
paceAndSendAid(AID_PF2);
|
||||
listener.onTransferAborted(message);
|
||||
}
|
||||
|
||||
private void paceAndSendAid(int aid) {
|
||||
if (input == null) return;
|
||||
long start = System.currentTimeMillis();
|
||||
while (input.isKeyboardLocked() && System.currentTimeMillis() - start < 1000) {
|
||||
try {
|
||||
Thread.sleep(10);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
break;
|
||||
}
|
||||
}
|
||||
input.sendAidForFT((byte) aid);
|
||||
}
|
||||
|
||||
// ========== Character Translation ==========
|
||||
|
||||
/**
|
||||
|
||||
@@ -49,6 +49,14 @@ public class FTDft {
|
||||
private int customMtuSize = 0;
|
||||
private int pendingByte = -1;
|
||||
|
||||
// Record formatting buffers and SO/SI tracking
|
||||
private final ByteArrayOutputStream downloadRecordBuffer = new ByteArrayOutputStream();
|
||||
private boolean dbcsMode = false;
|
||||
private boolean hasPendingByteInDBCS = false;
|
||||
private byte pendingByteDBCS = 0;
|
||||
private byte[] pendingUploadRecord = null;
|
||||
private int pendingUploadRecordOffset = 0;
|
||||
|
||||
// Savebuf for Read Modified retransmit
|
||||
private byte[] dftSaveBuf = null;
|
||||
private int dftSaveBufLen = 0;
|
||||
@@ -159,6 +167,12 @@ public class FTDft {
|
||||
dftSaveBufLen = 0;
|
||||
lastCr = false;
|
||||
pendingByte = -1;
|
||||
downloadRecordBuffer.reset();
|
||||
dbcsMode = false;
|
||||
hasPendingByteInDBCS = false;
|
||||
pendingByteDBCS = 0;
|
||||
pendingUploadRecord = null;
|
||||
pendingUploadRecordOffset = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -201,6 +215,12 @@ public class FTDft {
|
||||
|
||||
log.fine("DFT: request code 0x" + Integer.toHexString(requestCode));
|
||||
|
||||
// Check if this is a host error reply (e.g. TR_ERROR_REPLY 0x08, or contains TR_ERROR_HDR 0x6904)
|
||||
if ((requestCode & 0xFF) == TR_ERROR_REPLY || isErrorField(data, payloadStart, offset + length - payloadStart)) {
|
||||
handleHostErrorReply(data, payloadStart, offset + length - payloadStart);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (requestCode) {
|
||||
case TR_OPEN_REQ:
|
||||
dftOpenRequest(data, offset, length);
|
||||
@@ -228,8 +248,74 @@ public class FTDft {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isErrorField(byte[] data, int start, int length) {
|
||||
int end = Math.min(start + length, data.length);
|
||||
for (int i = start; i + 3 < end; i++) {
|
||||
int hdr = ((data[i] & 0xFF) << 8) | (data[i + 1] & 0xFF);
|
||||
if (hdr == TR_ERROR_HDR) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void handleHostErrorReply(byte[] data, int payloadStart, int length) {
|
||||
int end = Math.min(payloadStart + length, data.length);
|
||||
int errCode = 0;
|
||||
for (int i = payloadStart; i + 3 < end; i++) {
|
||||
int hdr = ((data[i] & 0xFF) << 8) | (data[i + 1] & 0xFF);
|
||||
if (hdr == TR_ERROR_HDR) {
|
||||
errCode = ((data[i + 2] & 0xFF) << 8) | (data[i + 3] & 0xFF);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
String message;
|
||||
switch (errCode) {
|
||||
case TR_ERR_OPEN_FAILED:
|
||||
message = "Host file open failed (file not found, cannot open, or dataset does not exist)";
|
||||
break;
|
||||
case TR_ERR_SEQ_NOT_ALLOWED:
|
||||
message = "Host transfer sequence not allowed (state violation)";
|
||||
break;
|
||||
case TR_ERR_GET_PAST_EOF:
|
||||
if (listener != null && listener.getCurrentState() == FTState.RUNNING) {
|
||||
log.info("DFT: Host reported end of file (0x2200)");
|
||||
listener.onTransferComplete(null);
|
||||
return;
|
||||
}
|
||||
message = "Host reached end of file";
|
||||
break;
|
||||
case TR_ERR_NOT_AUTHORIZED:
|
||||
message = "Host file access not authorized (security violation)";
|
||||
break;
|
||||
case TR_ERR_SYNTAX_ERROR:
|
||||
message = "Host command syntax error or invalid transfer options";
|
||||
break;
|
||||
default:
|
||||
message = "Host transfer error (code 0x" + Integer.toHexString(errCode) + ")";
|
||||
break;
|
||||
}
|
||||
log.warning("DFT host error reply: " + message + " (errCode=0x" + Integer.toHexString(errCode) + ")");
|
||||
dftAbort(message, TR_DATA_INSERT);
|
||||
}
|
||||
|
||||
// ========== Open Request ==========
|
||||
|
||||
private static boolean containsBytes(byte[] data, int start, int end, byte[] pattern) {
|
||||
if (data == null || pattern == null || pattern.length == 0) return false;
|
||||
int max = Math.min(end, data.length) - pattern.length;
|
||||
for (int i = Math.max(0, start); i <= max; i++) {
|
||||
boolean match = true;
|
||||
for (int j = 0; j < pattern.length; j++) {
|
||||
if (data[i + j] != pattern[j]) {
|
||||
match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (match) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void dftOpenRequest(byte[] data, int sfOffset, int sfLength) {
|
||||
log.fine("DFT: Open request");
|
||||
|
||||
@@ -247,7 +333,10 @@ public class FTDft {
|
||||
// Check for host DDM file attributes embedded in open request
|
||||
parseDdmAttributes(data, sfOffset + 3, sfLength - 3);
|
||||
|
||||
if (isMessageStream(nameBuf)) {
|
||||
int sfEnd = Math.min(sfOffset + sfLength, data.length);
|
||||
if (containsBytes(data, sfOffset, sfEnd, STREAM_NAME_FT_MSG_BYTES_ASCII) ||
|
||||
containsBytes(data, sfOffset, sfEnd, STREAM_NAME_FT_MSG_BYTES_EBCDIC) ||
|
||||
isMessageStream(nameBuf)) {
|
||||
messageFlag = true;
|
||||
log.info("DFT: Open request for message stream (" + nameBuf + ")");
|
||||
} else {
|
||||
@@ -258,7 +347,7 @@ public class FTDft {
|
||||
dftEof = false;
|
||||
recnum = 1;
|
||||
|
||||
// Acknowledge Open matching x3270 (SF_TRANSFER_DATA + 0x0009)
|
||||
// Acknowledge Open matching IBM Host On-Demand
|
||||
dftOpenAck();
|
||||
}
|
||||
|
||||
@@ -322,12 +411,7 @@ public class FTDft {
|
||||
}
|
||||
|
||||
private void dftOpenAck() {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream(6);
|
||||
out.write(AID_SF);
|
||||
out.write(0); out.write(5); // SF length
|
||||
out.write(SF_TRANSFER_DATA);
|
||||
out.write(0); out.write(9); // OpenAck response code 0x0009
|
||||
input.sendStructuredFieldData(out.toByteArray());
|
||||
input.sendStructuredFieldData(RB_OPEN_ACK.clone());
|
||||
}
|
||||
|
||||
// ========== Insert Request (host sending data for download) ==========
|
||||
@@ -441,31 +525,125 @@ public class FTDft {
|
||||
}
|
||||
|
||||
/**
|
||||
* Write download data to the local file, handling ASCII conversion.
|
||||
* Matching x3270 upload_convert logic.
|
||||
* Write download data to the local file, handling ASCII conversion, RECFM F/V formatting, and EOF stripping.
|
||||
* Conforms to IBM Host On-Demand specification.
|
||||
*/
|
||||
private void writeDownloadData(byte[] data, int offset, int length,
|
||||
FTConfig config) throws IOException {
|
||||
if (outputStream == null) return;
|
||||
if (outputStream == null || length <= 0) return;
|
||||
|
||||
if (config != null && config.isUnicode()) {
|
||||
outputStream.write(data, offset, length);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!config.isAscii()) {
|
||||
if (config != null && !config.isAscii()) {
|
||||
outputStream.write(data, offset, length);
|
||||
return;
|
||||
}
|
||||
|
||||
boolean isRecfmV = (config != null && config.getRecfm() == FTConfig.RecordFormat.VARIABLE) ||
|
||||
"V".equalsIgnoreCase(hostRecfm);
|
||||
boolean isRecfmF = (config != null && config.getRecfm() == FTConfig.RecordFormat.FIXED) ||
|
||||
"F".equalsIgnoreCase(hostRecfm);
|
||||
int lrecl = (config != null && config.getLrecl() > 0) ? config.getLrecl() : hostLrecl;
|
||||
|
||||
if (isRecfmV) {
|
||||
downloadRecordBuffer.write(data, offset, length);
|
||||
processVariableDownloadRecords(config);
|
||||
} else if (isRecfmF && lrecl > 0) {
|
||||
downloadRecordBuffer.write(data, offset, length);
|
||||
processFixedDownloadRecords(lrecl, config);
|
||||
} else {
|
||||
writeConvertedChunk(data, offset, length, config);
|
||||
}
|
||||
}
|
||||
|
||||
private void processVariableDownloadRecords(FTConfig config) throws IOException {
|
||||
while (downloadRecordBuffer.size() >= 4) {
|
||||
byte[] buf = downloadRecordBuffer.toByteArray();
|
||||
int rdwLen = ((buf[0] & 0xFF) << 8) | (buf[1] & 0xFF);
|
||||
if (rdwLen < 4) {
|
||||
downloadRecordBuffer.reset();
|
||||
if (buf.length > 1) {
|
||||
downloadRecordBuffer.write(buf, 1, buf.length - 1);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (buf.length < rdwLen) {
|
||||
break;
|
||||
}
|
||||
int recDataLen = rdwLen - 4;
|
||||
writeConvertedChunk(buf, 4, recDataLen, config);
|
||||
outputStream.write(System.lineSeparator().getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
downloadRecordBuffer.reset();
|
||||
if (buf.length > rdwLen) {
|
||||
downloadRecordBuffer.write(buf, rdwLen, buf.length - rdwLen);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void processFixedDownloadRecords(int lrecl, FTConfig config) throws IOException {
|
||||
while (downloadRecordBuffer.size() >= lrecl) {
|
||||
byte[] buf = downloadRecordBuffer.toByteArray();
|
||||
int end = lrecl;
|
||||
while (end > 0) {
|
||||
int b = buf[end - 1] & 0xFF;
|
||||
if (b == 0x20 || b == 0x40 || (config != null && config.isRemapFlag() && b == FT2ASC[0x40])) {
|
||||
end--;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
writeConvertedChunk(buf, 0, end, config);
|
||||
outputStream.write(System.lineSeparator().getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
downloadRecordBuffer.reset();
|
||||
if (buf.length > lrecl) {
|
||||
downloadRecordBuffer.write(buf, lrecl, buf.length - lrecl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void flushRemainingDownloadRecords() throws IOException {
|
||||
if (outputStream == null || downloadRecordBuffer.size() == 0) return;
|
||||
FTConfig config = listener != null ? listener.getConfig() : null;
|
||||
byte[] remaining = downloadRecordBuffer.toByteArray();
|
||||
downloadRecordBuffer.reset();
|
||||
if (config != null && config.isAscii()) {
|
||||
int end = remaining.length;
|
||||
while (end > 0) {
|
||||
int b = remaining[end - 1] & 0xFF;
|
||||
if (b == 0x20 || b == 0x40 || (config.isRemapFlag() && b == FT2ASC[0x40])) {
|
||||
end--;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (end > 0) {
|
||||
writeConvertedChunk(remaining, 0, end, config);
|
||||
}
|
||||
} else {
|
||||
outputStream.write(remaining);
|
||||
}
|
||||
outputStream.flush();
|
||||
}
|
||||
|
||||
private void writeConvertedChunk(byte[] data, int offset, int length, FTConfig config) throws IOException {
|
||||
if (outputStream == null || length <= 0) return;
|
||||
for (int i = 0; i < length; i++) {
|
||||
int b = data[offset + i] & 0xFF;
|
||||
|
||||
if (config.isCrFlag() && (b == '\r' || b == EOF_CTRL_Z || b == EOF_CTRL_D)) {
|
||||
continue; // Strip CR and EOF (^Z / ^D)
|
||||
if (b == EOF_CTRL_Z || b == EOF_CTRL_D) {
|
||||
continue; // Strip EOF (^Z / ^D)
|
||||
}
|
||||
|
||||
if (!config.isRemapFlag()) {
|
||||
if (config != null && config.isCrFlag() && b == '\r') {
|
||||
continue; // Strip CR
|
||||
}
|
||||
|
||||
if (config == null || !config.isRemapFlag()) {
|
||||
outputStream.write(b);
|
||||
continue;
|
||||
}
|
||||
@@ -506,30 +684,42 @@ public class FTDft {
|
||||
byte[] readBuf = new byte[numbytes];
|
||||
int totalRead = 0;
|
||||
|
||||
boolean isRecfmF = (config != null && config.getRecfm() == FTConfig.RecordFormat.FIXED) ||
|
||||
"F".equalsIgnoreCase(hostRecfm);
|
||||
boolean isRecfmV = (config != null && config.getRecfm() == FTConfig.RecordFormat.VARIABLE) ||
|
||||
"V".equalsIgnoreCase(hostRecfm);
|
||||
int lrecl = (config != null && config.getLrecl() > 0) ? config.getLrecl() : hostLrecl;
|
||||
|
||||
try {
|
||||
while (!dftEof && totalRead < numbytes) {
|
||||
if (config != null && config.isUnicode() && inputStream instanceof haus.nightmare.lib3270j.xfer.XferFileInputUnicode) {
|
||||
int n = ((haus.nightmare.lib3270j.xfer.XferFileInputUnicode) inputStream).readData(readBuf, totalRead, numbytes - totalRead);
|
||||
if (n <= 0) {
|
||||
dftEof = true;
|
||||
break;
|
||||
if (config != null && config.isAscii() && isRecfmF && lrecl > 0) {
|
||||
totalRead = readFixedRecordUpload(readBuf, numbytes, lrecl, config);
|
||||
} else if (config != null && config.isAscii() && isRecfmV) {
|
||||
totalRead = readVariableRecordUpload(readBuf, numbytes, config);
|
||||
} else {
|
||||
while (!dftEof && totalRead < numbytes) {
|
||||
if (config != null && config.isUnicode() && inputStream instanceof haus.nightmare.lib3270j.xfer.XferFileInputUnicode) {
|
||||
int n = ((haus.nightmare.lib3270j.xfer.XferFileInputUnicode) inputStream).readData(readBuf, totalRead, numbytes - totalRead);
|
||||
if (n <= 0) {
|
||||
dftEof = true;
|
||||
break;
|
||||
}
|
||||
totalRead += n;
|
||||
} else if (config != null && config.isAscii()) {
|
||||
int b = dftAsciiRead(config);
|
||||
if (b == -1) {
|
||||
dftEof = true;
|
||||
break;
|
||||
}
|
||||
readBuf[totalRead++] = (byte) b;
|
||||
} else {
|
||||
if (inputStream == null) { dftEof = true; break; }
|
||||
int n = inputStream.read(readBuf, totalRead, numbytes - totalRead);
|
||||
if (n <= 0) {
|
||||
dftEof = true;
|
||||
break;
|
||||
}
|
||||
totalRead += n;
|
||||
}
|
||||
totalRead += n;
|
||||
} else if (config.isAscii()) {
|
||||
int b = dftAsciiRead(config);
|
||||
if (b == -1) {
|
||||
dftEof = true;
|
||||
break;
|
||||
}
|
||||
readBuf[totalRead++] = (byte) b;
|
||||
} else {
|
||||
if (inputStream == null) { dftEof = true; break; }
|
||||
int n = inputStream.read(readBuf, totalRead, numbytes - totalRead);
|
||||
if (n <= 0) {
|
||||
dftEof = true;
|
||||
break;
|
||||
}
|
||||
totalRead += n;
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
@@ -537,17 +727,17 @@ public class FTDft {
|
||||
return;
|
||||
}
|
||||
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream(bufferSize);
|
||||
out.write(AID_SF);
|
||||
|
||||
int sfLenPos = out.size();
|
||||
out.write(0); out.write(0);
|
||||
|
||||
out.write(SF_TRANSFER_DATA);
|
||||
|
||||
if (totalRead > 0) {
|
||||
log.fine("DFT: > GetReply rec=" + recnum + " " + totalRead + " bytes");
|
||||
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream(bufferSize);
|
||||
out.write(AID_SF);
|
||||
|
||||
int sfLenPos = out.size();
|
||||
out.write(0); out.write(0);
|
||||
|
||||
out.write(SF_TRANSFER_DATA);
|
||||
|
||||
out.write((TR_GET_REPLY >> 8) & 0xFF);
|
||||
out.write(TR_GET_REPLY & 0xFF);
|
||||
|
||||
@@ -570,31 +760,167 @@ public class FTDft {
|
||||
out.write(readBuf, 0, totalRead);
|
||||
|
||||
bytesTransferred += totalRead;
|
||||
|
||||
byte[] result = out.toByteArray();
|
||||
int sfLen = result.length - 1;
|
||||
result[sfLenPos] = (byte) ((sfLen >> 8) & 0xFF);
|
||||
result[sfLenPos + 1] = (byte) (sfLen & 0xFF);
|
||||
|
||||
dftSaveBuf = result.clone();
|
||||
dftSaveBufLen = result.length;
|
||||
|
||||
input.sendStructuredFieldData(result);
|
||||
listener.onBytesTransferred(bytesTransferred);
|
||||
} else {
|
||||
log.fine("DFT: > GetReply EOF");
|
||||
|
||||
out.write((TR_GET_REQ >> 8) & 0xFF);
|
||||
out.write(TR_ERROR_REPLY & 0xFF);
|
||||
|
||||
out.write((TR_ERROR_HDR >> 8) & 0xFF);
|
||||
out.write(TR_ERROR_HDR & 0xFF);
|
||||
|
||||
out.write((TR_ERR_EOF >> 8) & 0xFF);
|
||||
out.write(TR_ERR_EOF & 0xFF);
|
||||
|
||||
dftEof = true;
|
||||
dftSaveBuf = RB_GET_PAST_EOF.clone();
|
||||
dftSaveBufLen = RB_GET_PAST_EOF.length;
|
||||
input.sendStructuredFieldData(RB_GET_PAST_EOF.clone());
|
||||
listener.onBytesTransferred(bytesTransferred);
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] readNextUploadLineConverted(FTConfig config) throws IOException {
|
||||
if (inputStream == null || dftEof) return null;
|
||||
ByteArrayOutputStream lineBytes = new ByteArrayOutputStream();
|
||||
int c;
|
||||
boolean hasData = false;
|
||||
while ((c = inputStream.read()) != -1) {
|
||||
hasData = true;
|
||||
if (c == '\r') {
|
||||
continue; // ignore CR
|
||||
}
|
||||
if (c == '\n') {
|
||||
break;
|
||||
}
|
||||
if (c == EOF_CTRL_Z || c == EOF_CTRL_D) {
|
||||
break;
|
||||
}
|
||||
lineBytes.write(c);
|
||||
}
|
||||
if (!hasData && c == -1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
byte[] result = out.toByteArray();
|
||||
int sfLen = result.length - 1;
|
||||
result[sfLenPos] = (byte) ((sfLen >> 8) & 0xFF);
|
||||
result[sfLenPos + 1] = (byte) (sfLen & 0xFF);
|
||||
byte[] raw = lineBytes.toByteArray();
|
||||
return convertLineForUpload(raw, config);
|
||||
}
|
||||
|
||||
dftSaveBuf = result.clone();
|
||||
dftSaveBufLen = result.length;
|
||||
private byte[] convertLineForUpload(byte[] raw, FTConfig config) {
|
||||
String lineStr = new String(raw, StandardCharsets.UTF_8);
|
||||
ByteArrayOutputStream converted = new ByteArrayOutputStream(raw.length + 8);
|
||||
boolean isDbcs = (config != null && config.isDbcs()) || (translator != null && translator.getCodePage() != null && translator.getCodePage().isDBCS());
|
||||
boolean noSo = config != null && config.isOptionNoso();
|
||||
boolean inDbcsState = false;
|
||||
|
||||
input.sendStructuredFieldData(result);
|
||||
listener.onBytesTransferred(bytesTransferred);
|
||||
for (int i = 0; i < lineStr.length(); i++) {
|
||||
char ch = lineStr.charAt(i);
|
||||
int ebc = translator != null ? translator.unicodeToEbcdic(ch) : -1;
|
||||
boolean charIsDbcs = isDbcs && (ch > 0xFF || ebc > 0xFF);
|
||||
if (charIsDbcs) {
|
||||
if (!inDbcsState && !noSo) {
|
||||
converted.write(0x0E); // Shift-Out
|
||||
inDbcsState = true;
|
||||
}
|
||||
converted.write((ebc >> 8) & 0xFF);
|
||||
converted.write(ebc & 0xFF);
|
||||
} else {
|
||||
if (inDbcsState && !noSo) {
|
||||
converted.write(0x0F); // Shift-In
|
||||
inDbcsState = false;
|
||||
}
|
||||
converted.write(convertAsciiByte(ch, config));
|
||||
}
|
||||
}
|
||||
if (inDbcsState && !noSo) {
|
||||
converted.write(0x0F); // Shift-In at end of record
|
||||
}
|
||||
return converted.toByteArray();
|
||||
}
|
||||
|
||||
private int readFixedRecordUpload(byte[] outBuf, int maxBytes, int lrecl, FTConfig config) throws IOException {
|
||||
int totalRead = 0;
|
||||
int padByte = (config != null && config.isRemapFlag()) ? FT2ASC[0x40] : 0x20;
|
||||
|
||||
while (totalRead + lrecl <= maxBytes && !dftEof) {
|
||||
byte[] rec;
|
||||
if (pendingUploadRecord != null) {
|
||||
rec = pendingUploadRecord;
|
||||
pendingUploadRecord = null;
|
||||
} else {
|
||||
rec = readNextUploadLineConverted(config);
|
||||
if (rec == null) {
|
||||
dftEof = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (rec.length <= lrecl) {
|
||||
System.arraycopy(rec, 0, outBuf, totalRead, rec.length);
|
||||
totalRead += rec.length;
|
||||
int padCount = lrecl - rec.length;
|
||||
for (int p = 0; p < padCount; p++) {
|
||||
outBuf[totalRead++] = (byte) padByte;
|
||||
}
|
||||
} else {
|
||||
// Longer than lrecl: split
|
||||
System.arraycopy(rec, 0, outBuf, totalRead, lrecl);
|
||||
totalRead += lrecl;
|
||||
int rem = rec.length - lrecl;
|
||||
pendingUploadRecord = new byte[rem];
|
||||
System.arraycopy(rec, lrecl, pendingUploadRecord, 0, rem);
|
||||
}
|
||||
}
|
||||
return totalRead;
|
||||
}
|
||||
|
||||
private int readVariableRecordUpload(byte[] outBuf, int maxBytes, FTConfig config) throws IOException {
|
||||
int totalRead = 0;
|
||||
while (totalRead + 4 < maxBytes && !dftEof) {
|
||||
byte[] rec;
|
||||
if (pendingUploadRecord != null) {
|
||||
rec = pendingUploadRecord;
|
||||
pendingUploadRecord = null;
|
||||
} else {
|
||||
rec = readNextUploadLineConverted(config);
|
||||
if (rec == null) {
|
||||
dftEof = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int needed = rec.length + 4;
|
||||
if (totalRead + needed <= maxBytes) {
|
||||
outBuf[totalRead++] = (byte) ((needed >> 8) & 0xFF);
|
||||
outBuf[totalRead++] = (byte) (needed & 0xFF);
|
||||
outBuf[totalRead++] = 0;
|
||||
outBuf[totalRead++] = 0;
|
||||
System.arraycopy(rec, 0, outBuf, totalRead, rec.length);
|
||||
totalRead += rec.length;
|
||||
} else {
|
||||
if (totalRead > 0) {
|
||||
// Save for next MTU buffer
|
||||
pendingUploadRecord = rec;
|
||||
break;
|
||||
} else {
|
||||
// Single record exceeds entire MTU: split
|
||||
int chunk = maxBytes - 4;
|
||||
int rdwLen = chunk + 4;
|
||||
outBuf[totalRead++] = (byte) ((rdwLen >> 8) & 0xFF);
|
||||
outBuf[totalRead++] = (byte) (rdwLen & 0xFF);
|
||||
outBuf[totalRead++] = 0;
|
||||
outBuf[totalRead++] = 0;
|
||||
System.arraycopy(rec, 0, outBuf, totalRead, chunk);
|
||||
totalRead += chunk;
|
||||
int rem = rec.length - chunk;
|
||||
pendingUploadRecord = new byte[rem];
|
||||
System.arraycopy(rec, chunk, pendingUploadRecord, 0, rem);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return totalRead;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -717,14 +1043,13 @@ public class FTDft {
|
||||
private void dftCloseRequest() {
|
||||
log.fine("DFT: Close");
|
||||
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream(6);
|
||||
out.write(AID_SF);
|
||||
out.write(0); out.write(5);
|
||||
out.write(SF_TRANSFER_DATA);
|
||||
out.write((TR_CLOSE_REPLY >> 8) & 0xFF);
|
||||
out.write(TR_CLOSE_REPLY & 0xFF);
|
||||
try {
|
||||
flushRemainingDownloadRecords();
|
||||
} catch (IOException e) {
|
||||
log.warning("DFT: Error flushing download buffer on close: " + e.getMessage());
|
||||
}
|
||||
|
||||
input.sendStructuredFieldData(out.toByteArray());
|
||||
input.sendStructuredFieldData(RB_CLOSE_ACK.clone());
|
||||
|
||||
if (listener.getCurrentState() != FTState.NONE) {
|
||||
log.info("DFT: File transfer completed on close request (" + bytesTransferred + " bytes)");
|
||||
@@ -735,21 +1060,7 @@ public class FTDft {
|
||||
// ========== Data Acknowledgement ==========
|
||||
|
||||
private void dftDataAck() {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream(12);
|
||||
out.write(AID_SF);
|
||||
out.write(0); out.write(11); // SF length = 11 (0x000B)
|
||||
out.write(SF_TRANSFER_DATA);
|
||||
out.write((TR_NORMAL_REPLY >> 8) & 0xFF);
|
||||
out.write(TR_NORMAL_REPLY & 0xFF);
|
||||
out.write((TR_RECNUM_HDR >> 8) & 0xFF);
|
||||
out.write(TR_RECNUM_HDR & 0xFF);
|
||||
out.write((int) ((recnum >> 24) & 0xFF));
|
||||
out.write((int) ((recnum >> 16) & 0xFF));
|
||||
out.write((int) ((recnum >> 8) & 0xFF));
|
||||
out.write((int) (recnum & 0xFF));
|
||||
|
||||
recnum++;
|
||||
input.sendStructuredFieldData(out.toByteArray());
|
||||
input.sendStructuredFieldData(createDataAck(recnum++));
|
||||
}
|
||||
|
||||
// ========== Abort ==========
|
||||
@@ -757,18 +1068,22 @@ public class FTDft {
|
||||
private void dftAbort(String message, int code) {
|
||||
log.warning("DFT: ABORT — " + message);
|
||||
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream(10);
|
||||
out.write(AID_SF);
|
||||
out.write(0); out.write(9);
|
||||
out.write(SF_TRANSFER_DATA);
|
||||
out.write((code >> 8) & 0xFF);
|
||||
out.write(TR_ERROR_REPLY & 0xFF);
|
||||
out.write((TR_ERROR_HDR >> 8) & 0xFF);
|
||||
out.write(TR_ERROR_HDR & 0xFF);
|
||||
out.write((TR_ERR_CMDFAIL >> 8) & 0xFF);
|
||||
out.write(TR_ERR_CMDFAIL & 0xFF);
|
||||
if (code == TR_OPEN_REQ) {
|
||||
input.sendStructuredFieldData(RB_OPEN_FAILED.clone());
|
||||
} else {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream(10);
|
||||
out.write(AID_SF);
|
||||
out.write(0); out.write(9);
|
||||
out.write(SF_TRANSFER_DATA);
|
||||
out.write((code >> 8) & 0xFF);
|
||||
out.write(TR_ERROR_REPLY & 0xFF);
|
||||
out.write((TR_ERROR_HDR >> 8) & 0xFF);
|
||||
out.write(TR_ERROR_HDR & 0xFF);
|
||||
out.write((TR_ERR_CMDFAIL >> 8) & 0xFF);
|
||||
out.write(TR_ERR_CMDFAIL & 0xFF);
|
||||
|
||||
input.sendStructuredFieldData(out.toByteArray());
|
||||
input.sendStructuredFieldData(out.toByteArray());
|
||||
}
|
||||
listener.onTransferAborted(message);
|
||||
}
|
||||
|
||||
|
||||
@@ -10,10 +10,14 @@ import java.util.List;
|
||||
* <p>A GOCA fillet curve passes from the initial point P_0 to the terminal point P_{N-1},
|
||||
* bending tangentially toward each intermediate control point P_i.
|
||||
*/
|
||||
public class FilletPts {
|
||||
public class FilletPts extends HODEllipse {
|
||||
|
||||
public static final int DEFAULT_STEPS_PER_SEGMENT = 24;
|
||||
|
||||
public FilletPts() {
|
||||
super(0.0, 0.0, 0.0, 0.0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates interpolated spline vertices across control points using double precision.
|
||||
*
|
||||
@@ -116,17 +120,115 @@ public class FilletPts {
|
||||
}
|
||||
|
||||
/**
|
||||
* Interpolates fillet points into provided output buffers.
|
||||
* Interpolates fillet points into provided output buffers using HoD's exact rational quadratic
|
||||
* polynomial spline algorithm.
|
||||
* Matches IBM Host On-Demand FilletPts.getFilletPoints.
|
||||
*/
|
||||
public int getFilletPoints(int[] xIn, int[] yIn, int n, int[] xOut, int[] yOut) {
|
||||
if (xIn == null || yIn == null || n <= 0 || xOut == null || yOut == null) {
|
||||
public int getFilletPoints(int[] nArray, int[] nArray2, int n, int[] nArray3, int[] nArray4) {
|
||||
if (nArray == null || nArray2 == null || n <= 0 || nArray3 == null || nArray4 == null) {
|
||||
return 0;
|
||||
}
|
||||
int[][] pts = calculate(xIn, yIn, n, 16);
|
||||
int count = Math.min(pts[0].length, Math.min(xOut.length, yOut.length));
|
||||
System.arraycopy(pts[0], 0, xOut, 0, count);
|
||||
System.arraycopy(pts[1], 0, yOut, 0, count);
|
||||
return count;
|
||||
|
||||
int n2;
|
||||
int n3 = 0;
|
||||
int n4 = 0;
|
||||
boolean bl = false;
|
||||
int n5 = 0;
|
||||
for (n2 = 1; n2 < n; ++n2) {
|
||||
if (nArray[n2] != nArray[n5] || nArray2[n2] != nArray2[n5]) continue;
|
||||
bl = true;
|
||||
}
|
||||
if (bl) {
|
||||
int[] nArray5 = new int[n];
|
||||
int[] nArray6 = new int[n];
|
||||
nArray5[0] = nArray[0];
|
||||
nArray6[0] = nArray2[0];
|
||||
n5 = 0;
|
||||
for (n2 = 1; n2 < n; ++n2) {
|
||||
if (nArray[n2] == nArray[n5] && nArray2[n2] == nArray2[n5]) continue;
|
||||
nArray5[++n5] = nArray[n2];
|
||||
nArray6[n5] = nArray2[n2];
|
||||
}
|
||||
nArray = nArray5;
|
||||
nArray2 = nArray6;
|
||||
n = n5 + 1;
|
||||
}
|
||||
nArray3[n3] = nArray[0];
|
||||
nArray4[n3] = nArray2[0];
|
||||
int n6 = nArray[0];
|
||||
int n9 = nArray2[0];
|
||||
n3++;
|
||||
|
||||
if (n == 1) {
|
||||
nArray3[n3] = nArray3[0];
|
||||
nArray4[n3++] = nArray4[0];
|
||||
return n3;
|
||||
}
|
||||
if (n == 2) {
|
||||
nArray3[n3] = nArray[1];
|
||||
nArray4[n3++] = nArray2[1];
|
||||
return n3;
|
||||
}
|
||||
int[] nArray7 = new int[3];
|
||||
int[] nArray8 = new int[3];
|
||||
nArray7[0] = nArray[n4];
|
||||
nArray8[0] = nArray2[n4++];
|
||||
--n;
|
||||
nArray7[1] = nArray[n4];
|
||||
nArray8[1] = nArray2[n4++];
|
||||
--n;
|
||||
while (true) {
|
||||
if (n == 0) {
|
||||
nArray3[n3] = nArray7[1];
|
||||
nArray4[n3++] = nArray8[1];
|
||||
return n3;
|
||||
}
|
||||
nArray7[2] = nArray[n4];
|
||||
nArray8[2] = nArray2[n4];
|
||||
if (n > 1) {
|
||||
nArray7[2] = (nArray7[2] + nArray7[1]) / 2;
|
||||
nArray8[2] = (nArray8[2] + nArray8[1]) / 2;
|
||||
}
|
||||
--n;
|
||||
this.dCenterX = nArray7[0] + nArray7[2] - nArray7[1];
|
||||
this.dCenterY = nArray8[0] + nArray8[2] - nArray8[1];
|
||||
this.dP = (double) nArray7[0] - this.dCenterX;
|
||||
this.dQ = (double) nArray8[2] - this.dCenterY;
|
||||
this.dR = (double) nArray7[2] - this.dCenterX;
|
||||
this.dS = (double) nArray8[0] - this.dCenterY;
|
||||
int n10 = (int) this.dP;
|
||||
int n11 = (int) this.dQ;
|
||||
int n12 = (int) this.dR;
|
||||
int n13 = (int) this.dS;
|
||||
int n14 = Math.max(Math.abs(n10) + Math.abs(n13), Math.abs(n12) + Math.abs(n11));
|
||||
this.dRad = n14;
|
||||
if (n14 == 0) {
|
||||
return n3;
|
||||
}
|
||||
int n15 = this.bres(this.dRad);
|
||||
for (n2 = 0; n2 <= n15; ++n2) {
|
||||
this.filletHODSegment2(nArray3, nArray4, n3, this.dXq[n2], this.dYq[n2]);
|
||||
if (n6 == nArray3[n3] && n9 == nArray4[n3]) continue;
|
||||
n6 = nArray3[n3];
|
||||
n9 = nArray4[n3++];
|
||||
}
|
||||
for (n2 = n15; n2 >= 0; --n2) {
|
||||
this.filletHODSegment2(nArray3, nArray4, n3, this.dYq[n2], this.dXq[n2]);
|
||||
if (n6 == nArray3[n3] && n9 == nArray4[n3]) continue;
|
||||
n6 = nArray3[n3];
|
||||
n9 = nArray4[n3++];
|
||||
}
|
||||
if (n6 != nArray7[2] || n9 != nArray8[2]) {
|
||||
nArray3[n3] = nArray7[2];
|
||||
nArray4[n3++] = nArray8[2];
|
||||
}
|
||||
if (n == 0) {
|
||||
return n3;
|
||||
}
|
||||
nArray7[0] = n6 = nArray7[2];
|
||||
nArray8[0] = n9 = nArray8[2];
|
||||
nArray7[1] = nArray[n4];
|
||||
nArray8[1] = nArray2[n4++];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,6 +50,19 @@ public class GddmCoordinateTransform {
|
||||
private double transformX = 1.0;
|
||||
private double transformY = 1.0;
|
||||
|
||||
// Aspect Ratio parameters (P, Q, R, S) per IBM Host On-Demand (HODDecoder / GSAP)
|
||||
public static final int DEFAULT_P = 1;
|
||||
public static final int DEFAULT_Q = 1;
|
||||
public static final int DEFAULT_R = 0;
|
||||
public static final int DEFAULT_S = 0;
|
||||
|
||||
private int paramP = DEFAULT_P;
|
||||
private int paramQ = DEFAULT_Q;
|
||||
private int paramR = DEFAULT_R;
|
||||
private int paramS = DEFAULT_S;
|
||||
private double scaleX = 1.0;
|
||||
private double scaleY = 1.0;
|
||||
|
||||
public GddmCoordinateTransform() {
|
||||
this(80, 24, DEFAULT_CHAR_WIDTH, DEFAULT_CHAR_HEIGHT_16);
|
||||
}
|
||||
@@ -270,4 +283,68 @@ public class GddmCoordinateTransform {
|
||||
public int convertYtoRow(int y) {
|
||||
return (defaultCharHeight > 0) ? (y / defaultCharHeight) + 1 : 1;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Aspect Ratio & Coordinate Inversion (Phase 17.2)
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* Sets GOCA aspect ratio parameters P, Q, R, S and updates scaling factors.
|
||||
* ScaleX = P/Q, ScaleY = R/S. Defaults: P=1, Q=1, R=0, S=0.
|
||||
*/
|
||||
public void setAspectRatio(int p, int q, int r, int s) {
|
||||
this.paramP = p;
|
||||
this.paramQ = q;
|
||||
this.paramR = r;
|
||||
this.paramS = s;
|
||||
this.scaleX = (q != 0) ? ((double) p / (double) q) : 1.0;
|
||||
this.scaleY = (s != 0) ? ((double) r / (double) s) : 1.0;
|
||||
if (this.scaleX <= 0.0) this.scaleX = 1.0;
|
||||
if (this.scaleY <= 0.0) this.scaleY = 1.0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets aspect ratio parameters to defaults: P=1, Q=1, R=0, S=0.
|
||||
*/
|
||||
public void resetAspectRatio() {
|
||||
setAspectRatio(DEFAULT_P, DEFAULT_Q, DEFAULT_R, DEFAULT_S);
|
||||
}
|
||||
|
||||
public int getParamP() { return paramP; }
|
||||
public int getParamQ() { return paramQ; }
|
||||
public int getParamR() { return paramR; }
|
||||
public int getParamS() { return paramS; }
|
||||
|
||||
public double getScaleX() { return scaleX; }
|
||||
public double getScaleY() { return scaleY; }
|
||||
|
||||
/**
|
||||
* Inverts GOCA Cartesian Y (positive upward from bottom-left) to raster Y (top-down):
|
||||
* Y_screen = (originY - gocaY) * ScaleY.
|
||||
*/
|
||||
public int mapGocaYToScreen(int originY, int gocaY) {
|
||||
return (int) Math.round((originY - gocaY) * scaleY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inverts GOCA Cartesian Y to raster screen Y using the transform's default origin YMax.
|
||||
*/
|
||||
public int mapGocaYToScreen(int gocaY) {
|
||||
return mapGocaYToScreen(getYMax(), gocaY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inverts raster Y (top-down) back to GOCA Cartesian Y (positive upward from bottom-left):
|
||||
* Y_goca = originY - (screenY / ScaleY).
|
||||
*/
|
||||
public int mapScreenYToGoca(int originY, int screenY) {
|
||||
return originY - (scaleY > 0 ? (int) Math.round(screenY / scaleY) : screenY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inverts raster screen Y back to GOCA Cartesian Y using the transform's default origin YMax.
|
||||
*/
|
||||
public int mapScreenYToGoca(int screenY) {
|
||||
return mapScreenYToGoca(getYMax(), screenY);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,10 +40,15 @@ public class GocaDecoder {
|
||||
private double charHeight = 16.0;
|
||||
private int charSet = 0;
|
||||
private int charPrecision = GocaConstants.CP_STRING;
|
||||
private int arcParamP = 1;
|
||||
private int arcParamQ = 0;
|
||||
private int arcParamR = 0;
|
||||
private int arcParamS = 1;
|
||||
public static final int DEFAULT_ARC_P = 1;
|
||||
public static final int DEFAULT_ARC_Q = 1;
|
||||
public static final int DEFAULT_ARC_R = 0;
|
||||
public static final int DEFAULT_ARC_S = 0;
|
||||
|
||||
private int arcParamP = DEFAULT_ARC_P;
|
||||
private int arcParamQ = DEFAULT_ARC_Q;
|
||||
private int arcParamR = DEFAULT_ARC_R;
|
||||
private int arcParamS = DEFAULT_ARC_S;
|
||||
|
||||
private boolean segChained = false;
|
||||
private boolean segDynamic = false;
|
||||
@@ -77,6 +82,7 @@ public class GocaDecoder {
|
||||
private int imgHeight = 0;
|
||||
private int imgBitDepth = GocaConstants.BPP_1;
|
||||
private int imgCompression = GocaConstants.IMG_UNCOMPRESSED;
|
||||
private int imgMultiplier = 1;
|
||||
private final List<Byte> imgBuffer = new ArrayList<>();
|
||||
|
||||
// Segment Store for retained graphics / segment calling (G_GCALL 0x2A)
|
||||
@@ -368,7 +374,15 @@ public class GocaDecoder {
|
||||
inImage = false;
|
||||
imgBitDepth = GocaConstants.BPP_1;
|
||||
imgCompression = GocaConstants.IMG_UNCOMPRESSED;
|
||||
imgMultiplier = 1;
|
||||
imgBuffer.clear();
|
||||
arcParamP = DEFAULT_ARC_P;
|
||||
arcParamQ = DEFAULT_ARC_Q;
|
||||
arcParamR = DEFAULT_ARC_R;
|
||||
arcParamS = DEFAULT_ARC_S;
|
||||
if (plane != null && plane.getTransform() != null) {
|
||||
plane.getTransform().setAspectRatio(arcParamP, arcParamQ, arcParamR, arcParamS);
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] partialOrderBuffer = new byte[0];
|
||||
@@ -567,7 +581,10 @@ public class GocaDecoder {
|
||||
int w = 0, h = 0;
|
||||
int bitDepth = GocaConstants.BPP_1;
|
||||
int compression = GocaConstants.IMG_UNCOMPRESSED;
|
||||
int imult = 1;
|
||||
if (payloadLen >= 6 && idx + 2 + payloadLen <= end) {
|
||||
imult = readCoord(inputData, idx + 2);
|
||||
if (imult <= 0) imult = 1;
|
||||
w = readCoord(inputData, idx + 4);
|
||||
h = readCoord(inputData, idx + 6);
|
||||
if (payloadLen >= 7) {
|
||||
@@ -592,7 +609,7 @@ public class GocaDecoder {
|
||||
compression = inputData[idx + 7] & 0xFF;
|
||||
}
|
||||
}
|
||||
beginImage(curX, curY, w, h, bitDepth, compression);
|
||||
beginImage(curX, curY, w, h, bitDepth, compression, imult);
|
||||
idx += orderLen;
|
||||
break;
|
||||
}
|
||||
@@ -672,13 +689,25 @@ public class GocaDecoder {
|
||||
idx += orderLen;
|
||||
break;
|
||||
}
|
||||
case GocaConstants.G_GSAP: { // Arc Parameters (0x22)
|
||||
case GocaConstants.G_GSAP: { // Arc Parameters / Set Aspect Ratio (0x22)
|
||||
if (idx + 9 < end) {
|
||||
arcParamP = readCoord(inputData, idx + 2);
|
||||
arcParamQ = readCoord(inputData, idx + 4);
|
||||
arcParamR = readCoord(inputData, idx + 6);
|
||||
arcParamS = readCoord(inputData, idx + 8);
|
||||
} else {
|
||||
arcParamP = DEFAULT_ARC_P;
|
||||
arcParamQ = DEFAULT_ARC_Q;
|
||||
arcParamR = DEFAULT_ARC_R;
|
||||
arcParamS = DEFAULT_ARC_S;
|
||||
}
|
||||
if (plane != null && plane.getTransform() != null) {
|
||||
plane.getTransform().setAspectRatio(arcParamP, arcParamQ, arcParamR, arcParamS);
|
||||
}
|
||||
logger.info(String.format("GOCA GSAP: P=%d Q=%d R=%d S=%d (ScaleX=%.2f ScaleY=%.2f)",
|
||||
arcParamP, arcParamQ, arcParamR, arcParamS,
|
||||
(arcParamQ != 0 ? (double) arcParamP / arcParamQ : 1.0),
|
||||
(arcParamS != 0 ? (double) arcParamR / arcParamS : 1.0)));
|
||||
idx += orderLen;
|
||||
break;
|
||||
}
|
||||
@@ -1012,7 +1041,10 @@ public class GocaDecoder {
|
||||
int w, h;
|
||||
int bitDepth = GocaConstants.BPP_1;
|
||||
int compression = GocaConstants.IMG_UNCOMPRESSED;
|
||||
int imult = 1;
|
||||
if (payloadLen >= 10) {
|
||||
imult = readCoord(inputData, idx + 6);
|
||||
if (imult <= 0) imult = 1;
|
||||
w = readCoord(inputData, idx + 8);
|
||||
h = readCoord(inputData, idx + 10);
|
||||
if (payloadLen >= 11) {
|
||||
@@ -1037,7 +1069,7 @@ public class GocaDecoder {
|
||||
compression = inputData[idx + 11] & 0xFF;
|
||||
}
|
||||
}
|
||||
beginImage(x, y, w, h, bitDepth, compression);
|
||||
beginImage(x, y, w, h, bitDepth, compression, imult);
|
||||
}
|
||||
idx += orderLen;
|
||||
break;
|
||||
@@ -1309,10 +1341,14 @@ public class GocaDecoder {
|
||||
}
|
||||
|
||||
private void beginImage(int x, int y, int w, int h) {
|
||||
beginImage(x, y, w, h, GocaConstants.BPP_1, GocaConstants.IMG_UNCOMPRESSED);
|
||||
beginImage(x, y, w, h, GocaConstants.BPP_1, GocaConstants.IMG_UNCOMPRESSED, 1);
|
||||
}
|
||||
|
||||
private void beginImage(int x, int y, int w, int h, int bitDepth, int compression) {
|
||||
beginImage(x, y, w, h, bitDepth, compression, 1);
|
||||
}
|
||||
|
||||
private void beginImage(int x, int y, int w, int h, int bitDepth, int compression, int multiplier) {
|
||||
this.inImage = true;
|
||||
this.imgX = x;
|
||||
this.imgY = y;
|
||||
@@ -1320,6 +1356,7 @@ public class GocaDecoder {
|
||||
this.imgHeight = h;
|
||||
this.imgBitDepth = bitDepth;
|
||||
this.imgCompression = compression;
|
||||
this.imgMultiplier = (multiplier > 0) ? multiplier : 1;
|
||||
this.imgBuffer.clear();
|
||||
}
|
||||
|
||||
@@ -1335,7 +1372,7 @@ public class GocaDecoder {
|
||||
|
||||
int px = plane.mapX(imgX);
|
||||
int py = plane.mapY(imgY);
|
||||
plane.drawImage(px, py, imgWidth, imgHeight, bytes, curColor, imgBitDepth, imgCompression);
|
||||
plane.drawImage(px, py, imgWidth, imgHeight, bytes, curColor, imgBitDepth, imgCompression, imgMultiplier);
|
||||
inImage = false;
|
||||
imgBuffer.clear();
|
||||
}
|
||||
@@ -1429,38 +1466,191 @@ public class GocaDecoder {
|
||||
|
||||
private void processArc(byte[] data, int off, int len, boolean fromCurPos, boolean isFull) {
|
||||
int pos = off;
|
||||
int end = off + len;
|
||||
|
||||
if (isFull) {
|
||||
// GFARC (0xC7) or GCFARC (0x87) Full Arc / Ellipse
|
||||
int centerX = curX;
|
||||
int centerY = curY;
|
||||
if (!fromCurPos && pos + 4 <= end) {
|
||||
centerX = readCoord(data, pos);
|
||||
centerY = readCoord(data, pos + 2);
|
||||
pos += 4;
|
||||
}
|
||||
|
||||
double multiplier = 1.0;
|
||||
if (pos + 2 <= end) {
|
||||
int intPart = data[pos];
|
||||
int fracPart = data[pos + 1] & 0xFF;
|
||||
multiplier = intPart + fracPart / 256.0;
|
||||
if (multiplier <= 0.0) multiplier = 1.0;
|
||||
pos += 2;
|
||||
}
|
||||
|
||||
double dP = arcParamP * multiplier;
|
||||
double dQ = arcParamQ * multiplier;
|
||||
double dR = arcParamR * multiplier;
|
||||
double dS = arcParamS * multiplier;
|
||||
|
||||
int[] arcX = new int[256];
|
||||
int[] arcY = new int[256];
|
||||
int nPts = HODEllipse.getHODEllipse(dP, dQ, dR, dS, arcX, arcY, centerX, centerY);
|
||||
|
||||
for (int i = 0; i < nPts; i++) {
|
||||
trackPoint(arcX[i], arcY[i]);
|
||||
}
|
||||
|
||||
if (inArea) {
|
||||
for (int i = 0; i < nPts; i++) {
|
||||
if (i == 0 && currentPolyPts == 0) {
|
||||
addAreaLineStart(arcX[i], arcY[i]);
|
||||
} else {
|
||||
addAreaPoint(arcX[i], arcY[i]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < nPts - 1; i++) {
|
||||
plane.drawLine(plane.mapXDouble(arcX[i]), plane.mapYDouble(arcY[i]),
|
||||
plane.mapXDouble(arcX[i + 1]), plane.mapYDouble(arcY[i + 1]),
|
||||
curColor, lineType, lineWidth);
|
||||
}
|
||||
}
|
||||
|
||||
curX = centerX;
|
||||
curY = centerY;
|
||||
return;
|
||||
}
|
||||
|
||||
// Partial Arc: GARC (0xC6) or GCARC (0x86)
|
||||
// 3-point circular arc definition per GOCA specs: start, intermediate, end
|
||||
int x0 = curX;
|
||||
int y0 = curY;
|
||||
boolean has3Point = false;
|
||||
int x1 = 0, y1 = 0, x2 = 0, y2 = 0;
|
||||
|
||||
if (!fromCurPos && len >= 12 && pos + 12 <= end) {
|
||||
x0 = readCoord(data, pos);
|
||||
y0 = readCoord(data, pos + 2);
|
||||
x1 = readCoord(data, pos + 4);
|
||||
y1 = readCoord(data, pos + 6);
|
||||
x2 = readCoord(data, pos + 8);
|
||||
y2 = readCoord(data, pos + 10);
|
||||
has3Point = true;
|
||||
} else if (fromCurPos && len >= 8 && pos + 8 <= end) {
|
||||
x1 = readCoord(data, pos);
|
||||
y1 = readCoord(data, pos + 2);
|
||||
x2 = readCoord(data, pos + 4);
|
||||
y2 = readCoord(data, pos + 6);
|
||||
has3Point = true;
|
||||
} else if (!fromCurPos && len >= 8 && pos + 8 <= end) {
|
||||
x1 = readCoord(data, pos);
|
||||
y1 = readCoord(data, pos + 2);
|
||||
x2 = readCoord(data, pos + 4);
|
||||
y2 = readCoord(data, pos + 6);
|
||||
has3Point = true;
|
||||
}
|
||||
|
||||
if (has3Point) {
|
||||
double d = 2.0 * (x0 * (y1 - y2) + x1 * (y2 - y0) + x2 * (y0 - y1));
|
||||
if (Math.abs(d) < 1e-6) {
|
||||
// Collinear points: draw straight line segments P0 -> P1 -> P2
|
||||
trackPoint(x0, y0);
|
||||
trackPoint(x1, y1);
|
||||
trackPoint(x2, y2);
|
||||
if (inArea) {
|
||||
if (currentPolyPts == 0) addAreaLineStart(x0, y0);
|
||||
addAreaPoint(x1, y1);
|
||||
addAreaPoint(x2, y2);
|
||||
} else {
|
||||
plane.drawLine(plane.mapXDouble(x0), plane.mapYDouble(y0),
|
||||
plane.mapXDouble(x1), plane.mapYDouble(y1),
|
||||
curColor, lineType, lineWidth);
|
||||
plane.drawLine(plane.mapXDouble(x1), plane.mapYDouble(y1),
|
||||
plane.mapXDouble(x2), plane.mapYDouble(y2),
|
||||
curColor, lineType, lineWidth);
|
||||
}
|
||||
curX = x2;
|
||||
curY = y2;
|
||||
return;
|
||||
}
|
||||
|
||||
double x0sq = (double) x0 * x0 + (double) y0 * y0;
|
||||
double x1sq = (double) x1 * x1 + (double) y1 * y1;
|
||||
double x2sq = (double) x2 * x2 + (double) y2 * y2;
|
||||
double ccx = (x0sq * (y1 - y2) + x1sq * (y2 - y0) + x2sq * (y0 - y1)) / d;
|
||||
double ccy = (x0sq * (x2 - x1) + x1sq * (x0 - x2) + x2sq * (x1 - x0)) / d;
|
||||
double rad = Math.hypot(x0 - ccx, y0 - ccy);
|
||||
|
||||
double theta0 = Math.atan2(y0 - ccy, x0 - ccx);
|
||||
double theta1 = Math.atan2(y1 - ccy, x1 - ccx);
|
||||
double theta2 = Math.atan2(y2 - ccy, x2 - ccx);
|
||||
if (theta0 < 0) theta0 += 2 * Math.PI;
|
||||
if (theta1 < 0) theta1 += 2 * Math.PI;
|
||||
if (theta2 < 0) theta2 += 2 * Math.PI;
|
||||
|
||||
double d01 = theta1 - theta0;
|
||||
while (d01 < 0) d01 += 2 * Math.PI;
|
||||
double d02 = theta2 - theta0;
|
||||
while (d02 < 0) d02 += 2 * Math.PI;
|
||||
|
||||
double sweep = (d01 < d02) ? d02 : (d02 - 2 * Math.PI);
|
||||
|
||||
int steps = Math.max(8, (int) Math.round(Math.abs(Math.toDegrees(sweep)) / 5.0));
|
||||
int prevX = x0;
|
||||
int prevY = y0;
|
||||
trackPoint(x0, y0);
|
||||
if (inArea && currentPolyPts == 0) {
|
||||
addAreaLineStart(x0, y0);
|
||||
}
|
||||
|
||||
for (int s = 1; s <= steps; s++) {
|
||||
double t = (double) s / steps;
|
||||
double angle = theta0 + t * sweep;
|
||||
int px = (s == steps) ? x2 : (int) Math.round(ccx + rad * Math.cos(angle));
|
||||
int py = (s == steps) ? y2 : (int) Math.round(ccy + rad * Math.sin(angle));
|
||||
trackPoint(px, py);
|
||||
if (inArea) {
|
||||
addAreaPoint(px, py);
|
||||
} else {
|
||||
plane.drawLine(plane.mapXDouble(prevX), plane.mapYDouble(prevY),
|
||||
plane.mapXDouble(px), plane.mapYDouble(py),
|
||||
curColor, lineType, lineWidth);
|
||||
}
|
||||
prevX = px;
|
||||
prevY = py;
|
||||
}
|
||||
|
||||
curX = x2;
|
||||
curY = y2;
|
||||
return;
|
||||
}
|
||||
|
||||
// Fallback: sweep angle parameter decoding
|
||||
int centerX = curX;
|
||||
int centerY = curY;
|
||||
|
||||
// For absolute variants, read the center point from data
|
||||
if (!fromCurPos && pos + 4 <= off + len) {
|
||||
if (!fromCurPos && pos + 4 <= end) {
|
||||
centerX = readCoord(data, pos);
|
||||
centerY = readCoord(data, pos + 2);
|
||||
pos += 4;
|
||||
}
|
||||
|
||||
// Read the multiplier (integer.fraction format)
|
||||
double multiplier = 1.0;
|
||||
if (pos + 2 <= off + len) {
|
||||
int intPart = data[pos]; // signed byte
|
||||
if (pos + 2 <= end) {
|
||||
int intPart = data[pos];
|
||||
int fracPart = data[pos + 1] & 0xFF;
|
||||
multiplier = intPart + fracPart / 256.0;
|
||||
if (multiplier == 0.0) multiplier = 1.0;
|
||||
pos += 2;
|
||||
}
|
||||
|
||||
// Compute semi-axes from arc parameters scaled by multiplier
|
||||
// P,Q and R,S are conjugate semi-diameter vectors
|
||||
double pScaled = arcParamP * multiplier;
|
||||
double qScaled = arcParamQ * multiplier;
|
||||
double rScaled = arcParamR * multiplier;
|
||||
double sScaled = arcParamS * multiplier;
|
||||
|
||||
// Semi-axis lengths in GOCA coordinate space
|
||||
double semiAxis1 = Math.sqrt(pScaled * pScaled + qScaled * qScaled);
|
||||
double semiAxis2 = Math.sqrt(rScaled * rScaled + sScaled * sScaled);
|
||||
|
||||
// If arc parameters are at default (1,0,0,1) and curPos != center, use distance to center as radius
|
||||
double dist = Math.hypot(curX - centerX, curY - centerY);
|
||||
if (semiAxis1 <= 1.01 && dist > 1.0) {
|
||||
semiAxis1 = dist * multiplier;
|
||||
@@ -1470,7 +1660,6 @@ public class GocaDecoder {
|
||||
if (semiAxis1 < 1.0) semiAxis1 = 1.0;
|
||||
if (semiAxis2 < 1.0) semiAxis2 = 1.0;
|
||||
|
||||
// Map to pixel space with double precision
|
||||
double cx = plane.mapXDouble(centerX);
|
||||
double cy = plane.mapYDouble(centerY);
|
||||
double rx = Math.abs(plane.mapXDouble(semiAxis1) - plane.mapXDouble(0));
|
||||
@@ -1478,38 +1667,24 @@ public class GocaDecoder {
|
||||
if (rx <= 0.0) rx = Math.max(1.0, semiAxis1);
|
||||
if (ry <= 0.0) ry = Math.max(1.0, semiAxis2);
|
||||
|
||||
// For partial arcs, determine start angle and sweep angle
|
||||
double startAngleDeg = 0.0;
|
||||
double sweepAngleDeg = 360.0;
|
||||
if (!isFull) {
|
||||
if (pos + 4 <= off + len) {
|
||||
// Sweep start: integer.fraction of full revolution
|
||||
int startInt = data[pos];
|
||||
int startFrac = data[pos + 1] & 0xFF;
|
||||
startAngleDeg = (startInt + startFrac / 256.0) * 360.0;
|
||||
pos += 2;
|
||||
// Sweep angle: integer.fraction of full revolution
|
||||
int sweepInt = data[pos];
|
||||
int sweepFrac = data[pos + 1] & 0xFF;
|
||||
sweepAngleDeg = (sweepInt + sweepFrac / 256.0) * 360.0;
|
||||
} else if (pos + 2 <= off + len) {
|
||||
// Single sweep parameter
|
||||
int sweepInt = data[pos];
|
||||
int sweepFrac = data[pos + 1] & 0xFF;
|
||||
sweepAngleDeg = (sweepInt + sweepFrac / 256.0) * 360.0;
|
||||
if (curX != centerX || curY != centerY) {
|
||||
double startRad = Math.atan2(plane.mapYDouble(centerY) - plane.mapYDouble(curY), plane.mapXDouble(curX) - plane.mapXDouble(centerX));
|
||||
startAngleDeg = Math.toDegrees(startRad);
|
||||
if (startAngleDeg < 0) startAngleDeg += 360.0;
|
||||
}
|
||||
} else {
|
||||
// No sweep data — if we have a current point, start there and sweep full circle
|
||||
if (curX != centerX || curY != centerY) {
|
||||
double startRad = Math.atan2(plane.mapYDouble(centerY) - plane.mapYDouble(curY), plane.mapXDouble(curX) - plane.mapXDouble(centerX));
|
||||
startAngleDeg = Math.toDegrees(startRad);
|
||||
if (startAngleDeg < 0) startAngleDeg += 360.0;
|
||||
}
|
||||
sweepAngleDeg = 360.0;
|
||||
if (pos + 4 <= end) {
|
||||
int startInt = data[pos];
|
||||
int startFrac = data[pos + 1] & 0xFF;
|
||||
startAngleDeg = (startInt + startFrac / 256.0) * 360.0;
|
||||
pos += 2;
|
||||
int sweepInt = data[pos];
|
||||
int sweepFrac = data[pos + 1] & 0xFF;
|
||||
sweepAngleDeg = (sweepInt + sweepFrac / 256.0) * 360.0;
|
||||
} else if (pos + 2 <= end) {
|
||||
int sweepInt = data[pos];
|
||||
int sweepFrac = data[pos + 1] & 0xFF;
|
||||
sweepAngleDeg = (sweepInt + sweepFrac / 256.0) * 360.0;
|
||||
if (curX != centerX || curY != centerY) {
|
||||
double startRad = Math.atan2(plane.mapYDouble(centerY) - plane.mapYDouble(curY), plane.mapXDouble(curX) - plane.mapXDouble(centerX));
|
||||
startAngleDeg = Math.toDegrees(startRad);
|
||||
if (startAngleDeg < 0) startAngleDeg += 360.0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1533,7 +1708,7 @@ public class GocaDecoder {
|
||||
}
|
||||
|
||||
plane.drawArc(cx, cy, rx, ry, startAngleDeg, sweepAngleDeg,
|
||||
curColor, lineType, lineWidth, isFull);
|
||||
curColor, lineType, lineWidth, false);
|
||||
|
||||
curX = centerX;
|
||||
curY = centerY;
|
||||
@@ -1543,63 +1718,62 @@ public class GocaDecoder {
|
||||
int pos = off;
|
||||
int end = off + len;
|
||||
|
||||
List<Double> ptsX = new ArrayList<>();
|
||||
List<Double> ptsY = new ArrayList<>();
|
||||
List<Integer> gocaPtsX = new ArrayList<>();
|
||||
List<Integer> gocaPtsY = new ArrayList<>();
|
||||
|
||||
if (fromCurPos) {
|
||||
trackPoint(curX, curY);
|
||||
ptsX.add(plane.mapXDouble(curX));
|
||||
ptsY.add(plane.mapYDouble(curY));
|
||||
gocaPtsX.add(curX);
|
||||
gocaPtsY.add(curY);
|
||||
if (inArea && currentPolyPts == 0) {
|
||||
addAreaLineStart(curX, curY);
|
||||
}
|
||||
}
|
||||
|
||||
while (pos + 4 <= end) {
|
||||
int x = readCoord(data, pos);
|
||||
int y = readCoord(data, pos + 2);
|
||||
trackPoint(x, y);
|
||||
ptsX.add(plane.mapXDouble(x));
|
||||
ptsY.add(plane.mapYDouble(y));
|
||||
gocaPtsX.add(x);
|
||||
gocaPtsY.add(y);
|
||||
if (inArea && ptsX.size() == 1 && currentPolyPts == 0) {
|
||||
addAreaLineStart(x, y);
|
||||
}
|
||||
curX = x;
|
||||
curY = y;
|
||||
pos += 4;
|
||||
}
|
||||
|
||||
if (ptsX.size() >= 2) {
|
||||
int n = ptsX.size();
|
||||
double[] px = new double[n];
|
||||
double[] py = new double[n];
|
||||
for (int i = 0; i < n; i++) {
|
||||
px[i] = ptsX.get(i);
|
||||
py[i] = ptsY.get(i);
|
||||
}
|
||||
int n = gocaPtsX.size();
|
||||
if (n < 2) return;
|
||||
|
||||
int[] ctrlX = new int[n];
|
||||
int[] ctrlY = new int[n];
|
||||
for (int i = 0; i < n; i++) {
|
||||
ctrlX[i] = gocaPtsX.get(i);
|
||||
ctrlY[i] = gocaPtsY.get(i);
|
||||
trackPoint(ctrlX[i], ctrlY[i]);
|
||||
}
|
||||
|
||||
FilletPts fillet = new FilletPts();
|
||||
int req = fillet.getPointsRequired(n);
|
||||
int[] outX = new int[Math.max(req, n * 32)];
|
||||
int[] outY = new int[Math.max(req, n * 32)];
|
||||
int nPoints = fillet.getFilletPoints(ctrlX, ctrlY, n, outX, outY);
|
||||
|
||||
if (nPoints > 0) {
|
||||
for (int i = 0; i < nPoints; i++) {
|
||||
trackPoint(outX[i], outY[i]);
|
||||
}
|
||||
if (inArea) {
|
||||
int gn = gocaPtsX.size();
|
||||
int[] gx = new int[gn];
|
||||
int[] gy = new int[gn];
|
||||
for (int i = 0; i < gn; i++) {
|
||||
gx[i] = gocaPtsX.get(i);
|
||||
gy[i] = gocaPtsY.get(i);
|
||||
}
|
||||
int[][] curve = FilletPts.calculate(gx, gy, gn, 16);
|
||||
for (int i = 1; i < curve[0].length; i++) {
|
||||
addAreaPoint(curve[0][i], curve[1][i]);
|
||||
for (int i = 0; i < nPoints; i++) {
|
||||
if (i == 0 && currentPolyPts == 0) {
|
||||
addAreaLineStart(outX[0], outY[0]);
|
||||
} else {
|
||||
addAreaPoint(outX[i], outY[i]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
plane.drawFillet(px, py, n, curColor, lineType, lineWidth);
|
||||
for (int i = 0; i < nPoints - 1; i++) {
|
||||
plane.drawLine(plane.mapXDouble(outX[i]), plane.mapYDouble(outY[i]),
|
||||
plane.mapXDouble(outX[i + 1]), plane.mapYDouble(outY[i + 1]),
|
||||
curColor, lineType, lineWidth);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
curX = ctrlX[n - 1];
|
||||
curY = ctrlY[n - 1];
|
||||
}
|
||||
|
||||
private void processMarker(byte[] data, int off, int len, boolean fromCurPos) {
|
||||
@@ -1824,6 +1998,15 @@ public class GocaDecoder {
|
||||
resetDefaults();
|
||||
}
|
||||
|
||||
public synchronized int getArcParamP() { return arcParamP; }
|
||||
public synchronized int getArcParamQ() { return arcParamQ; }
|
||||
public synchronized int getArcParamR() { return arcParamR; }
|
||||
public synchronized int getArcParamS() { return arcParamS; }
|
||||
public synchronized int getImageMultiplier() { return imgMultiplier; }
|
||||
public synchronized boolean hasActiveGraphics() {
|
||||
return graphicsCursorActive || (plane != null && plane.hasContent());
|
||||
}
|
||||
|
||||
private int readCoord(byte[] data, int off) {
|
||||
return (short) (((data[off] & 0xFF) << 8) | (data[off + 1] & 0xFF));
|
||||
}
|
||||
|
||||
@@ -1221,12 +1221,12 @@ public class GraphicsPlane implements PixelBuffer {
|
||||
for (int i = 0; i < pLen - 1; i++) {
|
||||
drawLine((double) px[offset + i], (double) py[offset + i],
|
||||
(double) px[offset + i + 1], (double) py[offset + i + 1],
|
||||
boundaryColorArgb, lineType, GocaConstants.LW_NORMAL);
|
||||
boundaryColorArgb, lineType, lineWidth);
|
||||
}
|
||||
if (pLen >= 3 && (px[offset] != px[offset + pLen - 1] || py[offset] != py[offset + pLen - 1])) {
|
||||
drawLine((double) px[offset + pLen - 1], (double) py[offset + pLen - 1],
|
||||
(double) px[offset], (double) py[offset],
|
||||
boundaryColorArgb, lineType, GocaConstants.LW_NORMAL);
|
||||
boundaryColorArgb, lineType, lineWidth);
|
||||
}
|
||||
}
|
||||
offset += pLen;
|
||||
@@ -1417,6 +1417,16 @@ public class GraphicsPlane implements PixelBuffer {
|
||||
drawVectorText((double) x, (double) y, text, colorArgb, (double) cellWidth, (double) cellHeight, dir, angle, shearAngle);
|
||||
}
|
||||
|
||||
public synchronized void drawVectorString(double x, double y, String text, int colorArgb,
|
||||
double cellWidth, double cellHeight, int dir, double angle, double shearAngle) {
|
||||
drawVectorText(x, y, text, colorArgb, cellWidth, cellHeight, dir, angle, shearAngle);
|
||||
}
|
||||
|
||||
public synchronized void drawVectorString(int x, int y, String text, int colorArgb,
|
||||
int cellWidth, int cellHeight, int dir, double angle) {
|
||||
drawVectorText((double) x, (double) y, text, colorArgb, (double) cellWidth, (double) cellHeight, dir, angle, 0.0);
|
||||
}
|
||||
|
||||
private void drawVssChar(double x, double y, char c, int color, double cw, double ch) {
|
||||
drawVssChar(x, y, c, color, cw, ch, 0.0, 0.0);
|
||||
}
|
||||
@@ -1530,8 +1540,17 @@ public class GraphicsPlane implements PixelBuffer {
|
||||
*/
|
||||
public synchronized void drawImage(int x, int y, int width, int height, byte[] imageData, int fgColorArgb,
|
||||
int bitDepth, int compressionMode) {
|
||||
drawImage(x, y, width, height, imageData, fgColorArgb, bitDepth, compressionMode, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws bitmap image with support for bit depth, RLE decompression, and image multiplier (imult).
|
||||
*/
|
||||
public synchronized void drawImage(int x, int y, int width, int height, byte[] imageData, int fgColorArgb,
|
||||
int bitDepth, int compressionMode, int multiplier) {
|
||||
if (imageData == null || width <= 0 || height <= 0) return;
|
||||
int fgColor = (fgColorArgb != 0) ? fgColorArgb : GocaConstants.GOCA_COLORS[0];
|
||||
int m = (multiplier > 0) ? multiplier : 1;
|
||||
|
||||
byte[] rawData = imageData;
|
||||
if (compressionMode == GocaConstants.IMG_RLE) {
|
||||
@@ -1549,7 +1568,15 @@ public class GraphicsPlane implements PixelBuffer {
|
||||
if (byteIdx < rawData.length) {
|
||||
boolean bit = ((rawData[byteIdx] >> (7 - (col % 8))) & 1) != 0;
|
||||
if (bit) {
|
||||
setPixel(x + col, y + row, fgColor);
|
||||
if (m <= 1) {
|
||||
setPixel(x + col, y + row, fgColor);
|
||||
} else {
|
||||
for (int dy = 0; dy < m; dy++) {
|
||||
for (int dx = 0; dx < m; dx++) {
|
||||
setPixel(x + col * m + dx, y + row * m + dy, fgColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1572,7 +1599,15 @@ public class GraphicsPlane implements PixelBuffer {
|
||||
case 3: pixelColor = GocaConstants.GOCA_COLORS[4]; break; // Green
|
||||
default: pixelColor = fgColor; break;
|
||||
}
|
||||
setPixel(x + col, y + row, pixelColor);
|
||||
if (m <= 1) {
|
||||
setPixel(x + col, y + row, pixelColor);
|
||||
} else {
|
||||
for (int dy = 0; dy < m; dy++) {
|
||||
for (int dx = 0; dx < m; dx++) {
|
||||
setPixel(x + col * m + dx, y + row * m + dy, pixelColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1588,7 +1623,16 @@ public class GraphicsPlane implements PixelBuffer {
|
||||
int shift = (1 - (col % 2)) * 4;
|
||||
int val = (rawData[byteIdx] >> shift) & 0x0F;
|
||||
if (val != 0) {
|
||||
setPixel(x + col, y + row, GocaConstants.getGocaColorArgb(val));
|
||||
int pixelColor = GocaConstants.getGocaColorArgb(val);
|
||||
if (m <= 1) {
|
||||
setPixel(x + col, y + row, pixelColor);
|
||||
} else {
|
||||
for (int dy = 0; dy < m; dy++) {
|
||||
for (int dx = 0; dx < m; dx++) {
|
||||
setPixel(x + col * m + dx, y + row * m + dy, pixelColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1602,7 +1646,16 @@ public class GraphicsPlane implements PixelBuffer {
|
||||
if (byteIdx < rawData.length) {
|
||||
int val = rawData[byteIdx] & 0xFF;
|
||||
if (val != 0) {
|
||||
setPixel(x + col, y + row, GocaConstants.getGocaColorArgb(val));
|
||||
int pixelColor = GocaConstants.getGocaColorArgb(val);
|
||||
if (m <= 1) {
|
||||
setPixel(x + col, y + row, pixelColor);
|
||||
} else {
|
||||
for (int dy = 0; dy < m; dy++) {
|
||||
for (int dx = 0; dx < m; dx++) {
|
||||
setPixel(x + col * m + dx, y + row * m + dy, pixelColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
package haus.nightmare.lib3270j.graphics;
|
||||
|
||||
/**
|
||||
* Calculates intermediate points for IBM GOCA ellipses and arcs using conjugate semi-axes P, Q, R, S.
|
||||
* Matches IBM Host On-Demand (com.ibm.eNetwork.ECL.hostgraphics.HODEllipse and HODGraphUtil).
|
||||
*/
|
||||
public class HODEllipse {
|
||||
|
||||
protected static final double[] dXgxdq = new double[] {
|
||||
1.0, 0.999847695, 0.999390827, 0.998629534, 0.99756405, 0.996194698,
|
||||
0.994521895, 0.992546151, 0.990268068, 0.98768834, 0.984807753, 0.981627183,
|
||||
0.9781476, 0.974370064, 0.970295726, 0.965925826, 0.961261695, 0.956304756,
|
||||
0.951056516, 0.945518575, 0.93969262, 0.933580426, 0.927183854, 0.920504853,
|
||||
0.913545457, 0.906307787, 0.898794046, 0.891006524, 0.882947592, 0.874619707,
|
||||
0.866025403, 0.8571673, 0.848048096, 0.838670567, 0.829037572, 0.819152044,
|
||||
0.809016994, 0.79863551, 0.788010753, 0.777145961, 0.766044443, 0.75470958,
|
||||
0.743144825, 0.731353701, 0.7193398, 0.707106781
|
||||
};
|
||||
|
||||
protected static final double[] dXgydq = new double[] {
|
||||
0.0, 0.017452406, 0.034899496, 0.052335956, 0.069756473, 0.087155742,
|
||||
0.104528463, 0.121869343, 0.139173101, 0.156434465, 0.173648177, 0.190808995,
|
||||
0.20791169, 0.224951054, 0.241921895, 0.258819045, 0.275637355, 0.292371704,
|
||||
0.309016994, 0.325568154, 0.342020143, 0.358367949, 0.374606593, 0.390731128,
|
||||
0.406736643, 0.422618261, 0.438371146, 0.453990499, 0.469471562, 0.48480962,
|
||||
0.5, 0.515038074, 0.529919264, 0.544639035, 0.559192903, 0.573576436,
|
||||
0.587785252, 0.601815023, 0.615661475, 0.629320391, 0.642787609, 0.656059029,
|
||||
0.669130606, 0.68199836, 0.69465837, 0.707106781
|
||||
};
|
||||
|
||||
protected double dP;
|
||||
protected double dQ;
|
||||
protected double dR;
|
||||
protected double dS;
|
||||
protected int iP;
|
||||
protected int iQ;
|
||||
protected int iR;
|
||||
protected int iS;
|
||||
protected double dRad;
|
||||
protected double dCenterX;
|
||||
protected double dCenterY;
|
||||
protected double[] dXq = new double[9];
|
||||
protected double[] dYq = new double[9];
|
||||
|
||||
public HODEllipse(double p, double q, double r, double s) {
|
||||
this.dP = p;
|
||||
this.dQ = q;
|
||||
this.dR = r;
|
||||
this.dS = s;
|
||||
this.iP = (int) Math.round(p);
|
||||
this.iQ = (int) Math.round(q);
|
||||
this.iR = (int) Math.round(r);
|
||||
this.iS = (int) Math.round(s);
|
||||
}
|
||||
|
||||
public int getHODPoints(int[] outX, int[] outY, int centerX, int centerY) {
|
||||
if (outX == null || outY == null || outX.length < 128 || outY.length < 128) {
|
||||
return 0;
|
||||
}
|
||||
int ptIdx = 0;
|
||||
int n5 = Math.max(Math.abs(this.iP) + Math.abs(this.iS), Math.abs(this.iR) + Math.abs(this.iQ));
|
||||
this.dRad = n5;
|
||||
this.dCenterX = centerX;
|
||||
this.dCenterY = centerY;
|
||||
if (n5 == 0) {
|
||||
outX[0] = centerX;
|
||||
outY[0] = centerY;
|
||||
return 1;
|
||||
}
|
||||
|
||||
int n6 = this.bres(this.dRad);
|
||||
int lastX = this.iP + centerX;
|
||||
int lastY = this.iS + centerY;
|
||||
|
||||
for (int i = 0; i <= n6; ++i) {
|
||||
this.filletHODSegment(outX, outY, ptIdx, this.dXq[i], this.dYq[i]);
|
||||
if (lastX == outX[ptIdx] && lastY == outY[ptIdx]) continue;
|
||||
lastX = outX[ptIdx];
|
||||
lastY = outY[ptIdx++];
|
||||
}
|
||||
for (int i = n6; i >= 0; --i) {
|
||||
this.filletHODSegment(outX, outY, ptIdx, this.dYq[i], this.dXq[i]);
|
||||
if (lastX == outX[ptIdx] && lastY == outY[ptIdx]) continue;
|
||||
lastX = outX[ptIdx];
|
||||
lastY = outY[ptIdx++];
|
||||
}
|
||||
for (int i = 0; i <= n6; ++i) {
|
||||
this.filletHODSegment(outX, outY, ptIdx, -this.dYq[i], this.dXq[i]);
|
||||
if (lastX == outX[ptIdx] && lastY == outY[ptIdx]) continue;
|
||||
lastX = outX[ptIdx];
|
||||
lastY = outY[ptIdx++];
|
||||
}
|
||||
for (int i = n6; i >= 0; --i) {
|
||||
this.filletHODSegment(outX, outY, ptIdx, -this.dXq[i], this.dYq[i]);
|
||||
if (lastX == outX[ptIdx] && lastY == outY[ptIdx]) continue;
|
||||
lastX = outX[ptIdx];
|
||||
lastY = outY[ptIdx++];
|
||||
}
|
||||
for (int i = 0; i <= n6; ++i) {
|
||||
this.filletHODSegment(outX, outY, ptIdx, -this.dXq[i], -this.dYq[i]);
|
||||
if (lastX == outX[ptIdx] && lastY == outY[ptIdx]) continue;
|
||||
lastX = outX[ptIdx];
|
||||
lastY = outY[ptIdx++];
|
||||
}
|
||||
for (int i = n6; i >= 0; --i) {
|
||||
this.filletHODSegment(outX, outY, ptIdx, -this.dYq[i], -this.dXq[i]);
|
||||
if (lastX == outX[ptIdx] && lastY == outY[ptIdx]) continue;
|
||||
lastX = outX[ptIdx];
|
||||
lastY = outY[ptIdx++];
|
||||
}
|
||||
for (int i = 0; i <= n6; ++i) {
|
||||
this.filletHODSegment(outX, outY, ptIdx, this.dYq[i], -this.dXq[i]);
|
||||
if (lastX == outX[ptIdx] && lastY == outY[ptIdx]) continue;
|
||||
lastX = outX[ptIdx];
|
||||
lastY = outY[ptIdx++];
|
||||
}
|
||||
for (int i = n6; i >= 0; --i) {
|
||||
this.filletHODSegment(outX, outY, ptIdx, this.dXq[i], -this.dYq[i]);
|
||||
if (lastX == outX[ptIdx] && lastY == outY[ptIdx]) continue;
|
||||
lastX = outX[ptIdx];
|
||||
lastY = outY[ptIdx++];
|
||||
}
|
||||
|
||||
outX[ptIdx] = outX[0];
|
||||
outY[ptIdx++] = outY[0];
|
||||
return ptIdx;
|
||||
}
|
||||
|
||||
protected int bres(double rad) {
|
||||
for (int i = 0; i < 9; ++i) {
|
||||
this.dXq[i] = rad * dXgxdq[i * 5 + 2];
|
||||
this.dYq[i] = rad * dXgydq[i * 5 + 2];
|
||||
}
|
||||
return 8;
|
||||
}
|
||||
|
||||
protected void filletHODSegment(int[] outX, int[] outY, int idx, double d, double d2) {
|
||||
outX[idx] = (int) Math.round(this.dP * d / this.dRad + this.dR * d2 / this.dRad + this.dCenterX);
|
||||
outY[idx] = (int) Math.round(-(this.dS * d / this.dRad + this.dQ * d2 / this.dRad) + this.dCenterY);
|
||||
}
|
||||
|
||||
protected void filletHODSegment2(int[] outX, int[] outY, int idx, double d, double d2) {
|
||||
outX[idx] = (int) Math.round(this.dP * d / this.dRad + this.dR * d2 / this.dRad + this.dCenterX);
|
||||
outY[idx] = (int) Math.round(this.dS * d / this.dRad + this.dQ * d2 / this.dRad + this.dCenterY);
|
||||
}
|
||||
|
||||
public static int getHODEllipse(double p, double q, double r, double s, int[] outX, int[] outY, int cx, int cy) {
|
||||
HODEllipse ellipse = new HODEllipse(p, q, r, s);
|
||||
return ellipse.getHODPoints(outX, outY, cx, cy);
|
||||
}
|
||||
}
|
||||
@@ -18,11 +18,12 @@ public class ProgramSymbolManager {
|
||||
public static final int NUMBER_GRAPHICS_SYMBOL_SETS = 4; // RWS 8..11 (Sets 6..9)
|
||||
|
||||
private int defaultCellWidth = 9;
|
||||
private int defaultCellHeight = 12; // Standard IBM 3279 PS Slot Default Height (SDH = 0x0C = 12)
|
||||
private int defaultCellHeight = 16; // Standard IBM 9x16 Programmed Symbol cell matrix
|
||||
|
||||
|
||||
public void setDefaultCellDimensions(int width, int height) {
|
||||
this.defaultCellWidth = (width > 0) ? width : 9;
|
||||
this.defaultCellHeight = (height > 0) ? height : 12;
|
||||
this.defaultCellHeight = (height > 0) ? height : 16;
|
||||
}
|
||||
|
||||
public int getDefaultCellWidth() {
|
||||
@@ -194,20 +195,24 @@ public class ProgramSymbolManager {
|
||||
boolean isTriplePlane = (rws >= 4 && rws <= 7);
|
||||
ProgramSymbolSet set = sets[setIndex];
|
||||
|
||||
if (clearAll) {
|
||||
set.clear();
|
||||
}
|
||||
|
||||
int extHeaderLen = 0;
|
||||
int cellWidth = defaultCellWidth;
|
||||
int cellHeight = defaultCellHeight;
|
||||
int colorPlane = 0; // 0 = all planes, 1 = Red, 2 = Green, 4 = Blue
|
||||
int cellWidth = (loadFormat == 1 || rws < 8) ? 9 : defaultCellWidth;
|
||||
int cellHeight = (loadFormat == 1 || rws < 8) ? 16 : defaultCellHeight;
|
||||
int colorPlane = 0; // 0 = all planes, 1 = Blue, 2 = Red, 4 = Green
|
||||
|
||||
if (hasExtHeader && data.length > 4) {
|
||||
extHeaderLen = data[4] & 0xFF;
|
||||
if (extHeaderLen > 3 && data.length > 6) {
|
||||
int lw = data[6] & 0xFF;
|
||||
if (lw > 0) cellWidth = lw;
|
||||
if (lw > 0 && loadFormat != 1) cellWidth = lw;
|
||||
}
|
||||
if (extHeaderLen > 4 && data.length > 7) {
|
||||
int lh = data[7] & 0xFF;
|
||||
if (lh > 0) cellHeight = lh;
|
||||
if (lh > 0 && loadFormat != 1) cellHeight = lh;
|
||||
}
|
||||
if (extHeaderLen >= 6 && data.length > 9) {
|
||||
colorPlane = data[9] & 0xFF;
|
||||
@@ -223,55 +228,34 @@ public class ProgramSymbolManager {
|
||||
int remaining = data.length - offset;
|
||||
int codeIndex = (startCodePoint >= 0x40) ? (startCodePoint - 0x40) : startCodePoint;
|
||||
|
||||
int sliceBytes = (loadFormat == 1) ? 18 : (cellWidth * cellHeight + 7) / 8;
|
||||
if (sliceBytes <= 0) sliceBytes = 18;
|
||||
|
||||
int bytesPerSymbol;
|
||||
if (isTriplePlane && colorPlane == 0 && remaining >= sliceBytes * 3) {
|
||||
bytesPerSymbol = sliceBytes * 3;
|
||||
} else {
|
||||
bytesPerSymbol = sliceBytes;
|
||||
}
|
||||
int bytesPerSymbol = (loadFormat == 1) ? 18 : (cellWidth * cellHeight + 7) / 8;
|
||||
if (bytesPerSymbol <= 0) bytesPerSymbol = 18;
|
||||
|
||||
while (remaining >= bytesPerSymbol && codeIndex < ProgramSymbolSet.NUM_SLOTS) {
|
||||
byte[] pixelData = new byte[cellWidth * cellHeight];
|
||||
ProgramSymbolSet.SymbolSlot existing = set.getSlot(codeIndex);
|
||||
if (!clearAll && existing != null && existing.getWidth() == cellWidth && existing.getHeight() == cellHeight) {
|
||||
System.arraycopy(existing.getPixelData(), 0, pixelData, 0, Math.min(existing.getPixelData().length, pixelData.length));
|
||||
|
||||
// If loading a specific color plane (colorPlane != 0), merge with existing ONLY IF existing was not all-planes
|
||||
if (isTriplePlane && colorPlane != 0 && existing != null && !existing.isAllPlanesLoaded()
|
||||
&& existing.getWidth() == cellWidth && existing.getHeight() == cellHeight) {
|
||||
System.arraycopy(existing.getPixelData(), 0, pixelData, 0,
|
||||
Math.min(existing.getPixelData().length, pixelData.length));
|
||||
}
|
||||
|
||||
if (isTriplePlane && colorPlane == 0 && bytesPerSymbol == sliceBytes * 3) {
|
||||
// 3 consecutive slices: Red (plane 1), Green (plane 2), Blue (plane 4)
|
||||
if (loadFormat == 1) {
|
||||
unpackFormat1(data, offset, pixelData, cellWidth, cellHeight, true, 1);
|
||||
unpackFormat1(data, offset + sliceBytes, pixelData, cellWidth, cellHeight, true, 2);
|
||||
unpackFormat1(data, offset + sliceBytes * 2, pixelData, cellWidth, cellHeight, true, 4);
|
||||
} else {
|
||||
unpackFormat3(data, offset, pixelData, cellWidth, cellHeight, true, 1);
|
||||
unpackFormat3(data, offset + sliceBytes, pixelData, cellWidth, cellHeight, true, 2);
|
||||
unpackFormat3(data, offset + sliceBytes * 2, pixelData, cellWidth, cellHeight, true, 4);
|
||||
}
|
||||
if (loadFormat == 1) {
|
||||
unpackFormat1(data, offset, pixelData, cellWidth, cellHeight, isTriplePlane, colorPlane);
|
||||
} else {
|
||||
if (loadFormat == 1) {
|
||||
unpackFormat1(data, offset, pixelData, cellWidth, cellHeight, isTriplePlane, colorPlane);
|
||||
} else {
|
||||
unpackFormat3(data, offset, pixelData, cellWidth, cellHeight, isTriplePlane, colorPlane);
|
||||
}
|
||||
unpackFormat3(data, offset, pixelData, cellWidth, cellHeight, isTriplePlane, colorPlane);
|
||||
}
|
||||
|
||||
set.setSlot(codeIndex, new ProgramSymbolSet.SymbolSlot(cellWidth, cellHeight, pixelData, isTriplePlane));
|
||||
boolean allPlanes = (colorPlane == 0);
|
||||
set.setSlot(codeIndex, new ProgramSymbolSet.SymbolSlot(cellWidth, cellHeight, pixelData, isTriplePlane, allPlanes));
|
||||
|
||||
codeIndex++;
|
||||
offset += bytesPerSymbol;
|
||||
remaining -= bytesPerSymbol;
|
||||
}
|
||||
|
||||
if (clearAll) {
|
||||
for (int i = codeIndex; i < ProgramSymbolSet.NUM_SLOTS; i++) {
|
||||
set.clearSlot(i);
|
||||
}
|
||||
}
|
||||
|
||||
if (logger.isLoggable(Level.FINE)) {
|
||||
logger.fine(String.format("LOADPS: Loaded PS Set LCID=0x%02X (RWS=%d, %s, %dx%d, %d glyphs)",
|
||||
lcid, rws, isTriplePlane ? "Triple-Plane" : "Single-Plane",
|
||||
|
||||
@@ -62,6 +62,7 @@ public class ProgramSymbolSet {
|
||||
private final int height;
|
||||
private final byte[] pixelData; // 1 byte per pixel: 0 = background, 1..7 = color index (or 1 for monochrome)
|
||||
private final boolean isTriplePlane;
|
||||
private final boolean allPlanesLoaded;
|
||||
private int[] cachedRgbArray;
|
||||
private int cachedFgRgb = -1;
|
||||
private int cachedBgRgb = -1;
|
||||
@@ -73,10 +74,19 @@ public class ProgramSymbolSet {
|
||||
private int cachedScaledBgRgb = -1;
|
||||
|
||||
public SymbolSlot(int width, int height, byte[] pixelData, boolean isTriplePlane) {
|
||||
this(width, height, pixelData, isTriplePlane, false);
|
||||
}
|
||||
|
||||
public SymbolSlot(int width, int height, byte[] pixelData, boolean isTriplePlane, boolean allPlanesLoaded) {
|
||||
this.width = width > 0 ? width : 9;
|
||||
this.height = height > 0 ? height : 16;
|
||||
this.pixelData = pixelData;
|
||||
this.isTriplePlane = isTriplePlane;
|
||||
this.allPlanesLoaded = allPlanesLoaded;
|
||||
}
|
||||
|
||||
public boolean isAllPlanesLoaded() {
|
||||
return allPlanesLoaded;
|
||||
}
|
||||
|
||||
public int getWidth() {
|
||||
@@ -183,18 +193,18 @@ public class ProgramSymbolSet {
|
||||
} else if (!isTriplePlane) {
|
||||
rgbArray[idx] = fgArgb;
|
||||
} else {
|
||||
// Triple-Plane RGB composite aligned with IBM Host On-Demand 16-color GOCA palette:
|
||||
// val is bitmask: bit 0 (0x01) = Red, bit 1 (0x02) = Green, bit 2 (0x04) = Blue
|
||||
// Triple-Plane RGB composite aligned with IBM 3179G / HoD GOCA palette:
|
||||
// val bitmask: bit 0 (0x01) = Red, bit 1 (0x02) = Green, bit 2 (0x04) = Blue
|
||||
int colorMask = val & 0x07;
|
||||
int colorArgb;
|
||||
switch (colorMask) {
|
||||
case 1: colorArgb = GocaConstants.GOCA_COLORS[2]; break; // Red
|
||||
case 2: colorArgb = GocaConstants.GOCA_COLORS[4]; break; // Green
|
||||
case 3: colorArgb = GocaConstants.GOCA_COLORS[6]; break; // Yellow
|
||||
case 4: colorArgb = GocaConstants.GOCA_COLORS[1]; break; // Blue (CUSTOMBLUE)
|
||||
case 5: colorArgb = GocaConstants.GOCA_COLORS[3]; break; // Pink
|
||||
case 6: colorArgb = GocaConstants.GOCA_COLORS[5]; break; // Turquoise
|
||||
case 7: colorArgb = GocaConstants.GOCA_COLORS[7]; break; // White
|
||||
case 3: colorArgb = GocaConstants.GOCA_COLORS[6]; break; // Yellow (Red + Green)
|
||||
case 4: colorArgb = GocaConstants.GOCA_COLORS[1]; break; // Blue
|
||||
case 5: colorArgb = GocaConstants.GOCA_COLORS[3]; break; // Pink (Red + Blue)
|
||||
case 6: colorArgb = GocaConstants.GOCA_COLORS[5]; break; // Turquoise (Green + Blue)
|
||||
case 7: colorArgb = GocaConstants.GOCA_COLORS[7]; break; // White (Red + Green + Blue)
|
||||
default: colorArgb = bgArgb; break;
|
||||
}
|
||||
rgbArray[idx] = colorArgb;
|
||||
|
||||
@@ -846,6 +846,8 @@ public class InputProcessor {
|
||||
if (graphicsPlane != null) {
|
||||
graphicsPlane.clear();
|
||||
}
|
||||
lastAid = aidCode;
|
||||
setKeyboardLocked(true);
|
||||
byte[] data = new byte[] { (byte) aidCode };
|
||||
sendAidResponse(data);
|
||||
return;
|
||||
@@ -948,6 +950,15 @@ public class InputProcessor {
|
||||
* Transmit a mouse / light-pen graphic input AID matching IBM Host On-Demand PS3179G and DS3270.
|
||||
*/
|
||||
public void sendGraphicMouseAid(int aidCode, int button, boolean isShift, boolean isCtrl) {
|
||||
boolean hasGraphics = gocaDecoder != null && (gocaDecoder.isGraphicsCursorActive() ||
|
||||
(gocaDecoder.getGraphicsPlane() != null && gocaDecoder.getGraphicsPlane().hasContent()));
|
||||
if (hasGraphics) {
|
||||
int gx = gocaDecoder.getGraphicCursorX();
|
||||
int gy = gocaDecoder.getGraphicCursorY();
|
||||
sendGraphicMouseAid(gx, gy, aidCode, button, isShift, isCtrl);
|
||||
return;
|
||||
}
|
||||
|
||||
if (fsm != null && fsm.getConnectionState() != null && !fsm.getConnectionState().isFullSession()) {
|
||||
return;
|
||||
}
|
||||
@@ -957,41 +968,51 @@ public class InputProcessor {
|
||||
setKeyboardLocked(true);
|
||||
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
if (gocaDecoder != null && gocaDecoder.isGraphicsCursorActive()) {
|
||||
int gx = gocaDecoder.getGraphicCursorX();
|
||||
int gy = gocaDecoder.getGraphicCursorY();
|
||||
int cols = (screen != null && screen.getCols() > 0) ? screen.getCols() : 80;
|
||||
int numRows = (screen != null && screen.getRows() > 0) ? screen.getRows() : 24;
|
||||
int cursorAddr = screen != null ? screen.getCursorAddress() : 0;
|
||||
int row = cursorAddr / cols;
|
||||
int col = cursorAddr % cols;
|
||||
byte[] rma = buildReadModifiedInboundData(aidCode, false);
|
||||
try {
|
||||
out.write(rma);
|
||||
} catch (java.io.IOException ignored) {}
|
||||
sendAidResponse(out.toByteArray());
|
||||
}
|
||||
|
||||
byte[] sf = haus.nightmare.lib3270j.graphics.GraphicInputBuilder.buildGraphicInput(
|
||||
gx, gy, row, col, button, true, isShift, isCtrl
|
||||
);
|
||||
|
||||
StringBuilder sfHex = new StringBuilder();
|
||||
for (byte b : sf) {
|
||||
sfHex.append(String.format("%02X ", b & 0xFF));
|
||||
}
|
||||
log.info(String.format(
|
||||
"sendGraphicMouseAid: goca=(%d, %d) row=%d col=%d cursorAddr=%d btn=%d shift=%b ctrl=%b SF_HEX=[%s]",
|
||||
gx, gy, row, col, cursorAddr, button, isShift, isCtrl, sfHex.toString().trim()
|
||||
));
|
||||
|
||||
// Structured Field AID (0x88) + 56-byte Graphic Input SF
|
||||
out.write(AID_SF);
|
||||
try {
|
||||
out.write(sf);
|
||||
byte[] rma = buildReadModifiedInboundData(aidCode, false);
|
||||
out.write(rma);
|
||||
} catch (java.io.IOException ignored) {}
|
||||
} else {
|
||||
byte[] rma = buildReadModifiedInboundData(aidCode, false);
|
||||
try {
|
||||
out.write(rma);
|
||||
} catch (java.io.IOException ignored) {}
|
||||
/**
|
||||
* Transmits a mouse / light-pen graphic input structured field AID with explicit GOCA coordinates.
|
||||
*/
|
||||
public void sendGraphicMouseAid(int gx, int gy, int aidCode, int button, boolean isShift, boolean isCtrl) {
|
||||
if (fsm != null && fsm.getConnectionState() != null && !fsm.getConnectionState().isFullSession()) {
|
||||
return;
|
||||
}
|
||||
if (isKeyboardLocked()) {
|
||||
return;
|
||||
}
|
||||
setKeyboardLocked(true);
|
||||
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
int cols = (screen != null && screen.getCols() > 0) ? screen.getCols() : 80;
|
||||
int cursorAddr = screen != null ? screen.getCursorAddress() : 0;
|
||||
int row = cursorAddr / cols;
|
||||
int col = cursorAddr % cols;
|
||||
|
||||
byte[] sf = haus.nightmare.lib3270j.graphics.GraphicInputBuilder.buildGraphicInput(
|
||||
gx, gy, row, col, button, true, isShift, isCtrl
|
||||
);
|
||||
|
||||
StringBuilder sfHex = new StringBuilder();
|
||||
for (byte b : sf) {
|
||||
sfHex.append(String.format("%02X ", b & 0xFF));
|
||||
}
|
||||
log.info(String.format(
|
||||
"sendGraphicMouseAid: goca=(%d, %d) row=%d col=%d cursorAddr=%d btn=%d shift=%b ctrl=%b SF_HEX=[%s]",
|
||||
gx, gy, row, col, cursorAddr, button, isShift, isCtrl, sfHex.toString().trim()
|
||||
));
|
||||
|
||||
// Structured Field AID (0x88) + 56-byte Graphic Input SF
|
||||
out.write(AID_SF);
|
||||
try {
|
||||
out.write(sf);
|
||||
byte[] rma = buildReadModifiedInboundData(aidCode, false);
|
||||
out.write(rma);
|
||||
} catch (java.io.IOException ignored) {}
|
||||
|
||||
sendAidResponse(out.toByteArray());
|
||||
}
|
||||
@@ -1030,7 +1051,12 @@ public class InputProcessor {
|
||||
screen.setCursorAddress(address);
|
||||
|
||||
// Immediate Selection (Space or Null) -> AID_SELECT (0x7E)
|
||||
// Per IBM 3270 Reference (GA23-0059), light-pen selection has no effect
|
||||
// on protected fields with a space or null designator character.
|
||||
if (ebcdic == 0x00 || ebcdic == 0x40 || ascii == ' ' || ascii == 0 || ascii == '\u3000') {
|
||||
if (haus.nightmare.lib3270j.protocol.DS3270Constants.faIsProtected(fa)) {
|
||||
return false;
|
||||
}
|
||||
faCell.fa |= haus.nightmare.lib3270j.protocol.DS3270Constants.FA_MODIFY;
|
||||
sendAid(haus.nightmare.lib3270j.protocol.DS3270Constants.AID_SELECT);
|
||||
return true;
|
||||
|
||||
@@ -1448,6 +1448,11 @@ public class TelnetFSM {
|
||||
* Send a 3270 data record, with TN3270E header if in TN3270E mode.
|
||||
*/
|
||||
public void send3270Data(byte[] data) {
|
||||
if (data != null && data.length >= 2 && (data[data.length - 2] & 0xFF) == IAC && (data[data.length - 1] & 0xFF) == EOR) {
|
||||
byte[] trimmed = new byte[data.length - 2];
|
||||
System.arraycopy(data, 0, trimmed, 0, trimmed.length);
|
||||
data = trimmed;
|
||||
}
|
||||
if (tn3270eNegotiated) {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream(data.length + EH_SIZE);
|
||||
// TN3270E header
|
||||
|
||||
@@ -376,6 +376,14 @@ public class Xfer3270 implements FileTransferInterface {
|
||||
config.setTransferMode(this.option_ASCII ? FTConfig.TransferMode.ASCII : FTConfig.TransferMode.BINARY);
|
||||
config.setCrAction(this.option_CRLF ? FTConfig.CrAction.REMOVE : FTConfig.CrAction.KEEP);
|
||||
config.setExistAction(this.option_APPEND ? FTConfig.ExistAction.APPEND : FTConfig.ExistAction.REPLACE);
|
||||
config.setClear(this.bSendClear);
|
||||
config.setDbcs(this.currentCodePage != null && this.currentCodePage.isDBCS());
|
||||
config.setOptionNew(this.option_NEW);
|
||||
config.setOptionNoso(this.option_NOSO);
|
||||
if (this.option_UNICODE) {
|
||||
config.setUnicode(true);
|
||||
config.setUnicodeType(this.unicodeType);
|
||||
}
|
||||
config.setDftBufferSize(bmtuSize);
|
||||
if (options != null && !options.isEmpty()) {
|
||||
config.parseOptions(options);
|
||||
@@ -387,7 +395,38 @@ public class Xfer3270 implements FileTransferInterface {
|
||||
public void getFile(String command, String hostFile, String localFile, int mode,
|
||||
String options, ECLXferListener listener) throws ECLErr {
|
||||
FileTransferStatusInterface statusAdapter = createStatusAdapter(listener, localFile, hostFile);
|
||||
getFile(hostFile, MVS_TSO, localFile, mode, statusAdapter, options);
|
||||
setOptionFlags(options);
|
||||
if (mode == BINARY) {
|
||||
this.option_ASCII = false;
|
||||
} else {
|
||||
this.option_ASCII = true;
|
||||
}
|
||||
|
||||
FTConfig config = new FTConfig();
|
||||
config.setDirection(FTConfig.Direction.RECEIVE);
|
||||
config.setLocalFilename(localFile);
|
||||
config.setHostFilename(hostFile);
|
||||
config.setHostType(FTConfig.HostType.TSO);
|
||||
config.setTransferMode(this.option_ASCII ? FTConfig.TransferMode.ASCII : FTConfig.TransferMode.BINARY);
|
||||
config.setCrAction(this.option_CRLF ? FTConfig.CrAction.REMOVE : FTConfig.CrAction.KEEP);
|
||||
config.setExistAction(this.option_APPEND ? FTConfig.ExistAction.APPEND : FTConfig.ExistAction.REPLACE);
|
||||
config.setClear(this.bSendClear);
|
||||
config.setDbcs(this.currentCodePage != null && this.currentCodePage.isDBCS());
|
||||
config.setOptionNew(this.option_NEW);
|
||||
config.setOptionNoso(this.option_NOSO);
|
||||
if (command != null && !command.trim().isEmpty()) {
|
||||
config.setProgramName(command.trim());
|
||||
}
|
||||
if (this.option_UNICODE) {
|
||||
config.setUnicode(true);
|
||||
config.setUnicodeType(this.unicodeType);
|
||||
}
|
||||
config.setDftBufferSize(bmtuSize);
|
||||
if (options != null && !options.isEmpty()) {
|
||||
config.parseOptions(options);
|
||||
}
|
||||
|
||||
executeTransfer(config, statusAdapter, localFile, hostFile);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -408,6 +447,15 @@ public class Xfer3270 implements FileTransferInterface {
|
||||
(hostType == CICS ? FTConfig.HostType.CICS : FTConfig.HostType.TSO));
|
||||
config.setTransferMode(this.option_ASCII ? FTConfig.TransferMode.ASCII : FTConfig.TransferMode.BINARY);
|
||||
config.setCrAction(this.option_CRLF ? FTConfig.CrAction.REMOVE : FTConfig.CrAction.KEEP);
|
||||
config.setExistAction(this.option_APPEND ? FTConfig.ExistAction.APPEND : FTConfig.ExistAction.REPLACE);
|
||||
config.setClear(this.bSendClear);
|
||||
config.setDbcs(this.currentCodePage != null && this.currentCodePage.isDBCS());
|
||||
config.setOptionNew(this.option_NEW);
|
||||
config.setOptionNoso(this.option_NOSO);
|
||||
if (this.option_UNICODE) {
|
||||
config.setUnicode(true);
|
||||
config.setUnicodeType(this.unicodeType);
|
||||
}
|
||||
config.setRecfm(recfm);
|
||||
config.setLrecl(String.valueOf(lrecl));
|
||||
config.setBlksize(String.valueOf(blksize));
|
||||
@@ -429,7 +477,48 @@ public class Xfer3270 implements FileTransferInterface {
|
||||
public void putFile(String command, String hostFile, String localFile, int mode,
|
||||
String options, ECLXferListener listener) throws ECLErr {
|
||||
FileTransferStatusInterface statusAdapter = createStatusAdapter(listener, localFile, hostFile);
|
||||
putFile(localFile, MVS_TSO, hostFile, mode, statusAdapter, options);
|
||||
setOptionFlags(options);
|
||||
if (mode == BINARY) {
|
||||
this.option_ASCII = false;
|
||||
} else {
|
||||
this.option_ASCII = true;
|
||||
}
|
||||
|
||||
FTConfig config = new FTConfig();
|
||||
config.setDirection(FTConfig.Direction.SEND);
|
||||
config.setLocalFilename(localFile);
|
||||
config.setHostFilename(hostFile);
|
||||
config.setHostType(FTConfig.HostType.TSO);
|
||||
config.setTransferMode(this.option_ASCII ? FTConfig.TransferMode.ASCII : FTConfig.TransferMode.BINARY);
|
||||
config.setCrAction(this.option_CRLF ? FTConfig.CrAction.REMOVE : FTConfig.CrAction.KEEP);
|
||||
config.setExistAction(this.option_APPEND ? FTConfig.ExistAction.APPEND : FTConfig.ExistAction.REPLACE);
|
||||
config.setClear(this.bSendClear);
|
||||
config.setDbcs(this.currentCodePage != null && this.currentCodePage.isDBCS());
|
||||
config.setOptionNew(this.option_NEW);
|
||||
config.setOptionNoso(this.option_NOSO);
|
||||
if (command != null && !command.trim().isEmpty()) {
|
||||
config.setProgramName(command.trim());
|
||||
}
|
||||
if (this.option_UNICODE) {
|
||||
config.setUnicode(true);
|
||||
config.setUnicodeType(this.unicodeType);
|
||||
}
|
||||
config.setRecfm(recfm);
|
||||
config.setLrecl(String.valueOf(lrecl));
|
||||
config.setBlksize(String.valueOf(blksize));
|
||||
config.setDftBufferSize(bmtuSize);
|
||||
if (primarySpace > 0) {
|
||||
config.setSpace(primarySpace + (secondarySpace > 0 ? "," + secondarySpace : ""));
|
||||
}
|
||||
if ("TRACKS".equalsIgnoreCase(spaceUnits)) config.setUnits(FTConfig.AllocationUnit.TRACKS);
|
||||
else if ("CYLINDERS".equalsIgnoreCase(spaceUnits)) config.setUnits(FTConfig.AllocationUnit.CYLINDERS);
|
||||
else if ("AVBLOCK".equalsIgnoreCase(spaceUnits)) config.setUnits(FTConfig.AllocationUnit.AVBLOCK);
|
||||
|
||||
if (options != null && !options.isEmpty()) {
|
||||
config.parseOptions(options);
|
||||
}
|
||||
|
||||
executeTransfer(config, statusAdapter, localFile, hostFile);
|
||||
}
|
||||
|
||||
private void executeTransfer(FTConfig config, FileTransferStatusInterface status,
|
||||
|
||||
Reference in New Issue
Block a user