/
Numbers.java
295 lines (261 loc) · 12 KB
/
Numbers.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
/*
* Copyright OmniFaces
*
* 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
*
* https://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.omnifaces.el.functions;
import static java.lang.String.format;
import static org.omnifaces.util.Faces.getLocale;
import static org.omnifaces.util.Utils.parseLocale;
import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.NumberFormat;
import java.text.ParseException;
import java.util.Locale;
import org.omnifaces.util.Faces;
/**
* <p>
* Collection of EL functions for number formatting: <code>of:formatBytes()</code>, <code>of:formatCurrency()</code>,
* <code>of:formatNumber()</code>, <code>of:formatNumberDefault()</code>, <code>of:formatPercent()</code>,
* <code>of:formatThousands()</code> and <code>of:formatThousandsUnit()</code>.
*
* @author Bauke Scholtz
* @since 1.2
*/
public final class Numbers {
// Constants ------------------------------------------------------------------------------------------------------
private static final int BYTES_1K = 1024;
private static final int NUMBER_1K = 1000;
private static final int PRECISION = 3;
// Constructors ---------------------------------------------------------------------------------------------------
private Numbers() {
// Hide constructor.
}
// Utility --------------------------------------------------------------------------------------------------------
/**
* Format the given bytes to nearest 10<sup>n</sup> with IEC binary unit (KiB, MiB, etc) with rounding precision of
* 1 fraction. For example:
* <ul>
* <li>1023 bytes will appear as 1023 B
* <li>1024 bytes will appear as 1.0 KiB
* <li>500000 bytes will appear as 488.3 KiB
* <li>1048576 bytes will appear as 1.0 MiB
* </ul>
* The format locale will be set to the one as obtained by {@link Faces#getLocale()}.
* @param bytes The bytes to be formatted.
* @return The formatted bytes.
*/
public static String formatBytes(Long bytes) {
return formatBaseUnit(bytes, BYTES_1K, 1, true, "B");
}
/**
* Format the given number as currency with the given symbol. This is useful when you want to format numbers as
* currency in for example the <code>title</code> attribute of an UI component, or the <code>itemLabel</code>
* attribute of select item, or wherever you can't use the <code><f:convertNumber></code> tag. The format
* locale will be set to the one as obtained by {@link Faces#getLocale()}.
* @param number The number to be formatted as currency.
* @param currencySymbol The currency symbol to be used.
* @return The number which is formatted as currency with the given symbol.
* @throws NullPointerException When the currency symbol is <code>null</code>.
*/
public static String formatCurrency(Number number, String currencySymbol) {
if (number == null) {
return null;
}
DecimalFormat formatter = (DecimalFormat) NumberFormat.getCurrencyInstance(getLocale());
DecimalFormatSymbols symbols = formatter.getDecimalFormatSymbols();
symbols.setCurrencySymbol(currencySymbol);
formatter.setDecimalFormatSymbols(symbols);
return formatter.format(number);
}
/**
* Format the given number in the given pattern. This is useful when you want to format numbers in for example the
* <code>title</code> attribute of an UI component, or the <code>itemLabel</code> attribute of select item, or
* wherever you can't use the <code><f:convertNumber></code> tag. The format locale will be set to the one as
* obtained by {@link Faces#getLocale()}.
* @param number The number to be formatted in the given pattern.
* @param pattern The pattern to format the given number in.
* @return The number which is formatted in the given pattern.
* @throws NullPointerException When the pattern is <code>null</code>.
*/
public static String formatNumber(Number number, String pattern) {
if (number == null) {
return null;
}
DecimalFormat formatter = (DecimalFormat) NumberFormat.getNumberInstance(getLocale());
formatter.applyPattern(pattern);
return formatter.format(number);
}
/**
* Format the given number in the default pattern of the default locale. This is useful when you want to format
* numbers in for example the <code>title</code> attribute of an UI component, or the <code>itemLabel</code>
* attribute of select item, or wherever you can't use the <code><f:convertNumber></code> tag. The default
* locale is the one as obtained by {@link Faces#getLocale()}.
* @param number The number to be formatted in the default pattern of the default locale.
* @return The number which is formatted in the default pattern of the default locale.
* @since 1.3
*/
public static String formatNumberDefault(Number number) {
return formatNumberDefaultForLocale(number, getLocale());
}
/**
* Format the given number in the default pattern of the given locale. This is useful when you want to format
* numbers in for example the <code>title</code> attribute of an UI component, or the <code>itemLabel</code>
* attribute of select item, or wherever you can't use the <code><f:convertNumber></code> tag. The given
* locale can be a {@link Locale} object or a string representation.
* @param number The number to be formatted in the default pattern of the given locale.
* @param locale The locale to obtain the default pattern from.
* @return The number which is formatted in the default pattern of the given locale.
* @since 2.3
*/
public static String formatNumberDefaultForLocale(Number number, Object locale) {
if (number == null) {
return null;
}
return NumberFormat.getNumberInstance(parseLocale(locale)).format(number);
}
/**
* Format the given number as percentage. This is useful when you want to format numbers as
* percentage in for example the <code>title</code> attribute of an UI component, or the <code>itemLabel</code>
* attribute of select item, or wherever you can't use the <code><f:convertNumber></code> tag. The format
* locale will be set to the one as obtained by {@link Faces#getLocale()}.
* @param number The number to be formatted as percentage.
* @return The number which is formatted as percentage.
* @since 1.6
*/
public static String formatPercent(Number number) {
if (number == null) {
return null;
}
return NumberFormat.getPercentInstance(getLocale()).format(number);
}
/**
* Format the given number to nearest 10<sup>n</sup> (rounded to thousands), immediately suffixed (without space)
* with metric unit (k, M, G, T, P or E), rounding half up with a precision of 3 digits, whereafter trailing zeroes
* in fraction part are stripped.
* For example (with English locale):
* <ul>
* <li>1.6666 will appear as 1.67
* <li>999.4 will appear as 999
* <li>999.5 will appear as 1k
* <li>1004 will appear as 1k
* <li>1005 will appear as 1.01k
* <li>1594 will appear as 1.59k
* <li>1595 will appear as 1.6k
* <li>9000 will appear as 9k
* <li>9900 will appear as 9.9k
* <li>9994 will appear as 9.99k
* <li>9995 will appear as 10k
* <li>99990 will appear as 100k
* <li>9994999 will appear as 9.99M
* <li>9995000 will appear as 10M
* </ul>
* The format locale will be set to the one as obtained by {@link Faces#getLocale()}.
* If the value is <code>null</code>, <code>NaN</code> or infinity, then this will return <code>null</code>.
* @param number The number to be formatted.
* @return The formatted number.
* @since 2.3
*/
public static String formatThousands(Number number) {
return formatThousandsUnit(number, null);
}
/**
* Format the given number to nearest 10<sup>n</sup> (rounded to thousands), suffixed with a space, the metric unit
* prefix (k, M, G, T, P or E) and the given unit, rounding half up with a precision of 3 digits, whereafter
* trailing zeroes in fraction part are stripped.
* For example (with English locale and unit <code>B</code>):
* <ul>
* <li>1.6666 will appear as 1.67 B
* <li>999.4 will appear as 999 B
* <li>999.5 will appear as 1 kB
* <li>1004 will appear as 1 kB
* <li>1005 will appear as 1.01 kB
* <li>1594 will appear as 1.59 kB
* <li>1595 will appear as 1.6 kB
* <li>9000 will appear as 9 kB
* <li>9900 will appear as 9.9 kB
* <li>9994 will appear as 9.99 kB
* <li>9995 will appear as 10 kB
* <li>99990 will appear as 100 kB
* <li>9994999 will appear as 9.99 MB
* <li>9995000 will appear as 10 MB
* </ul>
* The format locale will be set to the one as obtained by {@link Faces#getLocale()}.
* If the value is <code>null</code>, <code>NaN</code> or infinity, then this will return <code>null</code>.
* @param number The number to be formatted.
* @param unit The unit used in the format. E.g. <code>B</code> for Bytes, <code>W</code> for Watt, etc. If the unit
* is <code>null</code>, then this method will behave exactly as described in {@link #formatThousands(Number)}.
* @return The formatted number with unit.
* @since 2.3
*/
public static String formatThousandsUnit(Number number, String unit) {
return formatBaseUnit(number, NUMBER_1K, null, false, unit);
}
/**
* @param number Number to be formatted.
* @param base Rounding base.
* @param fractions Fraction length. If null, then precision of 3 digits will be assumed and all trailing zeroes will be stripped.
* @param iec IEC or metric. If IEC, then unit prefix "Ki", "Mi", "Gi", etc will be used, else "k", "M", "G", etc.
* @param unit Unit suffix. If null, then there is no space separator between number and unit prefix.
*/
private static String formatBaseUnit(Number number, int base, Integer fractions, boolean iec, String unit) {
if (number == null) {
return null;
}
BigDecimal decimal;
try {
decimal = (number instanceof BigDecimal) ? ((BigDecimal) number) : new BigDecimal(number.toString());
}
catch (NumberFormatException e) {
return null;
}
return formatBase(decimal, base, fractions, iec, unit);
}
private static String formatBase(BigDecimal decimal, int base, Integer fractions, boolean iec, String unit) {
int exponent = (int) (Math.log(decimal.abs().longValue()) / Math.log(base));
BigDecimal divisor = BigDecimal.valueOf(Math.pow(base, exponent));
BigDecimal divided = (divisor.signum() == 0) ? divisor : decimal.divide(divisor);
int maxfractions = (fractions != null) ? fractions : (PRECISION - String.valueOf(divided.abs().longValue()).length());
BigDecimal formatted;
try {
DecimalFormat formatter = (DecimalFormat) NumberFormat.getNumberInstance(getLocale());
formatter.setParseBigDecimal(true);
String format = "%." + maxfractions + "f";
formatted = (BigDecimal) formatter.parse(format(getLocale(), format, divided));
}
catch (ParseException e) {
throw new IllegalStateException(e);
}
if (formatted.longValue() >= base) { // E.g. 999.5 becomes 1000 which needs to be reformatted as 1k.
return formatBase(formatted, base, fractions, iec, unit);
}
else {
return formatUnit(formatted, iec, unit, exponent, maxfractions > 0 && fractions == null);
}
}
private static String formatUnit(BigDecimal decimal, boolean iec, String unit, int exponent, boolean stripZeroes) {
String formatted = decimal.toString();
if (stripZeroes) {
formatted = formatted.replaceAll("\\D?0+$", "");
}
String separator = (unit == null) ? "" : " ";
String unitString = (unit == null) ? "" : unit;
return formatted + separator + getUnitPrefix(iec, exponent) + unitString;
}
private static String getUnitPrefix(boolean iec, int exponent) {
if (exponent < 1) {
return "";
}
char unitPrefix = ((iec ? "K" : "k") + "MGTPE").charAt(exponent - 1);
String binaryPrefix = (iec ? "i" : "");
return unitPrefix + binaryPrefix;
}
}