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