This is the mavenized version of the project, and it's the one I'll be working with from now on.
git-svn-id: https://j8583.svn.sourceforge.net/svnroot/j8583/j8583@52 db60a039-f72e-0410-b1d2-baa000846441
This commit is contained in:
@@ -0,0 +1,373 @@
|
||||
/*
|
||||
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 2.1 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.solab.iso8583;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.BitSet;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/** 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;
|
||||
/** Indicates if the message is binary-coded. */
|
||||
private boolean binary;
|
||||
/** This is where the values are stored. */
|
||||
private Map<Integer,IsoValue<?>> fields = new ConcurrentHashMap<Integer,IsoValue<?>>();
|
||||
/** Stores the optional ISO header. */
|
||||
private String isoHeader;
|
||||
private int etx = -1;
|
||||
/** Flag to enforce secondary bitmap even if empty. */
|
||||
private boolean forceb2;
|
||||
|
||||
/** 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. */
|
||||
IsoMessage(String header) {
|
||||
isoHeader = header;
|
||||
}
|
||||
|
||||
/** 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;
|
||||
}
|
||||
public boolean getForceSecondaryBitmap() {
|
||||
return forceb2;
|
||||
}
|
||||
|
||||
/** 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;
|
||||
}
|
||||
/** Returns the ISO header that this message was created with. */
|
||||
public String getIsoHeader() {
|
||||
return isoHeader;
|
||||
}
|
||||
|
||||
/** 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. */
|
||||
public void setBinary(boolean flag) {
|
||||
binary = flag;
|
||||
}
|
||||
/** Returns true if the message is binary coded; default is false. */
|
||||
public boolean isBinary() {
|
||||
return binary;
|
||||
}
|
||||
|
||||
/** 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 Object getObjectValue(int field) {
|
||||
IsoValue<?> v = fields.get(field);
|
||||
if (v == null) {
|
||||
return null;
|
||||
}
|
||||
return v.getValue();
|
||||
}
|
||||
|
||||
/** Returns the IsoValue for the specified field. First real field is 2. */
|
||||
public IsoValue<?> getField(int field) {
|
||||
return fields.get(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. */
|
||||
public void setField(int index, IsoValue<?> field) {
|
||||
if (index < 2 || index > 128) {
|
||||
throw new IndexOutOfBoundsException("Field index must be between 2 and 128");
|
||||
}
|
||||
if (field == null) {
|
||||
fields.remove(index);
|
||||
} else {
|
||||
fields.put(index, field);
|
||||
}
|
||||
}
|
||||
|
||||
/** 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. */
|
||||
public void setValue(int index, Object value, IsoType t, int length) {
|
||||
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 CustomField to encode/decode 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. */
|
||||
@SuppressWarnings("unchecked")
|
||||
public void setValue(int index, Object value, CustomField<?> 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.remove(index);
|
||||
} else {
|
||||
IsoValue v = null;
|
||||
if (t.needsLength()) {
|
||||
v = new IsoValue(t, value, length, encoder);
|
||||
} else {
|
||||
v = new IsoValue(t, value, encoder);
|
||||
}
|
||||
fields.put(index, v);
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns true is the message has a value in the specified field.
|
||||
* @param idx The field number. */
|
||||
public boolean hasField(int idx) {
|
||||
return fields.get(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 = writeInternal();
|
||||
|
||||
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 = writeInternal();
|
||||
ByteBuffer buf = ByteBuffer.allocate(lengthBytes + data.length + (etx > -1 ? 1 : 0));
|
||||
if (lengthBytes > 0) {
|
||||
int l = data.length;
|
||||
if (etx > -1) {
|
||||
l++;
|
||||
}
|
||||
byte[] bbuf = new byte[lengthBytes];
|
||||
int pos = 0;
|
||||
if (lengthBytes == 4) {
|
||||
bbuf[0] = (byte)((l & 0xff000000) >> 24);
|
||||
pos++;
|
||||
}
|
||||
if (lengthBytes > 2) {
|
||||
bbuf[pos] = (byte)((l & 0xff0000) >> 16);
|
||||
pos++;
|
||||
}
|
||||
if (lengthBytes > 1) {
|
||||
bbuf[pos] = (byte)((l & 0xff00) >> 8);
|
||||
pos++;
|
||||
}
|
||||
bbuf[pos] = (byte)(l & 0xff);
|
||||
buf.put(bbuf);
|
||||
}
|
||||
buf.put(data);
|
||||
//ETX
|
||||
if (etx > -1) {
|
||||
buf.put((byte)etx);
|
||||
}
|
||||
buf.flip();
|
||||
return buf;
|
||||
}
|
||||
|
||||
/** This calls writeInternal(), allowing applications to get the byte buffer containing the
|
||||
* message data, without the length header. */
|
||||
public byte[] writeData() {
|
||||
return writeInternal();
|
||||
}
|
||||
|
||||
/** Writes the message to a memory buffer and returns it. The message does not include
|
||||
* the ETX character or the header length. */
|
||||
protected byte[] writeInternal() {
|
||||
ByteArrayOutputStream bout = new ByteArrayOutputStream();
|
||||
if (isoHeader != null) {
|
||||
try {
|
||||
bout.write(isoHeader.getBytes());
|
||||
} catch (IOException ex) {
|
||||
//should never happen, writing to a ByteArrayOutputStream
|
||||
}
|
||||
}
|
||||
//Message Type
|
||||
if (binary) {
|
||||
bout.write((type & 0xff00) >> 8);
|
||||
bout.write(type & 0xff);
|
||||
} else {
|
||||
try {
|
||||
bout.write(String.format("%04x", type).getBytes());
|
||||
} catch (IOException ex) {
|
||||
//should never happen, writing to a ByteArrayOutputStream
|
||||
}
|
||||
}
|
||||
|
||||
//Bitmap
|
||||
ArrayList<Integer> keys = new ArrayList<Integer>();
|
||||
keys.addAll(fields.keySet());
|
||||
Collections.sort(keys);
|
||||
BitSet bs = new BitSet(forceb2 ? 128 : 64);
|
||||
for (Integer i : keys) {
|
||||
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);
|
||||
}
|
||||
//Write bitmap to stream
|
||||
if (binary) {
|
||||
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 {
|
||||
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]);
|
||||
}
|
||||
}
|
||||
|
||||
//Fields
|
||||
for (Integer i : keys) {
|
||||
IsoValue<?> v = fields.get(i);
|
||||
try {
|
||||
v.write(bout, binary);
|
||||
} catch (IOException ex) {
|
||||
//should never happen, writing to a ByteArrayOutputStream
|
||||
}
|
||||
}
|
||||
return bout.toByteArray();
|
||||
}
|
||||
|
||||
//These are for Groovy compat
|
||||
/** Sets the specified value in the specified field, just like {@link #setField(int, IsoValue)}. */
|
||||
public void putAt(int i, IsoValue<?> v) {
|
||||
setField(i, v);
|
||||
}
|
||||
/** Returns the IsoValue in the specified field, just like {@link #getField(int)}. */
|
||||
public IsoValue<?> getAt(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<?> v = src.getField(i);
|
||||
if (v != null) {
|
||||
setValue(i, v.getValue(), v.getType(), v.getLength());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user