| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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 freemarker.core; |
| |
| import static freemarker.test.hamcerst.Matchers.*; |
| import static org.hamcrest.Matchers.*; |
| import static org.junit.Assert.*; |
| |
| import java.io.IOException; |
| import java.text.DecimalFormat; |
| import java.text.ParseException; |
| import java.util.Locale; |
| |
| import org.junit.Test; |
| |
| import freemarker.template.Configuration; |
| import freemarker.template.TemplateException; |
| import freemarker.test.TemplateTest; |
| |
| public class ExtendedDecimalFormatTest extends TemplateTest { |
| |
| private static final Locale LOC = Locale.US; |
| |
| @Test |
| public void testNonExtended() throws ParseException { |
| for (String fStr : new String[] { "0.00", "0.###", "#,#0.###", "#0.####", "0.0;m", "0.0;", |
| "0'x'", "0'x';'m'", "0';'", "0';';m", "0';';'#'m';'", "0';;'", "" }) { |
| assertFormatsEquivalent(new DecimalFormat(fStr), ExtendedDecimalFormatParser.parse(fStr, LOC)); |
| } |
| |
| try { |
| new DecimalFormat(";"); |
| fail(); |
| } catch (IllegalArgumentException e) { |
| // Expected |
| } |
| try { |
| ExtendedDecimalFormatParser.parse(";", LOC); |
| } catch (ParseException e) { |
| // Expected |
| } |
| } |
| |
| @Test |
| public void testNonExtended2() throws ParseException { |
| assertFormatsEquivalent(new DecimalFormat("0.0"), ExtendedDecimalFormatParser.parse("0.0;", LOC)); |
| assertFormatsEquivalent(new DecimalFormat("0.0"), ExtendedDecimalFormatParser.parse("0.0;;", LOC)); |
| assertFormatsEquivalent(new DecimalFormat("0.0;m"), ExtendedDecimalFormatParser.parse("0.0;m;", LOC)); |
| assertFormatsEquivalent(new DecimalFormat(""), ExtendedDecimalFormatParser.parse(";;", LOC)); |
| assertFormatsEquivalent(new DecimalFormat("0'x'"), ExtendedDecimalFormatParser.parse("0'x';;", LOC)); |
| assertFormatsEquivalent(new DecimalFormat("0'x';'m'"), ExtendedDecimalFormatParser.parse("0'x';'m';", LOC)); |
| assertFormatsEquivalent(new DecimalFormat("0';'"), ExtendedDecimalFormatParser.parse("0';';;", LOC)); |
| assertFormatsEquivalent(new DecimalFormat("0';';m"), ExtendedDecimalFormatParser.parse("0';';m;", LOC)); |
| assertFormatsEquivalent(new DecimalFormat("0';';'#'m';'"), ExtendedDecimalFormatParser.parse("0';';'#'m';';", |
| LOC)); |
| assertFormatsEquivalent(new DecimalFormat("0';;'"), ExtendedDecimalFormatParser.parse("0';;';;", LOC)); |
| |
| try { |
| new DecimalFormat(";m"); |
| fail(); |
| } catch (IllegalArgumentException e) { |
| // Expected |
| } |
| try { |
| new DecimalFormat("; ;"); |
| fail(); |
| } catch (IllegalArgumentException e) { |
| // Expected |
| } |
| try { |
| ExtendedDecimalFormatParser.parse("; ;", LOC); |
| fail(); |
| } catch (ParseException e) { |
| // Expected |
| } |
| try { |
| ExtendedDecimalFormatParser.parse(";m", LOC); |
| fail(); |
| } catch (ParseException e) { |
| // Expected |
| } |
| try { |
| ExtendedDecimalFormatParser.parse(";m;", LOC); |
| fail(); |
| } catch (ParseException e) { |
| // Expected |
| } |
| } |
| |
| @SuppressWarnings("boxing") |
| @Test |
| public void testExtendedParamsParsing() throws ParseException { |
| for (String fs : new String[] { |
| "00.##;; decimalSeparator='D'", |
| "00.##;;decimalSeparator=D", |
| "00.##;; decimalSeparator = D ", "00.##;; decimalSeparator = 'D' " }) { |
| assertFormatted(fs, 1.125, "01D12"); |
| } |
| for (String fs : new String[] { |
| ",#0.0;; decimalSeparator=D, groupingSeparator=_", |
| ",#0.0;;decimalSeparator=D,groupingSeparator=_", |
| ",#0.0;; decimalSeparator = D , groupingSeparator = _ ", |
| ",#0.0;; decimalSeparator='D', groupingSeparator='_'" |
| }) { |
| assertFormatted(fs, 12345, "1_23_45D0"); |
| } |
| |
| assertFormatted("0.0;;infinity=infinity", Double.POSITIVE_INFINITY, "infinity"); |
| assertFormatted("0.0;;infinity='infinity'", Double.POSITIVE_INFINITY, "infinity"); |
| assertFormatted("0.0;;infinity=\"infinity\"", Double.POSITIVE_INFINITY, "infinity"); |
| assertFormatted("0.0;;infinity=''", Double.POSITIVE_INFINITY, ""); |
| assertFormatted("0.0;;infinity=\"\"", Double.POSITIVE_INFINITY, ""); |
| assertFormatted("0.0;;infinity='x''y'", Double.POSITIVE_INFINITY, "x'y"); |
| assertFormatted("0.0;;infinity=\"x'y\"", Double.POSITIVE_INFINITY, "x'y"); |
| assertFormatted("0.0;;infinity='x\"\"y'", Double.POSITIVE_INFINITY, "x\"\"y"); |
| assertFormatted("0.0;;infinity=\"x''y\"", Double.POSITIVE_INFINITY, "x''y"); |
| assertFormatted("0.0;;decimalSeparator=''''", 1, "1'0"); |
| assertFormatted("0.0;;decimalSeparator=\"'\"", 1, "1'0"); |
| assertFormatted("0.0;;decimalSeparator='\"'", 1, "1\"0"); |
| assertFormatted("0.0;;decimalSeparator=\"\"\"\"", 1, "1\"0"); |
| |
| try { |
| ExtendedDecimalFormatParser.parse(";;decimalSeparator=D,", LOC); |
| fail(); |
| } catch (java.text.ParseException e) { |
| assertThat(e.getMessage(), |
| allOf(containsStringIgnoringCase("expected a(n) name"), containsString(" end of "))); |
| } |
| try { |
| ExtendedDecimalFormatParser.parse(";;foo=D,", LOC); |
| fail(); |
| } catch (java.text.ParseException e) { |
| assertThat(e.getMessage(), |
| allOf(containsString("\"foo\""), containsString("name"))); |
| } |
| try { |
| ExtendedDecimalFormatParser.parse(";;decimalSeparator='D", LOC); |
| fail(); |
| } catch (java.text.ParseException e) { |
| assertThat(e.getMessage(), |
| allOf(containsString("quotation"), containsString("closed"))); |
| } |
| try { |
| ExtendedDecimalFormatParser.parse(";;decimalSeparator=\"D", LOC); |
| fail(); |
| } catch (java.text.ParseException e) { |
| assertThat(e.getMessage(), |
| allOf(containsString("quotation"), containsString("closed"))); |
| } |
| try { |
| ExtendedDecimalFormatParser.parse(";;decimalSeparator='D'groupingSeparator=G", LOC); |
| fail(); |
| } catch (java.text.ParseException e) { |
| assertThat(e.getMessage(), allOf( |
| containsString("separator"), containsString("whitespace"), containsString("comma"))); |
| } |
| try { |
| ExtendedDecimalFormatParser.parse(";;decimalSeparator=., groupingSeparator=G", LOC); |
| fail(); |
| } catch (java.text.ParseException e) { |
| assertThat(e.getMessage(), allOf( |
| containsStringIgnoringCase("expected a(n) value"), containsString("., gr[...]"))); |
| } |
| try { |
| ExtendedDecimalFormatParser.parse("0.0;;decimalSeparator=''", LOC); |
| fail(); |
| } catch (java.text.ParseException e) { |
| assertThat(e.getMessage(), allOf( |
| containsStringIgnoringCase("\"decimalSeparator\""), containsString("exactly 1 char"))); |
| } |
| try { |
| ExtendedDecimalFormatParser.parse("0.0;;multipier=ten", LOC); |
| fail(); |
| } catch (java.text.ParseException e) { |
| assertThat(e.getMessage(), allOf( |
| containsString("\"multipier\""), containsString("\"ten\""), containsString("integer"))); |
| } |
| } |
| |
| @SuppressWarnings("boxing") |
| @Test |
| public void testExtendedParamsEffect() throws ParseException { |
| assertFormatted("0", |
| 1.5, "2", 2.5, "2", 3.5, "4", 1.4, "1", 1.6, "2", -1.4, "-1", -1.5, "-2", -2.5, "-2", -1.6, "-2"); |
| assertFormatted("0;; roundingMode=halfEven", |
| 1.5, "2", 2.5, "2", 3.5, "4", 1.4, "1", 1.6, "2", -1.4, "-1", -1.5, "-2", -2.5, "-2", -1.6, "-2"); |
| assertFormatted("0;; roundingMode=halfUp", |
| 1.5, "2", 2.5, "3", 3.5, "4", 1.4, "1", 1.6, "2", -1.4, "-1", -1.5, "-2", -2.5, "-3", -1.6, "-2"); |
| assertFormatted("0;; roundingMode=halfDown", |
| 1.5, "1", 2.5, "2", 3.5, "3", 1.4, "1", 1.6, "2", -1.4, "-1", -1.5, "-1", -2.5, "-2", -1.6, "-2"); |
| assertFormatted("0;; roundingMode=floor", |
| 1.5, "1", 2.5, "2", 3.5, "3", 1.4, "1", 1.6, "1", -1.4, "-2", -1.5, "-2", -2.5, "-3", -1.6, "-2"); |
| assertFormatted("0;; roundingMode=ceiling", |
| 1.5, "2", 2.5, "3", 3.5, "4", 1.4, "2", 1.6, "2", -1.4, "-1", -1.5, "-1", -2.5, "-2", -1.6, "-1"); |
| assertFormatted("0;; roundingMode=up", |
| 1.5, "2", 2.5, "3", 3.5, "4", 1.4, "2", 1.6, "2", -1.4, "-2", -1.5, "-2", -2.5, "-3", -1.6, "-2"); |
| assertFormatted("0;; roundingMode=down", |
| 1.5, "1", 2.5, "2", 3.5, "3", 1.4, "1", 1.6, "1", -1.4, "-1", -1.5, "-1", -2.5, "-2", -1.6, "-1"); |
| assertFormatted("0;; roundingMode=unnecessary", 2, "2"); |
| try { |
| assertFormatted("0;; roundingMode=unnecessary", 2.5, "2"); |
| fail(); |
| } catch (ArithmeticException e) { |
| // Expected |
| } |
| |
| assertFormatted("0.##;; multipier=100", 12.345, "1234.5"); |
| assertFormatted("0.##;; multipier=1000", 12.345, "12345"); |
| |
| assertFormatted(",##0.##;; groupingSeparator=_ decimalSeparator=D", 12345.1, "12_345D1", 1, "1"); |
| |
| assertFormatted("0.##E0;; exponentSeparator='*10^'", 12345.1, "1.23*10^4"); |
| |
| assertFormatted("0.##;; minusSign=m", -1, "m1", 1, "1"); |
| |
| assertFormatted("0.##;; infinity=foo", Double.POSITIVE_INFINITY, "foo", Double.NEGATIVE_INFINITY, "-foo"); |
| |
| assertFormatted("0.##;; nan=foo", Double.NaN, "foo"); |
| |
| assertFormatted("0%;; percent='c'", 0.75, "75c"); |
| |
| assertFormatted("0\u2030;; perMill='m'", 0.75, "750m"); |
| |
| assertFormatted("0.00;; zeroDigit='@'", 10.5, "A@.E@"); |
| |
| assertFormatted("0;; currencyCode=USD", 10, "10"); |
| assertFormatted("0 \u00A4;; currencyCode=USD", 10, "10 $"); |
| assertFormatted("0 \u00A4\u00A4;; currencyCode=USD", 10, "10 USD"); |
| assertFormatted(Locale.GERMANY, "0 \u00A4;; currencyCode=EUR", 10, "10 \u20AC"); |
| assertFormatted(Locale.GERMANY, "0 \u00A4\u00A4;; currencyCode=EUR", 10, "10 EUR"); |
| try { |
| assertFormatted("0;; currencyCode=USDX", 10, "10"); |
| } catch (ParseException e) { |
| assertThat(e.getMessage(), containsString("ISO 4217")); |
| } |
| assertFormatted("0 \u00A4;; currencyCode=USD currencySymbol=bucks", 10, "10 bucks"); |
| // Order doesn't mater: |
| assertFormatted("0 \u00A4;; currencySymbol=bucks currencyCode=USD", 10, "10 bucks"); |
| // International symbol isn't affected: |
| assertFormatted("0 \u00A4\u00A4;; currencyCode=USD currencySymbol=bucks", 10, "10 USD"); |
| |
| assertFormatted("0.0 \u00A4;; monetaryDecimalSeparator=m", 10.5, "10m5 $"); |
| assertFormatted("0.0 kg;; monetaryDecimalSeparator=m", 10.5, "10.5 kg"); |
| assertFormatted("0.0 \u00A4;; decimalSeparator=d", 10.5, "10.5 $"); |
| assertFormatted("0.0 kg;; decimalSeparator=d", 10.5, "10d5 kg"); |
| assertFormatted("0.0 \u00A4;; monetaryDecimalSeparator=m decimalSeparator=d", 10.5, "10m5 $"); |
| assertFormatted("0.0 kg;; monetaryDecimalSeparator=m decimalSeparator=d", 10.5, "10d5 kg"); |
| } |
| |
| @Test |
| public void testLocale() throws ParseException { |
| assertEquals("1000.0", ExtendedDecimalFormatParser.parse("0.0", Locale.US).format(1000)); |
| assertEquals("1000,0", ExtendedDecimalFormatParser.parse("0.0", Locale.FRANCE).format(1000)); |
| assertEquals("1_000.0", ExtendedDecimalFormatParser.parse(",000.0;;groupingSeparator=_", Locale.US).format(1000)); |
| assertEquals("1_000,0", ExtendedDecimalFormatParser.parse(",000.0;;groupingSeparator=_", Locale.FRANCE).format(1000)); |
| } |
| |
| @Test |
| public void testTemplates() throws IOException, TemplateException { |
| Configuration cfg = getConfiguration(); |
| cfg.setLocale(Locale.US); |
| |
| cfg.setNumberFormat(",000.#"); |
| assertOutput("${1000.15} ${1000.25}", "1,000.2 1,000.2"); |
| cfg.setNumberFormat(",000.#;; roundingMode=halfUp groupingSeparator=_"); |
| assertOutput("${1000.15} ${1000.25}", "1_000.2 1_000.3"); |
| cfg.setLocale(Locale.GERMANY); |
| assertOutput("${1000.15} ${1000.25}", "1_000,2 1_000,3"); |
| cfg.setLocale(Locale.US); |
| assertOutput( |
| "${1000.15}; " |
| + "${1000.15?string(',##.#;;groupingSeparator=\" \"')}; " |
| + "<#setting locale='de_DE'>${1000.15}; " |
| + "<#setting numberFormat='0.0;;roundingMode=down'>${1000.15}", |
| "1_000.2; 10 00.2; 1_000,2; 1000,1"); |
| assertErrorContains("${1?string('#E')}", |
| TemplateException.class, "\"#E\"", "format string", "exponential"); |
| assertErrorContains("<#setting numberFormat='#E'>${1}", |
| TemplateException.class, "\"#E\"", "format string", "exponential"); |
| assertErrorContains("<#setting numberFormat=';;foo=bar'>${1}", |
| TemplateException.class, "\"foo\"", "supported"); |
| assertErrorContains("<#setting numberFormat='0;;roundingMode=unnecessary'>${1.5}", |
| TemplateException.class, "can't format", "1.5", "UNNECESSARY"); |
| } |
| |
| private void assertFormatted(String formatString, Object... numberAndExpectedOutput) throws ParseException { |
| assertFormatted(LOC, formatString, numberAndExpectedOutput); |
| } |
| |
| private void assertFormatted(Locale loc, String formatString, Object... numberAndExpectedOutput) throws ParseException { |
| if (numberAndExpectedOutput.length % 2 != 0) { |
| throw new IllegalArgumentException(); |
| } |
| |
| DecimalFormat df = ExtendedDecimalFormatParser.parse(formatString, loc); |
| Number num = null; |
| for (int i = 0; i < numberAndExpectedOutput.length; i++) { |
| if (i % 2 == 0) { |
| num = (Number) numberAndExpectedOutput[i]; |
| } else { |
| assertEquals(numberAndExpectedOutput[i], df.format(num)); |
| } |
| } |
| } |
| |
| private void assertFormatsEquivalent(DecimalFormat dfExpected, DecimalFormat dfActual) { |
| for (int signum : new int[] { 1, -1 }) { |
| assertFormatsEquivalent(dfExpected, dfActual, signum * 0); |
| assertFormatsEquivalent(dfExpected, dfActual, signum * 0.5); |
| assertFormatsEquivalent(dfExpected, dfActual, signum * 0.25); |
| assertFormatsEquivalent(dfExpected, dfActual, signum * 0.125); |
| assertFormatsEquivalent(dfExpected, dfActual, signum * 1); |
| assertFormatsEquivalent(dfExpected, dfActual, signum * 10); |
| assertFormatsEquivalent(dfExpected, dfActual, signum * 100); |
| assertFormatsEquivalent(dfExpected, dfActual, signum * 1000); |
| assertFormatsEquivalent(dfExpected, dfActual, signum * 10000); |
| assertFormatsEquivalent(dfExpected, dfActual, signum * 100000); |
| } |
| } |
| |
| private void assertFormatsEquivalent(DecimalFormat dfExpected, DecimalFormat dfActual, double n) { |
| assertEquals(dfExpected.format(n), dfActual.format(n)); |
| } |
| |
| } |