View Javadoc

1   /*
2    *  Copyright 2009-2013 Stephen Colebourne
3    *
4    *  Licensed under the Apache License, Version 2.0 (the "License");
5    *  you may not use this file except in compliance with the License.
6    *  You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   *  Unless required by applicable law or agreed to in writing, software
11   *  distributed under the License is distributed on an "AS IS" BASIS,
12   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *  See the License for the specific language governing permissions and
14   *  limitations under the License.
15   */
16  package org.joda.money.format;
17  
18  import java.io.IOException;
19  import java.util.ArrayList;
20  import java.util.List;
21  import java.util.Locale;
22  
23  import org.joda.money.BigMoney;
24  import org.joda.money.CurrencyUnit;
25  import org.joda.money.IllegalCurrencyException;
26  
27  /**
28   * Provides the ability to build a formatter for monetary values.
29   * <p>
30   * This class is mutable and intended for use by a single thread.
31   * A new instance should be created for each use.
32   * The formatters produced by the builder are immutable and thread-safe.
33   */
34  public final class MoneyFormatterBuilder {
35  
36      /**
37       * The printers.
38       */
39      private final List<MoneyPrinter> printers = new ArrayList<MoneyPrinter>();
40      /**
41       * The parsers.
42       */
43      private final List<MoneyParser> parsers = new ArrayList<MoneyParser>();
44  
45      //-----------------------------------------------------------------------
46      /**
47       * Constructor, creating a new empty builder.
48       */
49      public MoneyFormatterBuilder() {
50      }
51  
52      //-----------------------------------------------------------------------
53      /**
54       * Appends the amount to the builder using a standard format.
55       * <p>
56       * The format used is {@link MoneyAmountStyle#ASCII_DECIMAL_POINT_GROUP3_COMMA}.
57       * The amount is the value itself, such as '12.34'.
58       * 
59       * @return this, for chaining, never null
60       */
61      public MoneyFormatterBuilder appendAmount() {
62          AmountPrinterParser pp = new AmountPrinterParser(MoneyAmountStyle.ASCII_DECIMAL_POINT_GROUP3_COMMA);
63          return appendInternal(pp, pp);
64      }
65  
66      /**
67       * Appends the amount to the builder using a grouped localized format.
68       * <p>
69       * The format used is {@link MoneyAmountStyle#LOCALIZED_GROUPING}.
70       * The amount is the value itself, such as '12.34'.
71       * 
72       * @return this, for chaining, never null
73       */
74      public MoneyFormatterBuilder appendAmountLocalized() {
75          AmountPrinterParser pp = new AmountPrinterParser(MoneyAmountStyle.LOCALIZED_GROUPING);
76          return appendInternal(pp, pp);
77      }
78  
79      /**
80       * Appends the amount to the builder using the specified amount style.
81       * <p>
82       * The amount is the value itself, such as '12.34'.
83       * <p>
84       * The amount style allows the formatting of the number to be controlled in detail.
85       * See {@link MoneyAmountStyle} for more details.
86       * 
87       * @param style  the style to use, not null
88       * @return this, for chaining, never null
89       */
90      public MoneyFormatterBuilder appendAmount(MoneyAmountStyle style) {
91          MoneyFormatter.checkNotNull(style, "MoneyAmountStyle must not be null");
92          AmountPrinterParser pp = new AmountPrinterParser(style);
93          return appendInternal(pp, pp);
94      }
95  
96      //-----------------------------------------------------------------------
97      /**
98       * Appends the currency code to the builder.
99       * <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 }