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