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