Skip to content

ISOMsg: strict path readers, dataset element writers, dataset-aware clone - #792

Merged
ar merged 2 commits into
mainfrom
feature/isomsg-strict-path-reader
Oct 9, 2026
Merged

ar merged 2 commits into
mainfrom
feature/isomsg-strict-path-reader

Conversation

@ar

@ar ar commented Oct 9, 2026

Copy link
Copy Markdown
Member

Second PR for #790, following the implementation plan. PR #791 was the first. Closes #790.

New API (ISOMsg)

  • Optional<Object> findValue(String fpath) throws ISOException
  • List<DatasetElement> findDatasetElements(String fpath) throws ISOException
  • setDatasetElement(fpath, elementId, value) / setDatasetElement(fpath, datasetId, elementId, value), plus withDatasetElement and unsetDatasetElement counterparts
  • DatasetElement.isConstructedTag(int), replacing private copies in DatasetPackager and XMLPackager (separate commit)

Behavior

Path grammar (new methods and clone(String...) only; the existing set/unset/hasField/getValue/getString/getComponent(String) stay lenient): segments are [0-9]+ or 0x[0-9a-fA-F]+, in the range 0..Integer.MAX_VALUE. Empty segments, signs, whitespace, 0X and out-of-range values throw IllegalArgumentException naming the path and the bad segment, whatever the message contains.

Readers

  • Every segment but the last must be a sub-ISOMsg. An absent field, or a path through a leaf, returns empty.
  • Dataset descent: if the field packager of the message holding the dataset field is a DatasetFieldPackager, hasDatasetEnvelope() decides the layout, and a mismatched path returns empty. Otherwise the layout is inferred from the remaining segments (1 = no envelope, 2 = envelope, more = empty). Nested dataset fields use the sub-message's packager.
  • findDatasetElements collects matches from every dataset with the addressed id. findValue returns the first, so findValue(p).isPresent() ⇔ !findDatasetElements(p).isEmpty().
  • Constructed elements are returned as encoded byte[]; the Javadoc points to TLVList for decoding children.

Writers

  • setDatasetElement needs no packager and creates intermediate composites. Ints carry the dataset coordinates, because a single string path is ambiguous when there's nothing to walk yet.
  • New datasets: no envelope → TLV; envelope → TLV up to 0x70, DBM above. Elements are replaced (putElement). TLV elements record the constructed flag. A null value removes the element.
  • A leaf in the way, or a non-dataset field at the target, throws ISOException. The value type is checked before anything is created.

clone(String...): copies every occurrence of a dataset element path, deep-copied, keeping dataset id, format and constructed. Elements appear in path order. Overlapping paths replace rather than duplicate. Same-id datasets stay apart, matched by position.

Behavior changes (for the ChangeLog)

  • Data-loss fix. With a dataset field packager, a dataset path whose segment count doesn't match the envelope made setDatasetPath return false. The plain path code then replaced the field: on main, set("104.0xDF02", …) after set("104.0x01.0xDF01", …) with cmfv3.xml turns DE 104 into an ISOMsg, and pack() fails with error packing field 104 (reproduced locally). set/unset now throw (IllegalArgumentException, or ISOException from set(String, ISOComponent)).
  • Envelope-less dataset fields numbered above 0x70 got DBM datasets from set(...); they now get TLV.
  • TLV elements written by path now record constructed. This is metadata only; TLV packing doesn't use it.
  • Unsetting a dataset path removes the element from every dataset with that id, not only the first.
  • clone(String...) throws IllegalArgumentException for malformed paths instead of an incidental NumberFormatException/NoSuchElementException.

Testing

  • New ISOMsgPathTest (49 tests): the grammar cases, strict walk, dataset descent with and without a packager (cmfv3.xml), mismatched layouts, nested 127.55.x with and without a sub-message packager, XMLPackager round-trip, set() without a packager producing a plain composite, repeated tags (issuer scripts decoded with TLVList) and repeated datasets, the writers including pack/unpack with cmfv3.xml, the mismatch regression, the TLV format fix, and clone with dataset paths.
  • ISOMsgTest.testCloneFPathBrokenHierarchy (from ISOMsg: return null from getComponent(String) on a broken hierarchy #791) no longer asserts that clone("55.0x9F26") skips DE 55, since that path is now cloned. The leaf case is still covered.
  • Full :jpos test suite: 4,766 tests, 0 failures, 0 errors, 69 skipped (local run, macOS). :jpos:javadoc reports no warnings.
  • Other modules were not run.

ar added 2 commits October 9, 2026 19:05
DatasetPackager and XMLPackager each had a private copy of the BER-TLV
constructed-tag check. Move it to a public static
DatasetElement.isConstructedTag(int) so ISOMsg can set the constructed
flag when it builds dataset elements (#790).

Refs #790
…lone

Add findValue(String) and findDatasetElements(String). Both validate the
whole path before reading (decimal or 0x-prefixed hex segments in
0..Integer.MAX_VALUE; anything else throws IllegalArgumentException),
return empty for absent or broken hierarchies, and descend into dataset
fields: 55.0x9F26 (no envelope) and 104.0x71.0x01 (envelope). The field
packager's hasDatasetEnvelope() decides the layout when available;
otherwise it is inferred from the remaining segment count.
findDatasetElements collects every occurrence across all datasets with
the addressed identifier; findValue returns the first.

Add setDatasetElement/withDatasetElement/unsetDatasetElement, which take
the dataset coordinates as ints and build dataset fields without a
packager. setDatasetPath/unsetDatasetPath share the same helpers.

clone(String...) now copies dataset element paths, keeping dataset
identifiers, formats and constructed flags.

Behavior changes:
- With a dataset field packager, set/unset with a dataset path whose
  segment count does not match the envelope now throw. Previously set
  silently replaced the dataset field with a plain sub-message.
- Datasets created for envelope-less fields are always TLV; fields
  numbered above 0x70 used to get DBM.
- TLV elements written by path record the constructed flag.
- Dataset paths remove an element from every dataset carrying the
  identifier, not only the first.
- clone(String...) throws IllegalArgumentException for malformed paths.

Refs #790
@ar
ar merged commit 7d9028b into main Oct 9, 2026
1 check passed
@ar
ar deleted the feature/isomsg-strict-path-reader branch October 9, 2026 22:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ISOMsg: strict path reader (findValue) with dataset descent, plus findDatasetElements

1 participant