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}