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 "http://www.evertype.com/standards/iso4217/iso4217-en.html" 034 * "http://www.iso.org/iso/en/prods-services/popstds/currencycodeslist.html" 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 @SuppressWarnings({"EmptyCatchBlock"}) 073 public static void loadPropertiesFromClasspath(String base) 074 { 075 InputStream in=loadResourceAsStream(base); 076 try 077 { 078 if(in!=null) 079 { 080 addBundle(new PropertyResourceBundle(in)); 081 } 082 } 083 catch (IOException e) 084 { 085 } 086 finally 087 { 088 if(in!=null) 089 { 090 try 091 { 092 in.close(); 093 } 094 catch (IOException e) 095 { 096 } 097 } 098 } 099 } 100 101 /** 102 * Converts from an ISO Amount (12 digit string) to a double taking in 103 * consideration the number of decimal digits according to currency 104 * 105 * @param isoamount - The ISO amount to be converted (eg. ISOField 4) 106 * @param currency - The ISO currency to be converted (eg. ISOField 49) 107 * @return result - A double representing the converted field 108 * @throws IllegalArgumentException if we fail to convert the amount 109 * @deprecated You should never use doubles 110 */ 111 @Deprecated 112 public static double convertFromIsoMsg(String isoamount, String currency) throws IllegalArgumentException 113 { 114 Currency c = findCurrency(currency); 115 return c.parseAmountFromISOMsg(isoamount); 116 } 117 public static String toISO87String (BigDecimal amount, String currency) 118 { 119 try { 120 Currency c = findCurrency(currency); 121 return ISOUtil.zeropad(amount.movePointRight(c.getDecimals()).setScale(0).toPlainString(), 12); 122 } 123 catch (ISOException e) { 124 throw new IllegalArgumentException("Failed to convert amount",e); 125 } 126 } 127 public static BigDecimal parseFromISO87String (String isoamount, String currency) { 128 int decimals = findCurrency(currency).getDecimals(); 129 return new BigDecimal(isoamount).movePointLeft(decimals); 130 } 131 132 public static void addBundle(String bundleName) 133 { 134 ResourceBundle r = ResourceBundle.getBundle(bundleName); 135 addBundle(r); 136 } 137 138 /** 139 * Converts an amount to an ISO Amount taking in consideration 140 * the number of decimal digits according to currency 141 * 142 * @param amount - The amount to be converted 143 * @param currency - The ISO currency to be converted (eg. ISOField 49) 144 * @return result - An iso amount representing the converted field 145 * @throws IllegalArgumentException if we fail to convert the amount 146 */ 147 public static String convertToIsoMsg(double amount, String currency) throws IllegalArgumentException 148 { 149 return findCurrency(currency).formatAmountForISOMsg(amount); 150 } 151 152 public static Object[] decomposeComposedCurrency(String incurr) throws IllegalArgumentException 153 { 154 final String[] strings = incurr.split(" "); 155 if (strings.length != 2) 156 { 157 throw new IllegalArgumentException("Invalid parameter: " + incurr); 158 } 159 return new Object[]{strings[0], Double.valueOf(strings[1])}; 160 } 161 162 public static String getIsoCodeFromAlphaCode(String alphacode) throws IllegalArgumentException 163 { 164 try 165 { 166 Currency c = findCurrency(alphacode); 167 return ISOUtil.zeropad(Integer.toString(c.getIsoCode()), 3); 168 } 169 catch (ISOException e) 170 { 171 throw new IllegalArgumentException("Failed getIsoCodeFromAlphaCode/ zeropad failed?", e); 172 } 173 } 174 175 public static Currency getCurrency(int code) throws ISOException 176 { 177 final String isoCode = ISOUtil.zeropad(Integer.toString(code), 3); 178 return findCurrency(isoCode); 179 } 180 181 public static Currency getCurrency(String code) throws ISOException 182 { 183 final String isoCode = ISOUtil.zeropad(code, 3); 184 return findCurrency(isoCode); 185 } 186 187 private static InputStream loadResourceAsStream(String name) 188 { 189 InputStream in = null; 190 191 ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); 192 if (contextClassLoader != null) 193 { 194 in = contextClassLoader.getResourceAsStream(name); 195 } 196 if (in == null) 197 { 198 in = ISOCurrency.class.getClassLoader().getResourceAsStream(name); 199 } 200 return in; 201 } 202 203 /** 204 * Should be called like this: put("ALL", "008", 2); 205 * Note: the second parameter is zero padded to three digits 206 * 207 * @param alphaCode An alphabetic code such as USD 208 * @param isoCode An ISO code such as 840 209 * @param numDecimals the number of implied decimals 210 */ 211 private static void addCurrency(String alphaCode, String isoCode, int numDecimals) 212 { 213 // to allow a clean replacement from a more specific resource bundle we 214 // require clearing instead of overriding. 215 if(currencies.containsKey(alphaCode) || currencies.containsKey(isoCode)) 216 { 217 currencies.remove(alphaCode); 218 currencies.remove(isoCode); 219 } 220 Currency ccy = new Currency(alphaCode, Integer.parseInt(isoCode), numDecimals); 221 currencies.put(alphaCode, ccy); 222 currencies.put(isoCode, ccy); 223 } 224 225 private static Currency findCurrency(String currency) 226 { 227 final Currency c = currencies.get(currency.toUpperCase()); 228 if (c == null) 229 { 230 throw new IllegalArgumentException("Currency with key '" + currency + "' was not found"); 231 } 232 return c; 233 } 234 235 private static void addBundle(ResourceBundle r) 236 { 237 Enumeration en = r.getKeys(); 238 while (en.hasMoreElements()) 239 { 240 String alphaCode = (String) en.nextElement(); 241 String[] tmp = r.getString(alphaCode).split(" "); 242 String isoCode = tmp[0]; 243 int numDecimals = Integer.parseInt(tmp[1]); 244 addCurrency(alphaCode, isoCode, numDecimals); 245 } 246 } 247}