Made several changes that result in considerable performance improvements:
- Fields inside the IsoMessage are now stored in an IsoValue[] instead of a map (this avoids the sorting step when encoding) - The constructor IsoValue(IsoType, T, int, CustomField) for LLVAR and LLLVAR types uses the received length parameter instead of reencoding the value with the CustomField; this results in much faster parsing performance. - FieldParseInfo passes the length of LLVAR and LLLVAR fields to the IsoValue constructor so that reencoding the value (or getting the length of the string) is not necessary. Field header length is checked to be digits when parsing ASCII. - IsoType now parses AMOUNT values as BigDecimals. - MessageFactory has separate code for parsing binary and ASCII messages. Also, the DTD was moved to the resources directory. git-svn-id: https://j8583.svn.sourceforge.net/svnroot/j8583/j8583@56 db60a039-f72e-0410-b1d2-baa000846441
This commit is contained in:
@@ -22,11 +22,7 @@ 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.
|
||||
@@ -46,7 +42,7 @@ public class IsoMessage {
|
||||
/** 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<?>>();
|
||||
private IsoValue<?>[] fields = new IsoValue<?>[129];
|
||||
/** Stores the optional ISO header. */
|
||||
private String isoHeader;
|
||||
private int etx = -1;
|
||||
@@ -108,16 +104,13 @@ public class IsoMessage {
|
||||
* @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();
|
||||
IsoValue<?> v = fields[field];
|
||||
return v == null ? null : v.getValue();
|
||||
}
|
||||
|
||||
/** Returns the IsoValue for the specified field. First real field is 2. */
|
||||
public IsoValue<?> getField(int field) {
|
||||
return fields.get(field);
|
||||
return fields[field];
|
||||
}
|
||||
|
||||
/** Stored the field in the specified index. The first field is the secondary bitmap and has index 1,
|
||||
@@ -126,11 +119,7 @@ public class IsoMessage {
|
||||
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);
|
||||
}
|
||||
fields[index] = field;
|
||||
}
|
||||
|
||||
/** Sets the specified value in the specified field, creating an IsoValue internally.
|
||||
@@ -156,7 +145,7 @@ public class IsoMessage {
|
||||
throw new IndexOutOfBoundsException("Field index must be between 2 and 128");
|
||||
}
|
||||
if (value == null) {
|
||||
fields.remove(index);
|
||||
fields[index] = null;
|
||||
} else {
|
||||
IsoValue v = null;
|
||||
if (t.needsLength()) {
|
||||
@@ -164,14 +153,14 @@ public class IsoMessage {
|
||||
} else {
|
||||
v = new IsoValue(t, value, encoder);
|
||||
}
|
||||
fields.put(index, v);
|
||||
fields[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;
|
||||
return fields[idx] != null;
|
||||
}
|
||||
|
||||
/** Writes a message to a stream, after writing the specified number of bytes indicating
|
||||
@@ -289,12 +278,11 @@ public class IsoMessage {
|
||||
}
|
||||
|
||||
//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);
|
||||
for (int i = 2 ; i < 129; i++) {
|
||||
if (fields[i] != null) {
|
||||
bs.set(i - 1);
|
||||
}
|
||||
}
|
||||
if (forceb2) {
|
||||
bs.set(0);
|
||||
@@ -338,12 +326,14 @@ public class IsoMessage {
|
||||
}
|
||||
|
||||
//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
|
||||
for (int i = 2; i < 129; i++) {
|
||||
IsoValue<?> v = fields[i];
|
||||
if (v != null) {
|
||||
try {
|
||||
v.write(bout, binary);
|
||||
} catch (IOException ex) {
|
||||
//should never happen, writing to a ByteArrayOutputStream
|
||||
}
|
||||
}
|
||||
}
|
||||
return bout.toByteArray();
|
||||
|
||||
@@ -114,6 +114,8 @@ public enum IsoType {
|
||||
}
|
||||
System.arraycopy(x, 0, c, lim, x.length);
|
||||
return new String(c);
|
||||
} else if (this == AMOUNT) {
|
||||
return IsoType.NUMERIC.format(new BigDecimal(value).movePointRight(2).intValue(), 12);
|
||||
}
|
||||
throw new IllegalArgumentException("Cannot format String as " + this);
|
||||
}
|
||||
|
||||
@@ -96,7 +96,9 @@ public class IsoValue<T> implements Cloneable {
|
||||
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) {
|
||||
length = custom == null ? val.toString().length() : custom.encodeField(value).length();
|
||||
if (len == 0) {
|
||||
length = custom == null ? val.toString().length() : custom.encodeField(value).length();
|
||||
}
|
||||
if (t == IsoType.LLVAR && length > 99) {
|
||||
throw new IllegalArgumentException("LLVAR can only hold values up to 99 chars");
|
||||
} else if (t == IsoType.LLLVAR && length > 999) {
|
||||
@@ -130,7 +132,11 @@ public class IsoValue<T> implements Cloneable {
|
||||
}
|
||||
if (type == IsoType.NUMERIC || type == IsoType.AMOUNT) {
|
||||
if (type == IsoType.AMOUNT) {
|
||||
return type.format((BigDecimal)value, 12);
|
||||
if (value instanceof BigDecimal) {
|
||||
return type.format((BigDecimal)value, 12);
|
||||
} else {
|
||||
return type.format(value.toString(), 12);
|
||||
}
|
||||
} else if (value instanceof Number) {
|
||||
return type.format(((Number)value).longValue(), length);
|
||||
} else {
|
||||
|
||||
@@ -313,25 +313,46 @@ public class MessageFactory {
|
||||
return m;
|
||||
}
|
||||
//Now we parse each field
|
||||
for (Integer i : index) {
|
||||
FieldParseInfo fpi = parseGuide.get(i);
|
||||
if (bs.get(i - 1)) {
|
||||
if (ignoreLast && pos >= buf.length && i == 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 {
|
||||
IsoValue<?> val = useBinary ? fpi.parseBinary(buf, pos, getCustomField(i)) : fpi.parse(buf, pos, getCustomField(i));
|
||||
m.setField(i, val);
|
||||
if (useBinary && !(val.getType() == IsoType.ALPHA || val.getType() == IsoType.LLVAR
|
||||
|| val.getType() == IsoType.LLLVAR)) {
|
||||
pos += (val.getLength() / 2) + (val.getLength() % 2);
|
||||
if (useBinary) {
|
||||
for (Integer i : index) {
|
||||
FieldParseInfo fpi = parseGuide.get(i);
|
||||
if (bs.get(i - 1)) {
|
||||
if (ignoreLast && pos >= buf.length && i == 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 {
|
||||
pos += val.getLength();
|
||||
IsoValue<?> val = fpi.parseBinary(buf, pos, getCustomField(i));
|
||||
m.setField(i, val);
|
||||
if (!(val.getType() == IsoType.ALPHA || val.getType() == IsoType.LLVAR
|
||||
|| val.getType() == IsoType.LLLVAR)) {
|
||||
pos += (val.getLength() / 2) + (val.getLength() % 2);
|
||||
} else {
|
||||
pos += val.getLength();
|
||||
}
|
||||
if (val.getType() == IsoType.LLVAR) {
|
||||
pos++;
|
||||
} else if (val.getType() == IsoType.LLLVAR) {
|
||||
pos += 2;
|
||||
}
|
||||
}
|
||||
if (val.getType() == IsoType.LLVAR) {
|
||||
pos += useBinary ? 1 : 2;
|
||||
} else if (val.getType() == IsoType.LLLVAR) {
|
||||
pos += useBinary ? 2 : 3;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (Integer i : index) {
|
||||
FieldParseInfo fpi = parseGuide.get(i);
|
||||
if (bs.get(i - 1)) {
|
||||
if (ignoreLast && pos >= buf.length && i == 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 {
|
||||
IsoValue<?> val = fpi.parse(buf, pos, getCustomField(i));
|
||||
m.setField(i, val);
|
||||
pos += val.getLength();
|
||||
if (val.getType() == IsoType.LLVAR) {
|
||||
pos += 2;
|
||||
} else if (val.getType() == IsoType.LLLVAR) {
|
||||
pos += 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -404,6 +425,14 @@ public class MessageFactory {
|
||||
return typeTemplates.get(type);
|
||||
}
|
||||
|
||||
public void freeze() {
|
||||
typeTemplates = Collections.unmodifiableMap(typeTemplates);
|
||||
parseMap = Collections.unmodifiableMap(parseMap);
|
||||
parseOrder = Collections.unmodifiableMap(parseOrder);
|
||||
isoHeaders = Collections.unmodifiableMap(isoHeaders);
|
||||
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.
|
||||
|
||||
@@ -88,29 +88,33 @@ public class FieldParseInfo {
|
||||
throw new ParseException(String.format("Insufficient data for LLVAR field, pos %d", pos), pos);
|
||||
}
|
||||
if (custom == null) {
|
||||
return new IsoValue<String>(type, new String(buf, pos + 2, length), null);
|
||||
return new IsoValue<String>(type, new String(buf, pos + 2, length), length, null);
|
||||
} else {
|
||||
IsoValue<T> v = new IsoValue<T>(type, custom.decodeField(new String(buf, pos + 2, length)), custom);
|
||||
IsoValue<T> v = new IsoValue<T>(type, custom.decodeField(new String(buf, pos + 2, length)), length, custom);
|
||||
if (v.getValue() == null) {
|
||||
return new IsoValue<String>(type, new String(buf, pos + 2, length), null);
|
||||
return new IsoValue<String>(type, new String(buf, pos + 2, length), length, null);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
} else if (type == IsoType.LLLVAR) {
|
||||
if (!(Character.isDigit(buf[pos]) && Character.isDigit(buf[pos+1]) && Character.isDigit(buf[pos+2]))) {
|
||||
throw new ParseException(String.format("Invalid LLLVAR length '%s' pos %d", new String(buf, pos, 3), pos), pos);
|
||||
}
|
||||
length = ((buf[pos] - 48) * 100) + ((buf[pos + 1] - 48) * 10) + (buf[pos + 2] - 48);
|
||||
if (length < 0) {
|
||||
throw new ParseException(String.format("Invalid LLLVAR length %d pos %d", length, pos), pos);
|
||||
}
|
||||
if (pos+3 > buf.length || length+pos+3 > buf.length) {
|
||||
if (length+pos+3 > buf.length) {
|
||||
throw new ParseException(String.format("Insufficient data for LLLVAR field, pos %d", pos), pos);
|
||||
}
|
||||
String _v = length == 0 ? "" : new String(buf, pos + 3, length);
|
||||
if (custom == null) {
|
||||
return new IsoValue<String>(type, new String(buf, pos + 3, length), null);
|
||||
return new IsoValue<String>(type, _v, length, null);
|
||||
} else {
|
||||
IsoValue<T> v = new IsoValue<T>(type, custom.decodeField(new String(buf, pos + 3, length)), custom);
|
||||
IsoValue<T> v = new IsoValue<T>(type, custom.decodeField(_v), length, custom);
|
||||
if (v.getValue() == null) {
|
||||
//problems decoding? return the string
|
||||
return new IsoValue<String>(type, new String(buf, pos + 3, length), null);
|
||||
return new IsoValue<String>(type, _v, length, null);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user