001/*
002 * jPOS Project [http://jpos.org]
003 * Copyright (C) 2000-2026 jPOS Software SRL
004 *
005 * This program is free software: you can redistribute it and/or modify
006 * it under the terms of the GNU Affero General Public License as
007 * published by the Free Software Foundation, either version 3 of the
008 * License, or (at your option) any later version.
009 *
010 * This program is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
013 * GNU Affero General Public License for more details.
014 *
015 * You should have received a copy of the GNU Affero General Public License
016 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
017 */
018
019package org.jpos.iso;
020
021import java.io.IOException;
022import java.io.InputStream;
023
024/**
025 * Generic class for handling binary fields in Tag-Len-Value format
026 * <code>
027 * Format is assemblied by header formatter
028 * Where
029 *       TT is the n>0 digit field number (Tag)
030 *       LL is the n>=0 digit field length (if n=0 it's means fixed length field with prefixer)
031 *       .. is the field content
032 * </code>
033 * @author Mikolaj Sosna
034 * @version $Revision: 2854 $ $Date: 2010-01-02 11:34:31 +0100 (sob) $
035 */
036public class ISOFormattableBinaryFieldPackager extends ISOFieldPackager
037{
038    private Prefixer tagPrefixer;
039    private BinaryInterpreter interpreter;
040    private Padder padder;
041    private Prefixer prefixer;
042    private IsoFieldHeaderFormatter headerFormatter;
043
044    /**
045     * Constructs a default ISOTagBinaryFieldPackager. There is ASCII tag L prefixer, no padding,
046     * no length prefix and a literal binary interpretation. The set methods must be called to
047     * make this ISOBaseFieldPackager useful.
048     */
049    public ISOFormattableBinaryFieldPackager() {
050        super();
051        this.tagPrefixer = AsciiPrefixer.L;
052        this.interpreter = LiteralBinaryInterpreter.INSTANCE;
053        this.padder = NullPadder.INSTANCE;
054        this.prefixer = NullPrefixer.INSTANCE;
055        this.headerFormatter = IsoFieldHeaderFormatter.TAG_FIRST;
056    }
057
058    /**
059     * Constructs an ISOTagBinaryFieldPackager with a specific Padder, Interpreter and Prefixer.
060     * The length and description should be set with setLength() and setDescription methods.
061     * @param tagPrefixer The type of tag prefixer used to encode tag.
062     * @param padder The type of padding used.
063     * @param interpreter The interpreter used to encode the field.
064     * @param prefixer The type of length prefixer used to encode this field.
065     */
066    public ISOFormattableBinaryFieldPackager(Prefixer tagPrefixer, Padder padder,
067                                             BinaryInterpreter interpreter, Prefixer prefixer) {
068        super();
069        this.tagPrefixer = tagPrefixer;
070        this.padder = padder;
071        this.interpreter = interpreter;
072        this.prefixer = prefixer;
073        this.headerFormatter = IsoFieldHeaderFormatter.TAG_FIRST;
074    }
075
076/**
077     * Constructs an ISOTagBinaryFieldPackager with a specific Padder, Interpreter and Prefixer.
078     * The length and description should be set with setLength() and setDescription methods.
079     * @param tagPrefixer The type of tag prefixer used to encode tag.
080     * @param padder The type of padding used.
081     * @param interpreter The interpreter used to encode the field.
082     * @param lengthPrefixer The type of length prefixer used to encode this field.
083     * @param headerFormatter The format of TAG TT and Length LL part
084     */
085    public ISOFormattableBinaryFieldPackager(Prefixer tagPrefixer, Padder padder,
086                                             BinaryInterpreter interpreter, Prefixer lengthPrefixer,
087                                             IsoFieldHeaderFormatter headerFormatter) {
088        super();
089        this.tagPrefixer = tagPrefixer;
090        this.padder = padder;
091        this.interpreter = interpreter;
092        this.prefixer = lengthPrefixer;
093        this.headerFormatter = headerFormatter;
094    }
095
096    /**
097     * Creates an ISOTagBinaryFieldPackager.
098     * @param maxLength The maximum length of the field in characters or bytes depending on the datatype.
099     * @param description The description of the field. For human readable output.
100     * @param tagPrefixer The type of tag prefixer used to encode tag.
101     * @param interpreter The interpreter used to encode the field.
102     * @param padder The type of padding used.
103     * @param lengthPrefixer The type of length prefixer used to encode this field.
104     * @param headerFormatter The format of TAG TT and Length LL part
105     */
106    public ISOFormattableBinaryFieldPackager(int maxLength, String description, Prefixer tagPrefixer,
107                                             Padder padder, BinaryInterpreter interpreter, Prefixer lengthPrefixer,
108                                             IsoFieldHeaderFormatter headerFormatter) {
109        super(maxLength, description);
110        this.tagPrefixer = tagPrefixer;
111        this.padder = padder;
112        this.interpreter = interpreter;
113        this.prefixer = lengthPrefixer;
114        this.headerFormatter = headerFormatter;
115    }
116
117    /**
118     * Sets the Padder.
119     * @param padder The padder to use during packing and unpacking.
120     */
121    public void setPadder(Padder padder) {
122        this.padder = padder;
123    }
124
125    /**
126     * Sets the Interpreter.
127     * @param interpreter The interpreter to use in packing and unpacking.
128     */
129    public void setInterpreter(BinaryInterpreter interpreter) {
130        this.interpreter = interpreter;
131    }
132
133    /**
134     * Sets the length prefixer.
135     * @param prefixer The length prefixer to use during packing and unpacking.
136     */
137    public void setPrefixer(Prefixer prefixer) {
138        this.prefixer = prefixer;
139    }
140
141    /**
142     * Gets the formatter, which assembles tag TT and length LL parts in required format
143     * @return the formatter of the header part (length and tag parts)
144     */
145    public IsoFieldHeaderFormatter getHeaderFormatter() {
146        return headerFormatter;
147    }
148
149    /**
150     * Sets the formatter, which assembles tag TT and length LL parts in required format
151     * @param headerFormatter the formatter of the header part (length and tag parts)
152     */
153    public void setHeaderFormatter(IsoFieldHeaderFormatter headerFormatter) {
154        this.headerFormatter = headerFormatter;
155    }
156
157    /**
158     * Returns the prefixer's packed length and the interpreter's packed length.
159     * @see ISOFieldPackager#getMaxPackedLength()
160     */
161    @Override
162    public int getMaxPackedLength() {
163        return tagPrefixer.getPackedLength() + prefixer.getPackedLength() + interpreter.getPackedLength(getLength());
164    }
165
166    /**
167     * Create a nice readable message for errors
168     */
169    private String makeExceptionMessage(ISOComponent c, String operation) {
170        Object fieldKey = "unknown";
171        if (c != null)
172            try{
173                fieldKey = c.getKey();
174            } catch (Exception ignore){}
175        return this.getClass().getName() + ": Problem " + operation + " field " + fieldKey;
176    }
177
178    /**
179     * Convert the component into a byte[].
180     */
181    @Override
182    public byte[] pack(ISOComponent c) throws ISOException {
183        try{
184            byte[] valueBytes = (byte[])c.getValue();
185            if (valueBytes.length < 0 || valueBytes.length > getLength())
186                throw new ISOException("Field length " + valueBytes.length + " too long. Max: " + getLength());
187
188            int tag = (Integer)c.getKey();
189            byte[] paddedValueBytes = valueBytes;
190
191            if (!(padder instanceof NullPadder)) //for save few cycles
192              paddedValueBytes = ISOUtil.hex2byte(padder.pad(ISOUtil.hexString(valueBytes), getLength()));
193
194            byte[] rawData = new byte[tagPrefixer.getPackedLength() + prefixer.getPackedLength() + interpreter.getPackedLength(paddedValueBytes.length)];
195            byte[] rawTagData = new byte[tagPrefixer.getPackedLength()];
196            tagPrefixer.encodeLength(tag, rawTagData);
197
198            byte[] rawLen = new byte[prefixer.getPackedLength()];
199            prefixer.encodeLength(!headerFormatter.isTagFirst() ? paddedValueBytes.length + tagPrefixer.getPackedLength() : paddedValueBytes.length, rawLen);
200
201            headerFormatter.format(tagPrefixer, prefixer, rawTagData, rawLen, rawData);
202
203            interpreter.interpret(paddedValueBytes, rawData, headerFormatter.getTotalLength(tagPrefixer, prefixer));
204
205            return rawData;
206        } catch(Exception e) {
207            throw new ISOException(makeExceptionMessage(c, "packing"), e);
208        }
209    }
210
211    /**
212     * Unpacks the byte array into the component.
213     * @param c The component to unpack into.
214     * @param b The byte array to unpack.
215     * @param offset The index in the byte array to start unpacking from.
216     * @return The number of bytes consumed unpacking the component.
217     */
218    @Override
219    public int unpack(ISOComponent c, byte[] b, int offset) throws ISOException {
220        try{
221            int tagLen = tagPrefixer.getPackedLength();
222            c.setFieldNumber(tagPrefixer.decodeLength(b, offset + headerFormatter.getTagIndex(prefixer)));
223            int len = prefixer.decodeLength(b, offset + headerFormatter.getLengthIndex(tagPrefixer));
224            if (!headerFormatter.isTagFirst()) {
225                len -= tagPrefixer.getPackedLength();
226            }
227            if (len == -1) {
228                // The prefixer doesn't know how long the field is, so use
229                // maxLength instead
230                len = getLength();
231            } else if (getLength() > 0 && len > getLength()) {
232                throw new ISOException("Field length " + len + " too long. Max: " + getLength());
233            }
234            int lenLen = prefixer.getPackedLength();
235            byte[] unpacked = interpreter.uninterpret(b, offset + tagPrefixer.getPackedLength() + prefixer.getPackedLength(), len);
236
237            byte[] paddedValueBytes = unpacked;
238            if (!(padder instanceof NullPadder)) //for save few cycles
239                paddedValueBytes = ISOUtil.hex2byte(padder.unpad(ISOUtil.hexString(unpacked)));
240
241            c.setValue(paddedValueBytes);
242            return tagLen + lenLen + interpreter.getPackedLength(len);
243        } catch(Exception e){
244            throw new ISOException(makeExceptionMessage(c, "unpacking"), e);
245        }
246    }
247
248    /**
249     * Unpack the input stream into the component.
250     * @param c  The Component to unpack into.
251     * @param in Input stream where the packed bytes come from.
252     * @exception IOException Thrown if there's a problem reading the input stream.
253     */
254    @Override
255    public void unpack (ISOComponent c, InputStream in)
256        throws IOException, ISOException {
257
258        try {
259            int tagLen = tagPrefixer.getPackedLength();
260            int lenLen = prefixer.getPackedLength() == 0 ? getLength() : prefixer.getPackedLength();
261            int len = -1;
262            if (headerFormatter.getTagIndex(prefixer) == 0) {
263                c.setFieldNumber(tagPrefixer.decodeLength(readBytes(in, tagLen), 0));
264                len = prefixer.decodeLength(readBytes(in, lenLen), 0);
265            } else {
266                len = prefixer.decodeLength(readBytes(in, lenLen), 0);
267                c.setFieldNumber(tagPrefixer.decodeLength(readBytes(in, tagLen), 0));
268            }
269            if (getLength() > 0 && len > 0 && len > getLength()) {
270                throw new ISOException("Field length " + len + " too long. Max: " + getLength());
271            }
272            int packedLen = interpreter.getPackedLength(len);
273            byte[] unpacked = interpreter.uninterpret(readBytes (in, packedLen), 0, len);
274            c.setValue(unpacked);
275        } catch(ISOException e){
276            throw new ISOException(makeExceptionMessage(c, "unpacking"), e);
277        }
278    }
279
280}