J8583包组合字段处理优化

最近研究ISO8583域时,采用了大佬编写的J8583公共jar包。不过对于需求中域的组合字段,压缩变长数据长度值时候,j8583不能把子域的长度位一起压缩。修改了j8583,以供参考。

需求:

域63
自定义域(Reserved Private)

变量属性
ANS…163(LLLVAR),3个字节的长度值+最大163个字节的数据。
压缩时用右靠BCD码表示的2个字节的长度值+用ASCII码表示的最大163个字节的数据。

域描述
该域为自定义域,划分成两个子域,格式定义如下:
—— 数据元长度 N3
—— 63.1 自定义域1 AN3
—— 63.2 自定义域2 ANS…120(LLLVAR)

63.1自定义域1
用法一:国际信用卡公司代码
交易响应消息中POS中心返回国际信用卡公司代码;POS上送的通知消息和离线交易,需上送国际信用卡公司代码。国际信用卡公司代码为:
表50 63.1域用法一
国际信用卡公司中文 国际信用卡公司英文 3 位代码
人民币卡 China Union Pay CUP
威士卡 VISA VIS
万事达卡 Master Card MCC
万事达卡 Maestro Card MAE
JCB卡 JCB JCB
大来卡 Dinner Club DCC
运通卡 American Express AEX

用法二:操作员代码
表示POS终端操作员代码,用作在POS签到和批结算交易中上送到POS中心,应答时原路返回。

63.2 自定义域2
该域为一变长域,长度值由右靠的BCD码表示,最大到120个字节。
该域由四个子域构成,具体描述如下:
—— 63.2.1 发卡方保留域 ANS…20(LLVAR)
—— 63.2.2 中国银联保留域 ANS…20(LLVAR)
—— 63.2.3 受理机构保留域 ANS…20(LLVAR)
—— 63.2.4 POS终端保留域 ANS…60(LLVAR)
上述四个子域顺序排列,若后续子域出现而前面子域没有取值,那么前面子域以空格填充。

解析

63域,定义格式如下
J8583包组合字段处理优化_第1张图片

修改对比如下:

在这里插入图片描述

LlvarParseInfo.java

/*
j8583 A Java implementation of the ISO8583 protocol
Copyright (C) 2011 Enrique Zamudio Lopez

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 3 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
*/
package com.swiftplus.posservice.prepose.iso8583.parse;

import java.io.UnsupportedEncodingException;
import java.text.ParseException;

import com.swiftplus.posservice.prepose.iso8583.CustomBinaryField;
import com.swiftplus.posservice.prepose.iso8583.CustomField;
import com.swiftplus.posservice.prepose.iso8583.IsoType;
import com.swiftplus.posservice.prepose.iso8583.IsoValue;
import com.swiftplus.posservice.prepose.iso8583.util.Bcd;

/** This class is used to parse fields of type LLVAR.
 * 
 * @author Enrique Zamudio
 */
public class LlvarParseInfo extends FieldParseInfo {

	public LlvarParseInfo() {
		super(IsoType.LLVAR, 0);
	}

    @Override
	public <T> IsoValue<?> parse(final int field, final byte[] buf,
                             final int pos, final CustomField<T> custom)
			throws ParseException, UnsupportedEncodingException {
		if (pos < 0) {
			throw new ParseException(String.format(
					"Invalid LLVAR field %d %d", field, pos), pos);
		} else if (pos+2 > buf.length) {
			throw new ParseException(String.format(
					"Insufficient data for LLVAR header, pos %d", pos), pos);
		}
		final int len = decodeLength(buf, pos, 2);
		if (len < 0) {
			throw new ParseException(String.format(
                    "Invalid LLVAR length %d, field %d pos %d", len, field, pos), pos);
		} else if (len+pos+2 > buf.length) {
			throw new ParseException(String.format(
                    "Insufficient data for LLVAR field %d, pos %d len %d",
                    field, pos, len), pos);
		}
		String _v;
        try {
            _v = len == 0 ? "" : new String(buf, pos + 2, len, getCharacterEncoding());
        } catch (IndexOutOfBoundsException ex) {
            throw new ParseException(String.format(
                    "Insufficient data for LLVAR header, field %d pos %d len %d",
                    field, pos, len), pos);
        }
		//This is new: if the String's length is different from the specified
		// length in the buffer, there are probably some extended characters.
		// So we create a String from the rest of the buffer, and then cut it to
		// the specified length.
		if (_v.length() != len) {
			_v = new String(buf, pos + 2, buf.length-pos-2,
					getCharacterEncoding()).substring(0, len);
		}
		if (custom == null) {
			return new IsoValue<>(type, _v, len, null);
		} else {
            T dec = custom.decodeField(_v);
            return dec == null ? new IsoValue<>(type, _v, len, null) :
                    new IsoValue<>(type, dec, len, custom);
		}
	}

	@Override
	public <T> IsoValue<?> parseBinary(final int field, final byte[] buf,
									   final int pos, final CustomField<T> custom)
			throws ParseException, UnsupportedEncodingException {
		return parseBinary(field,buf,pos,(CustomBinaryField<T>) custom);
	}

	public <T> IsoValue<?> parseBinary(final int field, final byte[] buf,
                                   final int pos, final CustomBinaryField<T> custom)
			throws ParseException, UnsupportedEncodingException {
		if (pos < 0) {
			throw new ParseException(String.format("Invalid bin LLVAR field %d pos %d",
                    field, pos), pos);
		} else if (pos+1 > buf.length) {
			throw new ParseException(String.format(
                    "Insufficient data for bin LLVAR header, field %d pos %d",
					field, pos), pos);
		}
		final int len = Bcd.parseBcdLength(buf[pos]);
		if (len < 0) {
			throw new ParseException(String.format(
                    "Invalid bin LLVAR length %d, field %d pos %d", len, field, pos), pos);
		}
		if (len+pos+1 > buf.length) {
			throw new ParseException(String.format(
                    "Insufficient data for bin LLVAR field %d, pos %d", field, pos), pos);
		}
		if (custom == null) {
			return new IsoValue<>(type, new String(buf, pos + 1, len,
					getCharacterEncoding()), null);
		} else {
			//组合字段改造
//            T dec = custom.decodeField(new String(buf, pos + 1, len, getCharacterEncoding()));
			T dec = custom.decodeBinaryField(new String(buf, pos + 1, len, getCharacterEncoding()).getBytes("ASCII"),0,len);
            return dec == null ? new IsoValue<>(type,
					new String(buf, pos + 1, len, getCharacterEncoding()), null) :
                    new IsoValue<>(type, dec, custom);
		}
	}

}

LllvarParseInfo.java

/*
j8583 A Java implementation of the ISO8583 protocol
Copyright (C) 2011 Enrique Zamudio Lopez

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 3 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
*/
package com.swiftplus.posservice.prepose.iso8583.parse;

import java.io.UnsupportedEncodingException;
import java.text.ParseException;

import com.swiftplus.posservice.prepose.iso8583.CustomBinaryField;
import com.swiftplus.posservice.prepose.iso8583.CustomField;
import com.swiftplus.posservice.prepose.iso8583.IsoType;
import com.swiftplus.posservice.prepose.iso8583.IsoValue;
import com.swiftplus.posservice.prepose.iso8583.util.Bcd;

/** This class is used to parse fields of type LLLVAR.
 * 
 * @author Enrique Zamudio
 */
public class LllvarParseInfo extends FieldParseInfo {

	
	public LllvarParseInfo() {
		super(IsoType.LLLVAR, 0);
	}

	public <T> IsoValue<?> parse(final int field, final byte[] buf,
                             final int pos, final CustomField<T> custom)
	throws ParseException, UnsupportedEncodingException {
		if (pos < 0) {
			throw new ParseException(String.format("Invalid LLLVAR field %d pos %d",
                    field, pos), pos);
		} else if (pos+3 > buf.length) {
			throw new ParseException(String.format(
                    "Insufficient data for LLLVAR header field %d pos %d", field, pos), pos);
		}
        final int len = decodeLength(buf, pos, 3);
		if (len < 0) {
            throw new ParseException(String.format("Invalid LLLVAR length %d(%s) field %d pos %d",
                    len, new String(buf, pos, 3), field, pos), pos);
		} else if (len+pos+3 > buf.length) {
			throw new ParseException(String.format("Insufficient data for LLLVAR field %d, pos %d len %d",
                    field, pos, len), pos);
		}
		String _v;
        try {
            _v = len == 0 ? "" : new String(buf, pos + 3, len, getCharacterEncoding());
        } catch (IndexOutOfBoundsException ex) {
            throw new ParseException(String.format(
                    "Insufficient data for LLLVAR header, field %d pos %d len %d", field, pos, len), pos);
        }
		//This is new: if the String's length is different from the specified length in the
		//buffer, there are probably some extended characters. So we create a String from
		//the rest of the buffer, and then cut it to the specified length.
		if (_v.length() != len) {
			_v = new String(buf, pos + 3, buf.length-pos-3,
					getCharacterEncoding()).substring(0, len);
		}
		if (custom == null) {
			return new IsoValue<>(type, _v, len, null);
		} else {
			T decoded = custom.decodeField(_v);
			//If decode fails, return string; otherwise use the decoded object and its codec
            return decoded == null ? new IsoValue<>(type, _v, len, null) :
                new IsoValue<>(type, decoded, len, custom);
		}
	}

	public <T> IsoValue<?> parseBinary(final int field, final byte[] buf,
                                   final int pos, final CustomField<T> custom)
			throws ParseException, UnsupportedEncodingException {
		return parseBinary(field,buf,pos,(CustomBinaryField<T>) custom);
	}

	public <T> IsoValue<?> parseBinary(final int field, final byte[] buf,
								   final int pos, final CustomBinaryField<T> custom)
			throws ParseException, UnsupportedEncodingException {
		if (pos < 0) {
			throw new ParseException(String.format("Invalid bin LLLVAR field %d pos %d", field, pos), pos);
		} else if (pos+2 > buf.length) {
			throw new ParseException(String.format(
                    "Insufficient data for bin LLLVAR header, field %d pos %d", field, pos), pos);
		}
		final int len = ((buf[pos] & 0x0f) * 100) + Bcd.parseBcdLength(buf[pos + 1]);
		if (len < 0) {
			throw new ParseException(String.format(
                    "Invalid bin LLLVAR length %d, field %d pos %d", len, field, pos), pos);
		} else if (len+pos+2 > buf.length) {
			throw new ParseException(String.format(
                    "Insufficient data for bin LLLVAR field %d, pos %d", field, pos), pos);
		}
		if (custom == null) {
			return new IsoValue<>(type, new String(buf, pos + 2, len, getCharacterEncoding()), null);
		} else {
			//组合字段改造
//			IsoValue v = new IsoValue<>(type, custom.decodeField(
//					new String(buf, pos + 2, len, getCharacterEncoding())), custom);

			IsoValue<T> v = new IsoValue<>(type, custom.decodeBinaryField(
					new String(buf, pos + 2, len, getCharacterEncoding()).getBytes("ASCII"),0,len), custom);
			if (v.getValue() == null) {
				return new IsoValue<>(type,
						new String(buf, pos + 2, len, getCharacterEncoding()), null);
			}
			return v;
		}
	}

}

LlllvarParseInfo.java

/*
 * j8583 A Java implementation of the ISO8583 protocol
 * Copyright (C) 2007 Enrique Zamudio Lopez
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 */
package com.swiftplus.posservice.prepose.iso8583.parse;

import com.swiftplus.posservice.prepose.iso8583.CustomBinaryField;
import com.swiftplus.posservice.prepose.iso8583.CustomField;
import com.swiftplus.posservice.prepose.iso8583.IsoType;
import com.swiftplus.posservice.prepose.iso8583.IsoValue;
import com.swiftplus.posservice.prepose.iso8583.util.Bcd;

import java.io.UnsupportedEncodingException;
import java.text.ParseException;

/**
 * Blabla.
 *
 * @author Enrique Zamudio
 *         Date: 19/02/15 18:30
 */
public class LlllvarParseInfo  extends FieldParseInfo {

	public LlllvarParseInfo() {
		super(IsoType.LLLLVAR, 0);
	}

    @Override
	public <T> IsoValue<?> parse(final int field, final byte[] buf,
                             final int pos, final CustomField<T> custom)
			throws ParseException, UnsupportedEncodingException {
		if (pos < 0) {
			throw new ParseException(String.format(
					"Invalid LLLLVAR field %d %d", field, pos), pos);
		} else if (pos+4 > buf.length) {
			throw new ParseException(String.format(
					"Insufficient data for LLLLVAR header, pos %d", pos), pos);
		}
		final int len = decodeLength(buf, pos, 4);
		if (len < 0) {
			throw new ParseException(String.format(
                    "Invalid LLLLVAR length %d, field %d pos %d", len, field, pos), pos);
		} else if (len+pos+4 > buf.length) {
			throw new ParseException(String.format(
                    "Insufficient data for LLLLVAR field %d, pos %d", field, pos), pos);
		}
		String _v;
        try {
            _v = len == 0 ? "" : new String(buf, pos + 4, len, getCharacterEncoding());
        } catch (IndexOutOfBoundsException ex) {
            throw new ParseException(String.format(
                    "Insufficient data for LLLLVAR header, field %d pos %d", field, pos), pos);
        }
		//This is new: if the String's length is different from the specified
		// length in the buffer, there are probably some extended characters.
		// So we create a String from the rest of the buffer, and then cut it to
		// the specified length.
		if (_v.length() != len) {
			_v = new String(buf, pos + 4, buf.length-pos-4,
					getCharacterEncoding()).substring(0, len);
		}
		if (custom == null) {
			return new IsoValue<>(type, _v, len, null);
		} else {
            T dec = custom.decodeField(_v);
            return dec == null ? new IsoValue<>(type, _v, len, null) :
                    new IsoValue<>(type, dec, len, custom);
		}
	}

	@Override
	public <T> IsoValue<?> parseBinary(final int field, final byte[] buf,
									   final int pos, final CustomField<T> custom)
			throws ParseException, UnsupportedEncodingException {
		return parseBinary(field,buf,pos,(CustomBinaryField<T>) custom);
	}

	public <T> IsoValue<?> parseBinary(final int field, final byte[] buf,
									   final int pos, final CustomBinaryField<T> custom)
			throws ParseException, UnsupportedEncodingException {
		if (pos < 0) {
			throw new ParseException(String.format("Invalid bin LLLLVAR field %d pos %d",
                    field, pos), pos);
		} else if (pos+2 > buf.length) {
			throw new ParseException(String.format(
                    "Insufficient data for bin LLLLVAR header, field %d pos %d",
					field, pos), pos);
		}
        final int len = Bcd.parseBcdLength2bytes(buf, pos);
		if (len < 0) {
			throw new ParseException(String.format(
                    "Invalid bin LLLLVAR length %d, field %d pos %d", len, field, pos), pos);
		}
		if (len+pos+2 > buf.length) {
			throw new ParseException(String.format(
                    "Insufficient data for bin LLLLVAR field %d, pos %d", field, pos), pos);
		}
		if (custom == null) {
			return new IsoValue<>(type, new String(buf, pos + 2, len,
					getCharacterEncoding()), null);
		} else {
			//组合字段改造
//            T dec = custom.decodeField(new String(buf, pos + 2, len, getCharacterEncoding()));
			T dec = custom.decodeBinaryField(new String(buf, pos + 2, len, getCharacterEncoding()).getBytes("ASCII"),0,len);
            return dec == null ? new IsoValue<>(type,
					new String(buf, pos + 2, len, getCharacterEncoding()), null) :
                    new IsoValue<>(type, dec, custom);
		}
	}

}

MessageFactory.java

/*
j8583 A Java implementation of the ISO8583 protocol
Copyright (C) 2007 Enrique Zamudio Lopez

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 3 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
*/
package com.swiftplus.posservice.prepose.iso8583;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.text.ParseException;
import java.util.*;

import com.swiftplus.posservice.prepose.iso8583.parse.DateTimeParseInfo;
import com.swiftplus.posservice.prepose.iso8583.util.Bcd;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.swiftplus.posservice.prepose.iso8583.parse.ConfigParser;
import com.swiftplus.posservice.prepose.iso8583.parse.FieldParseInfo;

/** This class is used to create messages, either from scratch or from an existing String or byte
 * buffer. It can be configured to put default values on newly created messages, and also to know
 * what to expect when reading messages from an InputStream.
 * 

* The factory can be configured to know what values to set for newly created messages, both from * a template (useful for fields that must be set with the same value for EVERY message created) * and individually (for trace [field 11] and message date [field 7]). *

* It can also be configured to know what fields to expect in incoming messages (all possible values * must be stated, indicating the date type for each). This way the messages can be parsed from * a byte buffer. * * @author Enrique Zamudio */ public class MessageFactory<T extends IsoMessage> { protected final Logger log = LoggerFactory.getLogger(getClass()); /** This map stores the message template for each message type. */ private Map<Integer, T> typeTemplates = new HashMap<>(); /** Stores the information needed to parse messages sorted by type. */ protected Map<Integer, Map<Integer, FieldParseInfo>> parseMap = new HashMap<>(); /** Stores the field numbers to be parsed, in order of appearance. */ protected Map<Integer, List<Integer>> parseOrder = new HashMap<>(); private TraceNumberGenerator traceGen; /** The ISO header to be included in each message type. */ private Map<Integer, String> isoHeaders = new HashMap<>(); private Map<Integer, byte[]> binIsoHeaders = new HashMap<>(); /** A map for the custom field encoder/decoders, keyed by field number. */ @SuppressWarnings("rawtypes") private Map<Integer, CustomField> customFields = new HashMap<>(); /** Indicates if the current date should be set on new messages (field 7). */ private boolean setDate; /** Indicates that the header should be written/parsed as binary */ private boolean binaryHeader; /** Indicates that the fields should be written/parsed as binary */ private boolean binaryFields; private int etx = -1; /** Flag to specify if missing fields should be ignored as long as they're at * the end of the message. */ private boolean ignoreLast; private boolean forceb2; private boolean binBitmap; private boolean forceStringEncoding; private String encoding = System.getProperty("file.encoding"); /** This flag gets passed on to newly created messages and also sets this value for all * field parsers in parsing guides. */ public void setForceStringEncoding(boolean flag) { forceStringEncoding = flag; for (Map<Integer,FieldParseInfo> pm : parseMap.values()) { for (FieldParseInfo parser : pm.values()) { parser.setForceStringDecoding(flag); } } } public boolean isForceStringEncoding() { return forceStringEncoding; } /** Tells the factory to create messages that encode their bitmaps in binary format * even when they're encoded as text. Has no effect on binary messages. */ public void setUseBinaryBitmap(boolean flag) { binBitmap = flag; } /** Returns true if the factory is set to create and parse bitmaps in binary format * when the messages are encoded as text. */ public boolean isUseBinaryBitmap() { return binBitmap; } /** Sets the character encoding used for parsing ALPHA, LLVAR and LLLVAR fields. */ public void setCharacterEncoding(String value) { if (encoding == null) { throw new IllegalArgumentException("Cannot set null encoding."); } encoding = value; if (!parseMap.isEmpty()) { for (Map<Integer, FieldParseInfo> 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 system property. */ public String getCharacterEncoding() { return encoding; } /** Sets or clears the flag to pass to new messages, to include a secondary bitmap * even if it's not needed. */ public void setForceSecondaryBitmap(boolean flag) { forceb2 = flag; } public boolean isForceSecondaryBitmap() { return forceb2; } /** Setting this property to true avoids getting a ParseException when parsing messages that don't have * the last field specified in the bitmap. This is common with certain providers where field 128 is * specified in the bitmap but not actually included in the messages. Default is false, which has * been the behavior in previous versions when this option didn't exist. */ public void setIgnoreLastMissingField(boolean flag) { ignoreLast = flag; } /** This flag indicates if the MessageFactory throws an exception if the last field of a message * is not really present even though it's specified in the bitmap. Default is false which means * an exception is thrown. */ public boolean getIgnoreLastMissingField() { return ignoreLast; } /** Specifies a map for custom field encoder/decoders. The keys are the field numbers. */ @SuppressWarnings("rawtypes") public void setCustomFields(Map<Integer, CustomField> value) { customFields = value; } /** Sets the CustomField encoder for the specified field number. */ public void setCustomField(int index, CustomField<?> value) { customFields.put(index, value); } /** Returns a custom field encoder/decoder for the specified field number, if one is available. */ @SuppressWarnings("unchecked") public <F> CustomField<F> 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 <F> CustomField<F> getCustomField(Integer index) { return customFields.get(index); } /** Tells the receiver to read the configuration at the specified path. This just calls * ConfigParser.configureFromClasspathConfig() with itself and the specified path at arguments, * but is really convenient in case the MessageFactory is being configured from within, say, Spring. */ public void setConfigPath(String path) throws IOException { ConfigParser.configureFromClasspathConfig(this, path); //Now re-set some properties that need to be propagated down to the recently assigned objects setCharacterEncoding(encoding); setForceStringEncoding(forceStringEncoding); } /** Tells the receiver to create and parse binary messages if the flag is true. * Default is false, that is, create and parse ASCII messages. Sets both binaryHeader and fields to the flag. */ public void setUseBinaryMessages(boolean flag) { binaryHeader = binaryFields = flag; } /** Returns true is the factory is set to create and parse binary messages, * false if it uses ASCII messages. Default is false. True if both binaryHeader & binaryFields * are set to true * @deprecated Check the new flags binaryHeader and binaryFields instead. */ @Deprecated public boolean getUseBinaryMessages() { return binaryHeader && binaryFields; } /** header portion of the message is written/parsed in binary, default is false */ public void setBinaryHeader(boolean flag){ binaryHeader = flag; } /** header portion of the message is written/parsed in binary, default is false */ public boolean isBinaryHeader(){ return binaryHeader; } /** fields portion of the message is written/parsed in binary, default is false */ public void setBinaryFields(boolean flag){ binaryFields = flag; } /** fields portion of the message is written/parsed in binary, default is false */ public boolean isBinaryFields(){ return binaryFields; } /** fields portion of the message is written/parsed in binary */ /** Sets the ETX character to be sent at the end of the message. This is optional and the * default is -1, which means nothing should be sent as terminator. * @param value The ASCII value of the ETX character or -1 to indicate no terminator should be used. */ public void setEtx(int value) { etx = value; } public int getEtx() { return etx; } /** Creates a new message of the specified type, with optional trace and date values as well * as any other values specified in a message template. If the factory is set to use binary * messages, then the returned message will be written using binary coding. * @param type The message type, for example 0x200, 0x400, etc. */ public T newMessage(int type) { T m; if (binIsoHeaders.get(type) != null) { m = createIsoMessageWithBinaryHeader(binIsoHeaders.get(type)); } else { m = createIsoMessage(isoHeaders.get(type)); } m.setType(type); m.setEtx(etx); m.setBinaryHeader(isBinaryHeader()); m.setBinaryFields(isBinaryFields()); m.setForceSecondaryBitmap(forceb2); m.setBinaryBitmap(binBitmap); m.setCharacterEncoding(encoding); m.setForceStringEncoding(forceStringEncoding); //Copy the values from the template IsoMessage templ = typeTemplates.get(type); if (templ != null) { for (int i = 2; i <= 128; i++) { if (templ.hasField(i)) { //We could detect here if there's a custom object with a CustomField, //but we can't copy the value so there's no point. m.setField(i, templ.getField(i).clone()); } } } if (traceGen != null) { m.setValue(11, traceGen.nextTrace(), IsoType.NUMERIC, 6); } if (setDate) { if (m.hasField(7)) { //We may have a field with a timezone but no value m.updateValue(7, new Date()); } else { IsoValue<Date> now = new IsoValue<>(IsoType.DATE10, new Date()); if (DateTimeParseInfo.getDefaultTimeZone() != null) { now.setTimeZone(DateTimeParseInfo.getDefaultTimeZone()); } m.setField(7, now); } } return m; } /** Creates a message to respond to a request. Increments the message type by 16, * sets all fields from the template if there is one, and copies all values from the request, * overwriting fields from the template if they overlap. * @param request An ISO8583 message with a request type (ending in 00). */ public T createResponse(T request) { T resp = createIsoMessage(isoHeaders.get(request.getType() + 16)); resp.setCharacterEncoding(request.getCharacterEncoding()); resp.setBinaryHeader(request.isBinaryHeader()); resp.setBinaryFields(request.isBinaryFields()); resp.setBinaryBitmap(request.isBinaryBitmap()); resp.setType(request.getType() + 16); resp.setEtx(etx); resp.setForceSecondaryBitmap(forceb2); //Copy the values from the template or the request (request has preference) IsoMessage templ = typeTemplates.get(resp.getType()); if (templ == null) { for (int i = 2; i < 128; i++) { if (request.hasField(i)) { resp.setField(i, request.getField(i).clone()); } } } else { for (int i = 2; i < 128; i++) { if (request.hasField(i)) { resp.setField(i, request.getField(i).clone()); } else if (templ.hasField(i)) { resp.setField(i, templ.getField(i).clone()); } } } return resp; } /** Sets the timezone for the specified FieldParseInfo, if it's needed for parsing dates. */ public void setTimezoneForParseGuide(int messageType, int field, TimeZone tz) { if (field == 0) { DateTimeParseInfo.setDefaultTimeZone(tz); } Map<Integer, FieldParseInfo> guide = parseMap.get(messageType); if (guide != null) { FieldParseInfo fpi = guide.get(field); if (fpi instanceof DateTimeParseInfo) { ((DateTimeParseInfo) fpi).setTimeZone(tz); return; } } log.warn("Field {} for message type {} is not for dates, cannot set timezone", field, messageType); } /** Convenience for parseMessage(buf, isoHeaderLength, false) */ public T parseMessage(byte[] buf, int isoHeaderLength) throws ParseException, UnsupportedEncodingException { return parseMessage(buf, isoHeaderLength, false); } /** Creates a new message instance from the buffer, which must contain a valid ISO8583 * message. If the factory is set to use binary messages then it will try to parse * a binary message. * @param buf The byte buffer containing the message. Must not include the length header. * @param isoHeaderLength The expected length of the ISO header, after which the message type * and the rest of the message must come. */ public T parseMessage(byte[] buf, int isoHeaderLength, boolean binaryIsoHeader) throws ParseException, UnsupportedEncodingException { final int minlength = isoHeaderLength+(binaryHeader?2:4)+(binBitmap||binaryHeader ? 8:16); if (buf.length < minlength) { throw new ParseException("Insufficient buffer length, needs to be at least " + minlength, 0); } final T m; if (binaryIsoHeader && isoHeaderLength > 0) { byte[] _bih = new byte[isoHeaderLength]; System.arraycopy(buf, 0, _bih, 0, isoHeaderLength); m = createIsoMessageWithBinaryHeader(_bih); } else { m = createIsoMessage(isoHeaderLength > 0 ? new String(buf, 0, isoHeaderLength, encoding) : null); } m.setCharacterEncoding(encoding); final int type; if (binaryHeader) { type = ((buf[isoHeaderLength] & 0xff) << 8) | (buf[isoHeaderLength + 1] & 0xff); } else if (forceStringEncoding) { type = Integer.parseInt(new String(buf, isoHeaderLength, 4, encoding), 16); } else { type = ((buf[isoHeaderLength] - 48) << 12) | ((buf[isoHeaderLength + 1] - 48) << 8) | ((buf[isoHeaderLength + 2] - 48) << 4) | (buf[isoHeaderLength + 3] - 48); } m.setType(type); //Parse the bitmap (primary first) final BitSet bs = new BitSet(64); int pos = 0; if (binaryHeader || binBitmap) { final int bitmapStart = isoHeaderLength + (binaryHeader ? 2 : 4); for (int i = bitmapStart; i < 8+bitmapStart; i++) { int bit = 128; for (int b = 0; b < 8; b++) { bs.set(pos++, (buf[i] & bit) != 0); bit >>= 1; } } //Check for secondary bitmap and parse if necessary if (bs.get(0)) { if (buf.length < minlength + 8) { throw new ParseException("Insufficient length for secondary bitmap", minlength); } for (int i = 8+bitmapStart; i < 16+bitmapStart; i++) { int bit = 128; for (int b = 0; b < 8; b++) { bs.set(pos++, (buf[i] & bit) != 0); bit >>= 1; } } pos = minlength + 8; } else { pos = minlength; } } else { //ASCII parsing try { final byte[] bitmapBuffer; if (forceStringEncoding) { byte[] _bb = new String(buf, isoHeaderLength+4, 16, encoding).getBytes(); bitmapBuffer = new byte[36+isoHeaderLength]; System.arraycopy(_bb, 0, bitmapBuffer, 4+isoHeaderLength, 16); } else { bitmapBuffer = buf; } for (int i = isoHeaderLength + 4; i < isoHeaderLength + 20; i++) { if (bitmapBuffer[i] >= '0' && bitmapBuffer[i] <= '9') { bs.set(pos++, ((bitmapBuffer[i] - 48) & 8) > 0); bs.set(pos++, ((bitmapBuffer[i] - 48) & 4) > 0); bs.set(pos++, ((bitmapBuffer[i] - 48) & 2) > 0); bs.set(pos++, ((bitmapBuffer[i] - 48) & 1) > 0); } else if (bitmapBuffer[i] >= 'A' && bitmapBuffer[i] <= 'F') { bs.set(pos++, ((bitmapBuffer[i] - 55) & 8) > 0); bs.set(pos++, ((bitmapBuffer[i] - 55) & 4) > 0); bs.set(pos++, ((bitmapBuffer[i] - 55) & 2) > 0); bs.set(pos++, ((bitmapBuffer[i] - 55) & 1) > 0); } else if (bitmapBuffer[i] >= 'a' && bitmapBuffer[i] <= 'f') { bs.set(pos++, ((bitmapBuffer[i] - 87) & 8) > 0); bs.set(pos++, ((bitmapBuffer[i] - 87) & 4) > 0); bs.set(pos++, ((bitmapBuffer[i] - 87) & 2) > 0); bs.set(pos++, ((bitmapBuffer[i] - 87) & 1) > 0); } } //Check for secondary bitmap and parse it if necessary if (bs.get(0)) { if (buf.length < minlength + 16) { throw new ParseException("Insufficient length for secondary bitmap", minlength); } if (forceStringEncoding) { byte[] _bb = new String(buf, isoHeaderLength+20, 16, encoding).getBytes(); System.arraycopy(_bb, 0, bitmapBuffer, 20+isoHeaderLength, 16); } for (int i = isoHeaderLength + 20; i < isoHeaderLength + 36; i++) { if (bitmapBuffer[i] >= '0' && bitmapBuffer[i] <= '9') { bs.set(pos++, ((bitmapBuffer[i] - 48) & 8) > 0); bs.set(pos++, ((bitmapBuffer[i] - 48) & 4) > 0); bs.set(pos++, ((bitmapBuffer[i] - 48) & 2) > 0); bs.set(pos++, ((bitmapBuffer[i] - 48) & 1) > 0); } else if (bitmapBuffer[i] >= 'A' && bitmapBuffer[i] <= 'F') { bs.set(pos++, ((bitmapBuffer[i] - 55) & 8) > 0); bs.set(pos++, ((bitmapBuffer[i] - 55) & 4) > 0); bs.set(pos++, ((bitmapBuffer[i] - 55) & 2) > 0); bs.set(pos++, ((bitmapBuffer[i] - 55) & 1) > 0); } else if (bitmapBuffer[i] >= 'a' && bitmapBuffer[i] <= 'f') { bs.set(pos++, ((bitmapBuffer[i] - 87) & 8) > 0); bs.set(pos++, ((bitmapBuffer[i] - 87) & 4) > 0); bs.set(pos++, ((bitmapBuffer[i] - 87) & 2) > 0); bs.set(pos++, ((bitmapBuffer[i] - 87) & 1) > 0); } } pos = 16 + minlength; } else { pos = minlength; } } catch (NumberFormatException ex) { ParseException _e = new ParseException("Invalid ISO8583 bitmap", pos); _e.initCause(ex); throw _e; } } //Parse each field Map<Integer, FieldParseInfo> parseGuide = parseMap.get(type); List<Integer> index = parseOrder.get(type); if (index == null) { log.error(String.format("ISO8583 MessageFactory has no parsing guide for message type %04x [%s]", type, new String(buf))); throw new ParseException(String.format( "ISO8583 MessageFactory has no parsing guide for message type %04x [%s]", type, new String(buf)), 0); } //First we check if the message contains fields not specified in the parsing template boolean abandon = false; for (int i = 1; i < bs.length(); i++) { if (bs.get(i) && !index.contains(i+1)) { log.warn("ISO8583 MessageFactory cannot parse field {}: unspecified in parsing guide for type {}", i+1, Integer.toString(type, 16)); abandon = true; } } if (abandon) { throw new ParseException("ISO8583 MessageFactory cannot parse fields", 0); } //Now we parse each field if (binaryFields) { for (Integer i : index) { FieldParseInfo fpi = parseGuide.get(i); if (bs.get(i - 1)) { if (ignoreLast && pos >= buf.length && i.intValue() == index.get(index.size() -1)) { log.warn("Field {} is not really in the message even though it's in the bitmap", i); bs.clear(i - 1); } else { CustomField<?> decoder = fpi.getDecoder(); if (decoder == null) { decoder = getCustomField(i); } IsoValue<?> val = fpi.parseBinary(i, buf, pos, decoder); m.setField(i, val); if (val != null) { if (val.getType() == IsoType.NUMERIC || val.getType() == IsoType.DATE10 || val.getType() == IsoType.DATE4 || val.getType() == IsoType.DATE12 || val.getType() == IsoType.DATE14 || val.getType() == IsoType.DATE_EXP || val.getType() == IsoType.AMOUNT || val.getType() == IsoType.TIME) { pos += (val.getLength() / 2) + (val.getLength() % 2); } else if (val.getType() == IsoType.LLBCDBIN || val.getType() == IsoType.LLLBCDBIN || val.getType() == IsoType.LLLLBCDBIN) { pos += val.getLength() / 2 + ((val.getLength() % 2 == 0) ? 0 : 1); } else { //组合字段,长度使用LLVAR中长度位"LL"通过BCD转码后的长度 if (decoder != null ){ if (val.getType() == IsoType.LLVAR) { pos += Bcd.parseBcdLength(buf[pos]); } if (val.getType() == IsoType.LLLVAR) { pos += ((buf[pos] & 0x0f) * 100) + Bcd.parseBcdLength(buf[pos + 1]); } if (val.getType() == IsoType.LLLLVAR) { pos += Bcd.parseBcdLength2bytes(buf, pos); } }else { pos += val.getLength(); } } if (val.getType() == IsoType.LLVAR || val.getType() == IsoType.LLBIN || val.getType() == IsoType.LLBCDBIN ) { pos++; } else if (val.getType() == IsoType.LLLVAR || val.getType() == IsoType.LLLBIN || val.getType() == IsoType.LLLBCDBIN || val.getType() == IsoType.LLLLVAR || val.getType() == IsoType.LLLLBIN || val.getType() == IsoType.LLLLBCDBIN) { pos += 2; } } } } } } else { for (Integer i : index) { FieldParseInfo fpi = parseGuide.get(i); if (bs.get(i - 1)) { if (ignoreLast && pos >= buf.length && i.intValue() == index.get(index.size() -1)) { log.warn("Field {} is not really in the message even though it's in the bitmap", i); bs.clear(i - 1); } else { CustomField<?> decoder = fpi.getDecoder(); if (decoder == null) { decoder = getCustomField(i); } IsoValue<?> val = fpi.parse(i, buf, pos, decoder); m.setField(i, val); //To get the correct next position, we need to get the number of bytes, not chars pos += val.toString().getBytes(fpi.getCharacterEncoding()).length; if (val.getType() == IsoType.LLVAR || val.getType() == IsoType.LLBIN || val.getType() == IsoType.LLBCDBIN) { pos += 2; } else if (val.getType() == IsoType.LLLVAR || val.getType() == IsoType.LLLBIN || val.getType() == IsoType.LLLBCDBIN) { pos += 3; } else if (val.getType() == IsoType.LLLLVAR || val.getType() == IsoType.LLLLBIN || val.getType() == IsoType.LLLLBCDBIN) { pos += 4; } } } } } m.setBinaryHeader(binaryHeader); m.setBinaryFields(binaryFields); m.setBinaryBitmap(binBitmap); return m; } /** Creates a Iso message, override this method in the subclass to provide your * own implementations of IsoMessage. * @param header The optional ISO header that goes before the message type * @return IsoMessage */ @SuppressWarnings("unchecked") protected T createIsoMessage(String header) { return (T)new IsoMessage(header); } /** Creates a Iso message with the specified binary ISO header. * Override this method in the subclass to provide your * own implementations of IsoMessage. * @param binHeader The optional ISO header that goes before the message type * @return IsoMessage */ @SuppressWarnings("unchecked") protected T createIsoMessageWithBinaryHeader(byte[] binHeader) { return (T)new IsoMessage(binHeader); } /** Sets whether the factory should set the current date on newly created messages, * in field 7. Default is false. */ public void setAssignDate(boolean flag) { setDate = flag; } /** Returns true if the factory is assigning the current date to newly created messages * (field 7). Default is false. */ public boolean getAssignDate() { return setDate; } /** Sets the generator that this factory will get new trace numbers from. There is no * default generator. */ public void setTraceNumberGenerator(TraceNumberGenerator value) { traceGen = value; } /** Returns the generator used to assign trace numbers to new messages. */ public TraceNumberGenerator getTraceNumberGenerator() { return traceGen; } /** Sets the ISO header to be used in each message type. * @param value A map where the keys are the message types and the values are the ISO headers. */ public void setIsoHeaders(Map<Integer, String> value) { isoHeaders.clear(); isoHeaders.putAll(value); } /** Sets the ISO header for a specific message type. * @param type The message type, for example 0x200. * @param value The ISO header, or NULL to remove any headers for this message type. */ public void setIsoHeader(int type, String value) { if (value == null) { isoHeaders.remove(type); } else { isoHeaders.put(type, value); binIsoHeaders.remove(type); } } /** Returns the ISO header used for the specified type. */ public String getIsoHeader(int type) { return isoHeaders.get(type); } /** Sets the ISO header for a specific message type, in binary format. * @param type The message type, for example 0x200. * @param value The ISO header, or NULL to remove any headers for this message type. */ public void setBinaryIsoHeader(int type, byte[] value) { if (value == null) { binIsoHeaders.remove(type); } else { binIsoHeaders.put(type, value); isoHeaders.remove(type); } } /** Returns the binary ISO header used for the specified type. */ public byte[] getBinaryIsoHeader(int type) { return binIsoHeaders.get(type); } /** Adds a message template to the factory. If there was a template for the same * message type as the new one, it is overwritten. */ public void addMessageTemplate(T templ) { if (templ != null) { typeTemplates.put(templ.getType(), templ); } } /** Removes the message template for the specified type. */ public void removeMessageTemplate(int type) { typeTemplates.remove(type); } /** Returns the template for the specified message type. This allows templates to be modified * programmatically. */ public T getMessageTemplate(int type) { return typeTemplates.get(type); } /** Invoke this method in case you want to freeze the configuration, making message and parsing * templates, as well as iso headers and custom fields, immutable. */ public void freeze() { typeTemplates = Collections.unmodifiableMap(typeTemplates); parseMap = Collections.unmodifiableMap(parseMap); parseOrder = Collections.unmodifiableMap(parseOrder); isoHeaders = Collections.unmodifiableMap(isoHeaders); binIsoHeaders = Collections.unmodifiableMap(binIsoHeaders); customFields = Collections.unmodifiableMap(customFields); } /** Sets a map with the fields that are to be expected when parsing a certain type of * message. * @param type The message type. * @param map A map of FieldParseInfo instances, each of which define what type and length * of field to expect. The keys will be the field numbers. */ public void setParseMap(int type, Map<Integer, FieldParseInfo> map) { parseMap.put(type, map); ArrayList<Integer> index = new ArrayList<>(); index.addAll(map.keySet()); Collections.sort(index); log.trace(String.format("ISO8583 MessageFactory adding parse map for type %04x with fields %s", type, index)); parseOrder.put(type, index); } }

IsoValue.java

/*
j8583 A Java implementation of the ISO8583 protocol
Copyright (C) 2007 Enrique Zamudio Lopez

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 3 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
*/
package com.swiftplus.posservice.prepose.iso8583;

import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Date;
import java.util.TimeZone;

import com.swiftplus.posservice.prepose.iso8583.util.Bcd;
import com.swiftplus.posservice.prepose.iso8583.util.HexCodec;

/** Represents a value that is stored in a field inside an ISO8583 message.
 * It can format the value when the message is generated.
 * Some values have a fixed length, other values require a length to be specified
 * so that the value can be padded to the specified length. LLVAR and LLLVAR
 * values do not need a length specification because the length is calculated
 * from the stored value.
 * 
 * @author Enrique Zamudio
 */
public class IsoValue<T> implements Cloneable {

	private IsoType type;
	private T value;
	private CustomFieldEncoder<T> encoder;
	private int length;
	private String encoding;
    private TimeZone tz;
	private boolean binaryField;

	public IsoValue(IsoType t, T value)  {
		this(t, value, null);
	}
	public IsoValue(IsoType t, T value,boolean binaryFlag)  {
		this(t, value, null,binaryFlag);
	}
	/** Creates a new instance that stores the specified value as the specified type.
	 * Useful for storing LLVAR or LLLVAR types, as well as fixed-length value types
	 * like DATE10, DATE4, AMOUNT, etc.
	 * @param t the ISO type.
	 * @param value The value to be stored.
	 * @param custom An optional CustomFieldEncoder for the value.
	 */
	public IsoValue(IsoType t, T value, CustomFieldEncoder<T> custom )  {
		this(t,value,custom,false);
	}
	public IsoValue(IsoType t, T value, CustomFieldEncoder<T> custom,boolean binaryFlag)  {
		if (t.needsLength()) {
			throw new IllegalArgumentException("Fixed-value types must use constructor that specifies length");
		}
		encoder = custom;
		type = t;
		this.value = value;
		this.binaryField = binaryFlag;
		if (type == IsoType.LLVAR || type == IsoType.LLLVAR || type == IsoType.LLLLVAR) {
			if (custom == null) {
				length = value.toString().length();
			} else {
				//二进制判断
				String enc;
				if (binaryFlag){
					try {
						enc = new String(((CustomBinaryField<T>)custom).encodeBinaryField(value),"UTF-8");
					} catch (UnsupportedEncodingException e) {
						enc = null;
					}
				}else {
					enc = custom.encodeField(value);
				}
				if (enc == null) {
					enc = value == null ? "" : value.toString();
				}
				length = enc.length();
			}
			validateTypeWithVariableLength();
		} else if (type == IsoType.LLBIN || type == IsoType.LLLBIN || type == IsoType.LLLLBIN) {
			if (custom == null) {
				if (value instanceof byte[]) {
					length = ((byte[])value).length;
				} else {
					length = value.toString().length() / 2 + (value.toString().length() % 2);
				}
            } else if (custom instanceof CustomBinaryField) {
                length = ((CustomBinaryField<T>)custom).encodeBinaryField(value).length;
			} else {
				//二进制判断
				String enc;
				if (binaryFlag){
					try {
						enc = new String(((CustomBinaryField<T>)custom).encodeBinaryField(value),"UTF-8");
					} catch (UnsupportedEncodingException e) {
						enc = null;
					}
				}else {
					enc = custom.encodeField(value);
				}
				if (enc == null) {
					enc = value == null ? "" : value.toString();
				}
				length = enc.length();
			}
			validateTypeWithVariableLength();
		} else if (type == IsoType.LLBCDBIN || type == IsoType.LLLBCDBIN || type == IsoType.LLLLBCDBIN) {
			if (value instanceof byte[]) {
                length = ((byte[])value).length * 2;
			} else {
				length = value.toString().length();
			}
			validateTypeWithVariableLength();
		} else {
			length = type.getLength();
		}
	}

	public IsoValue(IsoType t, T val, int len) {
		this(t, val, len, null);
	}
	public IsoValue(IsoType t, T val, int len,boolean binaryFlag) {
		this(t, val, len, null,binaryFlag);
	}
	/** Creates a new instance that stores the specified value as the specified type.
	 * Useful for storing fixed-length value types.
	 * @param t The ISO8583 type for this field.
	 * @param val The value to store in the field.
	 * @param len The length for the value.
	 * @param custom An optional CustomFieldEncoder for the value.
	 */
	public IsoValue(IsoType t, T val, int len, CustomFieldEncoder<T> custom) {
		this(t,val,len,custom,false);
	}
	public IsoValue(IsoType t, T val, int len, CustomFieldEncoder<T> custom,boolean binaryFlag) {
		type = t;
		value = val;
		length = len;
		encoder = custom;
		this.binaryField = binaryFlag;
		if (length == 0 && t.needsLength()) {
			throw new IllegalArgumentException(String.format("Length must be greater than zero for type %s (value '%s')", t, val));
		} else if (t == IsoType.LLVAR || t == IsoType.LLLVAR || t == IsoType.LLLLVAR) {
			if (len == 0) {
				if (binaryFlag){
					length = custom == null ? val.toString().length() : ((CustomBinaryField<T>)custom).encodeBinaryField(value).length;
				}else {
					length = custom == null ? val.toString().length() : custom.encodeField(value).length();
				}
			}
			validateTypeWithVariableLength();
		} else if (t == IsoType.LLBIN || t == IsoType.LLLBIN || t == IsoType.LLLLBIN) {
			if (len == 0) {
                if (custom == null) {
                    length = ((byte[])val).length;
                } else if (custom instanceof CustomBinaryField ) { //  || binaryFlag
                    length = ((CustomBinaryField<T>)custom).encodeBinaryField(value).length;
                } else {
                	length = custom.encodeField(value).length();
                }
                if (binaryFlag){
					length = custom == null ? ((byte[]) val).length : ((CustomBinaryField<T>)custom).encodeBinaryField(value).length;
				}else {
					length = custom == null ? ((byte[]) val).length : custom.encodeField(value).length();
				}
			}
			validateTypeWithVariableLength();
		} else if (t == IsoType.LLBCDBIN || t == IsoType.LLLBCDBIN || t == IsoType.LLLLBCDBIN) {
			if (len == 0) {
				if (value instanceof byte[]) {
					length = ((byte[]) value).length * 2;
				} else {
					length = value.toString().length();
				}
			}
			validateTypeWithVariableLength();
		}
	}

	/** Returns the ISO type to which the value must be formatted. */
	public IsoType getType() {
		return type;
	}

	/** Returns the length of the stored value, of the length of the formatted value
	 * in case of NUMERIC or ALPHA. It doesn't include the field length header in case
	 * of LLVAR or LLLVAR. */
	public int getLength() {
		return length;
	}

	/** Returns the stored value without any conversion or formatting. */
	public T getValue() {
		return value;
	}

	public void setCharacterEncoding(String value) {
		encoding = value;
	}
	public String getCharacterEncoding() {
		return encoding;
	}

    /** Sets the timezone, useful for date fields. */
    public void setTimeZone(TimeZone value) {
        tz = value;
    }
    public TimeZone getTimeZone() {
        return tz;
    }

	/** Returns the formatted value as a String. The formatting depends on the type of the
	 * receiver. */
	public String toString() {
		if (value == null) {
			return "ISOValue";
		}
		if (type == IsoType.NUMERIC || type == IsoType.AMOUNT) {
			if (type == IsoType.AMOUNT) {
				if (value instanceof BigDecimal) {
					return type.format((BigDecimal) value, 12);
				} else {
					return type.format(value.toString(), 12);
				}
            } else if (value instanceof BigInteger) {
                return type.format(encoder == null ? value.toString() : encoder.encodeField(value), length);
			} else if (value instanceof Number) {
				return type.format(((Number)value).longValue(), length);
			} else {
				return type.format(encoder == null ? value.toString() : encoder.encodeField(value), length);
			}
		} else if (type == IsoType.ALPHA) {
			return type.format(encoder == null ? value.toString() : encoder.encodeField(value), length);
		} else if (type == IsoType.LLVAR || type == IsoType.LLLVAR || type == IsoType.LLLLVAR) {
//			return getStringEncoded();
			return getBinStringEncoded();
		} else if (value instanceof Date) {
			return type.format((Date)value, tz);
		} else if (type == IsoType.BINARY) {
			if (value instanceof byte[]) {
                final byte[] _v = (byte[])value;
				return type.format(encoder == null ? HexCodec.hexEncode(_v, 0, _v.length) : encoder.encodeField(value), length * 2);
			} else {
				return type.format(encoder == null ? value.toString() : encoder.encodeField(value), length * 2);
			}
		} else if (type == IsoType.LLBIN || type == IsoType.LLLBIN || type == IsoType.LLLLBIN) {
			if (value instanceof byte[]) {
                final byte[] _v = (byte[])value;
				return encoder == null ? HexCodec.hexEncode(_v, 0, _v.length) : encoder.encodeField(value);
			} else {
//				final String _s = getStringEncoded();
				final String _s = getBinStringEncoded();
				return (_s.length() % 2 == 1) ? String.format("0%s", _s) : _s;
			}
		} else if (type == IsoType.LLBCDBIN || type == IsoType.LLLBCDBIN || type == IsoType.LLLLBCDBIN) {
			if (value instanceof byte[]) {
				final byte[] _v = (byte[])value;
				final String val = encoder == null ? HexCodec.hexEncode(_v, 0, _v.length) : encoder.encodeField(value);
				return val.substring(val.length() - length);
			} else {
//				return getStringEncoded();
				return getBinStringEncoded();
			}
		}
		return getStringEncoded();
	}

	private String getStringEncoded() {
		return encoder == null ? value.toString() : encoder.encodeField(value);
	}

	private String getBinStringEncoded() {
		if (binaryField){
			try {
				return encoder == null ? value.toString() : new String(((CustomBinaryField<T>) encoder).encodeBinaryField(value),encoding == null?"UTF-8":encoding);
			} catch (UnsupportedEncodingException e) {
				return "";
			}
		}else {
			return encoder == null ? value.toString() : encoder.encodeField(value);
		}
	}

	/** Returns a copy of the receiver that references the same value object. */
	@SuppressWarnings("unchecked")
	public IsoValue<T> clone() {
		try {
			return (IsoValue<T>)super.clone();
		} catch (CloneNotSupportedException ex) {
			return null;
		}
	}

	/** Returns true of the other object is also an IsoValue and has the same type and length,
	 * and if other.getValue().equals(getValue()) returns true. */
	public boolean equals(Object other) {
		if (other == null || !(other instanceof IsoValue<?>)) {
			return false;
		}
		IsoValue<?> comp = (IsoValue<?>)other;
		return (comp.getType() == getType() && comp.getValue().equals(getValue())
				&& comp.getLength() == getLength());
	}

	@Override
	public int hashCode() {
		return value == null ? 0 : toString().hashCode();
	}

	/** Returns the CustomFieldEncoder for this value. */
	public CustomFieldEncoder<T> getEncoder() {
		return encoder;
	}

    protected void writeLengthHeader(final int l, final OutputStream outs, final IsoType type,
                                     final boolean binary, final boolean forceStringEncoding)
            throws IOException {
        final int digits;
        if (type == IsoType.LLLLBIN || type == IsoType.LLLLVAR || type == IsoType.LLLLBCDBIN) {
            digits = 4;
        } else if (type == IsoType.LLLBIN || type == IsoType.LLLVAR || type == IsoType.LLLBCDBIN) {
            digits = 3;
        } else {
            digits = 2;
        }
        if (binary) {
            if (digits == 4) {
                outs.write((((l % 10000) / 1000) << 4) | ((l % 1000)/100));
            } else 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;
            } else if (ldiff == 3) {
                lhead = "000" + lhead;
            }
            outs.write(encoding == null ? lhead.getBytes():lhead.getBytes(encoding));
        } else {
            //write the length in ASCII
            if (digits == 4) {
                outs.write((l/1000)+48);
                outs.write(((l%1000)/100)+48);
            } else 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.
     * @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 || type == IsoType.LLLLVAR) {
            writeLengthHeader(length, outs, type, binary, forceStringEncoding);
		} else if (type == IsoType.LLBIN || type == IsoType.LLLBIN || type == IsoType.LLLLBIN) {
			writeLengthHeader(binary ? length : length*2, outs, type, binary, forceStringEncoding);
		} else if (type == IsoType.LLBCDBIN || type == IsoType.LLLBCDBIN || type == IsoType.LLLLBCDBIN) {
            writeLengthHeader(length, outs, type, binary, forceStringEncoding);
		} else if (binary) {
			//numeric types in binary are coded like this
			byte[] buf = null;
			if (type == IsoType.NUMERIC) {
				buf = new byte[(length / 2) + (length % 2)];
			} else if (type == IsoType.AMOUNT) {
				buf = new byte[6];
			} else if (type == IsoType.DATE10 || type == IsoType.DATE4 ||
					type == IsoType.DATE_EXP || type == IsoType.TIME ||
					type == IsoType.DATE12 || type == IsoType.DATE14) {
				buf = new byte[length / 2];
			}
			//Encode in BCD if it's one of these types
			if (buf != null) {
				Bcd.encode(toString(), buf);
				outs.write(buf);
				return;
			}
		}
		if (binary && (type == IsoType.BINARY || IsoType.VARIABLE_LENGTH_BIN_TYPES.contains(type))) {
			int missing;
			if (value instanceof byte[]) {
				outs.write((byte[])value);
				missing = length - ((byte[])value).length;
            } else if (encoder instanceof CustomBinaryField) {
                byte[] binval = ((CustomBinaryField<T>) encoder).encodeBinaryField(value);
                outs.write(binval);
                missing = length - binval.length;
			} else {
				byte[] binval = HexCodec.hexDecode(value.toString());
				outs.write(binval);
				missing = length - binval.length;
			}
			if (type == IsoType.BINARY && missing > 0) {
				for (int i = 0; i < missing; i++) {
					outs.write(0);
				}
			}
		} else {
			outs.write(encoding == null ? toString().getBytes() : toString().getBytes(encoding));
		}
	}

	private void validateTypeWithVariableLength() {
		if (type == IsoType.LLVAR && length > 99) {
			throwIllegalArgumentException(type, 99);
		} else if (type == IsoType.LLLVAR && length > 999) {
			throwIllegalArgumentException(type, 999);
		} else if (type == IsoType.LLLLVAR && length > 9999) {
			throwIllegalArgumentException(type, 9999);
		} else if (type == IsoType.LLBIN && length > 99) {
			throwIllegalArgumentException(type, 99);
		} else if (type == IsoType.LLLBIN && length > 999) {
			throwIllegalArgumentException(type, 999);
		} else if (type == IsoType.LLLLBIN && length > 9999) {
			throwIllegalArgumentException(type, 9999);
		} else if (type == IsoType.LLBCDBIN && length > 50) {
			throwIllegalArgumentException(type, 50);
		} else if (type == IsoType.LLLBCDBIN && length > 500) {
			throwIllegalArgumentException(type, 500);
		} else if (type == IsoType.LLLLBCDBIN && length > 5000) {
			throwIllegalArgumentException(type, 5000);
		}
	}

	private void throwIllegalArgumentException(IsoType t, int maxLength) {
		throw new IllegalArgumentException(t.name() + " can only hold values up to " + maxLength + " chars");
	}

}

IsoMessage.java

/*
j8583 A Java implementation of the ISO8583 protocol
Copyright (C) 2007 Enrique Zamudio Lopez

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 3 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
*/
package com.swiftplus.posservice.prepose.iso8583;

import com.swiftplus.posservice.prepose.iso8583.util.HexCodec;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.util.BitSet;
import java.util.Map;

/** Represents an ISO8583 message. This is the core class of the framework.
 * Contains the bitmap which is modified as fields are added/removed.
 * This class makes no assumptions as to what types belong in each field,
 * nor what fields should each different message type have; that is left
 * for the developer, since the different ISO8583 implementations can vary
 * greatly.
 * 
 * @author Enrique Zamudio
 */
public class IsoMessage {

	static final byte[] HEX = new byte[]{ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };

	/** The message type. */
    private int type;

    private boolean binaryHeader;
    private boolean binaryFields;

    /** This is where the values are stored. */
    @SuppressWarnings("rawtypes")
	private IsoValue[] fields = new IsoValue[129];
    /** Stores the optional ISO header. */
    private String isoHeader;
    private byte[] binIsoHeader;
    private int etx = -1;
    /** 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. */
    public IsoMessage() {
    }

    /** Creates a new message with the specified ISO header. This will be prepended to the message. */
    protected IsoMessage(String header) {
    	isoHeader = header;
    }
    /** Creates a new message with the specified binary ISO header. This will be prepended to the message. */
    protected IsoMessage(byte[] binaryHeader) {
    	binIsoHeader = binaryHeader;
    }

    /** Tells the message to encode its bitmap in binary format, even if the message
     * itself is encoded as text. This has no effect if the binary flag is set, which means
     * binary messages will always encode their bitmap in binary format. */
    public void setBinaryBitmap(boolean flag) {
        binBitmap = flag;
    }
    /** Returns true if the message's bitmap is encoded in binary format, when the message
     * is encoded as text. Default is false. */
    public boolean isBinaryBitmap() {
        return binBitmap;
    }

    /** If set, this flag will cause the secondary bitmap to be written even if it's not needed. */
    public void setForceSecondaryBitmap(boolean flag) {
    	forceb2 = flag;
    }
    /** Returns true if the secondary bitmap is always included in the message, even
     * if it's not needed. Default is false. */
    public boolean getForceSecondaryBitmap() {
    	return forceb2;
    }

    /** Sets the encoding to use. */
    public void setCharacterEncoding(String value) {
        if (value == null) {
            throw new IllegalArgumentException("Cannot set null encoding.");
        }
    	encoding = value;
    }
    /** Returns the character encoding for Strings inside the message. Default
     * is taken from the file.encoding system property. */
    public String getCharacterEncoding() {
    	return encoding;
    }

    /** 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;
        binIsoHeader = null;
    }
    /** Returns the ISO header that this message was created with. */
    public String getIsoHeader() {
    	return isoHeader;
    }

    /** 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 setBinaryIsoHeader(byte[] binaryHeader) {
        isoHeader = null;
        binIsoHeader = binaryHeader;
    }
    /** Returns the binary ISO header that this message was created with. */
    public byte[] getBinaryIsoHeader() {
        return binIsoHeader;
    }

    /** Sets the ISO message type. Common values are 0x200, 0x210, 0x400, 0x410, 0x800, 0x810. */
    public void setType(int value) {
    	type = value;
    }
    /** Returns the ISO message type. */
    public int getType() {
    	return type;
    }

    /** Indicates whether the message should be binary. Default is false.
     * To encode the message as text but the bitmap in binary format, you can set the
     * binaryBitmap flag. */
    public void setBinary(boolean flag) {
    	binaryHeader = binaryFields = flag;
    }

    /** Returns true if the message is binary coded (both header and fields); default is false.
     * @deprecated Use the new flags isBinaryHeader and isBinaryFields instead.
     */
    @Deprecated
    public boolean isBinary() {
    	return binaryHeader && binaryFields;
    }

    /** header information is binary encoded */
    public void setBinaryHeader(boolean flag) {
        binaryHeader = flag;
    }

    /** header information is binary encoded */
    public boolean isBinaryHeader(){
        return binaryHeader;
    }

    /** field data is binary encoded */
    public void setBinaryFields(boolean flag){
        binaryFields = flag;
    }

    /** field data is binary encoded */
    public boolean isBinaryFields(){
       return binaryFields;
    }

    /** Sets the ETX character, which is sent at the end of the message as a terminator.
     * Default is -1, which means no terminator is sent. */
    public void setEtx(int value) {
    	etx = value;
    }

    /** Returns the stored value in the field, without converting or formatting it.
     * @param field The field number. 1 is the secondary bitmap and is not returned as such;
     * real fields go from 2 to 128. */
    public <T> T getObjectValue(int field) {
    	@SuppressWarnings("unchecked")
    	IsoValue<T> v = fields[field];
    	return v == null ? null : v.getValue();
    }

    /** Returns the IsoValue for the specified field. First real field is 2. */
	@SuppressWarnings("unchecked")
    public <T> IsoValue<T> getField(int field) {
    	return fields[field];
    }

    /** Stored the field in the specified index. The first field is the secondary bitmap and has index 1,
     * so the first valid value for index must be 2.
     * @return The receiver (useful for setting several fields in sequence). */
    public IsoMessage setField(int index, IsoValue<?> field) {
    	if (index < 2 || index > 128) {
    		throw new IndexOutOfBoundsException("Field index must be between 2 and 128");
    	}
    	if (field != null) {
        	field.setCharacterEncoding(encoding);
    	}
    	fields[index] = field;
    	return this;
    }

    /** Convenience method for setting several fields in one call. */
    public IsoMessage setFields(Map<Integer, IsoValue<?>> values) {
    	for (Map.Entry<Integer, IsoValue<?>> e : values.entrySet()) {
    		setField(e.getKey(), e.getValue());
    	}
    	return this;
    }

    /** Sets the specified value in the specified field, creating an IsoValue internally.
     * @param index The field number (2 to 128)
     * @param value The value to be stored.
     * @param t The ISO type.
     * @param length The length of the field, used for ALPHA and NUMERIC values only, ignored
     * with any other type.
     * @return The receiver (useful for setting several values in sequence). */
    public IsoMessage setValue(int index, Object value, IsoType t, int length) {
    	return setValue(index, value, null, t, length);
    }

    /** Sets the specified value in the specified field, creating an IsoValue internally.
     * @param index The field number (2 to 128)
     * @param value The value to be stored.
     * @param encoder An optional CustomFieldEncoder for the value.
     * @param t The ISO type.
     * @param length The length of the field, used for ALPHA and NUMERIC values only, ignored
     * with any other type.
     * @return The receiver (useful for setting several values in sequence). */
    public <T> IsoMessage setValue(int index, T value, CustomFieldEncoder<T> encoder, IsoType t, int length) {
    	if (index < 2 || index > 128) {
    		throw new IndexOutOfBoundsException("Field index must be between 2 and 128");
    	}
    	if (value == null) {
    		fields[index] = null;
    	} else {
    		IsoValue<T> v = null;
    		if (t.needsLength()) {
    			v = new IsoValue<>(t, value, length, encoder,binaryFields);
    		} else {
    			v = new IsoValue<>(t, value, encoder,binaryFields);
    		}
    		v.setCharacterEncoding(encoding);
    		fields[index] = v;
    	}
    	return this;
    }

    /** A convenience method to set new values in fields that already contain values.
     * The field's type, length and custom encoder are taken from the current value.
     * This method can only be used with fields that have been previously set,
     * usually from a template in the MessageFactory.
     * @param index The field's index
     * @param value The new value to be set in that field.
     * @return The message itself.
     * @throws IllegalArgumentException if there is no current field at the specified index. */
    public <T> IsoMessage updateValue(int index, T value) {
        IsoValue<T> current = getField(index);
        if (current == null) {
            throw new IllegalArgumentException("Value-only field setter can only be used on existing fields");
        } else {
            setValue(index, value, current.getEncoder(), current.getType(), current.getLength());
            getField(index).setCharacterEncoding(current.getCharacterEncoding());
            getField(index).setTimeZone(current.getTimeZone());
        }
        return this;
    }

    /** Returns true is the message has a value in the specified field.
     * @param idx The field number. */
    public boolean hasField(int idx) {
    	return fields[idx] != null;
    }

    /** Writes a message to a stream, after writing the specified number of bytes indicating
     * the message's length. The message will first be written to an internal memory stream
     * which will then be dumped into the specified stream. This method flushes the stream
     * after the write. There are at most three write operations to the stream: one for the
     * length header, one for the message, and the last one with for the ETX.
     * @param outs The stream to write the message to.
     * @param lengthBytes The size of the message length header. Valid ranges are 0 to 4.
     * @throws IllegalArgumentException if the specified length header is more than 4 bytes.
     * @throws IOException if there is a problem writing to the stream. */
    public void write(OutputStream outs, int lengthBytes) throws IOException {
    	if (lengthBytes > 4) {
    		throw new IllegalArgumentException("The length header can have at most 4 bytes");
    	}
    	byte[] data = writeData();

    	if (lengthBytes > 0) {
    		int l = data.length;
    		if (etx > -1) {
    			l++;
    		}
    		byte[] buf = new byte[lengthBytes];
    		int pos = 0;
    		if (lengthBytes == 4) {
    			buf[0] = (byte)((l & 0xff000000) >> 24);
    			pos++;
    		}
    		if (lengthBytes > 2) {
    			buf[pos] = (byte)((l & 0xff0000) >> 16);
    			pos++;
    		}
    		if (lengthBytes > 1) {
    			buf[pos] = (byte)((l & 0xff00) >> 8);
    			pos++;
    		}
    		buf[pos] = (byte)(l & 0xff);
    		outs.write(buf);
    	}
    	outs.write(data);
    	//ETX
    	if (etx > -1) {
    		outs.write(etx);
    	}
    	outs.flush();
    }

    /** Creates and returns a ByteBuffer with the data of the message, including the length header.
     * The returned buffer is already flipped, so it is ready to be written to a Channel. */
    public ByteBuffer writeToBuffer(int lengthBytes) {
    	if (lengthBytes > 4) {
    		throw new IllegalArgumentException("The length header can have at most 4 bytes");
    	}

    	byte[] data = writeData();
    	ByteBuffer buf = ByteBuffer.allocate(lengthBytes + data.length + (etx > -1 ? 1 : 0));
    	if (lengthBytes > 0) {
    		int l = data.length;
    		if (etx > -1) {
    			l++;
    		}
    		if (lengthBytes == 4) {
                buf.put((byte)((l & 0xff000000) >> 24));
    		}
    		if (lengthBytes > 2) {
                buf.put((byte)((l & 0xff0000) >> 16));
    		}
    		if (lengthBytes > 1) {
                buf.put((byte)((l & 0xff00) >> 8));
    		}
            buf.put((byte)(l & 0xff));
    	}
    	buf.put(data);
    	//ETX
    	if (etx > -1) {
    		buf.put((byte)etx);
    	}
    	buf.flip();
    	return buf;
    }

    /** Creates a BitSet for the bitmap. */
    protected BitSet createBitmapBitSet() {
        BitSet bs = new BitSet(forceb2 ? 128 : 64);
        for (int i = 2 ; i < 129; i++) {
            if (fields[i] != null) {
                bs.set(i - 1);
            }
        }
        if (forceb2) {
            bs.set(0);
        } else if (bs.length() > 64) {
            //Extend to 128 if needed
            BitSet b2 = new BitSet(128);
            b2.or(bs);
            bs = b2;
            bs.set(0);
        }
        return bs;
    }

    /** Writes the message to a memory stream and returns a byte array with the result. */
    public byte[] writeData() {
    	ByteArrayOutputStream bout = new ByteArrayOutputStream();
    	if (isoHeader != null) {
    		try {
    			bout.write(isoHeader.getBytes(encoding));
    		} catch (IOException ex) {
    			//should never happen, writing to a ByteArrayOutputStream
    		}
    	} else if (binIsoHeader != null) {
            try {
                bout.write(binIsoHeader);
            } catch (IOException ex) {
                //should never happen, writing to a ByteArrayOutputStream
            }
        }
    	//Message Type
    	if (binaryHeader) {
        	bout.write((type & 0xff00) >> 8);
        	bout.write(type & 0xff);
    	} else {
    		try {
    			bout.write(String.format("%04x", type).getBytes(encoding));
    		} catch (IOException ex) {
    			//should never happen, writing to a ByteArrayOutputStream
    		}
    	}

    	//Bitmap
        BitSet bs = createBitmapBitSet();
    	//Write bitmap to stream
    	if (binaryHeader || binBitmap) {
    		int pos = 128;
    		int b = 0;
    		for (int i = 0; i < bs.size(); i++) {
    			if (bs.get(i)) {
    				b |= pos;
    			}
    			pos >>= 1;
    			if (pos == 0) {
    				bout.write(b);
    				pos = 128;
    				b = 0;
    			}
    		}
    	} else {
            ByteArrayOutputStream bout2 = null;
            if (forceStringEncoding) {
                bout2 = bout;
                bout = new ByteArrayOutputStream();
            }
            int pos = 0;
            int lim = bs.size() / 4;
            for (int i = 0; i < lim; i++) {
                int nibble = 0;
                if (bs.get(pos++))
                    nibble |= 8;
                if (bs.get(pos++))
                    nibble |= 4;
                if (bs.get(pos++))
                    nibble |= 2;
                if (bs.get(pos++))
                    nibble |= 1;
                bout.write(HEX[nibble]);
            }
            if (forceStringEncoding) {
                final String _hb = new String(bout.toByteArray());
                bout = bout2;
                try {
                    bout.write(_hb.getBytes(encoding));
                } catch (IOException ignore) {
                    //never happen
                }
            }
    	}

    	//Fields
    	for (int i = 2; i < 129; i++) {
    		IsoValue<?> v = fields[i];
    		if (v != null) {
        		try {
        			v.write(bout, binaryFields, forceStringEncoding);
        		} catch (IOException ex) {
        			//should never happen, writing to a ByteArrayOutputStream
        		}
    		}
    	}
    	return bout.toByteArray();
    }

    /** Returns a string representation of the message, as if it were encoded
     * in ASCII with no binary bitmap. */
    public String debugString() {
        StringBuilder sb = new StringBuilder();
        if (isoHeader != null) {
            sb.append(isoHeader);
        } else if (binIsoHeader != null) {
            sb.append("[0x").append(HexCodec.hexEncode(binIsoHeader, 0, binIsoHeader.length)).append("]");
        }
        sb.append(String.format("%04x", type));

        //Bitmap
        BitSet bs = createBitmapBitSet();
        int pos = 0;
        int lim = bs.size() / 4;
        for (int i = 0; i < lim; i++) {
            int nibble = 0;
            if (bs.get(pos++))
               nibble |= 8;
            if (bs.get(pos++))
               nibble |= 4;
            if (bs.get(pos++))
               nibble |= 2;
            if (bs.get(pos++))
               nibble |= 1;
            sb.append(new String(HEX, nibble, 1));
        }

        //Fields
        for (int i = 2; i < 129; i++) {
            IsoValue<?> v = fields[i];
            if (v != null) {
                String desc = v.toString();
                if (v.getType() == IsoType.LLBIN || v.getType() == IsoType.LLBCDBIN || v.getType() == IsoType.LLVAR) {
                    sb.append(String.format("%02d", desc.length()));
                } else if (v.getType() == IsoType.LLLBIN || v.getType() == IsoType.LLLBCDBIN || v.getType() == IsoType.LLLVAR) {
                    sb.append(String.format("%03d", desc.length()));
                } else if (v.getType() == IsoType.LLLLBIN || v.getType() == IsoType.LLLLBCDBIN || v.getType() == IsoType.LLLLVAR) {
                    sb.append(String.format("%04d", desc.length()));
                }
                sb.append(desc);
            }
        }
        return sb.toString();
    }

    //These are for Groovy compat
    /** Sets the specified value in the specified field, just like {@link #setField(int, IsoValue)}. */
    public <T> void putAt(int i, IsoValue<T> v) {
    	setField(i, v);
    }
    /** Returns the IsoValue in the specified field, just like {@link #getField(int)}. */
    public <T> IsoValue<T> getAt(int i) {
    	return getField(i);
    }

	//These are for Scala compat
    /** Sets the specified value in the specified field, just like {@link #setField(int, IsoValue)}. */
	public <T> void update(int i, IsoValue<T> v) {
		setField(i, v);
	}
    /** Returns the IsoValue in the specified field, just like {@link #getField(int)}. */
	public <T> IsoValue<T> apply(int i) {
		return getField(i);
	}

    /** Copies the specified fields from the other message into the recipient. If a specified field is
     * not present in the source message it is simply ignored. */
    public void copyFieldsFrom(IsoMessage src, int...idx) {
    	for (int i : idx) {
    		IsoValue<Object> v = src.getField(i);
    		if (v != null) {
        		setValue(i, v.getValue(), v.getEncoder(), v.getType(), v.getLength());
    		}
    	}
    }

    /** Remove the specified fields from the message. */
    public void removeFields(int... idx) {
        for (int i : idx) {
            setField(i, null);
        }
    }

    /** Returns true is the message contains all the specified fields.
     * A convenience for m.hasField(x) && m.hasField(y) && m.hasField(z) && ... */
    public boolean hasEveryField(int... idx) {
        for (int i : idx) {
            if (!hasField(i)) {
                return false;
            }
        }
        return true;
    }
    /** Returns true is the message contains at least one of the specified fields.
     * A convenience for m.hasField(x) || m.hasField(y) || m.hasField(z) || ... */
    public boolean hasAnyField(int... idx) {
        for (int i : idx) {
            if (hasField(i)) {
                return true;
            }
        }
        return false;
    }
}

测试

        isoMessage.setValue(62,"00062",IsoType.LLLVAR,0);
        
        CompositeField compositeField63 = new CompositeField();
        compositeField63.addValue(new IsoValue(IsoType.ALPHA,"123",3));
        CompositeField compositeField630 = new CompositeField();
        compositeField630.addValue(new IsoValue(IsoType.LLVAR,"CARD",0,true));
        compositeField630.addValue(new IsoValue(IsoType.LLVAR,"UPI",0,true));
        compositeField630.addValue(new IsoValue(IsoType.LLVAR,"ACCEPT",0,true));
        compositeField630.addValue(new IsoValue(IsoType.LLVAR,"POS",0,true));
        compositeField63.addValue(new IsoValue(IsoType.LLLVAR,compositeField630,0,compositeField630,true));
        isoMessage.setValue(63,compositeField63,compositeField63,IsoType.LLLVAR,0);

请求结果:
在这里插入图片描述

另提供对应解析配置的xml写法:

		<field num="63" type="LLLVAR" >
			<field num="1" type="ALPHA" length="3" />
			<field num="2" type="LLLVAR"  >
				<field num="1" type="LLVAR"  />
				<field num="2" type="LLVAR"  />
				<field num="3" type="LLVAR" />
				<field num="4" type="LLVAR"  />
			</field>
		</field>

你可能感兴趣的:(C1-Java)