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 * @author joconnor
026 * @version $Revision$ $Date$
027 */
028public class ISOStringFieldPackager extends ISOFieldPackager
029{
030    private Interpreter interpreter;
031    private Padder padder;
032    private Prefixer prefixer;
033
034    /**
035     * Constructs a default ISOStringFieldPackager. There is no padding,
036     * no length prefix and a literal interpretation. The set methods must be called to
037     * make this ISOBaseFieldPackager useful.
038     */
039    public ISOStringFieldPackager()
040    {
041        super();
042        this.padder = NullPadder.INSTANCE;
043        this.interpreter = LiteralInterpreter.INSTANCE;
044        this.prefixer = NullPrefixer.INSTANCE;
045    }
046
047    /**
048     * Constructs an ISOStringFieldPackager with a specific Padder, Interpreter and Prefixer.
049     * The length and description should be set with setLength() and setDescription methods.
050     * @param padder The type of padding used.
051     * @param interpreter The interpreter used to encode the field.
052     * @param prefixer The type of length prefixer used to encode this field.
053     */
054    public ISOStringFieldPackager(Padder padder, Interpreter interpreter, Prefixer prefixer)
055    {
056        super();
057        this.padder = padder;
058        this.interpreter = interpreter;
059        this.prefixer = prefixer;
060    }
061
062    /**
063     * Creates an ISOStringFieldPackager.
064     * @param maxLength The maximum length of the field in characters or bytes depending on the datatype.
065     * @param description The description of the field. For human readable output.
066     * @param interpreter The interpreter used to encode the field.
067     * @param padder The type of padding used.
068     * @param prefixer The type of length prefixer used to encode this field.
069     */
070    public ISOStringFieldPackager(int maxLength, String description, Padder padder,
071                                  Interpreter interpreter, Prefixer prefixer)
072    {
073        super(maxLength, description);
074        this.padder = padder;
075        this.interpreter = interpreter;
076        this.prefixer = prefixer;
077    }
078
079    /**
080     * Sets the Padder.
081     * @param padder The padder to use during packing and unpacking.
082     */
083    public void setPadder(Padder padder)
084    {
085        this.padder = padder;
086    }
087
088    /**
089     * Sets the Interpreter.
090     * @param interpreter The interpreter to use in packing and unpacking.
091     */
092    public void setInterpreter(Interpreter interpreter)
093    {
094        this.interpreter = interpreter;
095    }
096
097    /**
098     * Sets the length prefixer.
099     * @param prefixer The length prefixer to use during packing and unpacking.
100     */
101    public void setPrefixer(Prefixer prefixer)
102    {
103        this.prefixer = prefixer;
104    }
105
106    /**
107     * Returns the prefixer's packed length and the interpreter's packed length.
108     */
109    public int getMaxPackedLength()
110    {
111        return prefixer.getPackedLength() + interpreter.getPackedLength(getLength());
112    }
113
114    /** Create a nice readable message for errors */
115    private String makeExceptionMessage(ISOComponent c, String operation) {
116        Object fieldKey = "unknown";
117        if (c != null)
118        {
119            try
120            {
121                fieldKey = c.getKey();
122            } catch (Exception ignore)
123            {
124            }
125        }
126        return this.getClass().getName() + ": Problem " + operation + " field " + fieldKey;
127    }
128
129    /**
130     * Convert the component into a byte[].
131     * @return byte array representation of component
132     * @throws org.jpos.iso.ISOException
133         */
134    @Override
135    public byte[] pack(ISOComponent c) throws ISOException
136    {
137        try
138        {
139            String data;
140            if(c.getValue() instanceof byte[])
141                data = new String(c.getBytes(), ISOUtil.CHARSET); // transparent handling of complex fields
142            else
143                data = (String)c.getValue();
144
145            if (data.length() > getLength())
146            {
147                throw new ISOException("Field length " + data.length() + " too long. Max: " + getLength());
148            }
149            String paddedData = padder.pad(data, getLength());
150            byte[] rawData = new byte[prefixer.getPackedLength()
151                    + interpreter.getPackedLength(paddedData.length())];
152            prefixer.encodeLength(paddedData.length(), rawData);
153            interpreter.interpret(paddedData, rawData, prefixer.getPackedLength());
154            return rawData;
155        } catch(Exception e)
156        {
157            throw new ISOException(makeExceptionMessage(c, "packing"), e);
158        }
159    }
160
161    /**
162     * Unpacks the byte array into the component.
163     * @param c The component to unpack into.
164     * @param b The byte array to unpack.
165     * @param offset The index in the byte array to start unpacking from.
166     * @return The number of bytes consumed unpacking the component.
167     */
168    public int unpack(ISOComponent c, byte[] b, int offset) throws ISOException
169    {
170        try
171        {
172            int len = prefixer.decodeLength(b, offset);
173            if (len == -1) {
174                // The prefixer doesn't know how long the field is, so use
175                // maxLength instead
176                len = trim ? Math.min(getLength(),b.length-offset) : getLength();
177            }
178            else if (getLength() > 0 && len > getLength())
179                throw new ISOException("Field length " + len + " too long. Max: " + getLength());
180
181            int lenLen = prefixer.getPackedLength();
182            String unpacked = interpreter.uninterpret(b, offset + lenLen, len);
183            c.setValue(unpacked);
184            return lenLen + interpreter.getPackedLength(len);
185        } catch(Exception e)
186        {
187            throw new ISOException(makeExceptionMessage(c, "unpacking"), e);
188        }
189    }
190
191    /**
192     * Unpack the input stream into the component.
193     * @param c  The Component to unpack into.
194     * @param in Input stream where the packed bytes come from.
195     * @exception IOException Thrown if there's a problem reading the input stream.
196     */
197    public void unpack (ISOComponent c, InputStream in) 
198        throws IOException, ISOException
199    {
200        try
201        {
202            int lenLen = prefixer.getPackedLength ();
203            int len;
204            if (lenLen == 0)
205            {
206                len = getLength();
207            } else
208            {
209                len = prefixer.decodeLength (readBytes (in, lenLen), 0);
210                if (getLength() > 0 && len > 0 && len > getLength())
211                    throw new ISOException("Field length " + len + " too long. Max: " + getLength());
212            }
213            int packedLen = interpreter.getPackedLength(len);
214            String unpacked = interpreter.uninterpret(readBytes (in, packedLen), 0, len);
215            c.setValue(unpacked);
216        } catch(ISOException e)
217        {
218            throw new ISOException(makeExceptionMessage(c, "unpacking"), e);
219        }
220    }
221
222    @Override
223    public void setTrim (boolean trim) {
224        super.setTrim (trim);
225        if (trim)
226            padder = NullPadder.INSTANCE; // no padding
227    }
228
229    /**
230     * Checks the length of the data against the maximum, and throws an IllegalArgumentException.
231     * This is designed to be called from field Packager constructors and the setLength()
232     * method.
233     * @param len The length of the data for this field packager.
234     * @param maxLength The maximum length allowed for this type of field packager.
235     *          This depends on the prefixer that is used.
236     * @throws IllegalArgumentException If len > maxLength.
237     */
238    protected void checkLength(int len, int maxLength) throws IllegalArgumentException
239    {
240        if (len > maxLength)
241        {
242            throw new IllegalArgumentException("Length " + len + " too long for " + getClass().getName());
243        }
244    }
245}