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 string fields in format defined by formatter
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 */
035public class ISOFormattableStringFieldPackager extends ISOFieldPackager
036{
037    private Prefixer tagPrefixer;
038    private Interpreter interpreter;
039    private Padder padder;
040    private Prefixer prefixer;
041    private IsoFieldHeaderFormatter headerFormatter;
042
043    /**
044     * Constructs a default ISOTagBinaryFieldPackager. There is ASCII tag L prefixer, no padding,
045     * no length prefix and a literal binary interpretation. The set methods must be called to
046     * make this ISOBaseFieldPackager useful.
047     */
048    public ISOFormattableStringFieldPackager() {
049        super();
050        this.tagPrefixer = AsciiPrefixer.L;
051        this.interpreter = LiteralInterpreter.INSTANCE;
052        this.padder = NullPadder.INSTANCE;
053        this.prefixer = NullPrefixer.INSTANCE;
054        this.headerFormatter = IsoFieldHeaderFormatter.TAG_FIRST;
055    }
056
057    /**
058     * Constructs an ISOTagBinaryFieldPackager with a specific Padder, Interpreter and Prefixer.
059     * The length and description should be set with setLength() and setDescription methods.
060     * @param tagPrefixer The type of tag prefixer used to encode tag.
061     * @param padder The type of padding used.
062     * @param interpreter The interpreter used to encode the field.
063     * @param lengthPrefixer The type of length prefixer used to encode this field.
064     */
065    public ISOFormattableStringFieldPackager(Prefixer tagPrefixer, Padder padder,
066                                             Interpreter interpreter, Prefixer lengthPrefixer) {
067        super();
068        this.tagPrefixer = tagPrefixer;
069        this.padder = padder;
070        this.interpreter = interpreter;
071        this.prefixer = lengthPrefixer;
072        this.headerFormatter = IsoFieldHeaderFormatter.TAG_FIRST;
073    }
074
075/**
076     * Constructs an ISOTagBinaryFieldPackager with a specific Padder, Interpreter and Prefixer.
077     * The length and description should be set with setLength() and setDescription methods.
078     * @param tagPrefixer The type of tag prefixer used to encode tag.
079     * @param padder The type of padding used.
080     * @param interpreter The interpreter used to encode the field.
081     * @param lengthPrefixer The type of length prefixer used to encode this field.
082     * @param headerFormatter The format of TAG TT and Length LL part
083     */
084    public ISOFormattableStringFieldPackager(Prefixer tagPrefixer, Padder padder,
085                                             Interpreter interpreter, Prefixer lengthPrefixer,
086                                             IsoFieldHeaderFormatter headerFormatter) {
087        super();
088        this.tagPrefixer = tagPrefixer;
089        this.padder = padder;
090        this.interpreter = interpreter;
091        this.prefixer = lengthPrefixer;
092        this.headerFormatter = headerFormatter;
093    }
094
095    /**
096     * Creates an ISOTagBinaryFieldPackager.
097     * @param maxLength The maximum length of the field in characters or bytes depending on the datatype.
098     * @param description The description of the field. For human readable output.
099     * @param tagPrefixer The type of tag prefixer used to encode tag.
100     * @param interpreter The interpreter used to encode the field.
101     * @param padder The type of padding used.
102     * @param lengthPrefixer The type of length prefixer used to encode this field.
103     * @param headerFormatter The format of TAG TT and Length LL part
104     */
105    public ISOFormattableStringFieldPackager(int maxLength, String description, Prefixer tagPrefixer,
106                                             Padder padder, Interpreter interpreter, Prefixer lengthPrefixer,
107                                             IsoFieldHeaderFormatter headerFormatter) {
108        super(maxLength, description);
109        this.tagPrefixer = tagPrefixer;
110        this.padder = padder;
111        this.interpreter = interpreter;
112        this.prefixer = lengthPrefixer;
113        this.headerFormatter = headerFormatter;
114    }
115
116    /**
117     * Sets the Padder.
118     * @param padder The padder to use during packing and unpacking.
119     */
120    public void setPadder(Padder padder) {
121        this.padder = padder;
122    }
123
124    /**
125     * Sets the Interpreter.
126     * @param interpreter The interpreter to use in packing and unpacking.
127     */
128    public void setInterpreter(Interpreter interpreter) {
129        this.interpreter = interpreter;
130    }
131
132    /**
133     * Sets the length prefixer.
134     * @param prefixer The length prefixer to use during packing and unpacking.
135     */
136    public void setPrefixer(Prefixer prefixer) {
137        this.prefixer = prefixer;
138    }
139
140    /**
141     * Gets the formatter, which assembles tag TT and length LL parts in required format
142     * @return the formatter of the header part (length and tag parts)
143     */
144    public IsoFieldHeaderFormatter getHeaderFormatter() {
145        return headerFormatter;
146    }
147
148    /**
149     * Sets the formatter, which assembles tag TT and length LL parts in required format
150     * @param headerFormatter the formatter of the header part (length and tag parts)
151     */
152    public void setHeaderFormatter(IsoFieldHeaderFormatter headerFormatter) {
153        this.headerFormatter = headerFormatter;
154    }
155
156    /**
157     * Returns the prefixer's packed length and the interpreter's packed length.
158     * @see ISOFieldPackager#getMaxPackedLength()
159     */
160    @Override
161    public int getMaxPackedLength() {
162        return tagPrefixer.getPackedLength() + prefixer.getPackedLength() + interpreter.getPackedLength(getLength());
163    }
164
165    /**
166     * Create a nice readable message for errors
167     */
168    private String makeExceptionMessage(ISOComponent c, String operation) {
169        Object fieldKey = "unknown";
170        if (c != null)
171            try{
172                fieldKey = c.getKey();
173            } catch (Exception ignore){}
174        return this.getClass().getName() + ": Problem " + operation + " field " + fieldKey;
175    }
176
177    /**
178     * Convert the component into a byte[].
179     */
180    @Override
181    public byte[] pack(ISOComponent c) throws ISOException {
182        try{
183            String value = (String)c.getValue();
184            int valueLength = value.length();
185            if (valueLength < 0 || valueLength > getLength())
186                throw new ISOException("Field length " + valueLength + " too long. Max: " + getLength());
187
188            int tag = (Integer)c.getKey();
189            String paddedValue = padder.pad(value, getLength());
190
191            byte[] rawData = new byte[tagPrefixer.getPackedLength() + prefixer.getPackedLength() + interpreter.getPackedLength(paddedValue.length())];
192            byte[] rawTagData = new byte[tagPrefixer.getPackedLength()];
193            tagPrefixer.encodeLength(tag, rawTagData);
194
195            byte[] rawLen = new byte[prefixer.getPackedLength()];
196
197            prefixer.encodeLength(!headerFormatter.isTagFirst() ? paddedValue.length() + tagPrefixer.getPackedLength() : paddedValue.length(), rawLen);
198
199            headerFormatter.format(tagPrefixer, prefixer, rawTagData, rawLen, rawData);
200
201            interpreter.interpret(paddedValue, rawData, tagPrefixer.getPackedLength() + prefixer.getPackedLength());
202
203            return rawData;
204        } catch(Exception e) {
205            throw new ISOException(makeExceptionMessage(c, "packing"), e);
206        }
207    }
208
209    /**
210     * Unpacks the byte array into the component.
211     * @param c The component to unpack into.
212     * @param b The byte array to unpack.
213     * @param offset The index in the byte array where the whole field (header and value) starts.
214     * @return The number of bytes consumed unpacking the component.
215     */
216    @Override
217    public int unpack(ISOComponent c, byte[] b, int offset) throws ISOException {
218        try{
219            int tagLen = tagPrefixer.getPackedLength();
220            c.setFieldNumber(tagPrefixer.decodeLength(b, offset + headerFormatter.getTagIndex(prefixer)));
221            int len = prefixer.decodeLength(b, offset + headerFormatter.getLengthIndex(tagPrefixer));
222            if (!headerFormatter.isTagFirst()) {
223                len -= tagPrefixer.getPackedLength();
224            }
225            if (len == -1) {
226                // The prefixer doesn't know how long the field is, so use
227                // maxLength instead
228                len = getLength();
229            }
230            else if (getLength() > 0 && len > getLength()) {
231                throw new ISOException("Field length " + len + " too long. Max: " + getLength());
232            }
233            int lenLen = prefixer.getPackedLength();
234
235            String unpacked = interpreter.uninterpret(b, offset + tagPrefixer.getPackedLength() + prefixer.getPackedLength(), len);
236
237            c.setValue(padder.unpad(unpacked));
238            return tagLen + lenLen + interpreter.getPackedLength(len);
239        } catch(Exception e){
240            throw new ISOException(makeExceptionMessage(c, "unpacking"), e);
241        }
242    }
243
244    /**
245     * Unpack the input stream into the component.
246     * @param c  The Component to unpack into.
247     * @param in Input stream where the packed bytes come from.
248     * @exception IOException Thrown if there's a problem reading the input stream.
249     */
250    @Override
251    public void unpack (ISOComponent c, InputStream in) 
252        throws IOException, ISOException {
253        try {
254            int tagLen = tagPrefixer.getPackedLength();
255            int lenLen = prefixer.getPackedLength() == 0 ? getLength() : prefixer.getPackedLength();
256            int len = -1;
257            if (headerFormatter.getTagIndex(prefixer) == 0) {
258                c.setFieldNumber(tagPrefixer.decodeLength(readBytes(in, tagLen), 0));
259                len = prefixer.decodeLength(readBytes(in, lenLen), 0);
260            } else {
261                len = prefixer.decodeLength(readBytes(in, lenLen), 0);
262                c.setFieldNumber(tagPrefixer.decodeLength(readBytes(in, tagLen), 0));
263            }
264            if (getLength() > 0 && len > 0 && len > getLength()) {
265                throw new ISOException("Field length " + len + " too long. Max: " + getLength());
266            }
267            int packedLen = interpreter.getPackedLength(len);
268            String unpacked = interpreter.uninterpret(readBytes (in, packedLen), 0, len);
269            c.setValue(padder.unpad(unpacked));
270        } catch(ISOException e){
271            throw new ISOException(makeExceptionMessage(c, "unpacking"), e);
272        }
273    }
274
275}