Coverage Report - org.joda.money.format.MoneyFormatter
 
Classes in this File Line Coverage Branch Coverage Complexity
MoneyFormatter
100%
76/76
88%
60/68
3.571
 
 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.io.Serializable;
 20  
 import java.util.Arrays;
 21  
 import java.util.Locale;
 22  
 
 23  
 import org.joda.money.BigMoney;
 24  
 import org.joda.money.BigMoneyProvider;
 25  
 import org.joda.money.Money;
 26  
 
 27  
 /**
 28  
  * Formats instances of money to and from a String.
 29  
  * <p>
 30  
  * Instances of {@code MoneyFormatter} can be created by
 31  
  * {@code MoneyFormatterBuilder}.
 32  
  * <p>
 33  
  * This class is immutable and thread-safe.
 34  
  */
 35  1
 public final class MoneyFormatter implements Serializable {
 36  
 
 37  
     /**
 38  
      * Serialization version.
 39  
      */
 40  
     private static final long serialVersionUID = 2385346258L;
 41  
 
 42  
     /**
 43  
      * The locale to use.
 44  
      */
 45  
     private final Locale locale;
 46  
     /**
 47  
      * The printers.
 48  
      */
 49  
     private final MoneyPrinter[] printers;
 50  
     /**
 51  
      * The parsers.
 52  
      */
 53  
     private final MoneyParser[] parsers;
 54  
 
 55  
     //-----------------------------------------------------------------------
 56  
     /**
 57  
      * Validates that the object specified is not null
 58  
      *
 59  
      * @param object  the object to check, null throws exception
 60  
      * @param message  the message to use in the exception, not null
 61  
      * @throws NullPointerException if the input value is null
 62  
      */
 63  
     static void checkNotNull(Object object, String message) {
 64  584
         if (object == null) {
 65  8
             throw new NullPointerException(message);
 66  
         }
 67  576
     }
 68  
 
 69  
     //-----------------------------------------------------------------------
 70  
     /**
 71  
      * Constructor, creating a new formatter.
 72  
      * 
 73  
      * @param locale  the locale to use, not null
 74  
      * @param printers  the printers, not null
 75  
      * @param parsers  the parsers, not null
 76  
      */
 77  
     MoneyFormatter(
 78  
             Locale locale,
 79  
             MoneyPrinter[] printers,
 80  331
             MoneyParser[] parsers) {
 81  331
         assert locale != null;
 82  331
         assert printers != null;
 83  331
         assert parsers != null;
 84  331
         assert printers.length == parsers.length;
 85  331
         this.locale = locale;
 86  331
         this.printers = printers;
 87  331
         this.parsers = parsers;
 88  331
     }
 89  
 
 90  
     //-----------------------------------------------------------------------
 91  
     /**
 92  
      * Appends the printers and parsers from this formatter to the builder.
 93  
      * 
 94  
      * @param builder  the builder to append to not null
 95  
      */
 96  
     void appendTo(MoneyFormatterBuilder builder) {
 97  2
         for (int i = 0; i < printers.length; i++) {
 98  1
             builder.append(printers[i], parsers[i]);
 99  
         }
 100  1
     }
 101  
 
 102  
     //-----------------------------------------------------------------------
 103  
     /**
 104  
      * Gets the locale to use.
 105  
      * 
 106  
      * @return the locale, never null
 107  
      */
 108  
     public Locale getLocale() {
 109  5
         return locale;
 110  
     }
 111  
 
 112  
     /**
 113  
      * Returns a copy of this instance with the specified locale.
 114  
      * <p>
 115  
      * Changing the locale may change the style of output depending on how the
 116  
      * formatter has been configured.
 117  
      * 
 118  
      * @param locale  the locale, not null
 119  
      * @return the new instance, never null
 120  
      */
 121  
     public MoneyFormatter withLocale(Locale locale) {
 122  2
         checkNotNull(locale, "Locale must not be null");
 123  1
         return new MoneyFormatter(locale, printers, parsers);
 124  
     }
 125  
 
 126  
     //-----------------------------------------------------------------------
 127  
     /**
 128  
      * Checks whether this formatter can print.
 129  
      * <p>
 130  
      * If the formatter cannot print, an UnsupportedOperationException will
 131  
      * be thrown from the print methods.
 132  
      * 
 133  
      * @return true if the formatter can print
 134  
      */
 135  
     public boolean isPrinter() {
 136  175
         return Arrays.asList(printers).contains(null) == false;
 137  
     }
 138  
 
 139  
     /**
 140  
      * Checks whether this formatter can parse.
 141  
      * <p>
 142  
      * If the formatter cannot parse, an UnsupportedOperationException will
 143  
      * be thrown from the print methods.
 144  
      * 
 145  
      * @return true if the formatter can parse
 146  
      */
 147  
     public boolean isParser() {
 148  175
         return Arrays.asList(parsers).contains(null) == false;
 149  
     }
 150  
 
 151  
     //-----------------------------------------------------------------------
 152  
     /**
 153  
      * Prints a monetary value to a {@code String}.
 154  
      * 
 155  
      * @param moneyProvider  the money to print, not null
 156  
      * @return the string printed using the settings of this formatter
 157  
      * @throws UnsupportedOperationException if the formatter is unable to print
 158  
      * @throws MoneyFormatException if there is a problem while printing
 159  
      */
 160  
     public String print(BigMoneyProvider moneyProvider) {
 161  51
         StringBuilder buf = new StringBuilder();
 162  51
         print(buf, moneyProvider);
 163  49
         return buf.toString();
 164  
     }
 165  
 
 166  
     /**
 167  
      * Prints a monetary value to an {@code Appendable} converting
 168  
      * any {@code IOException} to a {@code MoneyFormatException}.
 169  
      * <p>
 170  
      * Example implementations of {@code Appendable} are {@code StringBuilder},
 171  
      * {@code StringBuffer} or {@code Writer}. Note that {@code StringBuilder}
 172  
      * and {@code StringBuffer} never throw an {@code IOException}.
 173  
      * 
 174  
      * @param appendable  the appendable to add to, not null
 175  
      * @param moneyProvider  the money to print, not null
 176  
      * @throws UnsupportedOperationException if the formatter is unable to print
 177  
      * @throws MoneyFormatException if there is a problem while printing
 178  
      */
 179  
     public void print(Appendable appendable, BigMoneyProvider moneyProvider) {
 180  
         try {
 181  56
             printIO(appendable, moneyProvider);
 182  1
         } catch (IOException ex) {
 183  1
             throw new MoneyFormatException(ex.getMessage(), ex);
 184  50
         }
 185  50
     }
 186  
 
 187  
     /**
 188  
      * Prints a monetary value to an {@code Appendable} potentially
 189  
      * throwing an {@code IOException}.
 190  
      * <p>
 191  
      * Example implementations of {@code Appendable} are {@code StringBuilder},
 192  
      * {@code StringBuffer} or {@code Writer}. Note that {@code StringBuilder}
 193  
      * and {@code StringBuffer} never throw an {@code IOException}.
 194  
      * 
 195  
      * @param appendable  the appendable to add to, not null
 196  
      * @param moneyProvider  the money to print, not null
 197  
      * @throws UnsupportedOperationException if the formatter is unable to print
 198  
      * @throws MoneyFormatException if there is a problem while printing
 199  
      * @throws IOException if an IO error occurs
 200  
      */
 201  
     public void printIO(Appendable appendable, BigMoneyProvider moneyProvider) throws IOException {
 202  61
         checkNotNull(moneyProvider, "BigMoneyProvider must not be null");
 203  58
         if (isPrinter() == false) {
 204  3
             throw new UnsupportedOperationException("MoneyFomatter has not been configured to be able to print");
 205  
         }
 206  
         
 207  55
         BigMoney money = BigMoney.of(moneyProvider);
 208  55
         MoneyPrintContext context = new MoneyPrintContext(locale);
 209  112
         for (MoneyPrinter printer : printers) {
 210  61
             printer.print(context, appendable, money);
 211  
         }
 212  51
     }
 213  
 
 214  
     //-----------------------------------------------------------------------
 215  
     /**
 216  
      * Fully parses the text into a {@code BigMoney}.
 217  
      * <p>
 218  
      * The parse must complete normally and parse the entire text (currency and amount).
 219  
      * If the parse completes without reading the entire length of the text, an exception is thrown.
 220  
      * If any other problem occurs during parsing, an exception is thrown.
 221  
      * 
 222  
      * @param text  the text to parse, not null
 223  
      * @return the parsed monetary value, never null
 224  
      * @throws UnsupportedOperationException if the formatter is unable to parse
 225  
      * @throws MoneyFormatException if there is a problem while parsing
 226  
      */
 227  
     public BigMoney parseBigMoney(CharSequence text) {
 228  21
         checkNotNull(text, "Text must not be null");
 229  19
         MoneyParseContext result = parse(text, 0);
 230  17
         if (result.isError() || result.isFullyParsed() == false || result.isComplete() == false) {
 231  13
             String str = (text.length() > 64 ? text.subSequence(0, 64).toString() + "..." : text.toString());
 232  13
             if (result.isError()) {
 233  6
                 throw new MoneyFormatException("Text could not be parsed at index " + result.getErrorIndex() + ": " + str);
 234  7
             } else if (result.isFullyParsed() == false) {
 235  6
                 throw new MoneyFormatException("Unparsed text found at index " + result.getIndex() + ": " + str);
 236  
             } else {
 237  1
                 throw new MoneyFormatException("Parsing did not find both currency and amount: " + str);
 238  
             }
 239  
         }
 240  4
         return result.toBigMoney();
 241  
     }
 242  
 
 243  
     /**
 244  
      * Fully parses the text into a {@code Money} requiring that the parsed
 245  
      * amount has the correct number of decimal places.
 246  
      * <p>
 247  
      * The parse must complete normally and parse the entire text (currency and amount).
 248  
      * If the parse completes without reading the entire length of the text, an exception is thrown.
 249  
      * If any other problem occurs during parsing, an exception is thrown.
 250  
      * 
 251  
      * @param text  the text to parse, not null
 252  
      * @return the parsed monetary value, never null
 253  
      * @throws UnsupportedOperationException if the formatter is unable to parse
 254  
      * @throws MoneyFormatException if there is a problem while parsing
 255  
      * @throws ArithmeticException if the scale of the parsed money exceeds the scale of the currency
 256  
      */
 257  
     public Money parseMoney(CharSequence text) {
 258  10
         return parseBigMoney(text).toMoney();
 259  
     }
 260  
 
 261  
     /**
 262  
      * Parses the text extracting monetary information.
 263  
      * <p>
 264  
      * This method parses the input providing low-level access to the parsing state.
 265  
      * The resulting context contains the parsed text, indicator of error, position
 266  
      * following the parse and the parsed currency and amount.
 267  
      * Together, these provide enough information for higher level APIs to use.
 268  
      *
 269  
      * @param text  the text to parse, not null
 270  
      * @param startIndex  the start index to parse from
 271  
      * @return the parsed monetary value, null only if the parse results in an error
 272  
      * @throws IndexOutOfBoundsException if the start index is invalid
 273  
      * @throws UnsupportedOperationException if this formatter cannot parse
 274  
      */
 275  
     public MoneyParseContext parse(CharSequence text, int startIndex) {
 276  61
         checkNotNull(text, "Text must not be null");
 277  60
         if (startIndex < 0 || startIndex > text.length()) {
 278  2
             throw new StringIndexOutOfBoundsException("Invalid start index: " + startIndex);
 279  
         }
 280  58
         if (isParser() == false) {
 281  3
             throw new UnsupportedOperationException("MoneyFomatter has not been configured to be able to parse");
 282  
         }
 283  55
         MoneyParseContext context = new MoneyParseContext(locale, text, startIndex);
 284  140
         for (MoneyParser parser : parsers) {
 285  107
             parser.parse(context);
 286  107
             if (context.isError()) {
 287  22
                 break;
 288  
             }
 289  
         }
 290  55
         return context;
 291  
     }
 292  
 
 293  
     //-----------------------------------------------------------------------
 294  
     /**
 295  
      * Gets a string summary of the formatter.
 296  
      * 
 297  
      * @return a string summarising the formatter, never null
 298  
      */
 299  
     @Override
 300  
     public String toString() {
 301  39
         StringBuilder buf1 = new StringBuilder();
 302  39
         if (isPrinter()) {
 303  72
             for (MoneyPrinter printer : printers) {
 304  35
                 buf1.append(printer.toString());
 305  
             }
 306  
         }
 307  39
         StringBuilder buf2 = new StringBuilder();
 308  39
         if (isParser()) {
 309  70
             for (MoneyParser parser : parsers) {
 310  34
                 buf2.append(parser.toString());
 311  
             }
 312  
         }
 313  39
         String str1 = buf1.toString();
 314  39
         String str2 = buf2.toString();
 315  39
         if (isPrinter() && isParser() == false) {
 316  2
             return str1;
 317  37
         } else if (isParser() && isPrinter() == false) {
 318  1
             return str2;
 319  36
         } else if (str1.equals(str2)) {
 320  35
             return str1;
 321  
         } else {
 322  1
             return str1 + ":" + str2;
 323  
         }
 324  
     }
 325  
 
 326  
 }