OLD | NEW |
| (Empty) |
1 // Copyright 2016 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 package org.chromium.chrome.browser.payments; | |
6 | |
7 import java.text.DecimalFormatSymbols; | |
8 import java.util.Currency; | |
9 import java.util.Locale; | |
10 import java.util.regex.Matcher; | |
11 import java.util.regex.Pattern; | |
12 | |
13 /** | |
14 * Formatter for currency strings that can be too large to parse into numbers. | |
15 * https://w3c.github.io/browser-payment-api/specs/paymentrequest.html#currencya
mount | |
16 */ | |
17 public class CurrencyStringFormatter { | |
18 // Amount value pattern and capture group numbers. | |
19 private static final String AMOUNT_VALUE_PATTERN = "^(-?)([0-9]+)(\\.([0-9]+
))?$"; | |
20 private static final int OPTIONAL_NEGATIVE_GROUP = 1; | |
21 private static final int DIGITS_BETWEEN_NEGATIVE_AND_PERIOD_GROUP = 2; | |
22 private static final int DIGITS_AFTER_PERIOD_GROUP = 4; | |
23 | |
24 // Max currency code length. Maximum length of currency code can be at most
2048. | |
25 private static final int MAX_CURRENCY_CODE_LEN = 2048; | |
26 | |
27 // Currency code exceeding 6 chars will be ellipsized during formatting for
display. | |
28 private static final int MAX_CURRENCY_CHARS = 6; | |
29 | |
30 // Unicode character for ellipsis. | |
31 private static final String ELLIPSIS = "\u2026"; | |
32 | |
33 // Formatting constants. | |
34 private static final int DIGIT_GROUPING_SIZE = 3; | |
35 | |
36 private final Pattern mAmountValuePattern; | |
37 | |
38 /** | |
39 * The currency formatted for display. Currency can be any string of at most | |
40 * 2048 characters.Currency code more than 6 character is formatted to first | |
41 * 5 characters and ellipsis. | |
42 */ | |
43 public final String mFormattedCurrencyCode; | |
44 | |
45 /** | |
46 * The symbol for the currency specified on the bill. For example, the symbo
l for "USD" is "$". | |
47 */ | |
48 private final String mCurrencySymbol; | |
49 | |
50 /** | |
51 * The number of digits after the decimal separator for the currency specifi
ed on the bill. For | |
52 * example, 2 for "USD" and 0 for "JPY". | |
53 */ | |
54 private final int mDefaultFractionDigits; | |
55 | |
56 /** | |
57 * The number grouping separator for the current locale. For example, "," in
US. 3-digit groups | |
58 * are assumed. | |
59 */ | |
60 private final char mGroupingSeparator; | |
61 | |
62 /** | |
63 * The monetary decimal separator for the current locale. For example, "." i
n US and "," in | |
64 * France. | |
65 */ | |
66 private final char mMonetaryDecimalSeparator; | |
67 | |
68 /** | |
69 * Builds the formatter for the given currency code and the current user loc
ale. | |
70 * | |
71 * @param currencyCode The currency code. Most commonly, this follows ISO 42
17 format: 3 upper | |
72 * case ASCII letters. For example, "USD". Format is not
restricted. Should | |
73 * not be null. | |
74 * @param userLocale User's current locale. Should not be null. | |
75 */ | |
76 public CurrencyStringFormatter(String currencyCode, Locale userLocale) { | |
77 assert currencyCode != null : "currencyCode should not be null"; | |
78 assert userLocale != null : "userLocale should not be null"; | |
79 | |
80 mAmountValuePattern = Pattern.compile(AMOUNT_VALUE_PATTERN); | |
81 | |
82 mFormattedCurrencyCode = currencyCode.length() <= MAX_CURRENCY_CHARS | |
83 ? currencyCode | |
84 : currencyCode.substring(0, MAX_CURRENCY_CHARS - 1) + ELLIPSIS; | |
85 | |
86 String currencySymbol; | |
87 int defaultFractionDigits; | |
88 try { | |
89 Currency currency = Currency.getInstance(currencyCode); | |
90 currencySymbol = currency.getSymbol(); | |
91 defaultFractionDigits = currency.getDefaultFractionDigits(); | |
92 } catch (IllegalArgumentException e) { | |
93 // The spec does not limit the currencies to official ISO 4217 curre
ncy code list, which | |
94 // is used by java.util.Currency. For example, "BTX" (bitcoin) is no
t an official ISO | |
95 // 4217 currency code, but is allowed by the spec. | |
96 currencySymbol = ""; | |
97 defaultFractionDigits = 0; | |
98 } | |
99 | |
100 // If the prefix of the currency symbol matches the prefix of the curren
cy code, remove the | |
101 // matching prefix from the symbol. The UI already shows the currency co
de, so there's no | |
102 // need to show duplicate information. | |
103 String symbol = ""; | |
104 for (int i = 0; i < currencySymbol.length(); i++) { | |
105 if (i >= currencyCode.length() || currencySymbol.charAt(i) != curren
cyCode.charAt(i)) { | |
106 symbol = currencySymbol.substring(i); | |
107 break; | |
108 } | |
109 } | |
110 mCurrencySymbol = symbol; | |
111 | |
112 mDefaultFractionDigits = defaultFractionDigits; | |
113 | |
114 // Use the symbols from user's current locale. For example, use "," for
decimal separator in | |
115 // France, even if paying in "USD". | |
116 DecimalFormatSymbols symbols = new DecimalFormatSymbols(userLocale); | |
117 mGroupingSeparator = symbols.getGroupingSeparator(); | |
118 mMonetaryDecimalSeparator = symbols.getMonetaryDecimalSeparator(); | |
119 } | |
120 | |
121 /** | |
122 * Returns true if the amount value string is in valid format. | |
123 * | |
124 * @param amountValue The number to check for validity. | |
125 * @return Whether the number is in valid format. | |
126 */ | |
127 public boolean isValidAmountValue(String amountValue) { | |
128 return amountValue != null && mAmountValuePattern.matcher(amountValue).m
atches(); | |
129 } | |
130 | |
131 /** | |
132 * Returns true if the currency code string is in valid format. | |
133 * | |
134 * @param amountCurrencyCode The currency code to check for validity. | |
135 * @return Whether the currency code is in valid format. | |
136 */ | |
137 public boolean isValidAmountCurrencyCode(String amountCurrencyCode) { | |
138 return amountCurrencyCode != null && amountCurrencyCode.length() <= MAX_
CURRENCY_CODE_LEN; | |
139 } | |
140 | |
141 /** @return The currency code formatted for display. */ | |
142 public String getFormattedCurrencyCode() { | |
143 return mFormattedCurrencyCode; | |
144 } | |
145 | |
146 /** | |
147 * Formats the currency string for display. Does not parse the string into a
number, because it | |
148 * might be too large. The number is formatted for the current locale and fo
llows the symbol of | |
149 * the currency code. | |
150 * | |
151 * @param amountValue The number to format. Should be in "^-?[0-9]+(\.[0-9]+
)?$" format. Should | |
152 * not be null. | |
153 * @return The currency symbol followed by a space and the formatted number. | |
154 */ | |
155 public String format(String amountValue) { | |
156 assert amountValue != null : "amountValue should not be null"; | |
157 | |
158 Matcher m = mAmountValuePattern.matcher(amountValue); | |
159 | |
160 // Required to capture the groups. | |
161 boolean matches = m.matches(); | |
162 assert matches; | |
163 | |
164 StringBuilder result = new StringBuilder(m.group(OPTIONAL_NEGATIVE_GROUP
)); | |
165 result.append(mCurrencySymbol); | |
166 int digitStart = result.length(); | |
167 | |
168 result.append(m.group(DIGITS_BETWEEN_NEGATIVE_AND_PERIOD_GROUP)); | |
169 for (int i = result.length() - DIGIT_GROUPING_SIZE; i > digitStart; | |
170 i -= DIGIT_GROUPING_SIZE) { | |
171 result.insert(i, mGroupingSeparator); | |
172 } | |
173 | |
174 String decimals = m.group(DIGITS_AFTER_PERIOD_GROUP); | |
175 int numberOfDecimals = decimals == null ? 0 : decimals.length(); | |
176 | |
177 if (numberOfDecimals > 0 || mDefaultFractionDigits > 0) { | |
178 result.append(mMonetaryDecimalSeparator); | |
179 if (null != decimals) result.append(decimals); | |
180 | |
181 for (int i = numberOfDecimals; i < mDefaultFractionDigits; i++) { | |
182 result.append("0"); | |
183 } | |
184 } | |
185 | |
186 return result.toString(); | |
187 } | |
188 } | |
OLD | NEW |