/* * Copyright 2017 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 * * 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.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.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.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 unitPrefix = (exponent > 0) ? ((iec ? "K" : "k") + "MGTPE").charAt(exponent - 1) + (iec ? "i" : "") : ""; String unitString = (unit == null) ? "" : unit; return formatted + separator + unitPrefix + unitString; } }