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