1.1 --- a/task4/solution13/src/org/apidesign/apifest08/currency/Convertor.java Sat Oct 11 23:38:46 2008 +0200
1.2 +++ b/task4/solution13/src/org/apidesign/apifest08/currency/Convertor.java Fri Oct 17 17:31:48 2008 +0200
1.3 @@ -3,6 +3,7 @@
1.4 import java.math.BigDecimal;
1.5 import java.math.MathContext;
1.6 import java.math.RoundingMode;
1.7 +import java.util.Date;
1.8
1.9 /** Convertor able to convert amount from one currency to other currency.
1.10 * <p>
1.11 @@ -20,6 +21,11 @@
1.12 */
1.13 public class Convertor {
1.14 private Convertor[] convertors;
1.15 + private IDateProviderEngine dateProvider;
1.16 + boolean remainderAllowed = true; //if false, remained is not allowed (should be true ideally, but can't handle it now)
1.17 + private ExchangeRateProvider exchangeRateProvider;
1.18 + private Date fromDate;
1.19 + private Date toDate;
1.20
1.21 /** Create new <code>Convertor</code> as merge of provided convertors. Merged convertor will use
1.22 * provided convertors to convert between currencies.
1.23 @@ -32,21 +38,26 @@
1.24 * @return Returns new convertor instance.
1.25 */
1.26 public static Convertor createConvertorAsMerge(Convertor[] convertors) {
1.27 + for (int i=0;i<convertors.length;i++) {
1.28 + if (convertors[i]==null) {
1.29 + throw new NullPointerException("Convertor at index "+i+" can't be null");
1.30 + }
1.31 + }
1.32 return new Convertor(convertors);
1.33 }
1.34
1.35 - boolean remainderAllowed = true; //if false, remained is not allowed (should be true ideally, but can't handle it now)
1.36 - ExchangeRateProvider exchangeRateProvider;
1.37
1.38 /** Create simle convertor.
1.39 */
1.40 private Convertor() {
1.41 + setDateProvider(DateProvider.createCurrentDateProvider());
1.42 this.convertors=new Convertor[0];
1.43 }
1.44
1.45 /** Create merge convertor.
1.46 */
1.47 private Convertor(Convertor[] convertors) {
1.48 + this();
1.49 this.convertors = convertors;
1.50 }
1.51
1.52 @@ -75,7 +86,7 @@
1.53 * @deprecated since version2. Use {@link #convert(ConvertorCurrency, ConvertorCurrency, BigDecimal) } - explicitly specify conversion currencies.
1.54 */
1.55 public ConversionResult convert(BigDecimal amount) {
1.56 - return convertValue(exchangeRateProvider.getFromCurrency(), exchangeRateProvider.getToCurrency(),amount, false,false);
1.57 + return convertValue(exchangeRateProvider.getFromCurrency(), exchangeRateProvider.getToCurrency(),amount, false,false,null);
1.58 }
1.59
1.60 /**
1.61 @@ -88,32 +99,36 @@
1.62 * @deprecated since version2. Use {@link #convert(ConvertorCurrency, ConvertorCurrency, BigDecimal) } - explicitly specify conversion currencies.
1.63 */
1.64 public ConversionResult convertBack(BigDecimal amount) {
1.65 - return convertValue(exchangeRateProvider.getFromCurrency(), exchangeRateProvider.getToCurrency(),amount, true,false);
1.66 + return convertValue(exchangeRateProvider.getFromCurrency(), exchangeRateProvider.getToCurrency(),amount, true,false,null);
1.67 }
1.68
1.69 - private ConversionResult convertUsingSimpleConvertor(ConvertorCurrency fromCurrency, ConvertorCurrency toCurrency, boolean reversibleExRate, BigDecimal amount, boolean convertBack) throws ConversionNotSupportedException, RuntimeException {
1.70 - ConversionResult result = new ConversionResult();
1.71 + private ConversionResult convertUsingSimpleConvertor(ConvertorCurrency fromCurrency, ConvertorCurrency toCurrency, boolean reversibleExRate, BigDecimal amount, boolean convertBack,Date date) throws ConversionNotSupportedException, RuntimeException {
1.72 + if (date == null) {
1.73 + date = dateProvider.getCurrentDate();
1.74 + }
1.75
1.76 - //ExchangeRate rate = exchangeRateProvider.getExchangeRate();
1.77 ExchangeRate rate;
1.78 if (reversibleExRate) {
1.79 - rate = exchangeRateProvider.getReversibleExchangeRate(fromCurrency, toCurrency);
1.80 + rate = exchangeRateProvider.getReversibleExchangeRate(fromCurrency, toCurrency, date);
1.81 } else {
1.82 - rate = exchangeRateProvider.getExchangeRate(fromCurrency, toCurrency);
1.83 + rate = exchangeRateProvider.getExchangeRate(fromCurrency, toCurrency, date);
1.84 }
1.85 if (rate == null) {
1.86 return null;
1.87 }
1.88 + ConversionResult result = new ConversionResult();
1.89
1.90 int fromFranctionDigits = fromCurrency.getDefaultFractionDigits();
1.91 int toFractionDigits = toCurrency.getDefaultFractionDigits();
1.92 +
1.93 + int usedFractionDigits = Math.max(fromFranctionDigits, toFractionDigits);
1.94
1.95 - if (toFractionDigits != 2) {
1.96 - throw new ConvertorException("Can't process currency with defaultFractionDigits!=2, " + exchangeRateProvider.getToCurrency() + " has " + toFractionDigits + " defaultFractionDigits");
1.97 - }
1.98 - if (fromFranctionDigits != 2) {
1.99 - throw new ConvertorException("Can't process currency with defaultFractionDigits!=2, " + exchangeRateProvider.getFromCurrency() + " has " + fromFranctionDigits + " defaultFractionDigits");
1.100 - }
1.101 + //if (toFractionDigits != 2) {
1.102 + // throw new ConvertorException("Can't process currency with defaultFractionDigits!=2, " + toCurrency + " has " + toFractionDigits + " defaultFractionDigits");
1.103 + //}
1.104 + //if (fromFranctionDigits != 2) {
1.105 + // throw new ConvertorException("Can't process currency with defaultFractionDigits!=2, " + fromCurrency + " has " + fromFranctionDigits + " defaultFractionDigits");
1.106 + //}
1.107
1.108 if (amount.signum() == -1) {
1.109 throw new RuntimeException("Can convert only non-negative value, current value is " + amount);
1.110 @@ -134,7 +149,7 @@
1.111 to = rate.getToValue();
1.112 }
1.113
1.114 - BigDecimal amountCent = amount.movePointRight(toFractionDigits);
1.115 + BigDecimal amountCent = amount.movePointRight(usedFractionDigits);
1.116
1.117 final BigDecimal multiplied = amountCent.multiply(to, context);
1.118 BigDecimal[] division = multiplied.divideAndRemainder(from, context);
1.119 @@ -142,26 +157,33 @@
1.120 if (!remainderAllowed && !(BigDecimal.ZERO.equals(division[1]))) {
1.121 throw new RuntimeException("Remained is not allowed - remaining amount is " + division[1] + " cents");
1.122 } else {
1.123 - result.setRemainder(BigDecimal.ZERO);
1.124 + result.setRemainder(BigDecimal.ZERO.setScale(fromFranctionDigits));
1.125 }
1.126
1.127 - BigDecimal converted = division[0].movePointLeft(toFractionDigits);
1.128 - converted = converted.setScale(toFractionDigits); //XXX ugly
1.129 + BigDecimal converted = division[0].movePointLeft(usedFractionDigits);
1.130 + converted = converted.setScale(toFractionDigits,RoundingMode.DOWN); //XXX ugly
1.131 result.setConverted(converted);
1.132 - //result.setRemainder(...);
1.133 + BigDecimal[] convertedInBase = converted.multiply(from).divideAndRemainder(to);
1.134 + BigDecimal x2 = convertedInBase[0].add(convertedInBase[1]);
1.135 + BigDecimal remainder = amount.subtract(x2);
1.136 + result.setRemainder(remainder);
1.137 + //System.out.println("ConvertedInBase="+Arrays.asList(convertedInBase)+":"+x2 +" ("+amount+") ["+converted+"] <"+from+","+to+"> ->"+remainder);
1.138 return result;
1.139 }
1.140
1.141
1.142 - private ConversionResult convertValue(ConvertorCurrency fromCurrency, ConvertorCurrency toCurrency,BigDecimal amount, boolean convertBack,boolean reversibleExRate) throws RuntimeException {
1.143 + private ConversionResult convertValue(ConvertorCurrency fromCurrency, ConvertorCurrency toCurrency,BigDecimal amount, boolean convertBack,boolean reversibleExRate,Date date) throws RuntimeException {
1.144 //result.setRemainder(...);
1.145 + if (!dateIsInLimit(date)) {
1.146 + return null;
1.147 + }
1.148 if (convertors.length==0) {
1.149 - return convertUsingSimpleConvertor(fromCurrency, toCurrency, reversibleExRate, amount, convertBack);
1.150 + return convertUsingSimpleConvertor(fromCurrency, toCurrency, reversibleExRate, amount, convertBack,date);
1.151 } else {
1.152 ConversionResult result = null;
1.153 for (int i = 0;i<convertors.length;i++) {
1.154 Convertor subConvertor = convertors[i];
1.155 - result = subConvertor.convertValue(fromCurrency, toCurrency, amount, convertBack, reversibleExRate);
1.156 + result = subConvertor.convertValue(fromCurrency, toCurrency, amount, convertBack, reversibleExRate,date);
1.157 if (result!=null) {
1.158 break;
1.159 }
1.160 @@ -175,6 +197,8 @@
1.161 * <p>
1.162 * Exchange rate is provided by exchange rate provider which was specified when Convertor was created.
1.163 * This method is using only exchange rate from->to and not trying to use reverted excange rate to->from.
1.164 + * <p>
1.165 + * This method is using date from <code>IDateProviderEngine</code> to get exchange rate (see {@link #getDateProvider() }.
1.166 *
1.167 * @param fromCurrency Source currency to convert from.
1.168 * @param toCurrency Target currency to convert to.
1.169 @@ -184,19 +208,40 @@
1.170 * @throws ConversionNotSupportedException If conversion from <code>fromCurrency</code> to <code>toCurrency</code> is not supported.
1.171 */
1.172 public ConversionResult convert(ConvertorCurrency fromCurrency, ConvertorCurrency toCurrency, BigDecimal value) {
1.173 - ConversionResult result = convertValue(fromCurrency, toCurrency, value, false,false);
1.174 + Date defaultDate = dateProvider.getCurrentDate();
1.175 + ConversionResult result = convertValue(fromCurrency, toCurrency, value, false,false,defaultDate);
1.176 if (result==null) {
1.177 //throw new ConversionNotSupportedException("Conversion from " + fromCurrency + " to " + toCurrency + " is not supported");
1.178 throw new ConversionNotSupportedException(fromCurrency.getCurrencyCode(),toCurrency.getCurrencyCode(),false);
1.179 }
1.180 return result;
1.181 }
1.182 + /**
1.183 + * Same as {@link #convert(ConvertorCurrency, ConvertorCurrency, java.math.BigDecimal)} but using provided rate instead of
1.184 + * <code>IDateProviderEngine</code>.
1.185 + *
1.186 + * @param fromCurrency Source currency to convert from.
1.187 + * @param toCurrency Target currency to convert to.
1.188 + * @param value Value in source currency which should be converted.
1.189 + * @return Return conversion result.
1.190 + * @param date Conversion date
1.191 + * @return Return conversion result.
1.192 + */
1.193 + public ConversionResult convert(ConvertorCurrency fromCurrency, ConvertorCurrency toCurrency, BigDecimal value, Date date) {
1.194 + ConversionResult result = convertValue(fromCurrency, toCurrency, value, false,false,date);
1.195 + if (result==null) {
1.196 + throw new ConversionNotSupportedException(fromCurrency.getCurrencyCode(),toCurrency.getCurrencyCode(),false);
1.197 + }
1.198 + return result;
1.199 + }
1.200
1.201 /**
1.202 * Convert <code>value</code> from <code>fromCurrency</code> to <code>toCurrency</code>.
1.203 * Exchange rate is provided by exchange rate provider which was specified when Convertor was created.
1.204 * <p>
1.205 * This method is using only exchange rate from->to and if not found, it is trying to use reverted excange rate to->from.
1.206 + * <p>
1.207 + * This method is using date from <code>IDateProviderEngine</code> to get exchange rate (see {@link #getDateProvider() }.
1.208 *
1.209 * @param fromCurrency Source currency to convert from.
1.210 * @param toCurrency Target currency to convert to.
1.211 @@ -207,7 +252,8 @@
1.212 * is not supported and neither conversion from <code>toCurrency</code> to <code>fromCurrency</code> is not supported.
1.213 */
1.214 public ConversionResult convertWithReversibleRates(ConvertorCurrency fromCurrency, ConvertorCurrency toCurrency, BigDecimal value) {
1.215 - ConversionResult result = convertValue(fromCurrency, toCurrency, value, false,true);
1.216 + Date defaultDate = dateProvider.getCurrentDate();
1.217 + ConversionResult result = convertValue(fromCurrency, toCurrency, value, false,true,defaultDate);
1.218 if (result==null) {
1.219 //throw new ConversionNotSupportedException("Neither onversion nor reverted conversion from " + fromCurrency + " to " + toCurrency + " is not supported,");
1.220 throw new ConversionNotSupportedException(fromCurrency.getCurrencyCode(),toCurrency.getCurrencyCode(),true);
1.221 @@ -215,4 +261,75 @@
1.222 return result;
1.223 }
1.224
1.225 + /**
1.226 + * Same as {@link #convertWithReversibleRates(ConvertorCurrency, ConvertorCurrency, java.math.BigDecimal)} but using provided rate instead of
1.227 + * <code>IDateProviderEngine</code>.
1.228 + *
1.229 + * @param fromCurrency Source currency to convert from.
1.230 + * @param toCurrency Target currency to convert to.
1.231 + * @param value Value in source currency which should be converted.
1.232 + * @return Return conversion result.
1.233 + * @param date Conversion date
1.234 + * @return Return conversion result.
1.235 + */
1.236 + public ConversionResult convertWithReversibleRates(ConvertorCurrency fromCurrency, ConvertorCurrency toCurrency, BigDecimal value, Date date) {
1.237 + ConversionResult result = convertValue(fromCurrency, toCurrency, value, false,true,date);
1.238 + if (result==null) {
1.239 + throw new ConversionNotSupportedException(fromCurrency.getCurrencyCode(),toCurrency.getCurrencyCode(),true);
1.240 + }
1.241 + return result;
1.242 + }
1.243 +
1.244 + private boolean dateIsInLimit(Date date) {
1.245 + boolean result;
1.246 +// if (date==null) {
1.247 +// result = true;
1.248 +// } else
1.249 + if (fromDate == null && toDate == null) {
1.250 + result = true;
1.251 + } else if (fromDate.getTime()<=date.getTime() && date.getTime()<=toDate.getTime()) {
1.252 + result = true;
1.253 + } else {
1.254 + result = false;
1.255 + }
1.256 +
1.257 + return result;
1.258 + }
1.259 +
1.260 +
1.261 + public void limitAllowedDates(Date from, Date till) {
1.262 + if (from==null) {
1.263 + throw new NullPointerException("from Date can't be null");
1.264 + }
1.265 + if (till==null) {
1.266 + throw new NullPointerException("till Date can't be null");
1.267 + }
1.268 + if (from.getTime()>till.getTime()) {
1.269 + throw new IllegalArgumentException("From date "+from+" must be before tii date "+till);
1.270 + }
1.271 + this.fromDate = from;
1.272 + this.toDate = till;
1.273 + }
1.274 +
1.275 +
1.276 + /** Return current date provider.
1.277 + * @return Returns current date provider.
1.278 + */
1.279 + public IDateProviderEngine getDateProvider() {
1.280 + return dateProvider;
1.281 + }
1.282 +
1.283 +
1.284 + /**
1.285 + * Set date provider. Date provider is used to get "current date". Current date
1.286 + * is used by methods {@link #convert(ConvertorCurrency, ConvertorCurrency, java.math.BigDecimal)} and
1.287 + * {@link #convertWithReversibleRates(ConvertorCurrency, ConvertorCurrency, java.math.BigDecimal)}.
1.288 + *
1.289 + * @param dateProvider Date provider which should be used by Convertor.
1.290 + */
1.291 + public void setDateProvider(IDateProviderEngine dateProvider) {
1.292 + this.dateProvider = dateProvider;
1.293 + }
1.294 +
1.295 +
1.296 }