334 lines
12 KiB
Java
334 lines
12 KiB
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 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.IOException;
|
|
import java.io.OutputStream;
|
|
import java.math.BigDecimal;
|
|
import java.util.Date;
|
|
|
|
import com.solab.iso8583.util.Bcd;
|
|
import com.solab.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 CustomField<T> encoder;
|
|
private int length;
|
|
private String encoding;
|
|
|
|
public IsoValue(IsoType t, T value) {
|
|
this(t, value, null);
|
|
}
|
|
|
|
/** 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 CustomField to encode/decode a custom value.
|
|
*/
|
|
public IsoValue(IsoType t, T value, CustomField<T> custom) {
|
|
if (t.needsLength()) {
|
|
throw new IllegalArgumentException("Fixed-value types must use constructor that specifies length");
|
|
}
|
|
encoder = custom;
|
|
type = t;
|
|
this.value = value;
|
|
if (type == IsoType.LLVAR || type == IsoType.LLLVAR) {
|
|
if (custom == null) {
|
|
length = value.toString().length();
|
|
} else {
|
|
String enc = custom.encodeField(value);
|
|
if (enc == null) {
|
|
enc = value == null ? "" : value.toString();
|
|
}
|
|
length = enc.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) {
|
|
throw new IllegalArgumentException("LLLVAR can only hold values up to 999 chars");
|
|
}
|
|
} else if (type == IsoType.LLBIN || type == IsoType.LLLBIN) {
|
|
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 = custom.encodeField(value);
|
|
if (enc == null) {
|
|
enc = value == null ? "" : value.toString();
|
|
}
|
|
length = enc.length();
|
|
}
|
|
if (t == IsoType.LLBIN && length > 99) {
|
|
throw new IllegalArgumentException("LLBIN can only hold values up to 99 chars");
|
|
} else if (t == IsoType.LLLBIN && length > 999) {
|
|
throw new IllegalArgumentException("LLLBIN can only hold values up to 999 chars");
|
|
}
|
|
} else {
|
|
length = type.getLength();
|
|
}
|
|
}
|
|
|
|
public IsoValue(IsoType t, T val, int len) {
|
|
this(t, val, len, null);
|
|
}
|
|
|
|
/** 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 CustomField to encode/decode a custom value.
|
|
*/
|
|
public IsoValue(IsoType t, T val, int len, CustomField<T> custom) {
|
|
type = t;
|
|
value = val;
|
|
length = len;
|
|
encoder = custom;
|
|
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) {
|
|
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) {
|
|
throw new IllegalArgumentException("LLLVAR can only hold values up to 999 chars");
|
|
}
|
|
} else if (t == IsoType.LLBIN || t == IsoType.LLLBIN) {
|
|
if (len == 0) {
|
|
if (custom == null) {
|
|
length = ((byte[])val).length;
|
|
} else if (custom instanceof CustomBinaryField) {
|
|
length = ((CustomBinaryField<T>)custom).encodeBinaryField(value).length;
|
|
} else {
|
|
length = custom.encodeField(value).length();
|
|
}
|
|
length = custom == null ? ((byte[])val).length : custom.encodeField(value).length();
|
|
}
|
|
if (t == IsoType.LLBIN && length > 99) {
|
|
throw new IllegalArgumentException("LLBIN can only hold values up to 99 chars");
|
|
} else if (t == IsoType.LLLBIN && length > 999) {
|
|
throw new IllegalArgumentException("LLLBIN can only hold values up to 999 chars");
|
|
}
|
|
}
|
|
}
|
|
|
|
/** 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;
|
|
}
|
|
|
|
/** Returns the formatted value as a String. The formatting depends on the type of the
|
|
* receiver. */
|
|
public String toString() {
|
|
if (value == null) {
|
|
return "ISOValue<null>";
|
|
}
|
|
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 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) {
|
|
return encoder == null ? value.toString() : encoder.encodeField(value);
|
|
} else if (value instanceof Date) {
|
|
return type.format((Date)value);
|
|
} 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) {
|
|
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 = encoder == null ? value.toString() : encoder.encodeField(value);
|
|
return (_s.length() % 2 == 1) ? String.format("0%s", _s) : _s;
|
|
}
|
|
}
|
|
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 CustomField encoder for this value. */
|
|
public CustomField<T> getEncoder() {
|
|
return encoder;
|
|
}
|
|
|
|
protected void writeLengthHeader(final int l, final OutputStream outs, final int digits,
|
|
final boolean binary, final boolean forceStringEncoding)
|
|
throws IOException {
|
|
if (binary) {
|
|
if (digits == 3) {
|
|
outs.write(l / 100); //00 to 09 automatically in BCD
|
|
}
|
|
//BCD encode the rest of the length
|
|
outs.write((((l % 100) / 10) << 4) | (l % 10));
|
|
} else if (forceStringEncoding) {
|
|
String lhead = Integer.toString(l);
|
|
final int ldiff = digits - lhead.length();
|
|
if (ldiff == 1) {
|
|
lhead = '0' + lhead;
|
|
} else if (ldiff == 2) {
|
|
lhead = "00" + lhead;
|
|
}
|
|
outs.write(lhead.getBytes(encoding));
|
|
} else {
|
|
//write the length in ASCII
|
|
if (digits == 3) {
|
|
outs.write((l / 100) + 48);
|
|
}
|
|
if (l >= 10) {
|
|
outs.write(((l % 100) / 10) + 48);
|
|
} else {
|
|
outs.write(48);
|
|
}
|
|
outs.write((l % 10) + 48);
|
|
}
|
|
}
|
|
|
|
/** Writes the formatted value to a stream, with the length header
|
|
* if it's a variable length type.
|
|
* @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) {
|
|
writeLengthHeader(length, outs, type == IsoType.LLLVAR ? 3 : 2, binary, forceStringEncoding);
|
|
} else if (type == IsoType.LLBIN || type == IsoType.LLLBIN) {
|
|
writeLengthHeader(binary ? length : length*2, outs, type == IsoType.LLLBIN ? 3 : 2, binary, forceStringEncoding);
|
|
} else if (binary) {
|
|
//numeric types in binary are coded like this
|
|
byte[] buf = null;
|
|
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) {
|
|
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 || type == IsoType.LLBIN || type == IsoType.LLLBIN)) {
|
|
int missing = 0;
|
|
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));
|
|
}
|
|
}
|
|
|
|
}
|