001/* 002 Copyright (c) 2012, 2014, Credit Suisse (Anatole Tresch), Werner Keil and others by the @author tag. 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.spi; 017 018import javax.money.CurrencyUnit; 019import javax.money.MonetaryAmount; 020import javax.money.MonetaryContext; 021import javax.money.MonetaryException; 022import java.math.BigDecimal; 023import java.math.MathContext; 024import java.math.RoundingMode; 025import java.util.Objects; 026import java.util.logging.Logger; 027 028/** 029 * Platform RI: This utility class simplifies implementing {@link MonetaryAmount}, 030 * by providing the common functionality. The different explicitly typed methods 031 * are all reduced to methods using {@link BigDecimal} as input, hereby 032 * performing any conversion to {@link BigDecimal} as needed. Obviously this 033 * takes some time, so implementors that want to avoid this overhead should 034 * implement {@link MonetaryAmount} directly. 035 * 036 * @author Anatole Tresch 037 */ 038public final class MoneyUtils { 039 /** 040 * The logger used. 041 */ 042 private static final Logger LOG = Logger.getLogger(MoneyUtils.class.getName()); 043 044 private MoneyUtils() { 045 } 046 047 048 // Supporting methods 049 050 /** 051 * Creates a {@link BigDecimal} from the given {@link Number} doing the 052 * valid conversion depending the type given. 053 * 054 * @param num the number type 055 * @return the corresponding {@link BigDecimal} 056 */ 057 public static BigDecimal getBigDecimal(long num) { 058 return BigDecimal.valueOf(num); 059 } 060 061 /** 062 * Creates a {@link BigDecimal} from the given {@link Number} doing the 063 * valid conversion depending the type given. 064 * 065 * @param num the number type 066 * @return the corresponding {@link BigDecimal} 067 */ 068 public static BigDecimal getBigDecimal(double num) { 069 if (Double.isNaN(num)) { 070 throw new ArithmeticException("Invalid input Double.NaN."); 071 } else if(Double.isInfinite(num)) { 072 throw new ArithmeticException("Invalid input Double.xxx_INFINITY."); 073 } 074 return new BigDecimal(String.valueOf(num)); 075 } 076 077 /** 078 * Creates a {@link BigDecimal} from the given {@link Number} doing the 079 * valid conversion depending the type given. 080 * 081 * @param num the number type 082 * @return the corresponding {@link BigDecimal} 083 */ 084 public static BigDecimal getBigDecimal(Number num) { 085 return ConvertBigDecimal.of(num); 086 } 087 088 /** 089 * Creates a {@link BigDecimal} from the given {@link Number} doing the 090 * valid conversion depending the type given, if a {@link MonetaryContext} 091 * is given, it is applied to the number returned. 092 * 093 * @param num the number type 094 * @return the corresponding {@link BigDecimal} 095 */ 096 public static BigDecimal getBigDecimal(Number num, MonetaryContext moneyContext) { 097 BigDecimal bd = getBigDecimal(num); 098 if (moneyContext!=null) { 099 MathContext mc = getMathContext(moneyContext, RoundingMode.HALF_EVEN); 100 bd = new BigDecimal(bd.toString(), mc); 101 if (moneyContext.getMaxScale() > 0) { 102 LOG.fine(String.format("Got Max Scale %s", moneyContext.getMaxScale())); 103 bd = bd.setScale(moneyContext.getMaxScale(), mc.getRoundingMode()); 104 } 105 } 106 return bd; 107 } 108 109 /** 110 * Evaluates the {@link MathContext} from the given {@link MonetaryContext}. 111 * 112 * @param monetaryContext the {@link MonetaryContext} 113 * @param defaultMode the default {@link RoundingMode}, to be used if no one is set 114 * in {@link MonetaryContext}. 115 * @return the corresponding {@link MathContext} 116 */ 117 public static MathContext getMathContext(MonetaryContext monetaryContext, RoundingMode defaultMode) { 118 MathContext ctx = monetaryContext.get(MathContext.class); 119 if (ctx!=null) { 120 return ctx; 121 } 122 RoundingMode roundingMode = monetaryContext.get(RoundingMode.class); 123 if (roundingMode == null) { 124 roundingMode = defaultMode; 125 } 126 if (roundingMode == null) { 127 roundingMode = RoundingMode.HALF_EVEN; 128 } 129 return new MathContext(monetaryContext.getPrecision(), roundingMode); 130 } 131 132 /** 133 * Method to check if a currency is compatible with this amount instance. 134 * 135 * @param amount The monetary amount to be compared to, never null. 136 * @param currencyUnit the currency unit to compare, never null. 137 * @throws MonetaryException If the amount is null, or the amount's {@link CurrencyUnit} is not 138 * compatible, meaning has a different value of 139 * {@link CurrencyUnit#getCurrencyCode()}). 140 */ 141 public static void checkAmountParameter(MonetaryAmount amount, CurrencyUnit currencyUnit) { 142 Objects.requireNonNull(amount, "Amount must not be null."); 143 final CurrencyUnit amountCurrency = amount.getCurrency(); 144 if (!(currencyUnit.getCurrencyCode().equals(amountCurrency.getCurrencyCode()))) { 145 throw new MonetaryException("Currency mismatch: " + currencyUnit + '/' + amountCurrency); 146 } 147 } 148 149 /** 150 * Internal method to check for correct number parameter. 151 * 152 * @param number the number to be checked. 153 * @throws IllegalArgumentException If the number is null 154 */ 155 public static void checkNumberParameter(Number number) { 156 Objects.requireNonNull(number, "Number is required."); 157 } 158 159}