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 org.jpos.iso.packager.XMLPackager;
022
023import java.io.*;
024import java.math.BigDecimal;
025import java.util.Objects;
026
027/** ISO component representing an amount with currency and decimal scale. */
028public class ISOAmount 
029    extends ISOComponent 
030    implements Cloneable, Externalizable
031{
032    static final long serialVersionUID = -6130248734056876225L;
033    private int fieldNumber;
034    private int currencyCode;
035    private String value;
036    private BigDecimal amount;
037
038    /** Default constructor. */
039    public ISOAmount () {
040        super();
041        setFieldNumber (-1);
042    }
043    /** Constructs an ISOAmount for the given field number.
044     * @param fieldNumber the ISO field number
045     */
046    
047    public ISOAmount (int fieldNumber) {
048        super ();
049        setFieldNumber (fieldNumber);
050    }
051    /**
052     * Constructs an ISOAmount with all fields.
053     * @param fieldNumber  the ISO field number
054     * @param currencyCode the ISO 4217 currency code
055     * @param amount       the monetary amount
056     * @throws ISOException if the value cannot be encoded
057     */
058    public ISOAmount (int fieldNumber, int currencyCode, BigDecimal amount) throws ISOException {
059        super ();
060        setFieldNumber(fieldNumber);
061        this.currencyCode = currencyCode;
062        try {
063            this.amount = amount.setScale(ISOCurrency.getCurrency(currencyCode).getDecimals());
064        } catch (ArithmeticException e) {
065            throw new ISOException (
066              "rounding problem, amount=" + amount + " scale=" + ISOCurrency.getCurrency(currencyCode).getDecimals()
067            );
068        }
069    }
070    public Object getKey() {
071        return fieldNumber;
072    }
073    public Object getValue() throws ISOException {
074        if (value == null) {
075            StringBuilder sb = new StringBuilder();
076            sb.append (ISOUtil.zeropad (Integer.toString(currencyCode), 3));
077            sb.append (Integer.toString (amount.scale()));
078            sb.append (
079                ISOUtil.zeropad (
080                    amount.movePointRight(amount.scale()).toString(),12
081                )
082            );
083            value = sb.toString();
084        }
085        return value;
086
087    }
088    public void setValue (Object obj) throws ISOException {
089        if (obj instanceof String) {
090            String s = (String) obj;
091            if (s.length() < 12) {
092                throw new ISOException (
093                    "ISOAmount invalid length " + s.length()
094                );
095            }
096            try {
097                currencyCode = Integer.parseInt (s.substring(0,3));
098                int dec = Integer.parseInt (s.substring(3,4));
099                amount = new BigDecimal (s.substring(4)).movePointLeft (dec);
100                value  = s;
101            } catch (NumberFormatException e) {
102                throw new ISOException (e.getMessage());
103            }
104        }
105    }
106    public void setFieldNumber (int fieldNumber) {
107        this.fieldNumber = fieldNumber;
108    }
109
110    @Override
111    public int getFieldNumber () {
112        return fieldNumber;
113    }
114    /** Returns the monetary amount.
115     * @return the amount
116     */
117    
118    public BigDecimal getAmount () {
119        return amount;
120    }
121    /** Returns the decimal scale.
122     * @return the scale
123     */
124    
125    public int getScale() {
126        return amount.scale() % 10;
127    }
128    /** Returns the scale as a two-character string.
129     * @return scale string
130     */
131    
132    public String getScaleAsString() {
133        return Integer.toString(getScale());
134    }
135    /** Returns the ISO 4217 currency code.
136     * @return currency code
137     */
138    
139    public int getCurrencyCode() {
140        return currencyCode;
141    }
142    /** Returns the currency code as a 3-digit string.
143     * @return currency code string
144     * @throws ISOException on error
145     */
146    
147    public String getCurrencyCodeAsString() throws ISOException {
148        return ISOUtil.zeropad(Integer.toString(currencyCode),3);
149    }
150    /** Returns the amount formatted as a legacy ISO string.
151     * @return formatted amount string
152     * @throws ISOException on error
153     */
154    
155    public String getAmountAsLegacyString() throws ISOException {
156        return ISOUtil.zeropad (amount.unscaledValue().toString(), 12);
157    }
158    /** Returns the amount as a formatted string.
159     * @return the formatted amount string
160     * @throws ISOException on error
161     */
162    public String getAmountAsString() throws ISOException {
163        StringBuilder sb = new StringBuilder(16);
164        sb.append (ISOUtil.zeropad (Integer.toString (currencyCode),3));
165        sb.append (Integer.toString(amount.scale() % 10));
166        sb.append (ISOUtil.zeropad (amount.unscaledValue().toString(), 12));
167        return sb.toString();
168    }
169    public byte[] pack() throws ISOException {
170        throw new ISOException ("Not available");
171    }
172    public int unpack(byte[] b) throws ISOException {
173        throw new ISOException ("Not available");
174    }
175    public void unpack(InputStream in) throws ISOException {
176        throw new ISOException ("Not available");
177    }
178    public void dump (PrintStream p, String indent) {
179        p.println (indent +"<"+XMLPackager.ISOFIELD_TAG + " " 
180          +XMLPackager.ID_ATTR +"=\"" +fieldNumber +"\" "
181          +"currency=\"" +ISOUtil.zeropad (currencyCode, 3)+"\" "
182          +XMLPackager.TYPE_ATTR +"=\"amount\" "
183          +XMLPackager.VALUE_ATTR+"=\"" + amount.toString() +"\"/>"
184        );
185    }
186    public void writeExternal (ObjectOutput out) throws IOException {
187        out.writeShort (fieldNumber);
188        try {
189            out.writeUTF ((String) getValue());
190        } catch (ISOException e) {
191            throw new IOException (e);
192        }
193    }
194    public void readExternal  (ObjectInput in) 
195        throws IOException, ClassNotFoundException
196    {
197        fieldNumber = in.readShort ();
198        try {
199            setValue(in.readUTF());
200        } catch (ISOException e) {
201            throw new IOException (e.getMessage());
202        }
203    }
204
205    @Override
206    public boolean equals(Object o) {
207        if (this == o) return true;
208        if (o == null || getClass() != o.getClass()) return false;
209        ISOAmount isoAmount = (ISOAmount) o;
210        return fieldNumber == isoAmount.fieldNumber &&
211          currencyCode == isoAmount.currencyCode &&
212          Objects.equals(amount, isoAmount.amount);
213    }
214
215    @Override
216    public int hashCode() {
217        return Objects.hash(fieldNumber, currencyCode, amount);
218    }
219
220    @Override
221    public String toString() {
222        return "ISOAmount{" +
223          "fieldNumber=" + fieldNumber +
224          ", currencyCode=" + currencyCode +
225          ", amount=" + amount +
226          '}';
227    }
228}