Files
Java-8583-Util/src/main/java/com/solab/iso8583/IsoValue.java
T
2013-05-13 18:54:44 -05:00

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));
}
}
}