001/* 002 * Copyright (c) 2012, 2013, Credit Suisse (Anatole Tresch), Werner Keil. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 005 * use this file except in compliance with the License. You may obtain a copy of 006 * the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 012 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 013 * License for the specific language governing permissions and limitations under 014 * the License. 015 */ 016package org.javamoney.moneta.function; 017 018import java.math.MathContext; 019import java.math.RoundingMode; 020import java.util.Collections; 021import java.util.Currency; 022import java.util.HashSet; 023import java.util.ServiceLoader; 024import java.util.Set; 025import java.util.logging.Level; 026import java.util.logging.Logger; 027 028import javax.money.CurrencyUnit; 029import javax.money.MonetaryAdjuster; 030import javax.money.MonetaryAmount; 031 032import org.javamoney.moneta.spi.RoundingProviderSpi; 033 034/** 035 * This class models the accessor for rounding instances, modeled by 036 * {@link MonetaryAdjuster}. 037 * <p> 038 * This class is thread-safe. 039 * 040 * @author Anatole Tresch 041 * @author Werner Keil 042 */ 043public final class MonetaryRoundings { 044 /** 045 * An adaptive rounding instance that transparently looks up the correct 046 * rounding. 047 */ 048 private static final MonetaryAdjuster DEFAULT_ROUNDING = new DefaultCurrencyRounding(); 049 /** 050 * The internal fallback provider, if no registered 051 * {@link RoundingProviderSpi} could return a rounding. 052 */ 053 private static DefaultRoundingProvider defaultProvider = new DefaultRoundingProvider(); 054 /** Currently loaded SPIs. */ 055 private static ServiceLoader<RoundingProviderSpi> providerSpis = loadSpis(); 056 057 /** 058 * Private singleton constructor. 059 */ 060 private MonetaryRoundings() { 061 // Singleton 062 } 063 064 private static ServiceLoader<RoundingProviderSpi> loadSpis() { 065 try { 066 return ServiceLoader.load(RoundingProviderSpi.class); 067 } catch (Exception e) { 068 Logger.getLogger(MonetaryRoundings.class.getName()).log( 069 Level.SEVERE, 070 "Error loading RoundingProviderSpi instances.", e); 071 return null; 072 } 073 } 074 075 /** 076 * Creates a rounding that can be added as {@link MonetaryAdjuster} to 077 * chained calculations. The instance will lookup the concrete 078 * {@link MonetaryAdjuster} instance from the {@link MonetaryRoundings} 079 * based on the input {@link MonetaryAmount}'s {@link CurrencyUnit}. 080 * 081 * @return the (shared) default rounding instance. 082 */ 083 public static MonetaryAdjuster getRounding() { 084 return DEFAULT_ROUNDING; 085 } 086 087 /** 088 * Creates an rounding instance. 089 * 090 * @param mathContext 091 * The {@link MathContext} to be used, not {@code null}. 092 */ 093 public static MonetaryAdjuster getRounding(int scale, 094 RoundingMode roundingMode) { 095 return new DefaultRounding(scale, roundingMode); 096 } 097 098 /** 099 * Creates an {@link MonetaryAdjuster} for rounding {@link MonetaryAmount} 100 * instances given a currency. 101 * 102 * @param currency 103 * The currency, which determines the required precision. As 104 * {@link RoundingMode}, by default, {@link RoundingMode#HALF_UP} 105 * is sued. 106 * @return a new instance {@link MonetaryAdjuster} implementing the 107 * rounding, never {@code null}. 108 */ 109 public static MonetaryAdjuster getRounding(CurrencyUnit currency) { 110 for (RoundingProviderSpi prov : providerSpis) { 111 try { 112 MonetaryAdjuster op = prov.getRounding(currency); 113 if (op != null) { 114 return op; 115 } 116 } catch (Exception e) { 117 Logger.getLogger(MonetaryRoundings.class.getName()).log( 118 Level.SEVERE, 119 "Error loading RoundingProviderSpi from ptovider: " 120 + prov, e); 121 } 122 } 123 return defaultProvider.getRounding(currency); 124 } 125 126 /** 127 * Creates an {@link MonetaryAdjuster} for rounding {@link MonetaryAmount} 128 * instances given a currency. 129 * 130 * @param currency 131 * The currency, which determines the required precision. As 132 * {@link RoundingMode}, by default, {@link RoundingMode#HALF_UP} 133 * is sued. 134 * @return a new instance {@link MonetaryAdjuster} implementing the 135 * rounding, never {@code null}. 136 */ 137 public static MonetaryAdjuster getCashRounding(CurrencyUnit currency) { 138 for (RoundingProviderSpi prov : providerSpis) { 139 try { 140 MonetaryAdjuster op = prov.getCashRounding(currency); 141 if (op != null) { 142 return op; 143 } 144 } catch (Exception e) { 145 Logger.getLogger(MonetaryRoundings.class.getName()).log( 146 Level.SEVERE, 147 "Error loading RoundingProviderSpi from ptovider: " 148 + prov, e); 149 } 150 } 151 return defaultProvider.getCashRounding(currency); 152 } 153 154 /** 155 * Creates an {@link MonetaryAdjuster} for rounding {@link MonetaryAmount} 156 * instances given a currency, hereby the rounding must be valid for the 157 * given timestamp. 158 * 159 * @param currency 160 * The currency, which determines the required precision. As 161 * {@link RoundingMode}, by default, {@link RoundingMode#HALF_UP} 162 * is used. 163 * @param timestamp 164 * the UTC timestamp. 165 * @return a new instance {@link MonetaryAdjuster} implementing the 166 * rounding, or {@code null}. 167 */ 168 public static MonetaryAdjuster getRounding(CurrencyUnit currency, 169 long timestamp) { 170 for (RoundingProviderSpi prov : providerSpis) { 171 try { 172 MonetaryAdjuster op = prov.getRounding(currency, timestamp); 173 if (op != null) { 174 return op; 175 } 176 } catch (Exception e) { 177 Logger.getLogger(MonetaryRoundings.class.getName()).log( 178 Level.SEVERE, 179 "Error loading RoundingProviderSpi from provider: " 180 + prov, e); 181 } 182 } 183 return defaultProvider.getRounding(currency, timestamp); 184 } 185 186 /** 187 * Creates an {@link MonetaryAdjuster} for rounding {@link MonetaryAmount} 188 * instances given a currency, hereby the rounding must be valid for the 189 * given timestamp. 190 * 191 * @param currency 192 * The currency, which determines the required precision. As 193 * {@link RoundingMode}, by default, {@link RoundingMode#HALF_UP} 194 * is sued. 195 * @param timestamp 196 * the UTC timestamp. 197 * @return a new instance {@link MonetaryAdjuster} implementing the 198 * rounding, or {@code null}. 199 */ 200 public static MonetaryAdjuster getCashRounding(CurrencyUnit currency, 201 long timestamp) { 202 for (RoundingProviderSpi prov : providerSpis) { 203 try { 204 MonetaryAdjuster op = prov.getCashRounding(currency, timestamp); 205 if (op != null) { 206 return op; 207 } 208 } catch (Exception e) { 209 Logger.getLogger(MonetaryRoundings.class.getName()).log( 210 Level.SEVERE, 211 "Error loading RoundingProviderSpi from ptovider: " 212 + prov, e); 213 } 214 } 215 return defaultProvider.getCashRounding(currency, timestamp); 216 } 217 218 /** 219 * Access an {@link MonetaryAdjuster} for custom rounding 220 * {@link MonetaryAmount} instances. 221 * 222 * @param customRounding 223 * The customRounding identifier. 224 * @return the corresponding {@link MonetaryAdjuster} implementing the 225 * rounding, never {@code null}. 226 * @throws IllegalArgumentException 227 * if no such rounding is registered using a 228 * {@link RoundingProviderSpi} instance. 229 */ 230 public static MonetaryAdjuster getRounding(String customRoundingId) { 231 for (RoundingProviderSpi prov : providerSpis) { 232 try { 233 MonetaryAdjuster op = prov.getCustomRounding(customRoundingId); 234 if (op != null) { 235 return op; 236 } 237 } catch (Exception e) { 238 Logger.getLogger(MonetaryRoundings.class.getName()).log( 239 Level.SEVERE, 240 "Error loading RoundingProviderSpi from provider: " 241 + prov, e); 242 } 243 } 244 return defaultProvider.getCustomRounding(customRoundingId); 245 } 246 247 /** 248 * Allows to access the identifiers of the current defined custom roundings. 249 * 250 * @return the set of custom rounding ids, never {@code null}. 251 */ 252 public static Set<String> getCustomRoundingIds() { 253 Set<String> result = new HashSet<String>(); 254 for (RoundingProviderSpi prov : providerSpis) { 255 try { 256 result.addAll(prov.getCustomRoundingIds()); 257 } catch (Exception e) { 258 Logger.getLogger(MonetaryRoundings.class.getName()).log( 259 Level.SEVERE, 260 "Error loading RoundingProviderSpi from provider: " 261 + prov, e); 262 } 263 } 264 return result; 265 } 266 267 /** 268 * Platform RI: Default Rounding that rounds a {@link MonetaryAmount} based 269 * on tis {@link Currency}. 270 * 271 * @author Anatole Tresch 272 */ 273 private static final class DefaultCurrencyRounding implements 274 MonetaryAdjuster { 275 276 @Override 277 public MonetaryAmount adjustInto(MonetaryAmount amount) { 278 MonetaryAdjuster r = MonetaryRoundings.getRounding(amount 279 .getCurrency()); 280 return r.adjustInto(amount); 281 } 282 283 } 284 285 private static final class DefaultRoundingProvider implements 286 RoundingProviderSpi { 287 288 @Override 289 public MonetaryAdjuster getRounding(CurrencyUnit currency) { 290 return new DefaultRounding(currency); 291 } 292 293 @Override 294 public MonetaryAdjuster getRounding(CurrencyUnit currency, 295 long timestamp) { 296 return null; 297 } 298 299 @Override 300 public MonetaryAdjuster getCashRounding(CurrencyUnit currency) { 301 return new DefaultCashRounding(currency); 302 } 303 304 @Override 305 public MonetaryAdjuster getCashRounding(CurrencyUnit currency, 306 long timestamp) { 307 return null; 308 } 309 310 @Override 311 public Set<String> getCustomRoundingIds() { 312 return Collections.emptySet(); 313 } 314 315 @Override 316 public MonetaryAdjuster getCustomRounding(String customRoundingId) { 317 throw new IllegalArgumentException("No such custom rounding: " 318 + customRoundingId); 319 } 320 321 } 322}