Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
149 changes: 149 additions & 0 deletions gitgalaxy/tools/cobol_to_java/cics_task_tsqueues.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
#!/usr/bin/env python3
# ==============================================================================
# GitGalaxy Tool: CicsTask's temporary-storage queue support (#4002, split out by #5274)
#
# PURPOSE:
# The TS queue family of the CICS task runtime the transaction forge emits (CICS_TASK_JAVA): withTempStorage, WRITEQ /
# REWRITEQ / READQ TS (readqTs, readqTsNext), the TsResult record and the region's TempStorage store. The forge
# splices it into CicsTask where it always sat, so the class is byte-identical.
# ==============================================================================

TSQUEUES_JAVA = """\
/** The region's temporary storage this task works on (#4002): one store is shared by every task of a
* conversation, the way a region's TS queues outlive the task that writes them. */
public CicsTask withTempStorage(TempStorage storage) {
this.tempStorage = storage;
return this;
}

/** WRITEQ TS QUEUE(queue) FROM(data) (#4002): appends an item, creating the queue with its first write;
* the result's item is the number assigned. An item is the bytes the program wrote, in the region's code
* page (EBCDIC). LENGERR when data is empty or longer than 32763 bytes. */
public TsResult writeqTs(String queue, byte[] data) {
return tempStorage.write(this, queue, 0, data);
}

/** WRITEQ TS QUEUE(queue) FROM(data) ITEM(item) REWRITE (#4002): QIDERR without the queue, ITEMERR outside it. */
public TsResult rewriteqTs(String queue, int item, byte[] data) {
return tempStorage.write(this, queue, item, data);
}

/** READQ TS QUEUE(queue) INTO LENGTH(maxLength) ITEM(item) (#4002, IBM EXEC CICS READQ TS): QIDERR when
* the queue does not exist, ITEMERR for an item outside it; else the item, truncated to maxLength with
* LENGERR when longer, and the result's length is the item's own. */
public TsResult readqTs(String queue, int item, int maxLength) {
return tempStorage.read(this, queue, item, maxLength);
}

/** DELETEQ TS QUEUE(queue) (#4415 slice 2, IBM EXEC CICS DELETEQ TS): the whole queue and its READQ NEXT position;
* QIDERR (44) when it does not exist, INVREQ (16) for a name of binary zeros only. Returns RESP (RESP2 is 0: IBM
* lists none). */
public int deleteqTs(String queue) {
return tempStorage.delete(queue);
}

/** READQ TS QUEUE(queue) NEXT: the item after the last one read by any task (ITEMERR past the end). */
public TsResult readqTsNext(String queue, int maxLength) {
return tempStorage.read(this, queue, 0, maxLength);
}

/** A TS command's outcome: its condition, the item (number assigned or read), the LENGTH it sets (READQ;
* -1 when it sets none), the data moved INTO (READQ; null when none) and NUMITEMS. */
public record TsResult(String resp, int item, int length, byte[] data, int numItems) {
}

/** A region's temporary storage (#4002): TS queues by name, each a list of items, and the READQ NEXT
* position of each queue (which counts a read by ITEM too). */
public static final class TempStorage {
private final Map<String, List<byte[]>> queues = new LinkedHashMap<>();
private final Map<String, Integer> next = new HashMap<>();
/** The intrapartition transient-data queues' records (#5079): first in first out. */
final Map<String, java.util.ArrayDeque<String>> td = new HashMap<>();
/** The run-time open state SET TDQUEUE OPENSTATUS left on an extrapartition queue (#5168, X61): true open, false closed. */
final Map<String, Boolean> tdOpen = new HashMap<>();
/** The queues SET TDQUEUE ENABLESTATUS(DISABLED) left disabled (#5168 round 5, X66). */
final java.util.Set<String> tdDisabled = new java.util.HashSet<>();
/** The levels SET TDQUEUE TRIGGERLEVEL left (#5168 round 7, X66). */
final java.util.Map<String, Integer> tdTrigger = new java.util.HashMap<>();
/** The extrapartition queues a READQ TD has taken a record from (reopening an input queue would restart it). */
final java.util.Set<String> tdRead = new java.util.HashSet<>();

/** A transient-data queue's records as they are before the conversation starts (#5168). */
public TempStorage seedTd(String queue, List<String> records) {
td.put(queue, new java.util.ArrayDeque<>(records));
return this;
}

/** A queue as it is before the conversation starts. */
public TempStorage seed(String queue, List<byte[]> items) {
queues.put(queue, new ArrayList<>(items));
return this;
}

/** Every queue and its items, in the order the queues were created. */
public Map<String, List<byte[]>> queues() {
Map<String, List<byte[]>> out = new LinkedHashMap<>();
queues.forEach((q, items) -> out.put(q, List.copyOf(items)));
return out;
}

int delete(String queue) {
if (!queue.isEmpty() && queue.chars().allMatch(c -> c == 0)) {
return 16;
}
if (queues.remove(queue) == null) {
return 44;
}
next.remove(queue);
return 0;
}

TsResult write(CicsTask task, String queue, int rewrite, byte[] data) {
List<byte[]> items = queues.get(queue);
String resp = "NORMAL";
int item = 0;
int[] planned = task.root().injected("WRITEQ-TS", queue.stripTrailing()); // #4049: nothing is written
if (planned != null) {
resp = respName(planned[0]);
} else if (data == null || data.length < 1 || data.length > 32763) {
resp = "LENGERR";
} else if (rewrite > 0 && items == null) {
resp = "QIDERR";
} else if (rewrite > 0 && rewrite > items.size()) {
resp = "ITEMERR";
} else {
if (items == null) {
items = new ArrayList<>();
queues.put(queue, items);
}
if (rewrite > 0) {
items.set(rewrite - 1, data.clone());
item = rewrite;
} else {
items.add(data.clone());
item = items.size();
}
}
task.event("WRITEQ-TS", "queue", queue, "data", data, "resp", resp, "item", item == 0 ? null : item);
return new TsResult(resp, item, -1, null, items == null ? 0 : items.size());
}

TsResult read(CicsTask task, String queue, int item, int maxLength) {
List<byte[]> items = queues.get(queue);
int want = item > 0 ? item : next.getOrDefault(queue, 0) + 1;
Object shown = item > 0 ? (Object) item : "NEXT";
if (items == null || want < 1 || want > items.size()) {
String resp = items == null ? "QIDERR" : "ITEMERR";
task.event("READQ-TS", "queue", queue, "item", shown, "resp", resp, "length", null, "data", null);
return new TsResult(resp, want, -1, null, 0);
}
next.put(queue, want);
byte[] stored = items.get(want - 1);
byte[] data = stored.length > maxLength ? Arrays.copyOf(stored, Math.max(maxLength, 0)) : stored.clone();
String resp = stored.length > maxLength ? "LENGERR" : "NORMAL";
task.event("READQ-TS", "queue", queue, "item", shown, "resp", resp, "length", stored.length, "data", data);
return new TsResult(resp, want, stored.length, data, items.size());
}
}

"""
141 changes: 4 additions & 137 deletions gitgalaxy/tools/cobol_to_java/cobol_to_java_transaction_forge.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
from gitgalaxy.tools.cobol_to_java.cics_task_filecontrol import FILECONTROL_JAVA
from gitgalaxy.tools.cobol_to_java.cics_task_taskcontrol import TASKCONTROL_JAVA
from gitgalaxy.tools.cobol_to_java.cics_task_tdqueues import TDQUEUES_JAVA
from gitgalaxy.tools.cobol_to_java.cics_task_tsqueues import TSQUEUES_JAVA
from gitgalaxy.tools.cobol_to_java.cics_task_web import WEB_REQUEST_JAVA, WEB_SESSION_JAVA
from gitgalaxy.tools.cobol_to_java.cobol_to_java_common import (
ClassNames,
Expand Down Expand Up @@ -1924,144 +1925,10 @@ def commarea_alternative_todos(commarea: dict) -> list[str]:
# #5274: the faults family's methods (withFaults, injected, logFault, logAbend, planned, the deferred DUPKEY) live in
# their own module (cics_task_faults.py).
CICS_TASK_JAVA += FAULTS_JAVA
# #5274: the temporary-storage queue family (withTempStorage, the TS commands, TsResult, TempStorage) lives in its own
# module (cics_task_tsqueues.py).
CICS_TASK_JAVA += TSQUEUES_JAVA
CICS_TASK_JAVA += """\
/** The region's temporary storage this task works on (#4002): one store is shared by every task of a
* conversation, the way a region's TS queues outlive the task that writes them. */
public CicsTask withTempStorage(TempStorage storage) {
this.tempStorage = storage;
return this;
}

/** WRITEQ TS QUEUE(queue) FROM(data) (#4002): appends an item, creating the queue with its first write;
* the result's item is the number assigned. An item is the bytes the program wrote, in the region's code
* page (EBCDIC). LENGERR when data is empty or longer than 32763 bytes. */
public TsResult writeqTs(String queue, byte[] data) {
return tempStorage.write(this, queue, 0, data);
}

/** WRITEQ TS QUEUE(queue) FROM(data) ITEM(item) REWRITE (#4002): QIDERR without the queue, ITEMERR outside it. */
public TsResult rewriteqTs(String queue, int item, byte[] data) {
return tempStorage.write(this, queue, item, data);
}

/** READQ TS QUEUE(queue) INTO LENGTH(maxLength) ITEM(item) (#4002, IBM EXEC CICS READQ TS): QIDERR when
* the queue does not exist, ITEMERR for an item outside it; else the item, truncated to maxLength with
* LENGERR when longer, and the result's length is the item's own. */
public TsResult readqTs(String queue, int item, int maxLength) {
return tempStorage.read(this, queue, item, maxLength);
}

/** DELETEQ TS QUEUE(queue) (#4415 slice 2, IBM EXEC CICS DELETEQ TS): the whole queue and its READQ NEXT position;
* QIDERR (44) when it does not exist, INVREQ (16) for a name of binary zeros only. Returns RESP (RESP2 is 0: IBM
* lists none). */
public int deleteqTs(String queue) {
return tempStorage.delete(queue);
}

/** READQ TS QUEUE(queue) NEXT: the item after the last one read by any task (ITEMERR past the end). */
public TsResult readqTsNext(String queue, int maxLength) {
return tempStorage.read(this, queue, 0, maxLength);
}

/** A TS command's outcome: its condition, the item (number assigned or read), the LENGTH it sets (READQ;
* -1 when it sets none), the data moved INTO (READQ; null when none) and NUMITEMS. */
public record TsResult(String resp, int item, int length, byte[] data, int numItems) {
}

/** A region's temporary storage (#4002): TS queues by name, each a list of items, and the READQ NEXT
* position of each queue (which counts a read by ITEM too). */
public static final class TempStorage {
private final Map<String, List<byte[]>> queues = new LinkedHashMap<>();
private final Map<String, Integer> next = new HashMap<>();
/** The intrapartition transient-data queues' records (#5079): first in first out. */
final Map<String, java.util.ArrayDeque<String>> td = new HashMap<>();
/** The run-time open state SET TDQUEUE OPENSTATUS left on an extrapartition queue (#5168, X61): true open, false closed. */
final Map<String, Boolean> tdOpen = new HashMap<>();
/** The queues SET TDQUEUE ENABLESTATUS(DISABLED) left disabled (#5168 round 5, X66). */
final java.util.Set<String> tdDisabled = new java.util.HashSet<>();
/** The levels SET TDQUEUE TRIGGERLEVEL left (#5168 round 7, X66). */
final java.util.Map<String, Integer> tdTrigger = new java.util.HashMap<>();
/** The extrapartition queues a READQ TD has taken a record from (reopening an input queue would restart it). */
final java.util.Set<String> tdRead = new java.util.HashSet<>();

/** A transient-data queue's records as they are before the conversation starts (#5168). */
public TempStorage seedTd(String queue, List<String> records) {
td.put(queue, new java.util.ArrayDeque<>(records));
return this;
}

/** A queue as it is before the conversation starts. */
public TempStorage seed(String queue, List<byte[]> items) {
queues.put(queue, new ArrayList<>(items));
return this;
}

/** Every queue and its items, in the order the queues were created. */
public Map<String, List<byte[]>> queues() {
Map<String, List<byte[]>> out = new LinkedHashMap<>();
queues.forEach((q, items) -> out.put(q, List.copyOf(items)));
return out;
}

int delete(String queue) {
if (!queue.isEmpty() && queue.chars().allMatch(c -> c == 0)) {
return 16;
}
if (queues.remove(queue) == null) {
return 44;
}
next.remove(queue);
return 0;
}

TsResult write(CicsTask task, String queue, int rewrite, byte[] data) {
List<byte[]> items = queues.get(queue);
String resp = "NORMAL";
int item = 0;
int[] planned = task.root().injected("WRITEQ-TS", queue.stripTrailing()); // #4049: nothing is written
if (planned != null) {
resp = respName(planned[0]);
} else if (data == null || data.length < 1 || data.length > 32763) {
resp = "LENGERR";
} else if (rewrite > 0 && items == null) {
resp = "QIDERR";
} else if (rewrite > 0 && rewrite > items.size()) {
resp = "ITEMERR";
} else {
if (items == null) {
items = new ArrayList<>();
queues.put(queue, items);
}
if (rewrite > 0) {
items.set(rewrite - 1, data.clone());
item = rewrite;
} else {
items.add(data.clone());
item = items.size();
}
}
task.event("WRITEQ-TS", "queue", queue, "data", data, "resp", resp, "item", item == 0 ? null : item);
return new TsResult(resp, item, -1, null, items == null ? 0 : items.size());
}

TsResult read(CicsTask task, String queue, int item, int maxLength) {
List<byte[]> items = queues.get(queue);
int want = item > 0 ? item : next.getOrDefault(queue, 0) + 1;
Object shown = item > 0 ? (Object) item : "NEXT";
if (items == null || want < 1 || want > items.size()) {
String resp = items == null ? "QIDERR" : "ITEMERR";
task.event("READQ-TS", "queue", queue, "item", shown, "resp", resp, "length", null, "data", null);
return new TsResult(resp, want, -1, null, 0);
}
next.put(queue, want);
byte[] stored = items.get(want - 1);
byte[] data = stored.length > maxLength ? Arrays.copyOf(stored, Math.max(maxLength, 0)) : stored.clone();
String resp = stored.length > maxLength ? "LENGERR" : "NORMAL";
task.event("READQ-TS", "queue", queue, "item", shown, "resp", resp, "length", stored.length, "data", data);
return new TsResult(resp, want, stored.length, data, items.size());
}
}

public void sendMap(String map, Object screen) {
event("SEND-MAP", "map", map, "screen", screen);
}
Expand Down
14 changes: 14 additions & 0 deletions tests/cobol_mainframe/test_cics_family_split.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
from gitgalaxy.tools.cobol_to_java import cics_task_filecontrol as tf # noqa: E402
from gitgalaxy.tools.cobol_to_java import cics_task_taskcontrol as tt # noqa: E402
from gitgalaxy.tools.cobol_to_java import cics_task_tdqueues as tq # noqa: E402
from gitgalaxy.tools.cobol_to_java import cics_task_tsqueues as tsq # noqa: E402
from gitgalaxy.tools.cobol_to_java import cics_task_web as tw # noqa: E402
from gitgalaxy.tools.cobol_to_java import cobol_to_java_transaction_forge as forge # noqa: E402
from gitgalaxy.tools.cobol_to_java.det import cics as C # noqa: E402
Expand Down Expand Up @@ -157,6 +158,19 @@ def test_the_det_translator_holds_the_task_control_methods_in_its_mixin():
assert "pointer" in vars(C.Cics) # shared with the other families: it stays the translator's own


def test_cicstask_holds_the_ts_queue_java_once_where_it_sat():
"""#5274: the TS queue family is spliced between the faults family and sendMap."""
java = forge.CICS_TASK_JAVA
assert java.count(tsq.TSQUEUES_JAVA) == 1
a = java.index(tsq.TSQUEUES_JAVA)
assert java[:a].endswith(tfl.FAULTS_JAVA)
assert java[a + len(tsq.TSQUEUES_JAVA) :].startswith(" public void sendMap(String map, Object screen) {")
for method in ("withTempStorage", "writeqTs", "rewriteqTs", "readqTs", "readqTsNext"):
assert re.search(rf"public \S+(?: \S+)? {method}\(", tsq.TSQUEUES_JAVA), method
assert "public record TsResult(" in tsq.TSQUEUES_JAVA
assert "public static final class TempStorage" in tsq.TSQUEUES_JAVA


def test_cicstask_holds_the_task_control_java_once_where_it_sat():
java = forge.CICS_TASK_JAVA
assert java.count(tt.TASKCONTROL_JAVA) == 1
Expand Down
2 changes: 2 additions & 0 deletions tests/cobol_mainframe/test_evidence_components.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,8 @@ def test_a_cics_family_module_stales_what_its_host_file_does():
"gitgalaxy/tools/cobol_to_java/cics_task_web.py"),
("gitgalaxy/tools/cobol_to_java/cobol_to_java_transaction_forge.py",
"gitgalaxy/tools/cobol_to_java/cics_task_tdqueues.py"),
("gitgalaxy/tools/cobol_to_java/cobol_to_java_transaction_forge.py",
"gitgalaxy/tools/cobol_to_java/cics_task_tsqueues.py"),
): # fmt: skip
for t in (CRUCIBLE, LINKING, DB2_ONLINE, ONLINE, BATCH, CALL, IMS):
assert stale_after(t, family) == stale_after(t, host), (t.key, family)
Expand Down
1 change: 1 addition & 0 deletions tests/tools/det_sweep_plan.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@
"gitgalaxy/tools/cobol_to_java/cics_task_taskcontrol.py",
"gitgalaxy/tools/cobol_to_java/cics_task_web.py",
"gitgalaxy/tools/cobol_to_java/cics_task_tdqueues.py",
"gitgalaxy/tools/cobol_to_java/cics_task_tsqueues.py",
"gitgalaxy/tools/cobol_to_java/cics_task_conditions.py",
"gitgalaxy/tools/cobol_to_java/cics_task_faults.py") # fmt: skip

Expand Down
Loading