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}