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 ISOBinaryFieldPackager extends ISOFieldPackager
029{
030    private BinaryInterpreter interpreter;
031    private Prefixer prefixer;
032
033    /**
034     * Constructs a default ISOBinaryFieldPackager. There is no length prefix and a
035     * literal interpretation. The set methods must be called to make this
036     * ISOBinaryFieldPackager useful.
037     */
038    public ISOBinaryFieldPackager()
039    {
040        super();
041        this.interpreter = LiteralBinaryInterpreter.INSTANCE;
042        this.prefixer = NullPrefixer.INSTANCE;
043    }
044
045    /**
046     * Creates an ISOBinaryFieldPackager.
047     * @param maxLength The maximum length of the field in characters or bytes depending on the datatype.
048     * @param description The description of the field. For human readable output.
049     * @param interpreter The interpreter used to encode the field.
050     * @param prefixer The type of length prefixer used to encode this field.
051     */
052    public ISOBinaryFieldPackager(int maxLength, String description,
053                                  BinaryInterpreter interpreter, Prefixer prefixer)
054    {
055        super(maxLength, description);
056        this.interpreter = interpreter;
057        this.prefixer = prefixer;
058    }
059
060    /**
061     * Creates an ISOBinaryFieldPackager.
062     * @param interpreter The interpreter used to encode the field.
063     * @param prefixer The type of length prefixer used to encode this field.
064     */
065    public ISOBinaryFieldPackager(BinaryInterpreter interpreter, Prefixer prefixer)
066    {
067        super();
068        this.interpreter = interpreter;
069        this.prefixer = prefixer;
070    }
071
072    /**
073     * Sets the Interpreter.
074     * @param interpreter The interpreter to use in packing and unpacking.
075     */
076    public void setInterpreter(BinaryInterpreter interpreter)
077    {
078        this.interpreter = interpreter;
079    }
080
081    /**
082     * Sets the length prefixer.
083     * @param prefixer The length prefixer to use during packing and unpacking.
084     */
085    public void setPrefixer(Prefixer prefixer)
086    {
087        this.prefixer = prefixer;
088    }
089
090    public int getMaxPackedLength()
091    {
092        return prefixer.getPackedLength() + interpreter.getPackedLength(getLength());
093    }
094
095    /**
096         * Convert the component into a byte[].
097         */
098    public byte[] pack(ISOComponent c) throws ISOException
099    {
100        try
101        {
102            byte[] data = c.getBytes();
103            int packedLength = prefixer.getPackedLength();
104            if (packedLength == 0 && data.length != getLength()) {
105                throw new ISOException("Binary data length not the same as the packager length (" + data.length + "/" + getLength() + ")");
106            }
107            byte[] ret = new byte[interpreter.getPackedLength(data.length) + packedLength];
108            prefixer.encodeLength(data.length, ret);
109            interpreter.interpret(data, ret, packedLength);
110            return ret;
111        } catch(Exception e) {
112            throw new ISOException(makeExceptionMessage(c, "packing"), e);
113        }
114    }
115
116    public int unpack(ISOComponent c, byte[] b, int offset) throws ISOException
117    {
118        try
119        {
120            int len = prefixer.decodeLength(b, offset);
121            if (len == -1) {
122                // The prefixer doesn't know how long the field is, so use
123                // maxLength instead
124                len = getLength();
125            }
126            else if (getLength() > 0 && len > getLength())
127                throw new ISOException("Field length " + len + " too long. Max: " + getLength());
128            int lenLen = prefixer.getPackedLength();
129            byte[] unpacked = interpreter.uninterpret(b, offset + lenLen, len);
130            c.setValue(unpacked);
131            return lenLen + interpreter.getPackedLength(len);
132        } catch(Exception e)
133        {
134            throw new ISOException(makeExceptionMessage(c, "unpacking"), e);
135        }
136    }
137
138    /** Unpack from an input stream */
139    public void unpack (ISOComponent c, InputStream in) 
140        throws IOException, ISOException
141    {
142        try
143        {
144            int lenLen = prefixer.getPackedLength ();
145            int len;
146            if (lenLen == 0)
147            {
148                len = getLength();
149            } else
150            {
151                len = prefixer.decodeLength (readBytes (in, lenLen), 0);
152                if (getLength() > 0 && len > 0 && len > getLength())
153                    throw new ISOException("Field length " + len + " too long. Max: " + getLength());
154            }
155            int packedLen = interpreter.getPackedLength(len);
156            byte[] unpacked = interpreter.uninterpret(readBytes (in, packedLen), 0, len);
157            c.setValue(unpacked);
158        } catch(ISOException e)
159        {
160            throw new ISOException(makeExceptionMessage(c, "unpacking"), e);
161        }
162    }
163
164    /**
165     * component factory
166     * @param fieldNumber - the field number
167     * @return the newly created component
168     */
169    public ISOComponent createComponent(int fieldNumber) {
170        return new ISOBinaryField (fieldNumber);
171    }
172
173    /** Create a nice readable message for errors */
174    private String makeExceptionMessage(ISOComponent c, String operation) {
175        Object fieldKey = "unknown";
176        if (c != null)
177        {
178            try
179            {
180                fieldKey = c.getKey();
181            } catch (Exception ignore)
182            {
183            }
184        }
185        return getClass().getName() + ": Problem " + operation + " field " + fieldKey;
186    }
187
188
189    /**
190     * Checks the length of the data against the maximum, and throws an IllegalArgumentException.
191     * This is designed to be called from field Packager constructors and the setLength()
192     * method.
193     * @param len The length of the data for this field packager.
194     * @param maxLength The maximum length allowed for this type of field packager.
195     *          This depends on the prefixer that is used.
196     * @throws IllegalArgumentException If len > maxLength.
197     */
198    protected void checkLength(int len, int maxLength) throws IllegalArgumentException
199    {
200        if (len > maxLength)
201        {
202            throw new IllegalArgumentException("Length " + len + " too long for " + getClass().getName());
203        }
204    }
205}