diff --git a/src/main/java/com/solab/iso8583/IsoMessage.java b/src/main/java/com/solab/iso8583/IsoMessage.java index a07070b..42d3457 100644 --- a/src/main/java/com/solab/iso8583/IsoMessage.java +++ b/src/main/java/com/solab/iso8583/IsoMessage.java @@ -51,6 +51,7 @@ public class IsoMessage { /** Flag to enforce secondary bitmap even if empty. */ private boolean forceb2; private boolean binBitmap; + private boolean forceStringEncoding; private String encoding = System.getProperty("file.encoding"); /** Creates a new empty message with no values set. */ @@ -96,7 +97,15 @@ public class IsoMessage { return encoding; } - /** Sets the string to be sent as ISO header, that is, after the length header but before the message type. + /** Specified whether the variable-length fields should encode their length + * headers using string conversion with the proper character encoding. Default + * is false, which is the old behavior (encoding as ASCII). This is only useful + * for text format. */ + public void setForceStringEncoding(boolean flag) { + forceStringEncoding = flag; + } + + /** Sets the string to be sent as ISO header, that is, after the length header but before the message type. * This is useful in case an application needs some custom data in the ISO header of each message (very rare). */ public void setIsoHeader(String value) { isoHeader = value; @@ -385,7 +394,7 @@ public class IsoMessage { IsoValue v = fields[i]; if (v != null) { try { - v.write(bout, binary); + v.write(bout, binary, forceStringEncoding); } catch (IOException ex) { //should never happen, writing to a ByteArrayOutputStream } diff --git a/src/main/java/com/solab/iso8583/IsoValue.java b/src/main/java/com/solab/iso8583/IsoValue.java index 195cde6..2936992 100644 --- a/src/main/java/com/solab/iso8583/IsoValue.java +++ b/src/main/java/com/solab/iso8583/IsoValue.java @@ -245,48 +245,50 @@ public class IsoValue implements Cloneable { return encoder; } + protected void writeLengthHeader(final int l, final OutputStream outs, final int digits, + final boolean binary, final boolean forceStringEncoding) + throws IOException { + if (binary) { + if (digits == 3) { + outs.write(l / 100); //00 to 09 automatically in BCD + } + //BCD encode the rest of the length + outs.write((((l % 100) / 10) << 4) | (l % 10)); + } else if (forceStringEncoding) { + String lhead = Integer.toString(l); + final int ldiff = digits - lhead.length(); + if (ldiff == 1) { + lhead = '0' + lhead; + } else if (ldiff == 2) { + lhead = "00" + lhead; + } + outs.write(lhead.getBytes(encoding)); + } else { + //write the length in ASCII + if (digits == 3) { + outs.write((l / 100) + 48); + } + if (l >= 10) { + outs.write(((l % 100) / 10) + 48); + } else { + outs.write(48); + } + outs.write((l % 10) + 48); + } + } + /** Writes the formatted value to a stream, with the length header - * if it's a variable length type. */ - public void write(OutputStream outs, boolean binary) throws IOException { + * if it's a variable length type. + * @param outs The stream to which the value will be written. + * @param binary Specifies whether the value should be written in binary or text format. + * @param forceStringEncoding When using text format, force the encoding of length headers + * for variable-length fields to be done with the proper character encoding. When false, + * the length headers are encoded as ASCII; this used to be the only behavior. */ + public void write(final OutputStream outs, final boolean binary, final boolean forceStringEncoding) throws IOException { if (type == IsoType.LLLVAR || type == IsoType.LLVAR) { - if (binary) { - if (type == IsoType.LLLVAR) { - outs.write(length / 100); //00 to 09 automatically in BCD - } - //BCD encode the rest of the length - outs.write((((length % 100) / 10) << 4) | (length % 10)); - } else { - //write the length in ASCII - if (type == IsoType.LLLVAR) { - outs.write((length / 100) + 48); - } - if (length >= 10) { - outs.write(((length % 100) / 10) + 48); - } else { - outs.write(48); - } - outs.write((length % 10) + 48); - } + writeLengthHeader(length, outs, type == IsoType.LLLVAR ? 3 : 2, binary, forceStringEncoding); } else if (type == IsoType.LLBIN || type == IsoType.LLLBIN) { - if (binary) { - if (type == IsoType.LLLBIN ) { - outs.write(length / 100); //00 to 09 automatically in BCD - } - //BCD encode the rest of the length - outs.write((((length % 100) / 10) << 4) | (length % 10)); - } else { - int _l = length*2; - //write the length in ASCII - if (type == IsoType.LLLBIN) { - outs.write((_l / 100) + 48); - } - if (_l >= 10) { - outs.write(((_l % 100) / 10) + 48); - } else { - outs.write(48); - } - outs.write((_l % 10) + 48); - } + writeLengthHeader(binary ? length : length*2, outs, type == IsoType.LLLBIN ? 3 : 2, binary, forceStringEncoding); } else if (binary) { //numeric types in binary are coded like this byte[] buf = null; diff --git a/src/main/java/com/solab/iso8583/MessageFactory.java b/src/main/java/com/solab/iso8583/MessageFactory.java index 68c22e1..a8d621d 100644 --- a/src/main/java/com/solab/iso8583/MessageFactory.java +++ b/src/main/java/com/solab/iso8583/MessageFactory.java @@ -94,13 +94,24 @@ public class MessageFactory { throw new IllegalArgumentException("Cannot set null encoding."); } encoding = value; - if (parseMap.size() > 0) { + if (!parseMap.isEmpty()) { for (Map pt : parseMap.values()) { for (FieldParseInfo fpi : pt.values()) { fpi.setCharacterEncoding(encoding); } } } + if (!typeTemplates.isEmpty()) { + for (T tmpl : typeTemplates.values()) { + tmpl.setCharacterEncoding(encoding); + for (int i = 2 ; i<129; i++) { + IsoValue v = tmpl.getField(i); + if (v != null) { + v.setCharacterEncoding(encoding); + } + } + } + } } /** Returns the encoding used to parse ALPHA, LLVAR and LLLVAR fields. The default is the file.encoding @@ -143,12 +154,12 @@ public class MessageFactory { } /** Returns a custom field encoder/decoder for the specified field number, if one is available. */ @SuppressWarnings("unchecked") - public CustomField getCustomField(int index) { + public CustomField getCustomField(int index) { return customFields.get(index); } /** Returns a custom field encoder/decoder for the specified field number, if one is available. */ @SuppressWarnings("unchecked") - public CustomField getCustomField(Integer index) { + public CustomField getCustomField(Integer index) { return customFields.get(index); } diff --git a/src/main/java/com/solab/iso8583/parse/FieldParseInfo.java b/src/main/java/com/solab/iso8583/parse/FieldParseInfo.java index b71ddfc..4761b20 100644 --- a/src/main/java/com/solab/iso8583/parse/FieldParseInfo.java +++ b/src/main/java/com/solab/iso8583/parse/FieldParseInfo.java @@ -34,6 +34,7 @@ public abstract class FieldParseInfo { protected IsoType type; protected int length; private String encoding = System.getProperty("file.encoding"); + protected boolean forceStringDecoding; /** Creates a new instance that parses a value of the specified type, with the specified length. * The length is only useful for ALPHA and NUMERIC types. @@ -47,6 +48,13 @@ public abstract class FieldParseInfo { length = len; } + /** Specified whether length headers for variable-length fields in text mode should + * be decoded using proper string conversion with the character encoding. Default is false, + * which means use the old behavior of decoding as ASCII. */ + public void setForceStringDecoding(boolean flag) { + forceStringDecoding = flag; + } + public void setCharacterEncoding(String value) { encoding = value; } @@ -119,4 +127,14 @@ public abstract class FieldParseInfo { return fpi; } + protected int decodeLength(byte[] buf, int pos, int digits) throws UnsupportedEncodingException { + if (forceStringDecoding) { + return Integer.parseInt(new String(buf, pos, digits, encoding)); + } else if (digits == 3) { + return ((buf[pos] - 48) * 100) + ((buf[pos + 1] - 48) * 10) + (buf[pos + 2] - 48); + } else { + return ((buf[pos] - 48) * 10) + (buf[pos + 1] - 48); + } + } + } diff --git a/src/main/java/com/solab/iso8583/parse/LlbinParseInfo.java b/src/main/java/com/solab/iso8583/parse/LlbinParseInfo.java index 1cfdedc..6e6989d 100644 --- a/src/main/java/com/solab/iso8583/parse/LlbinParseInfo.java +++ b/src/main/java/com/solab/iso8583/parse/LlbinParseInfo.java @@ -18,6 +18,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ package com.solab.iso8583.parse; +import java.io.UnsupportedEncodingException; import java.text.ParseException; import com.solab.iso8583.CustomBinaryField; @@ -40,7 +41,7 @@ public class LlbinParseInfo extends FieldParseInfo { @Override public IsoValue parse(final int field, final byte[] buf, final int pos, final CustomField custom) - throws ParseException { + throws ParseException, UnsupportedEncodingException { if (pos < 0) { throw new ParseException(String.format("Invalid LLBIN field %d position %d", field, pos), pos); @@ -48,7 +49,7 @@ public class LlbinParseInfo extends FieldParseInfo { throw new ParseException(String.format("Insufficient LLBIN header field %d", field), pos); } - length = ((buf[pos] - 48) * 10) + (buf[pos + 1] - 48); + length = decodeLength(buf, pos, 2); if (length < 0) { throw new ParseException(String.format("Invalid LLBIN field %d length %d pos %d", field, length, pos), pos); diff --git a/src/main/java/com/solab/iso8583/parse/LllbinParseInfo.java b/src/main/java/com/solab/iso8583/parse/LllbinParseInfo.java index 63bd537..03b27bd 100644 --- a/src/main/java/com/solab/iso8583/parse/LllbinParseInfo.java +++ b/src/main/java/com/solab/iso8583/parse/LllbinParseInfo.java @@ -18,6 +18,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ package com.solab.iso8583.parse; +import java.io.UnsupportedEncodingException; import java.text.ParseException; import com.solab.iso8583.CustomBinaryField; @@ -40,7 +41,7 @@ public class LllbinParseInfo extends FieldParseInfo { @Override public IsoValue parse(final int field, final byte[] buf, final int pos, final CustomField custom) - throws ParseException { + throws ParseException, UnsupportedEncodingException { if (pos < 0) { throw new ParseException(String.format("Invalid LLLBIN field %d pos %d", field, pos), pos); @@ -48,11 +49,7 @@ public class LllbinParseInfo extends FieldParseInfo { throw new ParseException(String.format("Insufficient LLLBIN header field %d", field), pos); } - if (!(Character.isDigit(buf[pos]) && Character.isDigit(buf[pos+1]) && Character.isDigit(buf[pos+2]))) { - throw new ParseException(String.format("Invalid LLLBIN length '%s' field %d pos %d", - new String(buf, pos, 3), field, pos), pos); - } - final int l = ((buf[pos] - 48) * 100) + ((buf[pos + 1] - 48) * 10) + (buf[pos + 2] - 48); + final int l = decodeLength(buf, pos, 3); if (l < 0) { throw new ParseException(String.format("Invalid LLLBIN length %d field %d pos %d", l, field, pos), pos); diff --git a/src/main/java/com/solab/iso8583/parse/LllvarParseInfo.java b/src/main/java/com/solab/iso8583/parse/LllvarParseInfo.java index 9b472d2..de7956c 100644 --- a/src/main/java/com/solab/iso8583/parse/LllvarParseInfo.java +++ b/src/main/java/com/solab/iso8583/parse/LllvarParseInfo.java @@ -46,11 +46,7 @@ public class LllvarParseInfo extends FieldParseInfo { throw new ParseException(String.format( "Insufficient data for LLLVAR header field %d pos %d", field, pos), pos); } - if (!(Character.isDigit(buf[pos]) && Character.isDigit(buf[pos+1]) && Character.isDigit(buf[pos+2]))) { - throw new ParseException(String.format("Invalid LLLVAR length '%s' field %d pos %d", - new String(buf, pos, 3), field, pos), pos); - } - length = ((buf[pos] - 48) * 100) + ((buf[pos + 1] - 48) * 10) + (buf[pos + 2] - 48); + length = decodeLength(buf, pos, 3); if (length < 0) { throw new ParseException(String.format("Invalid LLLVAR length %d field %d pos %d", length, field, pos), pos); diff --git a/src/main/java/com/solab/iso8583/parse/LlvarParseInfo.java b/src/main/java/com/solab/iso8583/parse/LlvarParseInfo.java index 8521508..b97576c 100644 --- a/src/main/java/com/solab/iso8583/parse/LlvarParseInfo.java +++ b/src/main/java/com/solab/iso8583/parse/LlvarParseInfo.java @@ -44,7 +44,7 @@ public class LlvarParseInfo extends FieldParseInfo { } else if (pos+2 > buf.length) { throw new ParseException(String.format("Insufficient data for LLVAR header, pos %d", pos), pos); } - length = ((buf[pos] - 48) * 10) + (buf[pos + 1] - 48); + length = decodeLength(buf, pos, 2); if (length < 0) { throw new ParseException(String.format( "Invalid LLVAR length %d, field %d pos %d", length, field, pos), pos);