001    /*
002     *  Copyright 2009-2013 Stephen Colebourne
003     *
004     *  Licensed under the Apache License, Version 2.0 (the "License");
005     *  you may not use this file except in compliance with the License.
006     *  You may obtain a copy of 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,
012     *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     *  See the License for the specific language governing permissions and
014     *  limitations under the License.
015     */
016    package org.joda.money.format;
017    
018    import java.io.IOException;
019    import java.util.ArrayList;
020    import java.util.List;
021    import java.util.Locale;
022    
023    import org.joda.money.BigMoney;
024    import org.joda.money.CurrencyUnit;
025    import org.joda.money.IllegalCurrencyException;
026    
027    /**
028     * Provides the ability to build a formatter for monetary values.
029     * <p>
030     * This class is mutable and intended for use by a single thread.
031     * A new instance should be created for each use.
032     * The formatters produced by the builder are immutable and thread-safe.
033     */
034    public final class MoneyFormatterBuilder {
035    
036        /**
037         * The printers.
038         */
039        private final List<MoneyPrinter> printers = new ArrayList<MoneyPrinter>();
040        /**
041         * The parsers.
042         */
043        private final List<MoneyParser> parsers = new ArrayList<MoneyParser>();
044    
045        //-----------------------------------------------------------------------
046        /**
047         * Constructor, creating a new empty builder.
048         */
049        public MoneyFormatterBuilder() {
050        }
051    
052        //-----------------------------------------------------------------------
053        /**
054         * Appends the amount to the builder using a standard format.
055         * <p>
056         * The format used is {@link MoneyAmountStyle#ASCII_DECIMAL_POINT_GROUP3_COMMA}.
057         * The amount is the value itself, such as '12.34'.
058         * 
059         * @return this, for chaining, never null
060         */
061        public MoneyFormatterBuilder appendAmount() {
062            AmountPrinterParser pp = new AmountPrinterParser(MoneyAmountStyle.ASCII_DECIMAL_POINT_GROUP3_COMMA);
063            return appendInternal(pp, pp);
064        }
065    
066        /**
067         * Appends the amount to the builder using a grouped localized format.
068         * <p>
069         * The format used is {@link MoneyAmountStyle#LOCALIZED_GROUPING}.
070         * The amount is the value itself, such as '12.34'.
071         * 
072         * @return this, for chaining, never null
073         */
074        public MoneyFormatterBuilder appendAmountLocalized() {
075            AmountPrinterParser pp = new AmountPrinterParser(MoneyAmountStyle.LOCALIZED_GROUPING);
076            return appendInternal(pp, pp);
077        }
078    
079        /**
080         * Appends the amount to the builder using the specified amount style.
081         * <p>
082         * The amount is the value itself, such as '12.34'.
083         * <p>
084         * The amount style allows the formatting of the number to be controlled in detail.
085         * See {@link MoneyAmountStyle} for more details.
086         * 
087         * @param style  the style to use, not null
088         * @return this, for chaining, never null
089         */
090        public MoneyFormatterBuilder appendAmount(MoneyAmountStyle style) {
091            MoneyFormatter.checkNotNull(style, "MoneyAmountStyle must not be null");
092            AmountPrinterParser pp = new AmountPrinterParser(style);
093            return appendInternal(pp, pp);
094        }
095    
096        //-----------------------------------------------------------------------
097        /**
098         * Appends the currency code to the builder.
099         * <p>
100         * The currency code is the three letter ISO code, such as 'GBP'.
101         * 
102         * @return this, for chaining, never null
103         */
104        public MoneyFormatterBuilder appendCurrencyCode() {
105            return appendInternal(Singletons.CODE, Singletons.CODE);
106        }
107    
108        /**
109         * Appends the currency code to the builder.
110         * <p>
111         * The numeric code is the ISO numeric code, such as '826' and is
112         * zero padded to three digits.
113         * 
114         * @return this, for chaining, never null
115         */
116        public MoneyFormatterBuilder appendCurrencyNumeric3Code() {
117            return appendInternal(Singletons.NUMERIC_3_CODE, Singletons.NUMERIC_3_CODE);
118        }
119    
120        /**
121         * Appends the currency code to the builder.
122         * <p>
123         * The numeric code is the ISO numeric code, such as '826'.
124         * 
125         * @return this, for chaining, never null
126         */
127        public MoneyFormatterBuilder appendCurrencyNumericCode() {
128            return appendInternal(Singletons.NUMERIC_CODE, Singletons.NUMERIC_CODE);
129        }
130    
131        /**
132         * Appends the localized currency symbol to the builder.
133         * <p>
134         * The localized currency symbol is the symbol as chosen by the locale
135         * of the formatter.
136         * <p>
137         * Symbols cannot be parsed.
138         * 
139         * @return this, for chaining, never null
140         */
141        public MoneyFormatterBuilder appendCurrencySymbolLocalized() {
142            return appendInternal(SingletonPrinters.LOCALIZED_SYMBOL, null);
143        }
144    
145        /**
146         * Appends a literal to the builder.
147         * <p>
148         * The localized currency symbol is the symbol as chosen by the locale
149         * of the formatter.
150         * 
151         * @param literal  the literal to append, null or empty ignored
152         * @return this, for chaining, never null
153         */
154        public MoneyFormatterBuilder appendLiteral(CharSequence literal) {
155            if (literal == null || literal.length() == 0) {
156                return this;
157            }
158            LiteralPrinterParser pp = new LiteralPrinterParser(literal.toString());
159            return appendInternal(pp, pp);
160        }
161    
162        //-----------------------------------------------------------------------
163        /**
164         * Appends the printers and parsers from the specified formatter to this builder.
165         * <p>
166         * If the specified formatter cannot print, then the the output of this
167         * builder will be unable to print. If the specified formatter cannot parse,
168         * then the output of this builder will be unable to parse.
169         * 
170         * @param formatter  the formatter to append, not null
171         * @return this for chaining, never null
172         */
173        public MoneyFormatterBuilder append(MoneyFormatter formatter) {
174            MoneyFormatter.checkNotNull(formatter, "MoneyFormatter must not be null");
175            formatter.appendTo(this);
176            return this;
177        }
178    
179        /**
180         * Appends the specified printer and parser to this builder.
181         * <p>
182         * If null is specified then the formatter will be unable to print/parse.
183         * 
184         * @param printer  the printer to append, null makes the formatter unable to print
185         * @param parser  the parser to append, null makes the formatter unable to parse
186         * @return this for chaining, never null
187         */
188        public MoneyFormatterBuilder append(MoneyPrinter printer, MoneyParser parser) {
189            return appendInternal(printer, parser);
190        }
191    
192        //-----------------------------------------------------------------------
193        /**
194         * Appends the specified printer and parser to this builder.
195         * <p>
196         * Either the printer or parser must be non-null.
197         * 
198         * @param printer  the printer to append, null makes the formatter unable to print
199         * @param parser  the parser to append, null makes the formatter unable to parse
200         * @return this for chaining, never null
201         */
202        private MoneyFormatterBuilder appendInternal(MoneyPrinter printer, MoneyParser parser) {
203            printers.add(printer);
204            parsers.add(parser);
205            return this;
206        }
207    
208        //-----------------------------------------------------------------------
209        /**
210         * Builds the formatter from the builder using the default locale.
211         * <p>
212         * Once the builder is in the correct state it must be converted to a
213         * {@code MoneyFormatter} to be used. Calling this method does not
214         * change the state of this instance, so it can still be used.
215         * <p>
216         * This method uses the default locale within the returned formatter.
217         * It can be changed by calling {@link MoneyFormatter#withLocale(Locale)}.
218         * 
219         * @return the formatter built from this builder, never null
220         */
221        public MoneyFormatter toFormatter() {
222            return toFormatter(Locale.getDefault());
223        }
224    
225        /**
226         * Builds the formatter from the builder setting the locale.
227         * <p>
228         * Once the builder is in the correct state it must be converted to a
229         * {@code MoneyFormatter} to be used. Calling this method does not
230         * change the state of this instance, so it can still be used.
231         * <p>
232         * This method uses the specified locale within the returned formatter.
233         * It can be changed by calling {@link MoneyFormatter#withLocale(Locale)}.
234         * 
235         * @param locale  the initial locale for the formatter, not null
236         * @return the formatter built from this builder, never null
237         */
238        @SuppressWarnings("cast")
239        public MoneyFormatter toFormatter(Locale locale) {
240            MoneyFormatter.checkNotNull(locale, "Locale must not be null");
241            MoneyPrinter[] printersCopy = (MoneyPrinter[]) printers.toArray(new MoneyPrinter[printers.size()]);
242            MoneyParser[] parsersCopy = (MoneyParser[]) parsers.toArray(new MoneyParser[parsers.size()]);
243            return new MoneyFormatter(locale, printersCopy, parsersCopy);
244        }
245    
246    //    /**
247    //     * Returns a copy of this instance with the specified pattern.
248    //     * <p>
249    //     * The specified pattern is used for positive and zero amounts, while for
250    //     * negative amounts it is prefixed by the negative sign.
251    //     * <p>
252    //     * A pattern is a simple way to define the characters which surround the numeric value.
253    //     * For example, {@code ${amount} ${code}} will print the ISO code after the value,
254    //     * producing an output like {@code 12.34 GBP}.
255    //     * Similarly, {@code -${symbol}${amount}} will produce the output {@code -£12.34}.
256    //     * <p>
257    //     * The pattern contains the following elements:<br />
258    //     * <ul>
259    //     * <li>{@code ${amount}} : the monetary amount itself
260    //     * <li>{@code ${code}} : the letter-based currency code, such as 'USD'
261    //     * <li>{@code ${numericCode}} : the numeric currency code, such as '840'
262    //     * <li>{@code ${symbol}} : the currency symbol, such as '$'
263    //     * </ul>
264    //     * 
265    //     * @param pattern  the pattern
266    //     * @return the new instance, never null
267    //     */
268    //    public MoneyFormatterBuilder withPattern(String pattern) {
269    //        return withPattern(pattern, "-" + pattern, pattern);
270    //    }
271    //
272    //    /**
273    //     * Returns a copy of this instance with the specified positive and negative pattern.
274    //     * <p>
275    //     * The specified positive pattern is also used for zero amounts.
276    //     * <p>
277    //     * A pattern is a simple way to define the characters which surround the numeric value.
278    //     * For example, {@code ${amount} ${code}} will print the ISO code after the value,
279    //     * producing an output like {@code 12.34 GBP}.
280    //     * Similarly, {@code -${symbol}${amount}} will produce the output {@code -£12.34}.
281    //     * <p>
282    //     * The pattern contains the following elements:<br />
283    //     * <ul>
284    //     * <li>{@code ${amount}} : the monetary amount itself
285    //     * <li>{@code ${code}} : the letter-based currency code, such as 'USD'
286    //     * <li>{@code ${numericCode}} : the numeric currency code, such as '840'
287    //     * <li>{@code ${symbol}} : the currency symbol, such as '$'
288    //     * </ul>
289    //     * 
290    //     * @param positivePattern  the positive pattern
291    //     * @param negativePattern  the negative pattern
292    //     * @return the new instance, never null
293    //     */
294    //    public MoneyFormatterBuilder withPattern(String positivePattern, String negativePattern) {
295    //        return withPattern(positivePattern, negativePattern, positivePattern);
296    //    }
297    //
298    //    /**
299    //     * Returns a copy of this instance with the specified positive, negative and zero pattern.
300    //     * <p>
301    //     * The positive pattern is used for positive amounts, the negative pattern for negative amounts
302    //     * and the zero pattern for zero amounts.
303    //     * <p>
304    //     * A pattern is a simple way to define the characters which surround the numeric value.
305    //     * For example, {@code ${amount} ${code}} will print the ISO code after the value,
306    //     * producing an output like {@code 12.34 GBP}.
307    //     * Similarly, {@code -${symbol}${amount}} will produce the output {@code -£12.34}.
308    //     * <p>
309    //     * The pattern contains the following elements:<br />
310    //     * <ul>
311    //     * <li>{@code ${amount}} : the monetary amount itself
312    //     * <li>{@code ${code}} : the letter-based currency code, such as 'USD'
313    //     * <li>{@code ${numericCode}} : the numeric currency code, such as '840'
314    //     * <li>{@code ${symbol}} : the currency symbol, such as '$'
315    //     * </ul>
316    //     * 
317    //     * @param positivePattern  the positive pattern
318    //     * @param negativePattern  the negative pattern
319    //     * @param zeroPattern  the zero pattern
320    //     * @return the new instance, never null
321    //     */
322    //    public MoneyFormatterBuilder withPattern(String positivePattern, String negativePattern, String zeroPattern) {
323    //        MoneyUtils.checkNotNull(positivePattern, "Positive pattern must not be null");
324    //        MoneyUtils.checkNotNull(negativePattern, "Negative pattern must not be null");
325    //        MoneyUtils.checkNotNull(zeroPattern, "Zero pattern must not be null");
326    //        return new MoneyFormatterBuilder(iLocale, iZeroCharacter, iDecimalPointCharacter, iGroupingCharacter,
327    //                iGrouping, iGroupingSize, iForceDecimalPoint, positivePattern, negativePattern, zeroPattern);
328    //    }
329    
330        //-----------------------------------------------------------------------
331        /**
332         * Handles the singleton outputs.
333         */
334        private static enum Singletons implements MoneyPrinter, MoneyParser {
335            CODE("${code}") {
336                public void print(MoneyPrintContext context, Appendable appendable, BigMoney money) throws IOException {
337                    appendable.append(money.getCurrencyUnit().getCode());
338                }
339                public void parse(MoneyParseContext context) {
340                    int endPos = context.getIndex() + 3;
341                    if (endPos > context.getTextLength()) {
342                        context.setError();
343                    } else {
344                        String code = context.getTextSubstring(context.getIndex(), endPos);
345                        try {
346                            context.setCurrency(CurrencyUnit.of(code));
347                            context.setIndex(endPos);
348                        } catch (IllegalCurrencyException ex) {
349                            context.setError();
350                        }
351                    }
352                }
353            },
354            NUMERIC_3_CODE("${numeric3Code}") {
355                public void print(MoneyPrintContext context, Appendable appendable, BigMoney money) throws IOException {
356                    appendable.append(money.getCurrencyUnit().getNumeric3Code());
357                }
358                public void parse(MoneyParseContext context) {
359                    int endPos = context.getIndex() + 3;
360                    if (endPos > context.getTextLength()) {
361                        context.setError();
362                    } else {
363                        String code = context.getTextSubstring(context.getIndex(), endPos);
364                        try {
365                            context.setCurrency(CurrencyUnit.ofNumericCode(code));
366                            context.setIndex(endPos);
367                        } catch (IllegalCurrencyException ex) {
368                            context.setError();
369                        }
370                    }
371                }
372            },
373            NUMERIC_CODE("${numericCode}") {
374                public void print(MoneyPrintContext context, Appendable appendable, BigMoney money) throws IOException {
375                    appendable.append(Integer.toString(money.getCurrencyUnit().getNumericCode()));
376                }
377                public void parse(MoneyParseContext context) {
378                    int count = 0;
379                    for ( ; count < 3 && context.getIndex() + count < context.getTextLength(); count++) {
380                        char ch = context.getText().charAt(context.getIndex() + count);
381                        if (ch < '0' || ch > '9') {
382                            break;
383                        }
384                    }
385                    int endPos = context.getIndex() + count;
386                    String code = context.getTextSubstring(context.getIndex(), endPos);
387                    try {
388                        context.setCurrency(CurrencyUnit.ofNumericCode(code));
389                        context.setIndex(endPos);
390                    } catch (IllegalCurrencyException ex) {
391                        context.setError();
392                    }
393                }
394            };
395            private final String toString;
396            private Singletons(String toString) {
397                this.toString = toString;
398            }
399            @Override
400            public String toString() {
401                return toString;
402            }
403        }
404    
405        //-----------------------------------------------------------------------
406        /**
407         * Handles the singleton outputs.
408         */
409        private static enum SingletonPrinters implements MoneyPrinter {
410            LOCALIZED_SYMBOL;
411            public void print(MoneyPrintContext context, Appendable appendable, BigMoney money) throws IOException {
412                appendable.append(money.getCurrencyUnit().getSymbol(context.getLocale()));
413            }
414            @Override
415            public String toString() {
416                return "${symbolLocalized}";
417            }
418        }
419    
420    }