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 }