Skip to main content

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

PrefixMeaning
IFA_ASCII — all data and length indicators are ASCII text bytes
IFB_Binary — numeric data packed as BCD; length indicators as BCD bytes
IF_CHARLiteral ASCII pass-through — no transformation, no prefix

Data type

SuffixValue typeNotes
NUMERICDecimal digitsFixed-width, padded with zeros
CHARASCII stringFixed-width, typically space-padded
BINARYRaw bytesFixed-width binary data
BITMAPBitSetSpecial — extends ISOBitMapPackager, not ISOStringFieldPackager
AMOUNTDecimal amountSpecial formatting with implied decimal point

Length prefix

SuffixL digitsMax valueL indicator wire bytes
(none)0fixed0 bytes
L191 ASCII digit or ½ BCD byte
LL2992 ASCII digits or 1 BCD byte
LLL39993 ASCII digits or 2 BCD bytes
LLLL499994 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:

ClassEncodingLength prefixMax data
IFA_NUMERIC(6, "Processing Code")ASCII digitsnone (fixed)6 ASCII chars
IFA_LLCHAR(99, "Additional data")ASCII chars2 ASCII digitsup to 99 chars
IFA_LLLCHAR(999, "DE48")ASCII chars3 ASCII digitsup to 999 chars
IFB_NUMERIC(4, "MTI", false)BCDnone4 digits in 2 bytes
IFB_LLNUM(19, "PAN", false)BCD1 BCD byte (LL)up to 19 digits
IFB_LLLCHAR(999, "DE48")ASCII chars2 BCD bytes (LLL)up to 999 chars
IFB_BINARY(8, "MAC")raw bytesnoneexactly 8 bytes
IFB_LLBINARY(32, "DE55")raw bytes1 BCD byteup 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.

ImplementationPad characterUsed for
LeftPadder.ZERO_PADDER'0' on the leftNumeric fields
RightPadder.SPACE_PADDER' ' on the rightCharacter 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.

ImplementationWire representationgetPackedLength(n)
AsciiInterpreter.INSTANCEOne byte per char, ISO-8859-1n
LiteralInterpreter.INSTANCESame as ASCIIn
EbcdicInterpreter.INSTANCEOne byte per char, IBM1047n
BCDInterpreter.RIGHT_PADDEDTwo digits per byte, trailing 0-nibble for odd lengths(n+1)/2
BCDInterpreter.LEFT_PADDEDTwo digits per byte, leading 0-nibble for odd lengths(n+1)/2
BCDInterpreter.RIGHT_PADDED_FAs RIGHT_PADDED but trailing F nibble(n+1)/2
HexInterpreter.INSTANCEASCII hex string → raw bytesn/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
}
ImplementationWire formatgetPackedLength()
NullPrefixer.INSTANCENo prefix (fixed-length field)0
AsciiPrefixer.L1 ASCII digit1
AsciiPrefixer.LL2 ASCII digits2
AsciiPrefixer.LLL3 ASCII digits3
BcdPrefixer.LL1 byte (2 BCD digits)1
BcdPrefixer.LLL2 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.