001package org.javamoney.moneta.function;
002
003import java.util.Comparator;
004import java.util.List;
005import java.util.Map;
006import java.util.Objects;
007import java.util.function.BinaryOperator;
008import java.util.function.Predicate;
009import java.util.function.Supplier;
010import java.util.stream.Collector;
011import java.util.stream.Collectors;
012
013import javax.money.CurrencyUnit;
014import javax.money.MonetaryAmount;
015import javax.money.MonetaryException;
016import javax.money.convert.CurrencyConversion;
017import javax.money.convert.ExchangeRate;
018import javax.money.convert.ExchangeRateProvider;
019
020import org.javamoney.moneta.spi.MoneyUtils;
021
022/**
023 * This singleton class provides access to the predefined monetary functions.
024 *
025 * @author otaviojava
026 * @author anatole
027 */
028public final class MonetaryFunctions {
029
030
031    /**
032     * Collector to group by CurrencyUnit
033     * @return the Collector to of Map<CurrencyUnit, List<MonetaryAmount>>
034     */
035    public static Collector<MonetaryAmount,?,Map<CurrencyUnit,List<MonetaryAmount>>> groupByCurrencyUnit(){
036        return Collectors.groupingBy(MonetaryAmount::getCurrency);
037    }
038
039    /**
040     * of the summary of the MonetaryAmount
041     * @param currencyUnit the target {@link javax.money.CurrencyUnit}
042     * @return the MonetarySummaryStatistics
043     */
044        public static Collector<MonetaryAmount, MonetarySummaryStatistics, MonetarySummaryStatistics> summarizingMonetary(
045            CurrencyUnit currencyUnit){
046                Supplier<MonetarySummaryStatistics> supplier = () -> new DefaultMonetarySummaryStatistics(currencyUnit);
047                return Collector.of(supplier, MonetarySummaryStatistics::accept, MonetarySummaryStatistics::combine);
048    }
049
050        /**
051         * of the summary of the MonetaryAmount
052         * @param currencyUnit
053         *            the target {@link javax.money.CurrencyUnit}
054         * @return the MonetarySummaryStatistics
055         */
056        public static Collector<MonetaryAmount, MonetarySummaryStatistics, MonetarySummaryStatistics> summarizingMonetary(
057                        CurrencyUnit currencyUnit, ExchangeRateProvider provider) {
058
059                Supplier<MonetarySummaryStatistics> supplier = () -> new ExchangeRateMonetarySummaryStatistics(
060                                currencyUnit, provider);
061                return Collector.of(supplier, MonetarySummaryStatistics::accept,
062                                MonetarySummaryStatistics::combine);
063        }
064
065    /**
066     * of MonetaryAmount group by MonetarySummary
067     * @return the MonetarySummaryStatistics
068     */
069    public static Collector<MonetaryAmount,GroupMonetarySummaryStatistics,GroupMonetarySummaryStatistics>
070    groupBySummarizingMonetary(){
071        return Collector.of(GroupMonetarySummaryStatistics::new, GroupMonetarySummaryStatistics::accept,
072                            GroupMonetarySummaryStatistics::combine);
073    }
074
075    /**
076     * Get a comparator for sorting CurrencyUnits ascending.
077     *
078     * @return the Comparator to sort by CurrencyUnit in ascending order, not null.
079     */
080    public static Comparator<MonetaryAmount> sortCurrencyUnit(){
081        return Comparator.comparing(MonetaryAmount::getCurrency);
082    }
083
084        /**
085         * comparator to sort the {@link MonetaryAmount} considering the
086         * {@link ExchangeRate}
087         * @param provider
088         * @return the sort of {@link MonetaryAmount} using {@link ExchangeRate}
089         */
090        public static Comparator<? super MonetaryAmount> sortValiable(
091                        ExchangeRateProvider provider) {
092
093                return (m1, m2) -> {
094                        CurrencyConversion conversor = provider.getCurrencyConversion(m1
095                                        .getCurrency());
096                        return m1.compareTo(conversor.apply(m2));
097                };
098        }
099
100        /**
101         * Descending order of
102         * {@link MonetaryFunctions#sortValiable(ExchangeRateProvider)}
103         * @param provider
104         * @return the Descending order of
105         *         {@link MonetaryFunctions#sortValiable(ExchangeRateProvider)}
106         */
107        public static Comparator<? super MonetaryAmount> sortValiableDesc(
108                        ExchangeRateProvider provider) {
109                return sortValiable(provider).reversed();
110        }
111
112    /**
113     * Get a comparator for sorting CurrencyUnits descending.
114     * @return the Comparator to sort by CurrencyUnit in descending order, not null.
115     */
116    public static Comparator<MonetaryAmount> sortCurrencyUnitDesc(){
117        return sortCurrencyUnit().reversed();
118    }
119
120    /**
121     * Get a comparator for sorting amount by number value ascending.
122     * @return the Comparator to sort by number in ascending way, not null.
123     */
124    public static Comparator<MonetaryAmount> sortNumber(){
125        return Comparator.comparing(MonetaryAmount::getNumber);
126    }
127
128    /**
129     * Get a comparator for sorting amount by number value descending.
130     * @return the Comparator to sort by number in descending way, not null.
131     */
132    public static Comparator<MonetaryAmount> sortNumberDesc(){
133        return sortNumber().reversed();
134    }
135
136    /**
137         * Create predicate that filters by CurrencyUnit.
138         * @param currencies
139         *            the target {@link javax.money.CurrencyUnit}
140         * @return the predicate from CurrencyUnit
141         */
142        public static Predicate<MonetaryAmount> isCurrency(
143                        CurrencyUnit... currencies) {
144
145                if (Objects.isNull(currencies) || currencies.length == 0) {
146                        return m -> true;
147                }
148                Predicate<MonetaryAmount> predicate = null;
149
150                for (CurrencyUnit currencyUnit : currencies) {
151                        if (Objects.isNull(predicate)) {
152                                predicate = m -> m.getCurrency().equals(currencyUnit);
153                        } else {
154                                predicate = predicate.or(m -> m.getCurrency().equals(
155                                                currencyUnit));
156                        }
157                }
158                return predicate;
159    }
160
161    /**
162     * Create predicate that filters by CurrencyUnit.
163     * @param currencyUnit the target {@link javax.money.CurrencyUnit}
164     * @return the predicate from CurrencyUnit
165     */
166        public static Predicate<MonetaryAmount> fiterByExcludingCurrency(
167                        CurrencyUnit... currencies) {
168
169                if (Objects.isNull(currencies) || currencies.length == 0) {
170                        return m -> true;
171                }
172                return isCurrency(currencies).negate();
173    }
174
175    /**
176     * Creates filter using isGreaterThan in MonetaryAmount.
177     * @param amount
178     * @return the filter with isGreaterThan conditions
179     */
180    public static Predicate<MonetaryAmount> isGreaterThan(MonetaryAmount amount){
181        return m -> m.isGreaterThan(amount);
182    }
183
184    /**
185     * Creates filter using isGreaterThanOrEqualTo in MonetaryAmount
186     * @param amount
187     * @return the filter with isGreaterThanOrEqualTo conditions
188     */
189    public static Predicate<MonetaryAmount> isGreaterThanOrEqualTo(MonetaryAmount amount){
190        return m -> m.isGreaterThanOrEqualTo(amount);
191    }
192
193    /**
194     * Creates filter using isLessThan in MonetaryAmount
195     * @param amount
196     * @return the filter with isLessThan conditions
197     */
198    public static Predicate<MonetaryAmount> isLessThan(MonetaryAmount amount){
199        return m -> m.isLessThan(amount);
200    }
201
202    /**
203     * Creates filter using isLessThanOrEqualTo in MonetaryAmount
204     * @param amount
205     * @return the filter with isLessThanOrEqualTo conditions
206     */
207    public static Predicate<MonetaryAmount> isLessThanOrEqualTo(MonetaryAmount amount){
208        return m -> m.isLessThanOrEqualTo(amount);
209    }
210
211    /**
212     * Creates a filter using the isBetween predicate.
213     * @param min min value inclusive, not null.
214     * @param max max value inclusive, not null.
215     * @return the Predicate between min and max.
216     */
217    public static Predicate<MonetaryAmount> isBetween(MonetaryAmount min, MonetaryAmount max){
218        return isLessThanOrEqualTo(max).and(isGreaterThanOrEqualTo(min));
219    }
220
221    /**
222     * Adds two monetary together
223     * @param a the first operand
224     * @param b the second operand
225     * @return the sum of {@code a} and {@code b}
226     * @throws NullPointerException if a o b be null
227     * @throws MonetaryException    if a and b have different currency
228     */
229    public static MonetaryAmount sum(MonetaryAmount a, MonetaryAmount b){
230        MoneyUtils.checkAmountParameter(Objects.requireNonNull(a), Objects.requireNonNull(b.getCurrency()));
231        return a.add(b);
232    }
233
234    /**
235     * Returns the smaller of two {@code MonetaryAmount} values. If the arguments
236     * have the same value, the result is that same value.
237     * @param a an argument.
238     * @param b another argument.
239     * @return the smaller of {@code a} and {@code b}.
240     */
241        static MonetaryAmount min(MonetaryAmount a, MonetaryAmount b) {
242        MoneyUtils.checkAmountParameter(Objects.requireNonNull(a), Objects.requireNonNull(b.getCurrency()));
243        return a.isLessThan(b) ? a : b;
244    }
245
246    /**
247     * Returns the greater of two {@code MonetaryAmount} values. If the
248     * arguments have the same value, the result is that same value.
249     * @param a an argument.
250     * @param b another argument.
251     * @return the larger of {@code a} and {@code b}.
252     */
253        static MonetaryAmount max(MonetaryAmount a, MonetaryAmount b) {
254        MoneyUtils.checkAmountParameter(Objects.requireNonNull(a), Objects.requireNonNull(b.getCurrency()));
255        return a.isGreaterThan(b) ? a : b;
256    }
257
258    /**
259     * Creates a BinaryOperator to sum.
260     * @return the sum BinaryOperator, not null.
261     */
262    public static BinaryOperator<MonetaryAmount> sum(){
263        return MonetaryFunctions::sum;
264    }
265
266        /**
267         * return the sum and convert all values to specific currency using the
268         * provider, if necessary
269         * @param provider
270         * @param currency
271         *            currency
272         * @return the list convert to specific currency unit
273         */
274        public static BinaryOperator<MonetaryAmount> sum(
275                        ExchangeRateProvider provider, CurrencyUnit currency) {
276                CurrencyConversion currencyConversion = provider
277                                .getCurrencyConversion(currency);
278
279                return (m1, m2) -> currencyConversion.apply(m1).add(
280                                currencyConversion.apply(m2));
281        }
282
283    /**
284         * Creates a BinaryOperator to calculate the minimum amount
285         * @return the minimum BinaryOperator, not null.
286         */
287    public static BinaryOperator<MonetaryAmount> min(){
288        return MonetaryFunctions::min;
289    }
290
291        /**
292         * return the minimum value, if the monetary amounts have different
293         * currencies, will converter first using the given ExchangeRateProvider
294         * @param provider
295         *            the ExchangeRateProvider to convert the currencies
296         * @return the minimum value
297         */
298        public static BinaryOperator<MonetaryAmount> min(
299                        ExchangeRateProvider provider) {
300
301                return (m1, m2) -> {
302                        CurrencyConversion convertion = provider.getCurrencyConversion(m1
303                                        .getCurrency());
304
305                        if (m1.isGreaterThan(convertion.apply(m2))) {
306                                return m2;
307                        }
308                        return m1;
309                };
310        }
311
312    /**
313         * Creates a BinaryOperator to calculate the maximum amount.
314         * @return the max BinaryOperator, not null.
315         */
316    public static BinaryOperator<MonetaryAmount> max(){
317        return MonetaryFunctions::max;
318    }
319
320        /**
321         * return the maximum value, if the monetary amounts have different
322         * currencies, will converter first using the given ExchangeRateProvider
323         * @param provider
324         *            the ExchangeRateProvider to convert the currencies
325         * @return the maximum value
326         */
327        public static BinaryOperator<MonetaryAmount> max(
328                        ExchangeRateProvider provider) {
329
330                return (m1, m2) -> {
331                        CurrencyConversion convertion = provider
332                                        .getCurrencyConversion(m1.getCurrency());
333
334                        if (m1.isGreaterThan(convertion.apply(m2))) {
335                                return m1;
336                        }
337                        return m2;
338                };
339        }
340
341
342}