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.spi; 017 018import java.io.Serializable; 019import java.math.BigDecimal; 020import java.math.BigInteger; 021import java.math.MathContext; 022import java.math.RoundingMode; 023import java.util.Objects; 024import java.util.concurrent.atomic.AtomicLong; 025 026import javax.money.CurrencyUnit; 027import javax.money.MonetaryAmount; 028import javax.money.MonetaryContext; 029import javax.money.NumberValue; 030import javax.money.MonetaryException; 031 032/** 033 * Platform RI: This base class simplifies implementing {@link MonetaryAmount}, 034 * by providing the common functionality. The different explicitly typed methods 035 * are all reduced to methods using {@link BigDecimal} as input, hereby 036 * performing any conversion to {@link BigDecimal} as needed. Obviosly this 037 * takes some time, so implementors that want to avoid this overhead should 038 * implement {@link MonetaryAmount} directly. 039 * 040 * @author Anatole Tresch 041 */ 042public abstract class AbstractMoney implements 043 MonetaryAmount, Serializable { 044 045 /** 046 * serialVersionUID. 047 */ 048 private static final long serialVersionUID = 1L; 049 050 /** The currency of this amount. */ 051 protected CurrencyUnit currency; 052 053 /** the {@link MonetaryContext} used by this instance, e.g. on division. */ 054 protected MonetaryContext monetaryContext; 055 056 /** 057 * Required for deserialization. 058 */ 059 protected AbstractMoney() { 060 } 061 062 /** 063 * Constructor of {@link AbstractMoney}. 064 * 065 * @param currency 066 * the currency, not {@code null}. 067 */ 068 protected AbstractMoney(CurrencyUnit currency) { 069 this(currency, null); 070 } 071 072 /** 073 * Creates a new instance os {@link AbstractMoney}. 074 * 075 * @param currency 076 * the currency, not {@code null}. 077 * @param monetaryContext 078 * the {@link MonetaryContext}, not {@code null}. 079 */ 080 protected AbstractMoney(CurrencyUnit currency, 081 MonetaryContext monetaryContext) { 082 Objects.requireNonNull(currency, "Currency is required."); 083 this.currency = currency; 084 if (monetaryContext != null) { 085 this.monetaryContext = monetaryContext; 086 } 087 else { 088 this.monetaryContext = getDefaultMonetaryContext(); 089 } 090 Objects.requireNonNull(this.monetaryContext); 091 } 092 093 /** 094 * Method to be implemented by superclasses to provide the default 095 * {@link MonetaryContext}, when not explicit {@link MonetaryContext} is 096 * available. 097 * 098 * @return the default {@link MonetaryContext}, never {@code null}. 099 */ 100 protected abstract MonetaryContext getDefaultMonetaryContext(); 101 102 /** 103 * Returns the amount’s currency, modelled as {@link CurrencyUnit}. 104 * Implementations may co-variantly change the return type to a more 105 * specific implementation of {@link CurrencyUnit} if desired. 106 * 107 * @return the currency, never {@code null} 108 * @see javax.money.MonetaryAmount#getCurrency() 109 */ 110 @Override 111 public CurrencyUnit getCurrency() { 112 return currency; 113 } 114 115 /** 116 * Access the {@link MonetaryContext} used by this instance. 117 * 118 * @return the {@link MonetaryContext} used, never null. 119 * @see javax.money.MonetaryAmount#getMonetaryContext() 120 */ 121 @Override 122 public MonetaryContext getMonetaryContext() { 123 return this.monetaryContext; 124 } 125 126 // Supporting methods 127 128 /** 129 * Creates a {@link BigDecimal} from the given {@link Number} doing the 130 * valid conversion depending the type given. 131 * 132 * @param num 133 * the number type 134 * @return the corresponding {@link BigDecimal} 135 */ 136 protected static BigDecimal getBigDecimal(long num) { 137 return BigDecimal.valueOf(num); 138 } 139 140 /** 141 * Creates a {@link BigDecimal} from the given {@link Number} doing the 142 * valid conversion depending the type given. 143 * 144 * @param num 145 * the number type 146 * @return the corresponding {@link BigDecimal} 147 */ 148 protected static BigDecimal getBigDecimal(double num) { 149 return new BigDecimal(String.valueOf(num)); 150 } 151 152 /** 153 * Creates a {@link BigDecimal} from the given {@link Number} doing the 154 * valid conversion depending the type given. 155 * 156 * @param num 157 * the number type 158 * @return the corresponding {@link BigDecimal} 159 */ 160 protected static BigDecimal getBigDecimal(Number num) { 161 checkNumberParameter(num); 162 if(num instanceof NumberValue){ 163 return ((NumberValue)num).numberValue(BigDecimal.class); 164 } 165 // try fast equality check first (delegates to identity!) 166 if (BigDecimal.class.equals(num.getClass())) { 167 return (BigDecimal) num; 168 } 169 if (Long.class.equals(num.getClass()) 170 || Integer.class.equals(num.getClass()) 171 || Short.class.equals(num.getClass()) 172 || Byte.class.equals(num.getClass()) 173 || AtomicLong.class.equals(num.getClass())) { 174 return BigDecimal.valueOf(num.longValue()); 175 } 176 if (Float.class.equals(num.getClass()) 177 || Double.class.equals(num.getClass())) { 178 return new BigDecimal(num.toString()); 179 } 180 // try instance of (slower) 181 if (num instanceof BigDecimal) { 182 return (BigDecimal) num; 183 } 184 if (num instanceof BigInteger) { 185 return new BigDecimal((BigInteger) num); 186 } 187 try { 188 // Avoid imprecise conversion to double value if at all possible 189 return new BigDecimal(num.toString()); 190 } catch (NumberFormatException e) { 191 } 192 return BigDecimal.valueOf(num.doubleValue()); 193 } 194 195 /** 196 * Creates a {@link BigDecimal} from the given {@link Number} doing the 197 * valid conversion depending the type given, if a {@link MonetaryContext} 198 * is given, it is applied to the number returned. 199 * 200 * @param num 201 * the number type 202 * @return the corresponding {@link BigDecimal} 203 */ 204 protected static BigDecimal getBigDecimal(Number num, 205 MonetaryContext moneyContext) { 206 BigDecimal bd = getBigDecimal(num); 207 if (moneyContext != null) { 208 return new BigDecimal(bd.toString(), 209 getMathContext(moneyContext, RoundingMode.HALF_EVEN)); 210 } 211 return bd; 212 } 213 214 /** 215 * Evaluates the {@link MathContext} from the given {@link MonetaryContext}. 216 * 217 * @param monetaryContext 218 * the {@link MonetaryContext} 219 * @param defaultMode 220 * the default {@link RoundingMode}, to be used if no one is set 221 * in {@link MonetaryContext}. 222 * @return the corresponding {@link MathContext} 223 */ 224 protected static MathContext getMathContext( 225 MonetaryContext monetaryContext, 226 RoundingMode defaultMode) { 227 MathContext ctx = monetaryContext.getAttribute(MathContext.class); 228 if (ctx != null) { 229 return ctx; 230 } 231 if (defaultMode != null) { 232 return new MathContext(monetaryContext.getPrecision(), 233 monetaryContext.getAttribute(RoundingMode.class, 234 defaultMode)); 235 } 236 return new MathContext(monetaryContext.getPrecision(), 237 monetaryContext.getAttribute(RoundingMode.class, 238 RoundingMode.HALF_EVEN)); 239 } 240 241 /** 242 * Method to check if a currency is compatible with this amount instance. 243 * 244 * @param amount 245 * The monetary amount to be compared to, never null. 246 * @throws MonetaryException 247 * If the amount is null, or the amount's currency is not 248 * compatible (same {@link CurrencyUnit#getNamespace()} and same 249 * {@link CurrencyUnit#getCurrencyCode()}). 250 */ 251 protected void checkAmountParameter(MonetaryAmount amount) { 252 Objects.requireNonNull(amount, "Amount must not be null."); 253 final CurrencyUnit amountCurrency = amount.getCurrency(); 254 if (!(this.currency 255 .getCurrencyCode().equals(amountCurrency.getCurrencyCode()))) { 256 throw new MonetaryException("Currency mismatch: " 257 + this.currency + '/' + amountCurrency); 258 } 259 } 260 261 /** 262 * Internal method to check for correct number parameter. 263 * 264 * @param number 265 * @throws IllegalArgumentException 266 * If the number is null 267 */ 268 protected static void checkNumberParameter(Number number) { 269 Objects.requireNonNull(number, "Number is required."); 270 } 271 272}