blob: 264c1ea5f5a79dccc3941c19df279d52fb5c3c83 [file] [log] [blame]
/*
* 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));
}
}