/* * Copyright 2014 Adam Mackler * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.bitcoinj.utils; import org.bitcoinj.utils.BtcAutoFormat.Style; import static org.bitcoinj.utils.BtcAutoFormat.Style.*; import org.bitcoinj.core.Coin; import com.google.common.base.Objects; import com.google.common.collect.ImmutableList; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkState; import com.google.common.base.Strings; import java.math.BigDecimal; import java.math.BigInteger; import static java.math.RoundingMode.HALF_UP; import java.text.AttributedCharacterIterator; import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; import java.text.FieldPosition; import java.text.Format; import java.text.NumberFormat; import java.text.ParseException; import java.text.ParsePosition; import java.util.Locale; import java.util.List; import java.util.ArrayList; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * <p>Instances of this class format and parse locale-specific numerical * representations of Bitcoin monetary values. * * <p>A primary goal of this class is to minimize the danger of * human-misreading of monetary values due to mis-counting the number * of zeros (or, more generally, of decimal places) in the number that * represents a Bitcoin monetary value. Some of the features offered for doing this * are: <ol> * <li>automatic adjustment of denominational units in which a * value is represented so as to lessen the number of adjacent zeros, * <li>use of locale-specific decimal-separators to group digits in * the integer portion of formatted numbers, * <li>fine control over the number and grouping of fractional decimal places, and * <li>access to character information that allows for vertical * alignment of tabular columns of formatted values.</ol> * * <h3>Basic Usage</h3> * * Basic usage is very simple: <ol> * <li>Construct a new formatter object using one of the factory methods. * <li>Format a value by passing it as an argument to the * {@link BtcFormat#format(Object)} method. * <li>Parse a value by passing a <code>String</code>-type * representation of it to the {@link BtcFormat#parse(String)} method.</ol> * * <p>For example, depending on your locale, values might be formatted * and parsed as follows: * * <blockquote><pre> * BtcFormat f = BtcFormat.getInstance(); * String c = f.format(Coin.COIN); <strong>// "BTC 1.00"</strong> * String k = f.format(Coin.COIN.multiply(1000)); <strong>// "BTC 1,000.00"</strong> * String m = f.format(Coin.COIN.divide(1000)); <strong>// "mBTC 1.00"</strong> * Coin all = f.parseObject("M฿ 21"); <strong>// All the money in the world</strong> * </pre></blockquote> * * <h3>Auto-Denomination versus Fixed-Denomination</h3> * * There are two provided concrete classes, one that automatically denominates values to * be formatted, {@link BtcAutoFormat}, and another that formats any value in units of a * fixed, specified denomination, {@link BtcFixedFormat}. * * <h5>Automatic Denomination</h5> * * Automatic denomination means that the formatter adjusts the denominational units in which a * formatted number is expressed based on the monetary value that number represents. An * auto-denominating formatter is defined by its style, specified by one of the enumerated * values of {@link BtcAutoFormat.Style}. There are two styles constants: {@link * BtcAutoFormat.Style#CODE} (the default), and {@link BtcAutoFormat.Style#SYMBOL}. The * difference is that the <code>CODE</code> style uses an internationally-distinct currency * code, such as <code>"BTC"</code>, to indicate the units of denomination, while the * <code>SYMBOL</code> style uses a possibly-ambiguous currency symbol such as * <code>"฿"</code>. * * <p>The denomination used when formatting will be either bitcoin, millicoin * or microcoin, depending on the value being represented, chosen so as to minimize the number * of consecutive zeros displayed without losing precision. For example, depending on the * locale, a value of one bitcoin might be formatted as <pre>฿1.00</pre> where a value * exceeding that by one satoshi would be <pre>µ฿1,000,000.01</pre> * * <h5>Fixed Denomination</h5> * * Fixed denomination means that the same denomination of units is used for every value that is * formatted or parsed by a given formatter instance. A fixed-denomination formatter is * defined by its scale, which is the number of places one must shift the decimal point in * increasing precision to convert the representation of a given quantity of bitcoins into a * representation of the same value denominated in the formatter's units. For example, a scale * value of <code>3</code> specifies a denomination of millibitcoins, because to represent * <code>1.0000 BTC</code>, or one bitcoin, in millibitcoins, one shifts the decimal point * three places, that is, to <code>1000.0 mBTC</code>. * * <h3>Construction</h3> * * There are two ways to obtain an instance of this class: <ol> * <li>Use one of the factory methods; or * <li>Use a {@link BtcFormat.Builder} object.</ol> * * <p>The factory methods are appropriate for basic use where the default * configuration is either used or modified. The <code>Builder</code> * class provides more control over the configuration, and gives * access to some features not available through the factory methods, * such as using custom formatting patterns and currency symbols. * * <h5>Factory Methods</h5> * * Although formatting and parsing is performed by one of the concrete * subclasses, you can obtain formatters using the various static factory * methods of this abstract base class <code>BtcFormat</code>. There * are a variety of overloaded methods that allow you to obtain a * formatter that behaves according to your needs. * * <p>The primary distinction is between automatic- and * fixed-denomination formatters. By default, the * <code>getInstance()</code> method with no arguments returns a new, * automatic-denominating <code>BtcAutoFormat</code> instance for your * default locale that will display exactly two fractional decimal * places and a currency code. For example, if you happen to be in * the USA: * * <blockquote><pre> * BtcFormat f = BtcFormat.getInstance(); * String s = f.format(Coin.COIN); <strong>// "BTC 1.00"</strong> * </pre></blockquote> * * <p>The first argument to <code>getInstance()</code> can determine * whether you get an auto- or fixed-denominating formatter. If the * type of the first argument is an <code>int</code>, then the value * of that <code>int</code> will be interpreted as the decimal-place scale of * the {@link BtcFixedFormat} instance that is returned, and thus will * determine its denomination. For example, if you want to format * values in units of microbitcoins: * * <blockquote><pre>BtcFormat m = BtcFormat.getInstance(6); *String s = m.format(Coin.COIN); <strong>// "1,000,000.00"</strong> * </blockquote> * * <p>This class provides several constants bound to common scale values: * * <blockquote><pre>BtcFormat milliFormat = BtcFormat.getInstance(MILLICOIN_SCALE);</pre></blockquote> * * <p>Alternatively, if the type of the first argument to * <code>getInstance()</code> is one of the enumerated values of the * {@link BtcAutoFormat.Style} type, either <code>CODE</code> or * <code>SYMBOL</code>, then you will get a {@link BtcAutoFormat} * instance that uses either a currency code or symbol, respectively, * to indicate the results of its auto-denomination. * * <blockquote><pre> * BtcFormat s = BtcFormat.getInstance(SYMBOL); * Coin value = Coin.parseCoin("0.1234"); * String mil = s.format(value); <strong>// "₥฿123.40"</strong> * String mic = s.format(value.divide(1000)); <strong>// "µ฿123.40"</strong> * </blockquote> * * <p>An alternative way to specify whether you want an auto- or fixed-denomination formatter * is to use one of the factory methods that is named to indicate that characteristics of the * new instance returned. For fixed-denomination formatters, these methods are {@link * #getCoinInstance()}, {@link #getMilliInstance()}, and {@link #getMicroInstance()}. These * three methods are equivalent to invoking <code>getInstance()</code> with a first argument of * <code>0</code>, <code>3</code> and <code>6</code>, respectively. For auto-denominating * formatters the relevant factory methods are {@link #getCodeInstance()} and {@link * #getSymbolInstance()}, which are equivalent to <code>getInstance(Style.CODE)</code>, and * <code>getInstance(Style.SYMBOL)</code>. * * <p>Regardless of how you specify whether your new formatter is to be of automatic- or * fixed-denomination, the next (and possibly first) parameter to each of the factory methods * is an optional <code>Locale</code> value. * * For example, here we construct four instances for the same locale that each format * differently the same one-bitcoin value: * * <blockquote><pre> * <strong>// Next line returns "1,00 BTC"</strong> * BtcFormat.getInstance(Locale.GERMANY).format(Coin.COIN); * <strong>// Next line returns "1,00 ฿"</strong> * BtcFormat.getInstance(SYMBOL, Locale.GERMANY).format(Coin.COIN); * <strong>// Next line returns "1.000,00"</strong> * BtcFormat.getMilliInstance(Locale.GERMANY).format(Coin.COIN); * <strong>// Next line returns "10.000,00"</strong> * BtcFormat.getInstance(4, Locale.GERMANY).format(Coin.COIN); * </pre></blockquote> * * Omitting such a <code>Locale</code> parameter will give you a * formatter for your default locale. * * <p>The final (and possibly only) arguments to the factory methods serve to set the default * number of fractional decimal places that will be displayed when formatting monetary values. * In the case of an auto-denominating formatter, this can be a single <code>int</code> value, * which will determine the number of fractional decimal places to be used in all cases, except * where either (1) doing so would provide a place for fractional satoshis, or (2) that default * value is overridden when invoking the <code>format()</code> method as described below. * * <p>In the case of a fixed-denomination formatter, you can pass any number of * <code>int</code> values. The first will determine the minimum number of fractional decimal * places, and each following <code>int</code> value specifies the size of an optional group of * decimal-places to be displayed only if useful for expressing precision. As with auto-denominating * formatters, numbers will never be formatted with a decimal place that represents a * fractional quantity of satoshis, and these defaults can be overridden by arguments to the * <code>format()</code> method. See below for examples. * * <h5>The <code>Builder</code> Class</h5> * * A new {@link BtcFormat.Builder} instance is returned by the {@link #builder()} method. Such * an object has methods that set the configuration parameters of a <code>BtcFormat</code> * object. Its {@link Builder#build()} method constructs and returns a <code>BtcFormat</code> instance * configured according to those settings. * * <p>In addition to setter-methods that correspond to the factory-method parameters explained * above, a <code>Builder</code> also allows you to specify custom formatting and parsing * patterns and currency symbols and codes. For example, rather than using the default * currency symbol, which has the same unicode character point as the national currency symbol of * Thailand, some people prefer to use a capital letter "B" with a vertical overstrike. * * <blockquote><pre> * BtcFormat.Builder builder = BtcFormat.builder(); * builder.style(SYMBOL); * builder.symbol("B\u20e6"); <strong>// unicode char "double vertical stroke overlay"</strong> * BtcFormat f = builder.build(); * String out = f.format(COIN); <strong>// "B⃦1.00" depending on locale</strong> * </pre></blockquote> * * The <code>Builder</code> methods are chainable. So, for example, if you are * deferential to ISO 4217, you might construct a formatter in a single line this way: * * <blockquote><pre> * BtcFormat f = BtcFormat.builder().style(CODE).code("XBT").build(); * String out = f.format(COIN); <strong>// "XBT 1.00"</strong> * </pre></blockquote> * * <p>See the documentation of the {@link BtcFormat.Builder} class for details. * * <h3>Formatting</h3> * * <p>You format a Bitcoin monetary value by passing it to the {@link BtcFormat#format(Object)} * method. This argument can be either a {@link org.bitcoinj.core.Coin}-type object or a * numerical object such as {@link java.lang.Long} or {@link java.math.BigDecimal}. * Integer-based types such as {@link java.math.BigInteger} are interpreted as representing a * number of satoshis, while a {@link java.math.BigDecimal} is interpreted as representing a * number of bitcoins. A value having a fractional amount of satoshis is rounded to the * nearest whole satoshi at least, and possibly to a greater unit depending on the number of * fractional decimal-places displayed. The <code>format()</code> method will not accept an * argument whose type is <code>String</code>, <code>Float</code> nor <code>Double</code>. * * <p>Subsequent to the monetary value to be formatted, the {@link #format(Object)} method also * accepts as arguments optional <code>int</code> values that specify the number of decimal * places to use to represent the fractional portion of the number. This overrides the * default, and enables a single formatter instance to be reused, formatting different values * that require different numbers of fractional decimal places. These parameters have the same * meaning as those that set the default values in the factory methods as described above. * Namely, a single <code>int</code> value determines the minimum number of fractional decimal * places that will be used in all cases, to a precision limit of satoshis. Instances of * {@link BtcFixedFormat} also accept a variable-length sequence of additional <code>int</code> * values, each of which specifies the size of a group of fractional decimal-places to be used * in addition to all preceding places, only if useful to express precision, and only to a * maximum precision of satoshis. For example: * * <blockquote><pre> * BtcFormat f = BtcFormat.getCoinInstance(); * Coin value = COIN.add(Coin.valueOf(5)); <strong>// 100000005 satoshis</strong> * f.format(value, 2); <strong>// "1.00"</strong> * f.format(value, 3); <strong>// "1.000"</strong> * f.format(value, 2, 3); <strong>// "1.00" three more zeros doesn't help </strong> * f.format(value, 2, 3, 3); <strong>// "1.00000005" </strong> * f.format(value, 2, 3, 4); <strong>// "1.00000005" fractions of satoshis have no place</strong> * f.format(value, 2, 3, 2); <strong>// "1.0000001" rounds to nearest usable place</strong> * </pre></blockquote> * * <p>Note that if using all the fractional decimal places in a specified group would give a * place to fractions of satoshis, then the size of that group will be reduced to a maximum * precision of satoshis. Either all or none of the allowed decimal places of that group will * still be applied as doing so is useful for expressing the precision of the value being * formatted. * * <p>Several convenient constants of repeating group-size sequences are provided: * {@link BtcFixedFormat#REPEATING_PLACES}, {@link * BtcFixedFormat#REPEATING_DOUBLETS} and {@link * BtcFixedFormat#REPEATING_TRIPLETS}. These signify repeating groups * of one, two and three decimals places, respectively. For example, * to display only as many fractional places as useful in order to * prevent hanging zeros on the least-significant end of formatted * numbers: * * <blockquote><pre> * format(value, 0, REPEATING_PLACES); * </pre></blockquote> * * <p>When using an automatically-denominating formatter, you might * want to know what denomination was chosen. You can get the * currency-units indicator, as well as any other field in the * formatted output, by using a {@link java.text.FieldPosition} instance * constructed using an appropriate constant from the {@link * java.text.NumberFormat.Field} class: * * <blockquote><pre> * BtcFormat de = BtcFormat.getInstance(Locale.GERMANY); * FieldPosition currField = new FieldPosition(NumberFormat.Field.CURRENCY); * <strong>// next line formats the value as "987.654.321,23 µBTC"</strong> * String output = de.format(valueOf(98765432123L), new StringBuffer(), currField); * <strong>// next line sets variable currencyCode to "µBTC"</strong> * String currencyCode = output.substring(currField.getBeginIndex(), currField.getEndIndex())); * </pre></blockquote> * * <p>When using a fixed-denomination formatter whose scale can be expressed as a standard * "metric" prefix, you can invoke the <code>code()</code> and <code>symbol()</code> methods to * obtain a <code>String</code> whose value is the appropriate currency code or symbol, * respectively, for that formatter. * * <blockquote><pre> * BtcFixedFormat kilo = (BtcFixedFormat)BtcFormat(-3); <strong>// scale -3 for kilocoins</strong> * Coin value = Coin.parseCoin("1230"); * <strong>// variable coded will be set to "kBTC 1.23"</strong> * String coded = kilo.code() + " " + kilo.format(value); * <strong>// variable symbolic will be set to "k฿1.23"</strong> * String symbolic = kilo.symbol() + kilo.format(value); * BtcFormat(4).code(); <strong>// unnamed denomination has no code; raises exception</strong> * </pre></blockquote> * * <h5>Formatting for Tabular Columns</h5> * * When displaying tables of monetary values, you can lessen the * risk of human misreading-error by vertically aligning the decimal * separator of those values. This example demonstrates one way to do that: * * <blockquote><pre> * <strong>// The elements of this array are the values we will format:</strong> * Coin[] rows = {MAX_MONEY, MAX_MONEY.subtract(SATOSHI), Coin.parseCoin("1234"), * COIN, COIN.divide(1000), * valueOf(10000), valueOf(1000), valueOf(100), * SATOSHI}; * BtcFormat f = BtcFormat.getCoinInstance(2, REPEATING_PLACES); * FieldPosition fp = new FieldPosition(DECIMAL_SEPARATOR); <strong>// see java.text.NumberFormat.Field</strong> * String[] output = new String[rows.length]; * int[] indexes = new int[rows.length]; * int maxIndex = 0; * for (int i = 0; i < rows.length; i++) { * output[i] = f.format(rows[i], new StringBuffer(), fp).toString(); * indexes[i] = fp.getBeginIndex(); * if (indexes[i] > maxIndex) maxIndex = indexes[i]; * } * for (int i = 0; i < output.length; i++) { * System.out.println(repeat(" ", (maxIndex - indexes[i])) + output[i]); * } * </pre></blockquote> * * Assuming you are using a monospaced font, and depending on your * locale, the foregoing will print the following: * * <blockquote><pre> * 21,000,000.00 * 20,999,999.99999999 * 1,234.00 * 1.00 * 0.001 * 0.0001 * 0.00001 * 0.000001 * 0.00000001 * </pre></blockquote> * * If you need to vertically-align columns printed in a proportional font, * then see the documentation for the {@link java.text.NumberFormat} class * for an explanation of how to do that. * * <h3>Parsing</h3> * * <p>The {@link #parse(String)} method accepts a <code>String</code> argument, and returns a * {@link Coin}-type value. The difference in parsing behavior between instances of {@link * BtcFixedFormat} and {@link BtcAutoFormat} is analogous to the difference in formatting * behavior between instances of those classes. Instances of {@link BtcAutoFormat} recognize * currency codes and symbols in the <code>String</code> being parsed, and interpret them as * indicators of the units in which the number being parsed is denominated. On the other hand, * instances of {@link BtcFixedFormat} by default recognize no codes nor symbols, but rather * interpret every number as being denominated in the units that were specified when * constructing the instance doing the parsing. This default behavior of {@link * BtcFixedFormat} can be overridden by setting a parsing pattern that includes a currency sign * using the {@link BtcFormat.Builder#pattern()} method. * * <p>The {@link BtcAutoFormat#parse(String)}</code> method of {@link BtcAutoFormat} (and of * {@link BtcAutoFormat} configured with applicable non-default pattern) will recognize a * variety of currency symbols and codes, including all standard international (metric) * prefixes from micro to mega. For example, denominational units of microcoins may be * specified by <code>µ฿</code>, <code>u฿</code>, <code>µB⃦</code>, <code>µɃ</code>, * <code>µBTC</code> or other appropriate permutations of those characters. Additionally, if * either or both of a custom currency code or symbol is configured using {@link * BtcFormat.Builder#code} or {@link BtcFormat.Builder#code}, then such code or symbol will * be recognized in addition to those recognized by default.. * * <p>Instances of this class that recognize currency signs will recognize both currency * symbols and codes, regardless of which that instance uses for formatting. However, if the * style is <code>CODE</code> (and unless overridden by a custom pattern) then a space character must * separate the units indicator from the number. When parsing with a <code>SYMBOL</code>-style * <code>BtcFormat</code> instance, on the other hand, whether or not the units indicator must * be separated by a space from the number is determined by the locale. The {@link * BtcFormat#pattern()} method returns a representation of the pattern that * can be examined to determine whether a space must separate currency signs from numbers in * parsed <code>String</code>s. * * <p>When parsing, if the currency-units indicator is absent, then a {@link BtcAutoFormat} * instance will infer a denomination of bitcoins while a {@link BtcFixedFormat} will infer the * denomination in which it expresses formatted values. Note: by default (unless overridden by * a custom pattern), if the locale or style requires a space to separate the number from the * units indicator, that space must be present in the String to be parsed, even if the units * indicator is absent. * * <p>The <code>parse()</code> method returns an instance of the * {@link Coin} class. Therefore, attempting to parse a value greater * than the maximum that a <code>Coin</code> object can represent will * raise a <code>ParseException</code>, as will any other detected * parsing error. * * <h3>Limitations</h3> * * <h5>Parsing</h5> * * Parsing is performed by an underlying {@link java.text.NumberFormat} object. While this * delivers the benefit of recognizing locale-specific patterns, some have criticized other * aspects of its behavior. For example, see <a * href="http://www.ibm.com/developerworks/library/j-numberformat/">this article by Joe Sam * Shirah</a>. In particular, explicit positive-signs are not recognized. If you are parsing * input from end-users, then you should consider whether you would benefit from any of the * work-arounds mentioned in that article. * * <h5>Exotic Locales</h5> * * This class is not well-tested in locales that use non-ascii * character sets, especially those where writing proceeds from * right-to-left. Helpful feedback in that regard is appreciated. * * <h3>Thread-Safety</h3> * * <p>Instances of this class are immutable. * * @see java.text.Format * @see java.text.NumberFormat * @see java.text.DecimalFormat * @see java.text.DecimalFormatSymbols * @see java.text.FieldPosition * @see org.bitcoinj.core.Coin */ public abstract class BtcFormat extends Format { /* CONCURRENCY NOTES * * There is one mutable member of this class, the `DecimalFormat` object bound to variable * `numberFormat`. The relevant methods invoked on it are: setMinimumFractionDigits(), * setMaximumFractionDigits(), and setDecimalFormatSymbols(), along with the respective * getter methods corresponding to each. The first two methods are used to set the number * of fractional decimal places displayed when formatting, which is reflected in the * patterns returned by the public pattern() and localizedPattern() methods. The last * method sets the value of that object's member `DecimalFormatSymbols` object for * formatting and parsing, which is also reflected in the aforementioned patterns. The * patterns, which are the passed-through return values of the DecimalFormat object's * toPattern() and toLocalizedPattern() methods, and the value of the DecimalFormat * object's DecimalFormatSymbols member are among the values compared between instances of * this class in determining the return values of the `equals()` and `hashCode()` methods. * * From the foregoing, you can understand that immutability is achieved as follows: access * to the variable `numberFormat` referent's fraction-digits and format-symbols fields are * synchronized on that DecimalFormat object. The state of those fraction-digits limits * and decimal-format symbols must be returned to a static state after being changed for * formatting or parsing since the user can see them reflected in the return values of * above-mentioned methods and because `equals()` and `hashCode()` use them for * comparisons. */ /** The conventional international currency code for bitcoins: "BTC" */ private static final String COIN_CODE = "BTC"; /** The default currency symbols for bitcoins */ private static final String COIN_SYMBOL = "฿"; /** An alternative currency symbol to use in locales where the default symbol is used for the national currency. */ protected static final String COIN_SYMBOL_ALT = "Ƀ"; protected final DecimalFormat numberFormat; // warning: mutable protected final int minimumFractionDigits; protected final List<Integer> decimalGroups; /* Scale is the number of decimal-places difference from same value in bitcoins */ /** A constant useful for specifying a denomination of bitcoins, the <code>int</code> value * <code>0</code>. */ public static final int COIN_SCALE = 0; /** A constant useful for specifying a denomination of millibitcoins, the <code>int</code> * value <code>3</code>. */ public static final int MILLICOIN_SCALE = 3; /** A constant useful for specifying a denomination of microbitcoins, the <code>int</code> * value <code>6</code>. */ public static final int MICROCOIN_SCALE = 6; /** Return the number of decimal places by which any value denominated in the * units indicated by the given scale differs from that same value denominated in satoshis */ private static int offSatoshis(int scale) { return Coin.SMALLEST_UNIT_EXPONENT - scale; } private static Locale defaultLocale() { return Locale.getDefault(); } /** * <p>This class constructs new instances of {@link BtcFormat}, allowing for the * configuration of those instances before they are constructed. After obtaining a * <code>Builder</code> object from the {@link BtcFormat#builder()} method, invoke the * necessary setter methods to obtain your desired configuration. Finaly, the {@link * #build()} method returns a new <code>BtcFormat</code> object that has the specified * configuration. * * <p>All the setter methods override defaults. Invoking <code>build()</code> without invoking any * of the setting methods is equivalent to invoking {@link BtcFormat#getInstance()} with no arguments. * * <p>Each setter methods returns the same instance on which it is invoked, * thus these methods can be chained. * * <p>Instances of this class are <strong>not</strong> thread-safe. */ public static class Builder { private enum Variant { AUTO { @Override BtcFormat newInstance(Builder b) { return getInstance(b.style, b.locale, b.minimumFractionDigits); }}, FIXED, UNSET; BtcFormat newInstance(Builder b) { return getInstance(b.scale, b.locale, b.minimumFractionDigits, b.fractionGroups); } } // Parameters are initialized to default or unset values private Variant variant = Variant.UNSET; private Locale locale = defaultLocale(); private int minimumFractionDigits = 2; private int[] fractionGroups = {}; private Style style = BtcAutoFormat.Style.CODE; private int scale = 0; private String symbol = "",code = "",pattern = "",localizedPattern = ""; private Builder() {} /** Specify the new <code>BtcFormat</code> is to be automatically-denominating. * The argument determines which of either codes or symbols the new <code>BtcFormat</code> * will use by default to indicate the denominations it chooses when formatting values. * * <p>Note that the <code>Style</code> argument specifies the * <em>default</em> style, which is overridden by invoking * either {@link #pattern(String)} or {@link #localizedPattern(String)}. * * @throws IllegalArgumentException if {@link #scale(int)} has * previously been invoked on this instance.*/ public Builder style(BtcAutoFormat.Style val) { if (variant == Variant.FIXED) throw new IllegalStateException("You cannot invoke both style() and scale()"); variant = Variant.AUTO; style = val; return this; } /** Specify the number of decimal places in the fraction part of formatted numbers. * This is equivalent to the {@link #minimumFractionDigits(int)} method, but named * appropriately for the context of generating {@link BtcAutoFormat} instances. * * <p>If neither this method nor <code>minimumFactionDigits()</code> is invoked, the default value * will be <code>2</code>. */ public Builder fractionDigits(int val) { return minimumFractionDigits(val); } /** Specify a fixed-denomination of units to use when formatting and parsing values. * The argument specifies the number of decimal places, in increasing * precision, by which each formatted value will differ from that same value * denominated in bitcoins. For example, a denomination of millibitcoins is specified * with a value of <code>3</code>. * * <p>The <code>BtcFormat</code> class provides appropriately named * <code>int</code>-type constants for the three common values, {@link BtcFormat#COIN_SCALE}, * {@link BtcFormat#MILLICOIN_SCALE} {@link BtcFormat#MICROCOIN_SCALE}. * * <p>If neither this method nor {@link #style(BtcAutoFormat.Style)} is invoked on a * <code>Builder</code>, then the <code>BtcFormat</code> will default to a * fixed-denomination of bitcoins, equivalent to invoking this method with an argument * of <code>0</code>. */ public Builder scale(int val) { if (variant == Variant.AUTO) throw new IllegalStateException("You cannot invoke both scale() and style()"); variant = Variant.FIXED; scale = val; return this; } /** Specify the minimum number of decimal places in the fraction part of formatted values. * This method is equivalent to {@link #fractionDigits(int)}, but named appropriately for * the context of generating a fixed-denomination formatter. * * <p>If neither this method nor <code>fractionDigits()</code> is invoked, the default value * will be <code>2</code>. */ public Builder minimumFractionDigits(int val) { minimumFractionDigits = val; return this; } /** Specify the sizes of a variable number of optional decimal-place groups in the * fraction part of formatted values. A group of each specified size will be used in * addition to all previously applied decimal places only if doing so is useful for * expressing precision. The size of each group is limited to a maximum precision of * satoshis. * * <p>If this method is not invoked, then the number of fractional decimal places will * be limited to the value passed to {@link #minimumFractionDigits}, or <code>2</code> * if that method is not invoked. */ public Builder fractionGroups(int... val) { fractionGroups = val; return this; } /** Specify the {@link java.util.Locale} for formatting and parsing. * If this method is not invoked, then the runtime default locale will be used. */ public Builder locale(Locale val) { locale = val; return this; } /** Specify a currency symbol to be used in the denomination-unit indicators * of formatted values. This method only sets the symbol, but does not cause * it to be used. You must also invoke either <code>style(SYMBOL)</code>, or else apply * a custom pattern that includes a single currency-sign character by invoking either * {@link #pattern(String)} or {@link #localizedPattern(String)}. * * <p>Specify only the base symbol. The appropriate prefix will be applied according * to the denomination of formatted and parsed values. */ public Builder symbol(String val) { symbol = val; return this; } /** Specify a custom currency code to be used in the denomination-unit indicators * of formatted values. This method only sets the code, but does not cause * it to be used. You must also invoke either <code>style(CODE)</code>, or else apply * a custom pattern that includes a double currency-sign character by invoking either * {@link #pattern(String)} or {@link #localizedPattern(String)}. * * <p>Specify only the base code. The appropriate prefix will be applied according * to the denomination of formatted and parsed values. */ public Builder code(String val) { code = val; return this; } /** Use the given pattern when formatting and parsing. The format of this pattern is * identical to that used by the {@link java.text.DecimalFormat} class. * * <p>If the pattern lacks a negative subpattern, then the formatter will indicate * negative values by placing a minus sign immediately preceding the number part of * formatted values. * * <p>Note that while the pattern format specified by the {@link * java.text.DecimalFormat} class includes a mechanism for setting the number of * fractional decimal places, that part of the pattern is ignored. Instead, use the * {@link #fractionDigits(int)}, {@link #minimumFractionDigits(int)} and {@link * #fractionGroups(int...)} methods. * * <p>Warning: if you set a pattern that includes a currency-sign for a * fixed-denomination formatter that uses a non-standard scale, then an exception will * be raised when you try to format a value. The standard scales include all for * which a metric prefix exists from micro to mega. * * <p>Note that by applying a pattern you override the configured formatting style of * {@link BtcAutoFormat} instances. */ public Builder pattern(String val) { if (localizedPattern != "") throw new IllegalStateException("You cannot invoke both pattern() and localizedPattern()"); pattern = val; return this; } /** Use the given localized-pattern for formatting and parsing. The format of this * pattern is identical to the patterns used by the {@link java.text.DecimalFormat} * class. * * <p>The pattern is localized according to the locale of the <code>BtcFormat</code> * instance, the symbols for which can be examined by inspecting the {@link * java.text.DecimalFormatSymbols} object returned by {@link BtcFormat#symbols()}. * So, for example, if you are in Germany, then the non-localized pattern of * <pre>"#,##0.###"</pre> would be localized as <pre>"#.##0,###"</pre> * * <p>If the pattern lacks a negative subpattern, then the formatter will indicate * negative values by placing a minus sign immediately preceding the number part of * formatted values. * * <p>Note that while the pattern format specified by the {@link * java.text.DecimalFormat} class includes a mechanism for setting the number of * fractional decimal places, that part of the pattern is ignored. Instead, use the * {@link #fractionDigits(int)}, {@link #minimumFractionDigits(int)} and {@link * #fractionGroups(int...)} methods. * * <p>Warning: if you set a pattern that includes a currency-sign for a * fixed-denomination formatter that uses a non-standard scale, then an exception will * be raised when you try to format a value. The standard scales include all for * which a metric prefix exists from micro to mega. * * <p>Note that by applying a pattern you override the configured formatting style of * {@link BtcAutoFormat} instances. */ public Builder localizedPattern(String val) { if (pattern != "") throw new IllegalStateException("You cannot invoke both pattern() and localizedPattern()."); localizedPattern = val; return this; } /** Return a new {@link BtcFormat} instance. The object returned will be configured according * to the state of this <code>Builder</code> instance at the time this method is invoked. */ public BtcFormat build() { BtcFormat f = variant.newInstance(this); if (symbol != "" || code != "") { synchronized(f.numberFormat) { DecimalFormatSymbols defaultSigns = f.numberFormat.getDecimalFormatSymbols(); setSymbolAndCode(f.numberFormat, symbol != "" ? symbol : defaultSigns.getCurrencySymbol(), code != "" ? code : defaultSigns.getInternationalCurrencySymbol() ); }} if (localizedPattern != "" || pattern != "") { int places = f.numberFormat.getMinimumFractionDigits(); if (localizedPattern != "") f.numberFormat.applyLocalizedPattern(negify(localizedPattern)); else f.numberFormat.applyPattern(negify(pattern)); f.numberFormat.setMinimumFractionDigits(places); f.numberFormat.setMaximumFractionDigits(places); } return f; } } /** Return a new {@link Builder} object. See the documentation of that class for usage details. */ public static Builder builder() { return new Builder(); } /** This single constructor is invoked by the overriding subclass constructors. */ protected BtcFormat(DecimalFormat numberFormat, int minDecimals, List<Integer> groups) { checkArgument(minDecimals >= 0, "There can be no fewer than zero fractional decimal places"); this.numberFormat = numberFormat; this.numberFormat.setParseBigDecimal(true); this.numberFormat.setRoundingMode(HALF_UP); this.minimumFractionDigits = minDecimals; this.numberFormat.setMinimumFractionDigits(this.minimumFractionDigits); this.numberFormat.setMaximumFractionDigits(this.minimumFractionDigits); this.decimalGroups = groups; synchronized (this.numberFormat) { setSymbolAndCode( this.numberFormat, (this.numberFormat.getDecimalFormatSymbols().getCurrencySymbol().contains(COIN_SYMBOL)) ? COIN_SYMBOL_ALT : COIN_SYMBOL, COIN_CODE );} } /** * Return a new instance of this class using all defaults. The returned formatter will * auto-denominate values so as to minimize zeros without loss of precision and display a * currency code, for example "<code>BTC</code>", to indicate that denomination. The * returned object will uses the default locale for formatting the number and placement of * the currency-code. Two fractional decimal places will be displayed in all formatted numbers. */ public static BtcFormat getInstance() { return getInstance(defaultLocale()); } /** * Return a new auto-denominating instance that will indicate units using a currency * symbol, for example, <code>"฿"</code>. Formatting and parsing will be done * according to the default locale. */ public static BtcFormat getSymbolInstance() { return getSymbolInstance(defaultLocale()); } /** * Return a new auto-denominating instance that will indicate units using a currency * code, for example, <code>"BTC"</code>. Formatting and parsing will be done * according to the default locale. */ public static BtcFormat getCodeInstance() { return getCodeInstance(defaultLocale()); } /** * Return a new symbol-style auto-formatter with the given number of fractional decimal * places. Denominational units will be indicated using a currency symbol, for example, * <code>"฿"</code>. The returned object will format the fraction-part of numbers using * the given number of decimal places, or fewer as necessary to avoid giving a place to * fractional satoshis. Formatting and parsing will be done according to the default * locale. */ public static BtcFormat getSymbolInstance(int fractionPlaces) { return getSymbolInstance(defaultLocale(), fractionPlaces); } /** * Return a new code-style auto-formatter with the given number of fractional decimal * places. Denominational units will be indicated using a currency code, for example, * <code>"BTC"</code>. The returned object will format the fraction-part of numbers using * the given number of decimal places, or fewer as necessary to avoid giving a place to * fractional satoshis. Formatting and parsing will be done according to the default * locale. */ public static BtcFormat getCodeInstance(int minDecimals) { return getCodeInstance(defaultLocale(), minDecimals); } /** * Return a new code-style auto-formatter for the given locale. The returned object will * select denominational units based on each value being formatted, and will indicate those * units using a currency code, for example, <code>"mBTC"</code>. */ public static BtcFormat getInstance(Locale locale) { return getCodeInstance(locale); } /** * Return a new code-style auto-formatter for the given locale. The returned object will * select denominational units based on each value being formatted, and will indicate those * units using a currency code, for example, <code>"mBTC"</code>. */ public static BtcFormat getCodeInstance(Locale locale) { return getInstance(CODE, locale); } /** * Return a new code-style auto-formatter for the given locale with the given number of * fraction places. The returned object will select denominational units based on each * value being formatted, and will indicate those units using a currency code, for example, * <code>"mBTC"</code>. The returned object will format the fraction-part of numbers using * the given number of decimal places, or fewer as necessary to avoid giving a place to * fractional satoshis. */ public static BtcFormat getInstance(Locale locale, int minDecimals) { return getCodeInstance(locale, minDecimals); } /** * Return a new code-style auto-formatter for the given locale with the given number of * fraction places. The returned object will select denominational units based on each * value being formatted, and will indicate those units using a currency code, for example, * <code>"mBTC"</code>. The returned object will format the fraction-part of numbers using * the given number of decimal places, or fewer as necessary to avoid giving a place to * fractional satoshis. */ public static BtcFormat getCodeInstance(Locale locale, int minDecimals) { return getInstance(CODE, locale, minDecimals); } /** * Return a new symbol-style auto-formatter for the given locale. The returned object will * select denominational units based on each value being formatted, and will indicate those * units using a currency symbol, for example, <code>"µ฿"</code>. */ public static BtcFormat getSymbolInstance(Locale locale) { return getInstance(SYMBOL, locale); } /** * Return a new symbol-style auto-formatter for the given locale with the given number of * fraction places. The returned object will select denominational units based on each * value being formatted, and will indicate those units using a currency symbol, for example, * <code>"µ฿"</code>. The returned object will format the fraction-part of numbers using * the given number of decimal places, or fewer as necessary to avoid giving a place to * fractional satoshis. */ public static BtcFormat getSymbolInstance(Locale locale, int fractionPlaces) { return getInstance(SYMBOL, locale, fractionPlaces); } /** * Return a new auto-denominating formatter. The returned object will indicate the * denominational units of formatted values using either a currency symbol, such as, * <code>"฿"</code>, or code, such as <code>"mBTC"</code>, depending on the value of * the argument. Formatting and parsing will be done according to the default locale. */ public static BtcFormat getInstance(Style style) { return getInstance(style, defaultLocale()); } /** * Return a new auto-denominating formatter with the given number of fractional decimal * places. The returned object will indicate the denominational units of formatted values * using either a currency symbol, such as, <code>"฿"</code>, or code, such as * <code>"mBTC"</code>, depending on the value of the first argument. The returned object * will format the fraction-part of numbers using the given number of decimal places, or * fewer as necessary to avoid giving a place to fractional satoshis. Formatting and * parsing will be done according to the default locale. */ public static BtcFormat getInstance(Style style, int fractionPlaces) { return getInstance(style, defaultLocale(), fractionPlaces); } /** * Return a new auto-formatter with the given style for the given locale. * The returned object that will auto-denominate each formatted value, and * will indicate that denomination using either a currency code, such as * "<code>BTC</code>", or symbol, such as "<code>฿</code>", depending on the value * of the first argument. * <p>The number of fractional decimal places in formatted number will be two, or fewer * as necessary to avoid giving a place to fractional satoshis. */ public static BtcFormat getInstance(Style style, Locale locale) { return getInstance(style, locale, 2); } /** * Return a new auto-formatter for the given locale with the given number of fraction places. * The returned object will automatically-denominate each formatted * value, and will indicate that denomination using either a currency code, * such as <code>"mBTC"</code>, or symbol, such as "<code>฿</code>", * according to the given style argument. It will format each number * according to the given locale. * * <p>The third parameter is the number of fractional decimal places to use for each * formatted number, reduced as neccesary when formatting to avoid giving a place to * fractional satoshis. */ public static BtcFormat getInstance(Style style, Locale locale, int fractionPlaces) { return new BtcAutoFormat(locale, style, fractionPlaces); } /** * Return a new coin-denominated formatter. The returned object will format and parse * values according to the default locale, and will format numbers with two fractional * decimal places, rounding values as necessary. */ public static BtcFormat getCoinInstance() { return getCoinInstance(defaultLocale()); } private static List<Integer> boxAsList(int[] elements) throws IllegalArgumentException { List<Integer> list = new ArrayList<Integer>(elements.length); for (int e : elements) { checkArgument(e > 0, "Size of decimal group must be at least one."); list.add(e); } return list; } /** * Return a new coin-denominated formatter with the specified fraction-places. The * returned object will format and parse values according to the default locale, and will * format the fraction part of numbers with at least two decimal places. The sizes of * additional groups of decimal places can be specified by a variable number of * <code>int</code> arguments. Each optional decimal-place group will be applied only if * useful for expressing precision, and will be only partially applied if necessary to * avoid giving a place to fractional satoshis. */ public static BtcFormat getCoinInstance(int minFractionPlaces, int... groups) { return getInstance(COIN_SCALE, defaultLocale(), minFractionPlaces, boxAsList(groups)); } /** * Return a new coin-denominated formatter for the given locale. The returned object will * format the fractional part of numbers with two decimal places, rounding as necessary. */ public static BtcFormat getCoinInstance(Locale locale) { return getInstance(COIN_SCALE, locale, 2); } /** * Return a newly-constructed instance for the given locale that will format * values in terms of bitcoins, with the given minimum number of fractional * decimal places. Optionally, repeating integer arguments can be passed, each * indicating the size of an additional group of fractional decimal places to be * used as necessary to avoid rounding, to a limiting precision of satoshis. */ public static BtcFormat getCoinInstance(Locale locale, int scale, int... groups) { return getInstance(COIN_SCALE, locale, scale, boxAsList(groups)); } /** * Return a new millicoin-denominated formatter. The returned object will format and * parse values for the default locale, and will format the fractional part of numbers with * two decimal places, rounding as necessary. */ public static BtcFormat getMilliInstance() { return getMilliInstance(defaultLocale()); } /** * Return a new millicoin-denominated formatter for the given locale. The returned object * will format the fractional part of numbers with two decimal places, rounding as * necessary. */ public static BtcFormat getMilliInstance(Locale locale) { return getInstance(MILLICOIN_SCALE, locale, 2); } /** * Return a new millicoin-denominated formatter with the specified fractional decimal * placing. The returned object will format and parse values according to the default * locale, and will format the fractional part of numbers with the given minimum number of * fractional decimal places. Optionally, repeating integer arguments can be passed, each * indicating the size of an additional group of fractional decimal places to be used as * necessary to avoid rounding, to a limiting precision of satoshis. */ public static BtcFormat getMilliInstance(int scale, int... groups) { return getInstance(MILLICOIN_SCALE, defaultLocale(), scale, boxAsList(groups)); } /** * Return a new millicoin-denominated formatter for the given locale with the specified * fractional decimal placing. The returned object will format the fractional part of * numbers with the given minimum number of fractional decimal places. Optionally, * repeating integer arguments can be passed, each indicating the size of an additional * group of fractional decimal places to be used as necessary to avoid rounding, to a * limiting precision of satoshis. */ public static BtcFormat getMilliInstance(Locale locale, int scale, int... groups) { return getInstance(MILLICOIN_SCALE, locale, scale, boxAsList(groups)); } /** * Return a new microcoin-denominated formatter for the default locale. The returned object * will format the fractional part of numbers with two decimal places, rounding as * necessary. */ public static BtcFormat getMicroInstance() { return getMicroInstance(defaultLocale()); } /** * Return a new microcoin-denominated formatter for the given locale. The returned object * will format the fractional part of numbers with two decimal places, rounding as * necessary. */ public static BtcFormat getMicroInstance(Locale locale) { return getInstance(MICROCOIN_SCALE, locale); } /** * Return a new microcoin-denominated formatter with the specified fractional decimal * placing. The returned object will format and parse values according to the default * locale, and will format the fractional part of numbers with the given minimum number of * fractional decimal places. Optionally, repeating integer arguments can be passed, each * indicating the size of an additional group of fractional decimal places to be used as * necessary to avoid rounding, to a limiting precision of satoshis. */ public static BtcFormat getMicroInstance(int scale, int... groups) { return getInstance(MICROCOIN_SCALE, defaultLocale(), scale, boxAsList(groups)); } /** * Return a new microcoin-denominated formatter for the given locale with the specified * fractional decimal placing. The returned object will format the fractional part of * numbers with the given minimum number of fractional decimal places. Optionally, * repeating integer arguments can be passed, each indicating the size of an additional * group of fractional decimal places to be used as necessary to avoid rounding, to a * limiting precision of satoshis. */ public static BtcFormat getMicroInstance(Locale locale, int scale, int... groups) { return getInstance(MICROCOIN_SCALE, locale, scale, boxAsList(groups)); } /** * Return a new fixeed-denomination formatter with the specified fractional decimal * placing. The first argument specifies the denomination as the size of the * shift from coin-denomination in increasingly-precise decimal places. The returned object will format * and parse values according to the default locale, and will format the fractional part of * numbers with the given minimum number of fractional decimal places. Optionally, * repeating integer arguments can be passed, each indicating the size of an additional * group of fractional decimal places to be used as necessary to avoid rounding, to a * limiting precision of satoshis. */ public static BtcFormat getInstance(int scale, int minDecimals, int... groups) { return getInstance(scale, defaultLocale(), minDecimals, boxAsList(groups)); } /** * Return a new fixeed-denomination formatter. The argument specifies the denomination as * the size of the shift from coin-denomination in increasingly-precise decimal places. * The returned object will format and parse values according to the default locale, and * will format the fractional part of numbers with two decimal places, or fewer as * necessary to avoid giving a place to fractional satoshis. */ public static BtcFormat getInstance(int scale) { return getInstance(scale, defaultLocale()); } /** * Return a new fixeed-denomination formatter for the given locale. The first argument * specifies the denomination as the size of the shift from coin-denomination in * increasingly-precise decimal places. The returned object will format and parse values * according to the locale specified by the second argument, and will format the fractional * part of numbers with two decimal places, or fewer as necessary to avoid giving a place * to fractional satoshis. */ public static BtcFormat getInstance(int scale, Locale locale) { return getInstance(scale, locale, 2); } /** * Return a new fixed-denomination formatter for the given locale, with the specified * fractional decimal placing. The first argument specifies the denomination as the size * of the shift from coin-denomination in increasingly-precise decimal places. The third * parameter is the minimum number of fractional decimal places to use, followed by * optional repeating integer parameters each specifying the size of an additional group of * fractional decimal places to use as necessary to avoid rounding, down to a maximum * precision of satoshis. */ public static BtcFormat getInstance(int scale, Locale locale, int minDecimals, int... groups) { return getInstance(scale, locale, minDecimals, boxAsList(groups)); } /** * Return a new fixed-denomination formatter for the given locale, with the specified * fractional decimal placing. The first argument specifies the denomination as the size * of the shift from coin-denomination in increasingly-precise decimal places. The third * parameter is the minimum number of fractional decimal places to use. The third argument * specifies the minimum number of fractional decimal places in formatted numbers. The * last argument is a <code>List</code> of <code>Integer</code> values, each of which * specifies the size of an additional group of fractional decimal places to use as * necessary to avoid rounding, down to a maximum precision of satoshis. */ public static BtcFormat getInstance(int scale, Locale locale, int minDecimals, List<Integer> groups) { return new BtcFixedFormat(locale, scale, minDecimals, groups); } /***********************/ /****** FORMATTING *****/ /***********************/ /** * Formats a bitcoin monetary value and returns an {@link java.text.AttributedCharacterIterator}. * By iterating, you can examine what fields apply to each character. This can be useful * since a character may be part of more than one field, for example a grouping separator * that is also part of the integer field. * * @see java.text.AttributedCharacterIterator */ @Override public AttributedCharacterIterator formatToCharacterIterator(Object obj) { synchronized(numberFormat) { DecimalFormatSymbols anteSigns = numberFormat.getDecimalFormatSymbols(); BigDecimal units = denominateAndRound(inSatoshis(obj), minimumFractionDigits, decimalGroups); List<Integer> anteDigits = setFormatterDigits(numberFormat, units.scale(), units.scale()); AttributedCharacterIterator i = numberFormat.formatToCharacterIterator(units); numberFormat.setDecimalFormatSymbols(anteSigns); setFormatterDigits(numberFormat, anteDigits.get(0), anteDigits.get(1)); return i; }} /** * Formats a bitcoin value as a number and possibly a units indicator and appends the * resulting text to the given string buffer. The type of monetary value argument can be * any one of any of the following classes: <code>{@link Coin}</code>, * <code>Integer</code>, <code>Long</code>, <code>BigInteger</code>, * <code>BigDecimal</code>. Numeric types that can represent only an integer are interpreted * as that number of satoshis. The value of a <code>BigDecimal</code> is interpreted as that * number of bitcoins, rounded to the nearest satoshi as necessary. * * @return the <code>StringBuffer</code> passed in as <code>toAppendTo</code> */ @Override public StringBuffer format(Object qty, StringBuffer toAppendTo, FieldPosition pos) { return format(qty, toAppendTo, pos, minimumFractionDigits, decimalGroups); } /** * Formats a bitcoin value as a number and possibly a units indicator to a * <code>String</code>.The type of monetary value argument can be any one of any of the * following classes: <code>{@link Coin}</code>, <code>Integer</code>, <code>Long</code>, * <code>BigInteger</code>, <code>BigDecimal</code>. Numeric types that can represent only * an integer are interpreted as that number of satoshis. The value of a * <code>BigDecimal</code> is interpreted as that number of bitcoins, rounded to the * nearest satoshi as necessary. * * @param minDecimals The minimum number of decimal places in the fractional part of the formatted number * @param fractionGroups The sizes of optional additional fractional decimal-place groups * @throws IllegalArgumentException if the number of fraction places is negative. */ public String format(Object qty, int minDecimals, int... fractionGroups) { return format(qty, new StringBuffer(), new FieldPosition(0), minDecimals, boxAsList(fractionGroups)).toString(); } /** * Formats a bitcoin value as a number and possibly a units indicator and appends the * resulting text to the given string buffer. The type of monetary value argument can be * any one of any of the following classes: <code>{@link Coin}</code>, * <code>Integer</code>, <code>Long</code>, <code>BigInteger</code>, * <code>BigDecimal</code>. Numeric types that can represent only an integer are interpreted * as that number of satoshis. The value of a <code>BigDecimal</code> is interpreted as that * number of bitcoins, rounded to the nearest satoshi as necessary. * * @param minDecimals The minimum number of decimal places in the fractional part of the formatted number * @param fractionGroups The sizes of optional additional fractional decimal-place groups * @throws IllegalArgumentException if the number of fraction places is negative. */ public StringBuffer format(Object qty, StringBuffer toAppendTo, FieldPosition pos, int minDecimals, int... fractionGroups) { return format(qty, toAppendTo, pos, minDecimals, boxAsList(fractionGroups)); } private StringBuffer format(Object qty, StringBuffer toAppendTo, FieldPosition pos, int minDecimals, List<Integer> fractionGroups) { checkArgument(minDecimals >= 0, "There can be no fewer than zero fractional decimal places"); synchronized (numberFormat) { DecimalFormatSymbols anteSigns = numberFormat.getDecimalFormatSymbols(); BigDecimal denominatedUnitCount = denominateAndRound(inSatoshis(qty), minDecimals, fractionGroups); List<Integer> antePlaces = setFormatterDigits(numberFormat, denominatedUnitCount.scale(), denominatedUnitCount.scale()); StringBuffer s = numberFormat.format(denominatedUnitCount, toAppendTo, pos); numberFormat.setDecimalFormatSymbols(anteSigns); setFormatterDigits(numberFormat, antePlaces.get(0), antePlaces.get(1)); return s; } } /** * Return the denomination for formatting the given value. The returned <code>int</code> * is the size of the decimal-place shift between the given Bitcoin-value denominated in * bitcoins and that same value as formatted. A fixed-denomination formatter will ignore * the arguments. * * @param satoshis The number of satoshis having the value for which the shift is calculated * @param fractionPlaces The number of decimal places available for displaying the fractional part of the denominated value * @return The size of the shift in increasingly-precise decimal places */ protected abstract int scale(BigInteger satoshis, int fractionPlaces); /** Return the denomination of this object. Fixed-denomination formatters will override * with their configured denomination, auto-formatters with coin denomination. This * determines the interpretation of parsed numbers lacking a units-indicator. */ protected abstract int scale(); /** * Takes a bitcoin monetary value that the client wants to format and returns the number of * denominational units having the equal value, rounded to the appropriate number of * decimal places. Calls the scale() method of the subclass, which may have the * side-effect of changing the currency symbol and code of the underlying `NumberFormat` * object, therefore only invoke this from a synchronized method that resets the NumberFormat. */ private BigDecimal denominateAndRound(BigInteger satoshis, int minDecimals, List<Integer> fractionGroups) { int scale = scale(satoshis, minDecimals); BigDecimal denominatedUnitCount = new BigDecimal(satoshis).movePointLeft(offSatoshis(scale)); int places = calculateFractionPlaces(denominatedUnitCount, scale, minDecimals, fractionGroups); return denominatedUnitCount.setScale(places, HALF_UP); } /** Sets the number of fractional decimal places to be displayed on the given * NumberFormat object to the value of the given integer. * @return The minimum and maximum fractional places settings that the * formatter had before this change, as an ImmutableList. */ private static ImmutableList<Integer> setFormatterDigits(DecimalFormat formatter, int min, int max) { ImmutableList<Integer> ante = ImmutableList.of( formatter.getMinimumFractionDigits(), formatter.getMaximumFractionDigits() ); formatter.setMinimumFractionDigits(min); formatter.setMaximumFractionDigits(max); return ante; } /** Return the number of fractional decimal places to be displayed when formatting * the given number of monetory units of the denomination indicated by the given decimal scale value, * where 0 = coin, 3 = millicoin, and so on. * * @param unitCount the number of monetary units to be formatted * @param scale the denomination of those units as the decimal-place shift from coins * @param minDecimals the minimum number of fractional decimal places * @param fractionGroups the sizes of option fractional decimal-place groups */ private static int calculateFractionPlaces( BigDecimal unitCount, int scale, int minDecimals, List<Integer> fractionGroups) { /* Taking into account BOTH the user's preference for decimal-place groups, AND the prohibition * against displaying a fractional number of satoshis, determine the maximum possible number of * fractional decimal places. */ int places = minDecimals; for (int group : fractionGroups) { places += group; } int max = Math.min(places, offSatoshis(scale)); places = Math.min(minDecimals,max); for (int group : fractionGroups) { /* Compare the value formatted using only this many decimal places to the * same value using as many places as possible. If there's no difference, then * there's no reason to continue adding more places. */ if (unitCount.setScale(places, HALF_UP).compareTo(unitCount.setScale(max, HALF_UP)) == 0) break; places += group; if (places > max) places = max; } return places; } /** * Takes an object representing a bitcoin quantity of any type the * client is permitted to pass us, and return a BigInteger representing the * number of satoshis having the equivalent value. */ private static BigInteger inSatoshis(Object qty) { BigInteger satoshis; // the value might be bitcoins or satoshis if (qty instanceof Long || qty instanceof Integer) satoshis = BigInteger.valueOf(((Number)qty).longValue()); else if (qty instanceof BigInteger) satoshis = (BigInteger)qty; else if (qty instanceof BigDecimal) satoshis = ((BigDecimal)qty).movePointRight(Coin.SMALLEST_UNIT_EXPONENT). setScale(0,BigDecimal.ROUND_HALF_UP).unscaledValue(); else if (qty instanceof Coin) satoshis = BigInteger.valueOf(((Coin)qty).value); else throw new IllegalArgumentException("Cannot format a " + qty.getClass().getSimpleName() + " as a Bicoin value"); return satoshis; } /********************/ /****** PARSING *****/ /********************/ /** * Parse a <code>String</code> representation of a Bitcoin monetary value. Returns a * {@link org.bitcoinj.core.Coin} object that represents the parsed value. * @see java.text.NumberFormat */ @Override public final Object parseObject(String source, ParsePosition pos) { return parse(source, pos); } private class ScaleMatcher { public Pattern pattern; public int scale; ScaleMatcher(Pattern p, int s) { pattern = p; scale = s; } } /* Lazy initialization; No reason to create all these objects unless needed for parsing */ // coin indicator regex String; TODO: does this need to be volatile? private volatile String ci = "(" + COIN_SYMBOL + "|" + COIN_SYMBOL_ALT + "|B⃦|" + COIN_CODE + "|XBT)"; private Pattern coinPattern; private volatile ScaleMatcher[] denoms; ScaleMatcher[] denomMatchers() { ScaleMatcher[] result = denoms; if (result == null) { synchronized(this) { result = denoms; if (result == null) { if (! coinSymbol().matches(ci)) ci = ci.replaceFirst("\\(", "(" + coinSymbol() + "|"); if (! coinCode().matches(ci)) { ci = ci.replaceFirst("\\)", "|" + coinCode() + ")"); } coinPattern = Pattern.compile(ci + "?"); result = denoms = new ScaleMatcher[]{ new ScaleMatcher(Pattern.compile("¢" + ci + "?|c" + ci), 2), // centi new ScaleMatcher(Pattern.compile("₥" + ci + "?|m" + ci), MILLICOIN_SCALE), new ScaleMatcher(Pattern.compile("([µu]" + ci + ")"), MICROCOIN_SCALE), new ScaleMatcher(Pattern.compile("(da" + ci + ")"), -1), // deka new ScaleMatcher(Pattern.compile("(h" + ci + ")"), -2), // hekto new ScaleMatcher(Pattern.compile("(k" + ci + ")"), -3), // kilo new ScaleMatcher(Pattern.compile("(M" + ci + ")"), -6) // mega }; } }} return result; } /** Set both the currency symbol and international code of the underlying {@link * java.text.NumberFormat} object to the value of the given <code>String</code>. * This method is invoked in the process of parsing, not formatting. * * Only invoke this from code synchronized on the value of the first argument, and don't * forget to put the symbols back otherwise equals(), hashCode() and immutability will * break. */ private static DecimalFormatSymbols setSymbolAndCode(DecimalFormat numberFormat, String sign) { return setSymbolAndCode(numberFormat, sign, sign); } /** Set the currency symbol and international code of the underlying {@link * java.text.NumberFormat} object to the values of the last two arguments, respectively. * This method is invoked in the process of parsing, not formatting. * * Only invoke this from code synchronized on value of the first argument, and don't * forget to put the symbols back otherwise equals(), hashCode() and immutability will * break. */ private static DecimalFormatSymbols setSymbolAndCode(DecimalFormat numberFormat, String symbol, String code) { checkState(Thread.holdsLock(numberFormat)); DecimalFormatSymbols fs = numberFormat.getDecimalFormatSymbols(); DecimalFormatSymbols ante = (DecimalFormatSymbols)fs.clone(); fs.setInternationalCurrencySymbol(code); fs.setCurrencySymbol(symbol); numberFormat.setDecimalFormatSymbols(fs); return ante; } /** * Set both the currency symbol and code of the underlying, mutable NumberFormat object * according to the given denominational units scale factor. This is for formatting, not parsing. * * <p>Set back to zero when you're done formatting otherwise immutability, equals() and * hashCode() will break! * * @param scale Number of places the decimal point will be shifted when formatting * a quantity of satoshis. * @return The DecimalFormatSymbols before changing */ protected static void prefixUnitsIndicator(DecimalFormat numberFormat, int scale) { checkState(Thread.holdsLock(numberFormat)); // make sure caller intends to reset before changing DecimalFormatSymbols fs = numberFormat.getDecimalFormatSymbols(); setSymbolAndCode(numberFormat, prefixSymbol(fs.getCurrencySymbol(), scale), prefixCode(fs.getInternationalCurrencySymbol(), scale) ); } /** Parse a <code>String</code> representation of a Bitcoin monetary value. If this * object's pattern includes a currency sign, either symbol or code, as by default is true * for instances of {@link BtcAutoFormat} and false for instances of {@link * BtcFixedFormat}, then denominated (i.e., prefixed) currency signs in the parsed String * will be recognized, and the parsed number will be interpreted as a quantity of units * having that recognized denomination. * <p>If the pattern includes a currency sign but no currency sign is detected in the parsed * String, then the number is interpreted as a quatity of bitcoins. * <p>If the pattern contains neither a currency symbol nor sign, then instances of {@link * BtcAutoFormat} will interpret the parsed number as a quantity of bitcoins, and instances * of {@link BtcAutoFormat} will interpret the number as a quantity of that instance's * configured denomination, which can be ascertained by invoking the {@link * BtcFixedFormat#symbol()} or {@link BtcFixedFormat#code()} method. * * <p>Consider using the single-argument version of this overloaded method unless you need to * keep track of the current parse position. * * @return a Coin object representing the parsed value * @see java.text.ParsePosition */ public Coin parse(String source, ParsePosition pos) { DecimalFormatSymbols anteSigns = null; int parseScale = COIN_SCALE; // default Coin coin = null; synchronized (numberFormat) { if (numberFormat.toPattern().contains("¤")) { for(ScaleMatcher d : denomMatchers()) { Matcher matcher = d.pattern.matcher(source); if (matcher.find()) { anteSigns = setSymbolAndCode(numberFormat, matcher.group()); parseScale = d.scale; break; } } if (parseScale == COIN_SCALE) { Matcher matcher = coinPattern.matcher(source); matcher.find(); anteSigns = setSymbolAndCode(numberFormat, matcher.group()); } } else parseScale = scale(); Number number = numberFormat.parse(source, pos); if (number != null) try { coin = Coin.valueOf( ((BigDecimal)number).movePointRight(offSatoshis(parseScale)).setScale(0, HALF_UP).longValue() ); } catch (IllegalArgumentException e) { pos.setIndex(0); } if (anteSigns != null) numberFormat.setDecimalFormatSymbols(anteSigns); } return coin; } /** Parse a <code>String</code> representation of a Bitcoin monetary value. If this * object's pattern includes a currency sign, either symbol or code, as by default is true * for instances of {@link BtcAutoFormat} and false for instances of {@link * BtcFixedFormat}, then denominated (i.e., prefixed) currency signs in the parsed String * will be recognized, and the parsed number will be interpreted as a quantity of units * having that recognized denomination. * <p>If the pattern includes a currency sign but no currency sign is detected in the parsed * String, then the number is interpreted as a quatity of bitcoins. * <p>If the pattern contains neither a currency symbol nor sign, then instances of {@link * BtcAutoFormat} will interpret the parsed number as a quantity of bitcoins, and instances * of {@link BtcAutoFormat} will interpret the number as a quantity of that instance's * configured denomination, which can be ascertained by invoking the {@link * BtcFixedFormat#symbol()} or {@link BtcFixedFormat#code()} method. * * @return a Coin object representing the parsed value */ public Coin parse(String source) throws ParseException { return (Coin)parseObject(source); } /*********************************/ /****** END OF PARSING STUFF *****/ /*********************************/ protected static String prefixCode(String code, int scale) { switch (scale) { case COIN_SCALE: return code; case 1: return "d" + code; case 2: return "c" + code; case MILLICOIN_SCALE: return "m" + code; case MICROCOIN_SCALE: return "µ" + code; case -1: return "da" + code; case -2: return "h" + code; case -3: return "k" + code; case -6: return "M" + code; default: throw new IllegalStateException("No known prefix for scale " + String.valueOf(scale)); } } protected static String prefixSymbol(String symbol, int scale) { switch (scale) { case COIN_SCALE: return symbol; case 1: return "d" + symbol; case 2: return "¢" + symbol; case MILLICOIN_SCALE: return "₥" + symbol; case MICROCOIN_SCALE: return "µ" + symbol; case -1: return "da" + symbol; case -2: return "h" + symbol; case -3: return "k" + symbol; case -6: return "M" + symbol; default: throw new IllegalStateException("No known prefix for scale " + String.valueOf(scale)); } } /** Guarantee a formatting pattern has a subpattern for negative values. This method takes * a pattern that may be missing a negative subpattern, and returns the same pattern with * a negative subpattern appended as needed. * * <p>This method accommodates an imperfection in the Java formatting code and distributed * locale data. To wit: the subpattern for negative numbers is optional and not all * locales have one. In those cases, {@link java.text.DecimalFormat} will indicate numbers * less than zero by adding a negative sign as the first character of the prefix of the * positive subpattern. * * <p>We don't like this, since we claim the negative sign applies to the number not the * units, and therefore it ought to be adjacent to the number, displacing the * currency-units indicator if necessary. */ protected static String negify(String pattern) { if (pattern.contains(";")) return pattern; else { if (pattern.contains("-")) throw new IllegalStateException("Positive pattern contains negative sign"); // the regex matches everything until the first non-quoted number character return pattern + ";" + pattern.replaceFirst("^([^#0,.']*('[^']*')?)*", "$0-"); } } /** * Return an array of all locales for which the getInstance() method of this class can * return localized instances. See {@link java.text.NumberFormat#getAvailableLocales()} */ public static Locale[] getAvailableLocales() { return NumberFormat.getAvailableLocales(); } /** Return the unprefixed currency symbol for bitcoins configured for this object. The * return value of this method is constant throughough the life of an instance. */ public String coinSymbol() { synchronized(numberFormat) { return numberFormat.getDecimalFormatSymbols().getCurrencySymbol(); }} /** Return the unprefixed international currency code for bitcoins configured for this * object. The return value of this method is constant throughough the life of an instance. */ public String coinCode() { synchronized(numberFormat) { return numberFormat.getDecimalFormatSymbols().getInternationalCurrencySymbol(); }} /** Return a representation of the pattern used by this instance for formatting and * parsing. The format is similar to, but not the same as the format recognized by the * {@link Builder#pattern} and {@link Builder#localizedPattern} methods. The pattern * returned by this method is localized, any currency signs expressed are literally, and * optional fractional decimal places are shown grouped in parentheses. */ public String pattern() { synchronized(numberFormat) { StringBuilder groups = new StringBuilder(); for (int group : decimalGroups) { groups.append("(").append(Strings.repeat("#", group)).append(")"); } DecimalFormatSymbols s = numberFormat.getDecimalFormatSymbols(); String digit = String.valueOf(s.getDigit()); String exp = s.getExponentSeparator(); String groupSep = String.valueOf(s.getGroupingSeparator()); String moneySep = String.valueOf(s.getMonetaryDecimalSeparator()); String zero = String.valueOf(s.getZeroDigit()); String boundary = String.valueOf(s.getPatternSeparator()); String minus = String.valueOf(s.getMinusSign()); String decSep = String.valueOf(s.getDecimalSeparator()); String prefixAndNumber = "(^|" + boundary+ ")" + "([^" + Matcher.quoteReplacement(digit + zero + groupSep + decSep + moneySep) + "']*('[^']*')?)*" + "[" + Matcher.quoteReplacement(digit + zero + groupSep + decSep + moneySep + exp) + "]+"; return numberFormat.toLocalizedPattern(). replaceAll(prefixAndNumber, "$0" + groups.toString()). replaceAll("¤¤", Matcher.quoteReplacement(coinCode())). replaceAll("¤", Matcher.quoteReplacement(coinSymbol())); }} /** Return a copy of the localized symbols used by this instance for formatting and parsing. */ public DecimalFormatSymbols symbols() { synchronized(numberFormat) { return numberFormat.getDecimalFormatSymbols(); }} /** Return true if the given object is equivalent to this one. * Formatters for different locales will never be equal, even * if they behave identically. */ @Override public boolean equals(Object o) { if (o == this) return true; if (!(o instanceof BtcFormat)) return false; BtcFormat other = (BtcFormat)o; return other.pattern().equals(pattern()) && other.symbols().equals(symbols()) && other.minimumFractionDigits == minimumFractionDigits; } /** Return a hash code value for this instance. * @see java.lang.Object#hashCode */ @Override public int hashCode() { return Objects.hashCode(pattern(), symbols(), minimumFractionDigits, decimalGroups); } }