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 }