001/* 002 * Copyright (c) 2012, 2013, Credit Suisse (Anatole Tresch), Werner Keil, 003 * and individual contributors by the @author tags. 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 006 * use this file except in compliance with the License. You may obtain a copy of 007 * the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 013 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 014 * License for the specific language governing permissions and limitations under 015 * the License. 016 */ 017package org.javamoney.moneta.format; 018 019import java.io.IOException; 020import java.text.ParseException; 021import java.util.ArrayList; 022import java.util.List; 023import java.util.Locale; 024 025import javax.money.CurrencyUnit; 026import javax.money.MonetaryAmount; 027 028import org.javamoney.moneta.Money; 029import org.javamoney.moneta.function.MonetaryRoundings; 030 031/** 032 * Formats instances of {@code MonetaryAmount} to a {@link String} or an 033 * {@link Appendable}. 034 * <p> 035 * Instances of this class are not thread-safe. Basically when using 036 * {@link MonetaryAmountFormat} instances a new instance should be created on 037 * each access. 038 */ 039public final class MonetaryAmountFormat { 040 041 public static enum CurrencyStyle { 042 CODE, NAME, NUMERIC_CODE, SYMBOL 043 } 044 045 /** The tokens to be used for formatting/parsing. */ 046 private List<FormatToken> tokens = new ArrayList<FormatToken>(); 047 048 private CurrencyUnit defaultCurrency; 049 050 public AmountStyle getAmountStyle() { 051 AmountNumberToken numberToken = getNumberToken(); 052 if (numberToken == null) { 053 throw new IllegalStateException( 054 "This format has no numer value attached."); 055 } 056 return numberToken.getAmountStyle(); 057 } 058 059 private CurrencyToken getCurrencyToken() { 060 for (FormatToken t : this.tokens) { 061 if (t instanceof CurrencyToken) { 062 return (CurrencyToken) t; 063 } 064 } 065 return null; 066 } 067 068 private AmountNumberToken getNumberToken() { 069 for (FormatToken t : this.tokens) { 070 if (t instanceof CurrencyToken) { 071 return (AmountNumberToken) t; 072 } 073 } 074 return null; 075 } 076 077 /** 078 * Creates a new instance. 079 * 080 * @param buildItemFormat 081 * the base buildItemFormat, not null. 082 * @param itemFactory 083 * the itemFactory to be used, not null. 084 */ 085 private MonetaryAmountFormat( 086 List<FormatToken> tokens, CurrencyUnit defaultCurrency) { 087 if (tokens == null || tokens.isEmpty()) { 088 throw new IllegalArgumentException( 089 "tokens must not be null or empty."); 090 } 091 this.tokens.addAll(tokens); 092 this.defaultCurrency = defaultCurrency; 093 } 094 095 public CurrencyUnit getDefaultCurrency() { 096 return defaultCurrency; 097 } 098 099 /** 100 * Formats a value of {@code T} to a {@code String}. The {@link Locale} 101 * passed defines the overall target {@link Locale}, whereas the 102 * {@link LocalizationStyle} attached with the instances configures, how the 103 * {@link MonetaryFormat} should generally behave. The 104 * {@link LocalizationStyle} allows to configure the formatting and parsing 105 * in arbitrary details. The attributes that are supported are determined by 106 * the according {@link MonetaryFormat} implementation: 107 * <ul> 108 * <li>When the {@link MonetaryFormat} was created using the {@link Builder} 109 * , all the {@link FormatToken}, that model the overall format, and the 110 * {@link ItemFactory}, that is responsible for extracting the final parsing 111 * result, returned from a parsing call, are all possible recipients for 112 * attributes of the configuring {@link LocalizationStyle}. 113 * <li>When the {@link MonetaryFormat} was provided by an instance of 114 * {@link ItemFormatFactorySpi} the {@link MonetaryFormat} returned 115 * determines the capabilities that can be configured. 116 * </ul> 117 * 118 * So, regardless if an {@link MonetaryFormat} is created using the fluent 119 * style {@link Builder} pattern, or provided as preconfigured 120 * implementation, {@link LocalizationStyle}s allow to configure them both 121 * effectively. 122 * 123 * @param amount 124 * the amount to print, not {@code null} 125 * @return the string printed using the settings of this formatter 126 * @throws UnsupportedOperationException 127 * if the formatter is unable to print 128 */ 129 public String format(MonetaryAmount amount) { 130 StringBuilder builder = new StringBuilder(); 131 try { 132 print(builder, amount); 133 } catch (IOException e) { 134 throw new IllegalStateException("Error foratting of " + amount, e); 135 } 136 return builder.toString(); 137 } 138 139 /** 140 * Prints a item value to an {@code Appendable}. 141 * <p> 142 * Example implementations of {@code Appendable} are {@code StringBuilder}, 143 * {@code StringBuffer} or {@code Writer}. Note that {@code StringBuilder} 144 * and {@code StringBuffer} never throw an {@code IOException}. 145 * 146 * @param appendable 147 * the appendable to add to, not null 148 * @param item 149 * the item to print, not null 150 * @param locale 151 * the main target {@link Locale} to be used, not {@code null} 152 * @throws UnsupportedOperationException 153 * if the formatter is unable to print 154 * @throws ItemFormatException 155 * if there is a problem while printing 156 * @throws IOException 157 * if an IO error occurs 158 */ 159 public void print(Appendable appendable, MonetaryAmount amount) 160 throws IOException { 161 for (FormatToken token : tokens) { 162 token.print(appendable, amount); 163 } 164 } 165 166 /** 167 * Fully parses the text into an instance of {@code T}. 168 * <p> 169 * The parse must complete normally and parse the entire text. If the parse 170 * completes without reading the entire length of the text, an exception is 171 * thrown. If any other problem occurs during parsing, an exception is 172 * thrown. 173 * <p> 174 * This method uses a {@link Locale} as an input parameter. Additionally the 175 * {@link ItemFormatException} instance is configured by a 176 * {@link LocalizationStyle}. {@link LocalizationStyle}s allows to configure 177 * formatting input in detail. This allows to implement complex formatting 178 * requirements using this interface. 179 * 180 * @param text 181 * the text to parse, not null 182 * @param locale 183 * the main target {@link Locale} to be used, not {@code null} 184 * @return the parsed value, never {@code null} 185 * @throws UnsupportedOperationException 186 * if the formatter is unable to parse 187 * @throws ItemParseException 188 * if there is a problem while parsing 189 */ 190 public MonetaryAmount parse(CharSequence text) 191 throws ParseException { 192 ParseContext ctx = new ParseContext(text); 193 for (FormatToken token : tokens) { 194 token.parse(ctx); 195 } 196 CurrencyUnit unit = ctx.getParsedCurrency(); 197 Number num = ctx.getParsedNumber(); 198 if (unit == null) { 199 unit = defaultCurrency; 200 } 201 if (num == null) { 202 throw new ParseException(text.toString(), -1); 203 } 204 return Money.of(unit, num); 205 } 206 207 /** 208 * This class implements a builder that allows creating of 209 * {@link MonetaryFormat} instances programmatically using a fluent API. The 210 * formatting hereby is modeled by a concatenation of {@link FormatToken} 211 * instances. The same {@link FormatToken} instances also are responsible 212 * for implementing the opposite, parsing, of an item from an input 213 * character sequence. Each {@link FormatToken} gets access to the current 214 * parsing location, and the original and current character input sequence, 215 * modeled by the {@link ParseContext}. Finall if parsing of a part failed, 216 * a {@link FormatToken} throws an {@link ItemParseException} describing the 217 * problem. 218 * <p> 219 * This class is not thread-safe and therefore should not be shared among 220 * different threads. 221 * 222 * @author Anatole Tresch 223 * 224 * @param <T> 225 * the target type. 226 */ 227 public static final class Builder { 228 /** The tokens to be used for formatting/parsing. */ 229 private List<FormatToken> tokens = new ArrayList<FormatToken>(); 230 231 private Locale locale; 232 233 /** 234 * The default currency, used, when parsing amounts, where no currency 235 * is available. 236 */ 237 private CurrencyUnit defaultCurrency; 238 239 private char[] groupChars; 240 241 private int[] groupSizes; 242 243 /** 244 * Creates a new Builder. 245 * 246 * @param targetType 247 * the target class. 248 */ 249 public Builder(Locale locale) { 250 if (locale == null) { 251 throw new IllegalArgumentException("Locale required."); 252 } 253 this.locale = locale; 254 } 255 256 public Builder withDefaultCurrency(CurrencyUnit currency) { 257 this.defaultCurrency = currency; 258 return this; 259 } 260 261 /** 262 * Add a {@link FormatToken} to the token list. 263 * 264 * @param token 265 * the token to add. 266 * @return the builder, for chaining. 267 */ 268 public Builder appendAmount(AmountStyle style) { 269 this.tokens.add(new AmountNumberToken(style)); 270 return this; 271 } 272 273 /** 274 * Add the amount to the given format. Hereby the number default style 275 * for the {@link #locale} is used, and the number is rounded with the 276 * currencies, default rounding as returned by 277 * {@link MonetaryRoundings#getRounding()}. 278 * 279 * @param token 280 * the token to add. 281 * @return the builder, for chaining. 282 */ 283 public Builder appendAmount() { 284 AmountStyle style = new AmountStyle.Builder(locale).withRounding( 285 MonetaryRoundings.getRounding()).build(); 286 this.tokens.add(new AmountNumberToken(style)); 287 return this; 288 } 289 290 /** 291 * Adds a currency unit to the format using the given 292 * {@link CurrencyStyle}. 293 * 294 * @param style 295 * the style to be used, not {@code null}. 296 * @return the builder, for chaining. 297 */ 298 public Builder appendCurrency(CurrencyStyle style) { 299 this.tokens.add(new CurrencyToken(style, locale)); 300 return this; 301 } 302 303 /** 304 * Adds a currency to the format printing using the currency code. 305 * 306 * @return the builder, for chaining. 307 */ 308 public Builder appendCurrency() { 309 return appendCurrency(CurrencyStyle.CODE); 310 } 311 312 /** 313 * Add a {@link FormatToken} to the token list. 314 * 315 * @param literal 316 * the literal to add, not {@code null}. 317 * @return the builder, for chaining. 318 */ 319 public Builder appendLiteral(String literal) { 320 this.tokens.add(new LiteralToken(literal)); 321 return this; 322 } 323 324 /** 325 * This method creates an {@link MonetaryFormat} based on this instance, 326 * hereby using the given a {@link ItemFactory} to extract the item to 327 * be returned from the {@link ParseContext}'s results. 328 * 329 * @return the {@link MonetaryFormat} instance, never null. 330 */ 331 public MonetaryAmountFormat build() { 332 if (tokens.isEmpty()) { 333 // create default JDK currency format 334 this.tokens.add(new AmountNumberToken(new AmountStyle.Builder( 335 locale).withCurrencyFormat(locale) 336 .withNumberGroupChars(groupChars) 337 .withNumberGroupSizes(groupSizes).build())); 338 } 339 return new MonetaryAmountFormat(tokens, defaultCurrency); 340 } 341 342 /* 343 * (non-Javadoc) 344 * 345 * @see java.lang.Object#toString() 346 */ 347 @Override 348 public String toString() { 349 return "MonetaryAmountFormat.Builder [tokens=" + tokens + "]"; 350 } 351 352 public Builder withNumberGroupSizes(int... groupSizes) { 353 this.groupSizes = groupSizes.clone(); 354 return this; 355 } 356 357 public Builder withNumberGroupChars(char... groupChars) { 358 this.groupChars = groupChars.clone(); 359 return this; 360 } 361 362 } 363}