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