Field Packagers
ISOFieldPackager is the abstract base for everything that knows how to convert a single ISOComponent to and from bytes:
public abstract class ISOFieldPackager {
private int len; // max field length
private String description;
protected boolean pad;
protected boolean trim;
public abstract int getMaxPackedLength();
public abstract byte[] pack(ISOComponent c) throws ISOException;
public abstract int unpack(ISOComponent c, byte[] b, int offset) throws ISOException;
public void unpack(ISOComponent c, InputStream in) throws IOException, ISOException;
public ISOComponent createComponent(int fieldNumber) { return new ISOField(fieldNumber); }
}
The createComponent() factory method matters: ISOBasePackager.unpack() calls it to allocate the right Leaf type for each field before calling unpack(). The default produces ISOField; ISOBitMapPackager overrides to produce ISOBitMap.
Flyweight
Field packager instances hold no per-message state. The entire fld[] array in a packager is created once at startup and shared across all threads and all messages. This is the Flyweight pattern — passing the per-message state in as method parameters (ISOComponent c, byte[] b, int offset).
Decoding the IF* class names
The naming convention encodes the field's wire representation directly in the class name:
I F [encoding] _ [data-type] [length-prefix]
Encoding prefix
| Prefix | Meaning |
|---|---|
IFA_ | ASCII — all data and length indicators are ASCII text bytes |
IFB_ | Binary — numeric data packed as BCD; length indicators as BCD bytes |
IF_CHAR | Literal ASCII pass-through — no transformation, no prefix |
Data type
| Suffix | Value type | Notes |
|---|---|---|
NUMERIC | Decimal digits | Fixed-width, padded with zeros |
CHAR | ASCII string | Fixed-width, typically space-padded |
BINARY | Raw bytes | Fixed-width binary data |
BITMAP | BitSet | Special — extends ISOBitMapPackager, not ISOStringFieldPackager |
AMOUNT | Decimal amount | Special formatting with implied decimal point |
Length prefix
| Suffix | L digits | Max value | L indicator wire bytes |
|---|---|---|---|
| (none) | 0 | fixed | 0 bytes |
L | 1 | 9 | 1 ASCII digit or ½ BCD byte |
LL | 2 | 99 | 2 ASCII digits or 1 BCD byte |
LLL | 3 | 999 | 3 ASCII digits or 2 BCD bytes |
LLLL | 4 | 9999 | 4 ASCII digits or 2 BCD bytes |
The number of Ls tells you the maximum length of the field in the number of data units — characters for CHAR/NUMERIC, bytes for BINARY.
Examples decoded:
| Class | Encoding | Length prefix | Max data |
|---|---|---|---|
IFA_NUMERIC(6, "Processing Code") | ASCII digits | none (fixed) | 6 ASCII chars |
IFA_LLCHAR(99, "Additional data") | ASCII chars | 2 ASCII digits | up to 99 chars |
IFA_LLLCHAR(999, "DE48") | ASCII chars | 3 ASCII digits | up to 999 chars |
IFB_NUMERIC(4, "MTI", false) | BCD | none | 4 digits in 2 bytes |
IFB_LLNUM(19, "PAN", false) | BCD | 1 BCD byte (LL) | up to 19 digits |
IFB_LLLCHAR(999, "DE48") | ASCII chars | 2 BCD bytes (LLL) | up to 999 chars |
IFB_BINARY(8, "MAC") | raw bytes | none | exactly 8 bytes |
IFB_LLBINARY(32, "DE55") | raw bytes | 1 BCD byte | up to 32 bytes |
ISOStringFieldPackager: three strategies
Most field packagers extend ISOStringFieldPackager which delegates to three pluggable strategy objects:
public class ISOStringFieldPackager extends ISOFieldPackager {
private Padder padder;
private Interpreter interpreter;
private Prefixer prefixer;
...
}
Each strategy is stateless — implementations are typically singletons (static INSTANCE fields).
Padder
public interface Padder {
String pad(String data, int maxLength) throws ISOException;
String unpad(String paddedData) throws ISOException;
}
Used during pack() to fill a fixed-width field, and unpad() during unpack() to strip that padding.
| Implementation | Pad character | Used for |
|---|---|---|
LeftPadder.ZERO_PADDER | '0' on the left | Numeric fields |
RightPadder.SPACE_PADDER | ' ' on the right | Character fields |
NullPadder.INSTANCE | (no padding) | Variable-length fields |
Interpreter
public interface Interpreter {
void interpret(String data, byte[] b, int offset) throws ISOException;
String uninterpret(byte[] rawData, int offset, int length) throws ISOException;
int getPackedLength(int nDataUnits);
}
The interpreter is the encoding engine.
| Implementation | Wire representation | getPackedLength(n) |
|---|---|---|
AsciiInterpreter.INSTANCE | One byte per char, ISO-8859-1 | n |
LiteralInterpreter.INSTANCE | Same as ASCII | n |
EbcdicInterpreter.INSTANCE | One byte per char, IBM1047 | n |
BCDInterpreter.RIGHT_PADDED | Two digits per byte, trailing 0-nibble for odd lengths | (n+1)/2 |
BCDInterpreter.LEFT_PADDED | Two digits per byte, leading 0-nibble for odd lengths | (n+1)/2 |
BCDInterpreter.RIGHT_PADDED_F | As RIGHT_PADDED but trailing F nibble | (n+1)/2 |
HexInterpreter.INSTANCE | ASCII hex string → raw bytes | n/2 |
Prefixer
public interface Prefixer {
void encodeLength(int length, byte[] b) throws ISOException;
int decodeLength(byte[] b, int offset) throws ISOException;
int getPackedLength(); // bytes consumed by the prefix itself
}
| Implementation | Wire format | getPackedLength() |
|---|---|---|
NullPrefixer.INSTANCE | No prefix (fixed-length field) | 0 |
AsciiPrefixer.L | 1 ASCII digit | 1 |
AsciiPrefixer.LL | 2 ASCII digits | 2 |
AsciiPrefixer.LLL | 3 ASCII digits | 3 |
BcdPrefixer.LL | 1 byte (2 BCD digits) | 1 |
BcdPrefixer.LLL | 2 bytes (3 BCD digits, left-zero padded) | 2 |
Composition examples
Reading a concrete class tells you everything about its wire format:
// IFA_LLCHAR — ASCII LL-prefixed character field
public class IFA_LLCHAR extends ISOStringFieldPackager {
public IFA_LLCHAR(int len, String description) {
super(len, description,
NullPadder.INSTANCE, // variable-length: no padding
AsciiInterpreter.INSTANCE, // ASCII text on the wire
AsciiPrefixer.LL); // 2-byte ASCII length indicator
}
}
// Wire: "05HELLO" (2 bytes length "05", then 5 ASCII bytes)
// IFA_NUMERIC — ASCII fixed numeric
public class IFA_NUMERIC extends ISOStringFieldPackager {
public IFA_NUMERIC(int len, String description) {
super(len, description,
LeftPadder.ZERO_PADDER, // zero-pad on the left
AsciiInterpreter.INSTANCE, // ASCII text on the wire
NullPrefixer.INSTANCE); // no length prefix
}
}
// Wire for IFA_NUMERIC(6,"PC") packing "42": "000042"
// IFB_NUMERIC — BCD fixed numeric
public class IFB_NUMERIC extends ISOStringFieldPackager {
public IFB_NUMERIC(int len, String description, boolean isLeftPadded) {
super(len, description,
LeftPadder.ZERO_PADDER,
isLeftPadded ? BCDInterpreter.LEFT_PADDED
: BCDInterpreter.RIGHT_PADDED,
NullPrefixer.INSTANCE);
}
}
// Wire for IFB_NUMERIC(4,"MTI",false) packing "0200": 0x02 0x00
// IFB_LLNUM — BCD LL-prefixed numeric
public class IFB_LLNUM extends ISOStringFieldPackager {
public IFB_LLNUM(int len, String description, boolean isLeftPadded) {
super(len, description,
NullPadder.INSTANCE,
isLeftPadded ? BCDInterpreter.LEFT_PADDED
: BCDInterpreter.RIGHT_PADDED,
BcdPrefixer.LL); // 1-byte BCD length prefix
}
}
// Wire for 16-digit PAN: 0x16 0x41 0x11 0x11 0x11 0x11 0x11 0x11 0x11
// ^^^^^^ LL=16 as BCD ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// 16 digits packed in 8 bytes (right-padded BCD)
// IFB_BINARY — raw bytes, fixed length
public class IFB_BINARY extends ISOBinaryFieldPackager {
public IFB_BINARY(int len, String description) {
super(len, description,
LiteralBinaryInterpreter.INSTANCE,
NullPrefixer.INSTANCE);
}
}
// Wire for IFB_BINARY(8,"MAC"): exactly 8 raw bytes
The pack() cycle
The full ISOStringFieldPackager.pack() pipeline for a single field:
ISOComponent.getValue() → String data
│
▼
Padder.pad(data, maxLength) → paddedData (only for fixed-length fields)
│
▼
Prefixer.encodeLength(paddedData.length, rawData[0..])
│
▼
Interpreter.interpret(paddedData, rawData, prefixerPackedLength)
│
▼
byte[] rawData (returned to ISOBasePackager)
And unpack() reverses it:
byte[] b → Prefixer.decodeLength(b, offset) → len
→ Interpreter.uninterpret(b, offset+lenLen, len) → String
→ (NullPadder or trimming applied)
→ ISOComponent.setValue(string)
ISOBinaryFieldPackager
Binary fields (holding byte[] rather than String) use ISOBinaryFieldPackager instead of ISOStringFieldPackager. The structure is the same — Interpreter + Prefixer, no Padder — but operates on byte[] directly. IFB_BINARY, IFB_LLBINARY, IFB_LLLBINARY are typical uses.
ISOBinaryFieldPackager.createComponent() returns new ISOBinaryField(fieldNumber) so the packager correctly allocates a binary leaf.