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.*;
019import java.math.BigDecimal;
020import java.util.Objects;
021
022/**
023 * Basic implementation of {@link javax.money.MonetaryAmountFactory}, which simplifies development of the SPI interface.
024 *
025 * @param <T> the target class implementing {@link javax.money.MonetaryAmount}.
026 */
027public abstract class AbstractAmountBuilder<T extends MonetaryAmount> implements MonetaryAmountFactory<T> {
028
029    /**
030     * The default {@link MonetaryContext} applied, if not set explicitly on creation.
031     */
032    private MonetaryContext DEFAULT_MONETARY_CONTEXT = loadDefaultMonetaryContext();
033
034    /**
035     * The default {@link MonetaryContext} applied, if not set explicitly on creation.
036     */
037    private MonetaryContext MAX_MONETARY_CONTEXT = loadMaxMonetaryContext();
038
039    private CurrencyUnit currency;
040    private Number number;
041    private MonetaryContext monetaryContext = DEFAULT_MONETARY_CONTEXT;
042
043    /**
044     * Creates a new instance of {@link MonetaryAmount}, using the default {@link MonetaryContext}.
045     *
046     * @return a {@code MonetaryAmount} combining the numeric value and currency unit.
047     * @throws ArithmeticException If the number exceeds the capabilities of the default {@link MonetaryContext}
048     *                             used.
049     */
050    @Override
051    public T create() {
052        if (currency == null) {
053            throw new MonetaryException("Cannot of FastMoney instance: missing currency.");
054        }
055        if (number == null) {
056            throw new MonetaryException("Cannot of FastMoney instance: missing number.");
057        }
058        if (monetaryContext == null) {
059            throw new MonetaryException("Cannot of FastMoney instance: missing context.");
060        }
061        return create(number, currency, monetaryContext);
062    }
063
064    protected abstract T create(Number number, CurrencyUnit currency, MonetaryContext monetaryContext);
065
066    protected abstract MonetaryContext loadDefaultMonetaryContext();
067
068    protected abstract MonetaryContext loadMaxMonetaryContext();
069
070
071    /*
072     * (non-Javadoc)
073     * @see javax.money.MonetaryAmountFactory#withCurrency(javax.money.CurrencyUnit)
074     */
075    @Override
076    public MonetaryAmountFactory<T> setCurrency(CurrencyUnit currency) {
077        Objects.requireNonNull(currency);
078        this.currency = currency;
079        return this;
080    }
081
082    /*
083     * (non-Javadoc)
084     * @see javax.money.MonetaryAmountFactory#with(java.lang.Number)
085     */
086    @Override
087    public MonetaryAmountFactory<T> setNumber(Number number) {
088        this.number = getBigDecimal(number);
089        return this;
090    }
091
092    /*
093     * (non-Javadoc)
094     * @see javax.money.MonetaryAmountFactory#withCurrency(java.lang.String)
095     */
096    @Override
097    public MonetaryAmountFactory<T> setCurrency(String currencyCode) {
098        this.currency = MonetaryCurrencies.getCurrency(currencyCode);
099        return this;
100    }
101
102    /**
103     * Creates a new instance of {@link MonetaryAmounts}, using the default {@link MonetaryContext}.
104     *
105     * @param number numeric value.
106     * @return a {@code Money} combining the numeric value and currency unit.
107     * @throws ArithmeticException      If the number exceeds the capabilities of the default {@link MonetaryContext}
108     *                                  used.
109     * @throws UnknownCurrencyException if the currency code can not be resolved to {@link CurrencyUnit}.
110     */
111    @Override
112    public MonetaryAmountFactory<T> setNumber(double number) {
113        this.number = new BigDecimal(String.valueOf(number));
114        return this;
115    }
116
117    /*
118     * (non-Javadoc)
119     * @see javax.money.MonetaryAmountFactory#with(long)
120     */
121    @Override
122    public MonetaryAmountFactory<T> setNumber(long number) {
123        this.number = BigDecimal.valueOf(number);
124        return this;
125    }
126
127    /*
128     * (non-Javadoc)
129     * @see javax.money.MonetaryAmountFactory#with(javax.money.MonetaryContext)
130     */
131    @Override
132    public MonetaryAmountFactory<T> setContext(MonetaryContext monetaryContext) {
133        Objects.requireNonNull(monetaryContext);
134        int maxScale = getMaximalMonetaryContext().getMaxScale();
135        if (maxScale != -1 && maxScale < monetaryContext.getMaxScale()) {
136            throw new MonetaryException(
137                    "Context exceeds maximal capabilities (scale) of this type: " + monetaryContext);
138        }
139        int precision = getMaximalMonetaryContext().getPrecision();
140        if (precision != 0 && precision < monetaryContext.getPrecision()) {
141            throw new MonetaryException(
142                    "Contexts exceeds maximal capabilities (precision) of this type: " + monetaryContext);
143        }
144        this.monetaryContext = monetaryContext;
145        return this;
146    }
147
148    /**
149     * Returns the default {@link MonetaryContext} used, when no {@link MonetaryContext} is
150     * provided.
151     *
152     * @return the default {@link MonetaryContext}, never {@code null}.
153     */
154    @Override
155    public MonetaryContext getDefaultMonetaryContext() {
156        return DEFAULT_MONETARY_CONTEXT;
157    }
158
159    /**
160     * Returns the maximal {@link MonetaryContext} supported.
161     *
162     * @return the maximal {@link MonetaryContext}, never {@code null}.
163     */
164    @Override
165    public MonetaryContext getMaximalMonetaryContext() {
166        return MAX_MONETARY_CONTEXT;
167    }
168
169    /**
170     * Converts (if necessary) the given {@link MonetaryAmount} to a new {@link MonetaryAmount}
171     * instance, hereby supporting the {@link MonetaryContext} given.
172     *
173     * @param amt the amount to be converted, if necessary.
174     * @return an according Money instance.
175     */
176    @Override
177    public MonetaryAmountFactory<T> setAmount(MonetaryAmount amt) {
178        this.currency = amt.getCurrency();
179        this.number = amt.getNumber().numberValue(BigDecimal.class);
180        this.monetaryContext = MonetaryContextBuilder.of(DEFAULT_MONETARY_CONTEXT.getAmountType())
181                .importContext(amt.getContext()).build();
182        return this;
183    }
184
185    /**
186     * Creates a {@link BigDecimal} from the given {@link Number} doing the valid conversion
187     * depending the type given.
188     *
189     * @param num the number type
190     * @return the corresponding {@link BigDecimal}
191     */
192    protected static BigDecimal getBigDecimal(Number num) {
193        return ConvertBigDecimal.of(num);
194    }
195
196}