001package org.javamoney.moneta.spi;
002
003import javax.money.*;
004import java.math.BigDecimal;
005import java.math.BigInteger;
006import java.util.Objects;
007import java.util.concurrent.atomic.AtomicLong;
008
009/**
010 * Basic implementation of {@link MonetaryAmountFactory}, which simplifies development of the SPI interface.
011 *
012 * @param <T> the target class implementing {@link javax.money.MonetaryAmount}.
013 */
014public abstract class AbstractAmountFactory<T extends MonetaryAmount> implements MonetaryAmountFactory<T>{
015
016    /**
017     * The default {@link MonetaryContext} applied, if not set explicitly on creation.
018     */
019    private MonetaryContext DEFAULT_MONETARY_CONTEXT = loadDefaultMonetaryContext();
020
021    /**
022     * The default {@link MonetaryContext} applied, if not set explicitly on creation.
023     */
024    private MonetaryContext MAX_MONETARY_CONTEXT = loadMaxMonetaryContext();
025
026    private CurrencyUnit currency;
027    private Number number;
028    private MonetaryContext monetaryContext = DEFAULT_MONETARY_CONTEXT;
029
030    /**
031     * Creates a new instance of {@link MonetaryAmount}, using the default {@link MonetaryContext}.
032     *
033     * @return a {@code MonetaryAmount} combining the numeric value and currency unit.
034     * @throws ArithmeticException If the number exceeds the capabilities of the default {@link MonetaryContext}
035     *                             used.
036     */
037    @Override
038    public T create(){
039        return create(currency, number, monetaryContext);
040    }
041
042    protected abstract T create(CurrencyUnit currency, Number number, MonetaryContext monetaryContext);
043
044    protected abstract MonetaryContext loadDefaultMonetaryContext();
045
046    protected abstract MonetaryContext loadMaxMonetaryContext();
047
048
049    /*
050     * (non-Javadoc)
051     * @see javax.money.MonetaryAmountFactory#withCurrency(javax.money.CurrencyUnit)
052     */
053    @Override
054    public MonetaryAmountFactory<T> setCurrency(CurrencyUnit currency){
055        Objects.requireNonNull(currency);
056        this.currency = currency;
057        return this;
058    }
059
060    /*
061     * (non-Javadoc)
062     * @see javax.money.MonetaryAmountFactory#with(java.lang.Number)
063     */
064    @Override
065    public MonetaryAmountFactory<T> setNumber(Number number){
066        this.number = getBigDecimal(number);
067        return this;
068    }
069
070    /*
071     * (non-Javadoc)
072     * @see javax.money.MonetaryAmountFactory#withCurrency(java.lang.String)
073     */
074    @Override
075    public MonetaryAmountFactory<T> setCurrency(String currencyCode){
076        this.currency = MonetaryCurrencies.getCurrency(currencyCode);
077        return this;
078    }
079
080    /**
081     * Creates a new instance of {@link MonetaryAmounts}, using the default {@link MonetaryContext}.
082     *
083     * @param number numeric value.
084     * @return a {@code Money} combining the numeric value and currency unit.
085     * @throws ArithmeticException      If the number exceeds the capabilities of the default {@link MonetaryContext}
086     *                                  used.
087     * @throws UnknownCurrencyException if the currency code can not be resolved to {@link CurrencyUnit}.
088     */
089    @Override
090    public MonetaryAmountFactory<T> setNumber(double number){
091        this.number = new BigDecimal(String.valueOf(number));
092        return this;
093    }
094
095    /*
096     * (non-Javadoc)
097     * @see javax.money.MonetaryAmountFactory#with(long)
098     */
099    @Override
100    public MonetaryAmountFactory<T> setNumber(long number){
101        this.number = BigDecimal.valueOf(number);
102        return this;
103    }
104
105    /*
106     * (non-Javadoc)
107     * @see javax.money.MonetaryAmountFactory#with(javax.money.MonetaryContext)
108     */
109    @Override
110    public MonetaryAmountFactory<T> setContext(MonetaryContext monetaryContext){
111        Objects.requireNonNull(monetaryContext);
112        this.monetaryContext = monetaryContext;
113        return this;
114    }
115
116    /**
117     * Returns the default {@link MonetaryContext} used, when no {@link MonetaryContext} is
118     * provided.
119     *
120     * @return the default {@link MonetaryContext}, never {@code null}.
121     */
122    @Override
123    public MonetaryContext getDefaultMonetaryContext(){
124        return DEFAULT_MONETARY_CONTEXT;
125    }
126
127    /**
128     * Returns the maximal {@link MonetaryContext} supported.
129     *
130     * @return the maximal {@link MonetaryContext}, never {@code null}.
131     */
132    @Override
133    public MonetaryContext getMaximalMonetaryContext(){
134        return MAX_MONETARY_CONTEXT;
135    }
136
137    /**
138     * Converts (if necessary) the given {@link MonetaryAmount} to a new {@link MonetaryAmount}
139     * instance, hereby supporting the {@link MonetaryContext} given.
140     *
141     * @param amt the amount to be converted, if necessary.
142     * @return an according Money instance.
143     */
144    @Override
145    public MonetaryAmountFactory<T> setAmount(MonetaryAmount amt){
146        this.currency = amt.getCurrency();
147        this.number = amt.getNumber().numberValue(BigDecimal.class);
148        this.monetaryContext = new MonetaryContext.Builder(amt.getMonetaryContext())
149                .setAmountType(DEFAULT_MONETARY_CONTEXT.getAmountType()).create();
150        return this;
151    }
152
153    /**
154     * Creates a {@link BigDecimal} from the given {@link Number} doing the valid conversion
155     * depending the type given.
156     *
157     * @param num the number type
158     * @return the corresponding {@link BigDecimal}
159     */
160    protected static BigDecimal getBigDecimal(Number num){
161        // try fast equality check first (delegates to identity!)
162        if(BigDecimal.class.equals(num.getClass())){
163            return (BigDecimal) num;
164        }
165        if(Long.class.equals(num.getClass()) || Integer.class.equals(num.getClass()) ||
166                Short.class.equals(num.getClass()) || Byte.class.equals(num.getClass()) ||
167                AtomicLong.class.equals(num.getClass())){
168            return BigDecimal.valueOf(num.longValue());
169        }
170        if(Float.class.equals(num.getClass()) || Double.class.equals(num.getClass())){
171            return new BigDecimal(num.toString());
172        }
173        // try instance of (slower)
174        if(num instanceof BigDecimal){
175            return (BigDecimal) num;
176        }
177        if(num instanceof BigInteger){
178            return new BigDecimal((BigInteger) num);
179        }
180        try{
181            // Avoid imprecise conversion to double value if at all possible
182            return new BigDecimal(num.toString());
183        }
184        catch(NumberFormatException e){
185        }
186        return BigDecimal.valueOf(num.doubleValue());
187    }
188
189}