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; 023import java.math.BigDecimal; 024import java.util.*; 025import java.util.stream.Collectors; 026 027/** 028 * ISO Currency Conversion package 029 * 030 * @author vsalaman@gmail.com 031 * @author Jonathan.O'Connor@xcom.de 032 * @version $Id$ 033 * @see <a href="http://www.evertype.com/standards/iso4217/iso4217-en.html">ISO 4217 currency codes</a> 034 * @see <a href="http://www.iso.org/iso/en/prods-services/popstds/currencycodeslist.html">ISO currency codes list</a> 035 */ 036public class ISOCurrency 037{ 038 private static final Map<String, Currency> currencies = new HashMap<String, Currency>(); 039 040 // Avoid creation of instances. 041 private ISOCurrency() 042 { 043 } 044 045 static 046 { 047 addJavaCurrencies(); 048 loadPropertiesFromClasspath("org/jpos/iso/ISOCurrency.properties"); 049 loadPropertiesFromClasspath("META-INF/org/jpos/config/ISOCurrency.properties"); 050 } 051 052 private static void addJavaCurrencies() 053 { 054 List<java.util.Currency> currencies = java.util.Currency.getAvailableCurrencies() 055 .stream() 056 .sorted(Comparator.comparing(java.util.Currency::getCurrencyCode)) 057 .collect(Collectors.toList()); 058 for (java.util.Currency sc : currencies) 059 { 060 try 061 { 062 addCurrency(sc.getCurrencyCode().toUpperCase(), 063 ISOUtil.zeropad(Integer.toString(sc.getNumericCode()), 3), 064 sc.getDefaultFractionDigits()); 065 } 066 catch (ISOException ignored) 067 { 068 } 069 } 070 } 071 072 /** 073 * Loads currency properties from the classpath. 074 * @param base base path for properties resource 075 */ 076 @SuppressWarnings({"EmptyCatchBlock"}) 077 public static void loadPropertiesFromClasspath(String base) 078 { 079 InputStream in=loadResourceAsStream(base); 080 try 081 { 082 if(in!=null) 083 { 084 addBundle(new PropertyResourceBundle(in)); 085 } 086 } 087 catch (IOException e) 088 { 089 } 090 finally 091 { 092 if(in!=null) 093 { 094 try 095 { 096 in.close(); 097 } 098 catch (IOException e) 099 { 100 } 101 } 102 } 103 } 104 105 /** 106 * Converts from an ISO Amount (12 digit string) to a double taking in 107 * consideration the number of decimal digits according to currency 108 * 109 * @param isoamount - The ISO amount to be converted (eg. ISOField 4) 110 * @param currency - The ISO currency to be converted (eg. ISOField 49) 111 * @return result - A double representing the converted field 112 * @throws IllegalArgumentException if we fail to convert the amount 113 * @deprecated You should never use doubles 114 */ 115 @Deprecated 116 public static double convertFromIsoMsg(String isoamount, String currency) throws IllegalArgumentException 117 { 118 Currency c = findCurrency(currency); 119 return c.parseAmountFromISOMsg(isoamount); 120 } 121 /** 122 * Converts a BigDecimal amount to an ISO 8583 amount string. 123 * @param amount the monetary amount 124 * @param currency the ISO 4217 currency code 125 * @return the formatted ISO 87 string 126 */ 127 128 public static String toISO87String (BigDecimal amount, String currency) 129 { 130 try { 131 Currency c = findCurrency(currency); 132 return ISOUtil.zeropad(amount.movePointRight(c.getDecimals()).setScale(0).toPlainString(), 12); 133 } 134 catch (ISOException e) { 135 throw new IllegalArgumentException("Failed to convert amount",e); 136 } 137 } 138 /** 139 * Parses an ISO 8583 amount string to a BigDecimal. 140 * @param isoamount the ISO 87 amount string 141 * @param currency the ISO 4217 currency code 142 * @return the parsed BigDecimal amount 143 */ 144 145 public static BigDecimal parseFromISO87String (String isoamount, String currency) { 146 int decimals = findCurrency(currency).getDecimals(); 147 return new BigDecimal(isoamount).movePointLeft(decimals); 148 } 149 150 /** 151 * Adds a resource bundle for currency definitions. 152 * @param bundleName the bundle name to add 153 */ 154 155 public static void addBundle(String bundleName) 156 { 157 ResourceBundle r = ResourceBundle.getBundle(bundleName); 158 addBundle(r); 159 } 160 161 /** 162 * Converts an amount to an ISO Amount taking in consideration 163 * the number of decimal digits according to currency 164 * 165 * @param amount - The amount to be converted 166 * @param currency - The ISO currency to be converted (eg. ISOField 49) 167 * @return result - An iso amount representing the converted field 168 * @throws IllegalArgumentException if we fail to convert the amount 169 */ 170 public static String convertToIsoMsg(double amount, String currency) throws IllegalArgumentException 171 { 172 return findCurrency(currency).formatAmountForISOMsg(amount); 173 } 174 175 /** 176 * Decomposes a composed currency string into its components. 177 * @param incurr the composed currency string 178 * @return an Object array with the decomposed parts 179 * @throws IllegalArgumentException if the currency is invalid 180 */ 181 182 public static Object[] decomposeComposedCurrency(String incurr) throws IllegalArgumentException 183 { 184 final String[] strings = incurr.split(" "); 185 if (strings.length != 2) 186 { 187 throw new IllegalArgumentException("Invalid parameter: " + incurr); 188 } 189 return new Object[]{strings[0], Double.valueOf(strings[1])}; 190 } 191 192 /** 193 * Returns the ISO numeric code for the given alpha currency code. 194 * @param alphacode the 3-letter alpha currency code 195 * @return the ISO numeric code as a string 196 * @throws IllegalArgumentException if the code is unknown 197 */ 198 199 public static String getIsoCodeFromAlphaCode(String alphacode) throws IllegalArgumentException 200 { 201 try 202 { 203 Currency c = findCurrency(alphacode); 204 return ISOUtil.zeropad(Integer.toString(c.getIsoCode()), 3); 205 } 206 catch (ISOException e) 207 { 208 throw new IllegalArgumentException("Failed getIsoCodeFromAlphaCode/ zeropad failed?", e); 209 } 210 } 211 212 /** 213 * Returns the Currency for the given numeric ISO 4217 code. 214 * @param code the numeric currency code 215 * @return the corresponding Currency 216 * @throws ISOException if the code is unknown 217 */ 218 219 public static Currency getCurrency(int code) throws ISOException 220 { 221 final String isoCode = ISOUtil.zeropad(Integer.toString(code), 3); 222 return findCurrency(isoCode); 223 } 224 225 /** 226 * Returns the Currency for the given string code. 227 * @param code the currency code (numeric or alpha) 228 * @return the corresponding Currency 229 * @throws ISOException if the code is unknown 230 */ 231 232 public static Currency getCurrency(String code) throws ISOException 233 { 234 final String isoCode = ISOUtil.zeropad(code, 3); 235 return findCurrency(isoCode); 236 } 237 238 private static InputStream loadResourceAsStream(String name) 239 { 240 InputStream in = null; 241 242 ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); 243 if (contextClassLoader != null) 244 { 245 in = contextClassLoader.getResourceAsStream(name); 246 } 247 if (in == null) 248 { 249 in = ISOCurrency.class.getClassLoader().getResourceAsStream(name); 250 } 251 return in; 252 } 253 254 /** 255 * Should be called like this: put("ALL", "008", 2); 256 * Note: the second parameter is zero padded to three digits 257 * 258 * @param alphaCode An alphabetic code such as USD 259 * @param isoCode An ISO code such as 840 260 * @param numDecimals the number of implied decimals 261 */ 262 private static void addCurrency(String alphaCode, String isoCode, int numDecimals) 263 { 264 // to allow a clean replacement from a more specific resource bundle we 265 // require clearing instead of overriding. 266 if(currencies.containsKey(alphaCode) || currencies.containsKey(isoCode)) 267 { 268 currencies.remove(alphaCode); 269 currencies.remove(isoCode); 270 } 271 Currency ccy = new Currency(alphaCode, Integer.parseInt(isoCode), numDecimals); 272 currencies.put(alphaCode, ccy); 273 currencies.put(isoCode, ccy); 274 } 275 276 private static Currency findCurrency(String currency) 277 { 278 final Currency c = currencies.get(currency.toUpperCase()); 279 if (c == null) 280 { 281 throw new IllegalArgumentException("Currency with key '" + currency + "' was not found"); 282 } 283 return c; 284 } 285 286 private static void addBundle(ResourceBundle r) 287 { 288 Enumeration en = r.getKeys(); 289 while (en.hasMoreElements()) 290 { 291 String alphaCode = (String) en.nextElement(); 292 String[] tmp = r.getString(alphaCode).split(" "); 293 String isoCode = tmp[0]; 294 int numDecimals = Integer.parseInt(tmp[1]); 295 addCurrency(alphaCode, isoCode, numDecimals); 296 } 297 } 298}