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;
017
018import java.io.Serializable;
019import java.util.Currency;
020import java.util.Map;
021import java.util.concurrent.ConcurrentHashMap;
022
023import javax.money.CurrencyUnit;
024
025/**
026 * Platform RI: Adapter that implements the new {@link CurrencyUnit} interface
027 * using the JDK's {@link Currency}.
028 * 
029 * @version 0.5.1
030 * @author Anatole Tresch
031 * @author Werner Keil
032 */
033public final class MoneyCurrency implements CurrencyUnit, Serializable,
034                Comparable<CurrencyUnit> {
035
036        /**
037         * serialVersionUID.
038         */
039        private static final long serialVersionUID = -2523936311372374236L;
040
041        /** currency code for this currency. */
042        private String currencyCode;
043        /** numeric code, or -1. */
044        private int numericCode;
045        /** fraction digits, or -1. */
046        private int defaultFractionDigits;
047        /** The cache rounding value, -1 if not defined. */
048        private int cacheRounding = -1;
049
050        private static final Map<String, MoneyCurrency> CACHED = new ConcurrentHashMap<String, MoneyCurrency>();
051
052        public static final String ISO_NAMESPACE = "IDO 4217";
053
054        /**
055         * Private constructor.
056         * 
057         * @param currency
058         */
059        private MoneyCurrency(String code,
060                        int numCode,
061                        int fractionDigits) {
062                this.currencyCode = code;
063                this.numericCode = numCode;
064                this.defaultFractionDigits = fractionDigits;
065        }
066
067        /**
068         * Private constructor.
069         * 
070         * @param currency
071         */
072        private MoneyCurrency(Currency currency) {
073                if (currency == null) {
074                        throw new IllegalArgumentException("Currency required.");
075                }
076                this.currencyCode = currency.getCurrencyCode();
077                this.numericCode = currency.getNumericCode();
078                this.defaultFractionDigits = currency.getDefaultFractionDigits();
079        }
080
081        /**
082         * Access a new instance based on {@link Currency}.
083         * 
084         * @param currency
085         *            the currency unit not null.
086         * @return the new instance, never null.
087         */
088        public static MoneyCurrency of(Currency currency) {
089                String key = currency.getCurrencyCode();
090                MoneyCurrency cachedItem = CACHED.get(key);
091                if (cachedItem == null) {
092                        cachedItem = new MoneyCurrency(currency);
093                        CACHED.put(key, cachedItem);
094                }
095                return cachedItem;
096        }
097
098        /**
099         * Access a new instance based on the ISO currency code. The code must
100         * return a {@link Currency} when passed to
101         * {@link Currency#getInstance(String)}.
102         * 
103         * @param namespace
104         *            the target namespace.
105         * @param currencyCode
106         *            the ISO currency code, not null.
107         * @return the corresponding {@link MonetaryCurrency} instance.
108         * @throws IllegalArgumentException
109         *             if no such currency exists.
110         */
111        public static MoneyCurrency of(String currencyCode) {
112                MoneyCurrency cu = CACHED.get(currencyCode);
113                if (cu == null) {
114                        if (MoneyCurrency.isJavaCurrency(currencyCode)) {
115                                Currency cur = Currency.getInstance(currencyCode);
116                                if (cur != null) {
117                                        return of(cur);
118                                }
119                        }
120                }
121                if (cu == null) {
122                        throw new IllegalArgumentException("No such currency: "
123                                        + currencyCode);
124                }
125                return cu;
126        }
127
128        /*
129         * (non-Javadoc)
130         * 
131         * @see javax.money.CurrencyUnit#getCurrencyCode()
132         */
133        public String getCurrencyCode() {
134                return currencyCode;
135        }
136
137        /**
138         * Gets a numeric currency code. within the ISO-4217 name space, this equals
139         * to the ISO numeric code. In other currency name spaces this number may be
140         * different, or even undefined (-1).
141         * <p>
142         * The numeric code is an optional alternative to the standard currency
143         * code. If defined, the numeric code is required to be unique within its
144         * namespace.
145         * <p>
146         * This method matches the API of <type>java.util.Currency</type>.
147         * 
148         * @see #getNamespace()
149         * @return the numeric currency code
150         */
151        public int getNumericCode() {
152                return numericCode;
153        }
154
155        /**
156         * Gets the number of fractional digits typically used by this currency.
157         * <p>
158         * Different currencies have different numbers of fractional digits by
159         * default. * For example, 'GBP' has 2 fractional digits, but 'JPY' has
160         * zero. * virtual currencies or those with no applicable fractional are
161         * indicated by -1. *
162         * <p>
163         * This method matches the API of <type>java.util.Currency</type>.
164         * 
165         * @return the fractional digits, from 0 to 9 (normally 0, 2 or 3), or -1
166         *         for pseudo-currencies.
167         * 
168         */
169        public int getDefaultFractionDigits() {
170                return defaultFractionDigits;
171        }
172
173        /**
174         * Get the rounding steps in minor units for when using a cash amount of
175         * this currency. E.g. Swiss Francs in cash are always rounded in 5 minor
176         * unit steps. This results in {@code 1.00, 1.05, 1.10} etc. The cash
177         * rounding consequently extends the default fraction units for certain
178         * currencies.
179         * 
180         * @return the cash rounding, or -1, if not defined.
181         */
182        public int getCashRounding() {
183                return cacheRounding;
184        }
185
186        /*
187         * (non-Javadoc)
188         * 
189         * @see java.lang.Comparable#compareTo(java.lang.Object)
190         */
191        public int compareTo(CurrencyUnit currency) {
192                return getCurrencyCode().compareTo(currency.getCurrencyCode());
193        }
194
195        /*
196         * (non-Javadoc)
197         * 
198         * @see java.lang.Object#hashCode()
199         */
200        @Override
201        public int hashCode() {
202                final int prime = 31;
203                int result = 1;
204                result = prime * result
205                                + ((currencyCode == null) ? 0 : currencyCode.hashCode());
206                return result;
207        }
208
209        /*
210         * (non-Javadoc)
211         * 
212         * @see java.lang.Object#equals(java.lang.Object)
213         */
214        @Override
215        public boolean equals(Object obj) {
216                if (this == obj)
217                        return true;
218                if (obj == null)
219                        return false;
220                if (getClass() != obj.getClass())
221                        return false;
222                MoneyCurrency other = (MoneyCurrency) obj;
223                if (currencyCode == null) {
224                        if (other.currencyCode != null)
225                                return false;
226                } else if (!currencyCode.equals(other.currencyCode))
227                        return false;
228                return true;
229        }
230
231        /**
232         * Returns {@link #getCurrencyCode()}
233         * 
234         * @see java.lang.Object#toString()
235         */
236        @Override
237        public String toString() {
238                return currencyCode;
239        }
240
241        /**
242         * Platform RI: Builder class that supports building complex instances of
243         * {@link MoneyCurrency}.
244         * 
245         * @author Anatole Tresch
246         */
247        public static final class Builder {
248                /** currency code for this currency. */
249                private String currencyCode;
250                /** numeric code, or -1. */
251                private int numericCode = -1;
252                /** fraction digits, or -1. */
253                private int defaultFractionDigits = -1;
254                /** Cache rounding. */
255                private int cacheRounding = -1;
256
257                /**
258                 * Creates a new {@link Builder}.
259                 */
260                public Builder() {
261                }
262
263                /**
264                 * Set the currency code.
265                 * 
266                 * @param namespace
267                 *            the currency code, not null
268                 * @return the builder, for chaining
269                 */
270                public Builder withCurrencyCode(String currencyCode) {
271                        if (currencyCode == null) {
272                                throw new IllegalArgumentException(
273                                                "currencyCode may not be null.");
274                        }
275                        this.currencyCode = currencyCode;
276                        return this;
277                }
278
279                /**
280                 * Set the default fraction digits.
281                 * 
282                 * @param defaultFractionDigits
283                 *            the default fraction digits
284                 * @return the builder, for chaining
285                 */
286                public Builder withDefaultFractionDigits(int defaultFractionDigits) {
287                        if (defaultFractionDigits < -1) {
288                                throw new IllegalArgumentException(
289                                                "Invalid value for defaultFractionDigits: "
290                                                                + defaultFractionDigits);
291                        }
292                        this.defaultFractionDigits = defaultFractionDigits;
293                        return this;
294                }
295
296                /**
297                 * Set the default fraction digits.
298                 * 
299                 * @param defaultFractionDigits
300                 *            the default fraction digits
301                 * @return the builder, for chaining
302                 */
303                public Builder withCashRounding(int cacheRounding) {
304                        if (cacheRounding < -1) {
305                                throw new IllegalArgumentException(
306                                                "Invalid value for cacheRounding: " + cacheRounding);
307                        }
308                        this.cacheRounding = cacheRounding;
309                        return this;
310                }
311
312                /**
313                 * Set the numeric currency code.
314                 * 
315                 * @param numericCode
316                 *            the numeric currency code
317                 * @return the builder, for chaining
318                 */
319                public Builder withNumericCode(int numericCode) {
320                        if (numericCode < -1) {
321                                throw new IllegalArgumentException(
322                                                "Invalid value for numericCode: " + numericCode);
323                        }
324                        this.numericCode = numericCode;
325                        return this;
326                }
327
328                /**
329                 * Builds a new currency instance, the instance build is not cached
330                 * internally.
331                 * 
332                 * @see #build(boolean)
333                 * @return a new instance of {@link MoneyCurrency}.
334                 */
335                public MoneyCurrency build() {
336                        return build(true);
337                }
338
339                /**
340                 * Builds a new currency instance, which ia additinoally stored to the
341                 * internal cache for reuse.
342                 * 
343                 * @param cache
344                 *            flag to optionally store the instance created into the
345                 *            locale cache.
346                 * @return a new instance of {@link MoneyCurrency}.
347                 */
348                public MoneyCurrency build(boolean cache) {
349                        if (cache) {
350                                MoneyCurrency current = CACHED.get(currencyCode);
351                                if (current == null) {
352                                        current = new MoneyCurrency(currencyCode,
353                                                        numericCode, defaultFractionDigits);
354                                        CACHED.put(currencyCode, current);
355                                }
356                                return current;
357                        }
358                        return new MoneyCurrency(currencyCode, numericCode,
359                                        defaultFractionDigits);
360                }
361        }
362
363        public static MoneyCurrency from(CurrencyUnit currency) {
364                if (MoneyCurrency.class == currency.getClass()) {
365                        return (MoneyCurrency) currency;
366                }
367                return MoneyCurrency.of(currency.getCurrencyCode());
368        }
369
370        public static boolean isJavaCurrency(String code) {
371                try {
372                        return Currency.getInstance(code) != null;
373                } catch (Exception e) {
374                        return false;
375                }
376        }
377
378        public static boolean isAvailable(String code) {
379                try {
380                        of(code);
381                        return true;
382                } catch (Exception e) {
383                        return false;
384                }
385        }
386
387}