Repository: incubator-freemarker Updated Branches: refs/heads/3 6774e61fa -> ef9687577
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ef968757/src/test/java/org/apache/freemarker/core/userpkg/BaseNTemplateNumberFormatFactory.java ---------------------------------------------------------------------- diff --git a/src/test/java/org/apache/freemarker/core/userpkg/BaseNTemplateNumberFormatFactory.java b/src/test/java/org/apache/freemarker/core/userpkg/BaseNTemplateNumberFormatFactory.java index f310d30..83e6116 100644 --- a/src/test/java/org/apache/freemarker/core/userpkg/BaseNTemplateNumberFormatFactory.java +++ b/src/test/java/org/apache/freemarker/core/userpkg/BaseNTemplateNumberFormatFactory.java @@ -21,12 +21,12 @@ package org.apache.freemarker.core.userpkg; import java.util.Locale; import org.apache.freemarker.core.Environment; -import org.apache.freemarker.core.InvalidFormatParametersException; -import org.apache.freemarker.core.TemplateFormatUtil; -import org.apache.freemarker.core.TemplateNumberFormat; -import org.apache.freemarker.core.TemplateNumberFormatFactory; -import org.apache.freemarker.core.TemplateValueFormatException; -import org.apache.freemarker.core.UnformattableValueException; +import org.apache.freemarker.core.valueformat.InvalidFormatParametersException; +import org.apache.freemarker.core.valueformat.TemplateFormatUtil; +import org.apache.freemarker.core.valueformat.TemplateNumberFormat; +import org.apache.freemarker.core.valueformat.TemplateNumberFormatFactory; +import org.apache.freemarker.core.valueformat.TemplateValueFormatException; +import org.apache.freemarker.core.valueformat.UnformattableValueException; import org.apache.freemarker.core.model.TemplateModelException; import org.apache.freemarker.core.model.TemplateNumberModel; import org.apache.freemarker.core.util._NumberUtil; http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ef968757/src/test/java/org/apache/freemarker/core/userpkg/EpochMillisDivTemplateDateFormatFactory.java ---------------------------------------------------------------------- diff --git a/src/test/java/org/apache/freemarker/core/userpkg/EpochMillisDivTemplateDateFormatFactory.java b/src/test/java/org/apache/freemarker/core/userpkg/EpochMillisDivTemplateDateFormatFactory.java index 3eb8969..3404729 100644 --- a/src/test/java/org/apache/freemarker/core/userpkg/EpochMillisDivTemplateDateFormatFactory.java +++ b/src/test/java/org/apache/freemarker/core/userpkg/EpochMillisDivTemplateDateFormatFactory.java @@ -23,13 +23,13 @@ import java.util.Locale; import java.util.TimeZone; import org.apache.freemarker.core.Environment; -import org.apache.freemarker.core.InvalidFormatParametersException; -import org.apache.freemarker.core.TemplateDateFormat; -import org.apache.freemarker.core.TemplateDateFormatFactory; -import org.apache.freemarker.core.TemplateFormatUtil; -import org.apache.freemarker.core.UnformattableValueException; -import org.apache.freemarker.core.UnknownDateTypeFormattingUnsupportedException; -import org.apache.freemarker.core.UnparsableValueException; +import org.apache.freemarker.core.valueformat.InvalidFormatParametersException; +import org.apache.freemarker.core.valueformat.TemplateDateFormat; +import org.apache.freemarker.core.valueformat.TemplateDateFormatFactory; +import org.apache.freemarker.core.valueformat.TemplateFormatUtil; +import org.apache.freemarker.core.valueformat.UnformattableValueException; +import org.apache.freemarker.core.valueformat.UnknownDateTypeFormattingUnsupportedException; +import org.apache.freemarker.core.valueformat.UnparsableValueException; import org.apache.freemarker.core.model.TemplateDateModel; import org.apache.freemarker.core.model.TemplateModelException; import org.apache.freemarker.core.util._StringUtil; http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ef968757/src/test/java/org/apache/freemarker/core/userpkg/EpochMillisTemplateDateFormatFactory.java ---------------------------------------------------------------------- diff --git a/src/test/java/org/apache/freemarker/core/userpkg/EpochMillisTemplateDateFormatFactory.java b/src/test/java/org/apache/freemarker/core/userpkg/EpochMillisTemplateDateFormatFactory.java index 4e3394b..4948722 100644 --- a/src/test/java/org/apache/freemarker/core/userpkg/EpochMillisTemplateDateFormatFactory.java +++ b/src/test/java/org/apache/freemarker/core/userpkg/EpochMillisTemplateDateFormatFactory.java @@ -23,12 +23,12 @@ import java.util.Locale; import java.util.TimeZone; import org.apache.freemarker.core.Environment; -import org.apache.freemarker.core.InvalidFormatParametersException; -import org.apache.freemarker.core.TemplateDateFormat; -import org.apache.freemarker.core.TemplateDateFormatFactory; -import org.apache.freemarker.core.TemplateFormatUtil; -import org.apache.freemarker.core.UnformattableValueException; -import org.apache.freemarker.core.UnparsableValueException; +import org.apache.freemarker.core.valueformat.InvalidFormatParametersException; +import org.apache.freemarker.core.valueformat.TemplateDateFormat; +import org.apache.freemarker.core.valueformat.TemplateDateFormatFactory; +import org.apache.freemarker.core.valueformat.TemplateFormatUtil; +import org.apache.freemarker.core.valueformat.UnformattableValueException; +import org.apache.freemarker.core.valueformat.UnparsableValueException; import org.apache.freemarker.core.model.TemplateDateModel; import org.apache.freemarker.core.model.TemplateModelException; http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ef968757/src/test/java/org/apache/freemarker/core/userpkg/HTMLISOTemplateDateFormatFactory.java ---------------------------------------------------------------------- diff --git a/src/test/java/org/apache/freemarker/core/userpkg/HTMLISOTemplateDateFormatFactory.java b/src/test/java/org/apache/freemarker/core/userpkg/HTMLISOTemplateDateFormatFactory.java index a46de8c..6e61053 100644 --- a/src/test/java/org/apache/freemarker/core/userpkg/HTMLISOTemplateDateFormatFactory.java +++ b/src/test/java/org/apache/freemarker/core/userpkg/HTMLISOTemplateDateFormatFactory.java @@ -23,14 +23,14 @@ import java.util.Locale; import java.util.TimeZone; import org.apache.freemarker.core.Environment; -import org.apache.freemarker.core.InvalidFormatParametersException; -import org.apache.freemarker.core.TemplateDateFormat; -import org.apache.freemarker.core.TemplateDateFormatFactory; -import org.apache.freemarker.core.TemplateFormatUtil; -import org.apache.freemarker.core.TemplateValueFormatException; -import org.apache.freemarker.core.UnformattableValueException; -import org.apache.freemarker.core.UnknownDateTypeFormattingUnsupportedException; -import org.apache.freemarker.core.UnparsableValueException; +import org.apache.freemarker.core.valueformat.InvalidFormatParametersException; +import org.apache.freemarker.core.valueformat.TemplateDateFormat; +import org.apache.freemarker.core.valueformat.TemplateDateFormatFactory; +import org.apache.freemarker.core.valueformat.TemplateFormatUtil; +import org.apache.freemarker.core.valueformat.TemplateValueFormatException; +import org.apache.freemarker.core.valueformat.UnformattableValueException; +import org.apache.freemarker.core.valueformat.UnknownDateTypeFormattingUnsupportedException; +import org.apache.freemarker.core.valueformat.UnparsableValueException; import org.apache.freemarker.core.model.TemplateDateModel; import org.apache.freemarker.core.model.TemplateModelException; import org.apache.freemarker.core.outputformat.impl.HTMLOutputFormat; http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ef968757/src/test/java/org/apache/freemarker/core/userpkg/HexTemplateNumberFormatFactory.java ---------------------------------------------------------------------- diff --git a/src/test/java/org/apache/freemarker/core/userpkg/HexTemplateNumberFormatFactory.java b/src/test/java/org/apache/freemarker/core/userpkg/HexTemplateNumberFormatFactory.java index 06bc6cb..565eedd 100644 --- a/src/test/java/org/apache/freemarker/core/userpkg/HexTemplateNumberFormatFactory.java +++ b/src/test/java/org/apache/freemarker/core/userpkg/HexTemplateNumberFormatFactory.java @@ -21,11 +21,11 @@ package org.apache.freemarker.core.userpkg; import java.util.Locale; import org.apache.freemarker.core.Environment; -import org.apache.freemarker.core.InvalidFormatParametersException; -import org.apache.freemarker.core.TemplateFormatUtil; -import org.apache.freemarker.core.TemplateNumberFormat; -import org.apache.freemarker.core.TemplateNumberFormatFactory; -import org.apache.freemarker.core.UnformattableValueException; +import org.apache.freemarker.core.valueformat.InvalidFormatParametersException; +import org.apache.freemarker.core.valueformat.TemplateFormatUtil; +import org.apache.freemarker.core.valueformat.TemplateNumberFormat; +import org.apache.freemarker.core.valueformat.TemplateNumberFormatFactory; +import org.apache.freemarker.core.valueformat.UnformattableValueException; import org.apache.freemarker.core.model.TemplateModelException; import org.apache.freemarker.core.model.TemplateNumberModel; import org.apache.freemarker.core.util._NumberUtil; http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ef968757/src/test/java/org/apache/freemarker/core/userpkg/LocAndTZSensitiveTemplateDateFormatFactory.java ---------------------------------------------------------------------- diff --git a/src/test/java/org/apache/freemarker/core/userpkg/LocAndTZSensitiveTemplateDateFormatFactory.java b/src/test/java/org/apache/freemarker/core/userpkg/LocAndTZSensitiveTemplateDateFormatFactory.java index fa8fbf7..f8f419d 100644 --- a/src/test/java/org/apache/freemarker/core/userpkg/LocAndTZSensitiveTemplateDateFormatFactory.java +++ b/src/test/java/org/apache/freemarker/core/userpkg/LocAndTZSensitiveTemplateDateFormatFactory.java @@ -23,13 +23,13 @@ import java.util.Locale; import java.util.TimeZone; import org.apache.freemarker.core.Environment; -import org.apache.freemarker.core.InvalidFormatParametersException; -import org.apache.freemarker.core.TemplateDateFormat; -import org.apache.freemarker.core.TemplateDateFormatFactory; -import org.apache.freemarker.core.TemplateFormatUtil; -import org.apache.freemarker.core.UnformattableValueException; -import org.apache.freemarker.core.UnknownDateTypeFormattingUnsupportedException; -import org.apache.freemarker.core.UnparsableValueException; +import org.apache.freemarker.core.valueformat.InvalidFormatParametersException; +import org.apache.freemarker.core.valueformat.TemplateDateFormat; +import org.apache.freemarker.core.valueformat.TemplateDateFormatFactory; +import org.apache.freemarker.core.valueformat.TemplateFormatUtil; +import org.apache.freemarker.core.valueformat.UnformattableValueException; +import org.apache.freemarker.core.valueformat.UnknownDateTypeFormattingUnsupportedException; +import org.apache.freemarker.core.valueformat.UnparsableValueException; import org.apache.freemarker.core.model.TemplateDateModel; import org.apache.freemarker.core.model.TemplateModelException; http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ef968757/src/test/java/org/apache/freemarker/core/userpkg/LocaleSensitiveTemplateNumberFormatFactory.java ---------------------------------------------------------------------- diff --git a/src/test/java/org/apache/freemarker/core/userpkg/LocaleSensitiveTemplateNumberFormatFactory.java b/src/test/java/org/apache/freemarker/core/userpkg/LocaleSensitiveTemplateNumberFormatFactory.java index 7bc23ba..231246b 100644 --- a/src/test/java/org/apache/freemarker/core/userpkg/LocaleSensitiveTemplateNumberFormatFactory.java +++ b/src/test/java/org/apache/freemarker/core/userpkg/LocaleSensitiveTemplateNumberFormatFactory.java @@ -21,11 +21,11 @@ package org.apache.freemarker.core.userpkg; import java.util.Locale; import org.apache.freemarker.core.Environment; -import org.apache.freemarker.core.InvalidFormatParametersException; -import org.apache.freemarker.core.TemplateFormatUtil; -import org.apache.freemarker.core.TemplateNumberFormat; -import org.apache.freemarker.core.TemplateNumberFormatFactory; -import org.apache.freemarker.core.UnformattableValueException; +import org.apache.freemarker.core.valueformat.InvalidFormatParametersException; +import org.apache.freemarker.core.valueformat.TemplateFormatUtil; +import org.apache.freemarker.core.valueformat.TemplateNumberFormat; +import org.apache.freemarker.core.valueformat.TemplateNumberFormatFactory; +import org.apache.freemarker.core.valueformat.UnformattableValueException; import org.apache.freemarker.core.model.TemplateModelException; import org.apache.freemarker.core.model.TemplateNumberModel; http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ef968757/src/test/java/org/apache/freemarker/core/userpkg/PrintfGTemplateNumberFormatFactory.java ---------------------------------------------------------------------- diff --git a/src/test/java/org/apache/freemarker/core/userpkg/PrintfGTemplateNumberFormatFactory.java b/src/test/java/org/apache/freemarker/core/userpkg/PrintfGTemplateNumberFormatFactory.java index b81f043..7364df2 100644 --- a/src/test/java/org/apache/freemarker/core/userpkg/PrintfGTemplateNumberFormatFactory.java +++ b/src/test/java/org/apache/freemarker/core/userpkg/PrintfGTemplateNumberFormatFactory.java @@ -23,11 +23,11 @@ import java.math.BigInteger; import java.util.Locale; import org.apache.freemarker.core.Environment; -import org.apache.freemarker.core.InvalidFormatParametersException; -import org.apache.freemarker.core.TemplateFormatUtil; -import org.apache.freemarker.core.TemplateNumberFormat; -import org.apache.freemarker.core.TemplateNumberFormatFactory; -import org.apache.freemarker.core.UnformattableValueException; +import org.apache.freemarker.core.valueformat.InvalidFormatParametersException; +import org.apache.freemarker.core.valueformat.TemplateFormatUtil; +import org.apache.freemarker.core.valueformat.TemplateNumberFormat; +import org.apache.freemarker.core.valueformat.TemplateNumberFormatFactory; +import org.apache.freemarker.core.valueformat.UnformattableValueException; import org.apache.freemarker.core.model.TemplateModelException; import org.apache.freemarker.core.model.TemplateNumberModel; import org.apache.freemarker.core.outputformat.impl.HTMLOutputFormat; http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ef968757/src/test/java/org/apache/freemarker/core/valueformat/NumberFormatTest.java ---------------------------------------------------------------------- diff --git a/src/test/java/org/apache/freemarker/core/valueformat/NumberFormatTest.java b/src/test/java/org/apache/freemarker/core/valueformat/NumberFormatTest.java new file mode 100644 index 0000000..d5c8adb --- /dev/null +++ b/src/test/java/org/apache/freemarker/core/valueformat/NumberFormatTest.java @@ -0,0 +1,325 @@ +/* + * 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 org.apache.freemarker.core.valueformat; + +import static org.hamcrest.Matchers.instanceOf; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertThat; + +import java.io.IOException; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.Collections; +import java.util.Locale; +import java.util.Map; + +import org.apache.freemarker.core.*; +import org.apache.freemarker.core.model.TemplateDirectiveBody; +import org.apache.freemarker.core.model.TemplateDirectiveModel; +import org.apache.freemarker.core.model.TemplateModel; +import org.apache.freemarker.core.model.TemplateModelException; +import org.apache.freemarker.core.model.TemplateNumberModel; +import org.apache.freemarker.core.model.impl.SimpleNumber; +import org.apache.freemarker.core.templateresolver.ConditionalTemplateConfigurationFactory; +import org.apache.freemarker.core.templateresolver.FileNameGlobMatcher; +import org.apache.freemarker.core.userpkg.BaseNTemplateNumberFormatFactory; +import org.apache.freemarker.core.userpkg.HexTemplateNumberFormatFactory; +import org.apache.freemarker.core.userpkg.LocaleSensitiveTemplateNumberFormatFactory; +import org.apache.freemarker.core.userpkg.PrintfGTemplateNumberFormatFactory; +import org.apache.freemarker.core.valueformat.impl.AliasTemplateNumberFormatFactory; +import org.apache.freemarker.test.TemplateTest; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; + +import com.google.common.collect.ImmutableMap; + +@SuppressWarnings("boxing") +public class NumberFormatTest extends TemplateTest { + + @Before + public void setup() { + Configuration cfg = getConfiguration(); + cfg.setLocale(Locale.US); + + cfg.setCustomNumberFormats(ImmutableMap.of( + "hex", HexTemplateNumberFormatFactory.INSTANCE, + "loc", LocaleSensitiveTemplateNumberFormatFactory.INSTANCE, + "base", BaseNTemplateNumberFormatFactory.INSTANCE, + "printfG", PrintfGTemplateNumberFormatFactory.INSTANCE)); + } + + @Test + public void testUnknownCustomFormat() throws Exception { + { + getConfiguration().setNumberFormat("@noSuchFormat"); + Throwable exc = assertErrorContains("${1}", "\"@noSuchFormat\"", "\"noSuchFormat\""); + assertThat(exc.getCause(), instanceOf(UndefinedCustomFormatException.class)); + } + + { + getConfiguration().setNumberFormat("number"); + Throwable exc = assertErrorContains("${1?string('@noSuchFormat2')}", + "\"@noSuchFormat2\"", "\"noSuchFormat2\""); + assertThat(exc.getCause(), instanceOf(UndefinedCustomFormatException.class)); + } + } + + @Test + public void testStringBI() throws Exception { + assertOutput("${11} ${11?string.@hex} ${12} ${12?string.@hex}", "11 b 12 c"); + } + + @Test + public void testSetting() throws Exception { + getConfiguration().setNumberFormat("@hex"); + assertOutput("${11?string.number} ${11} ${12?string.number} ${12}", "11 b 12 c"); + } + + @Test + public void testSetting2() throws Exception { + assertOutput( + "<#setting numberFormat='@hex'>${11?string.number} ${11} ${12?string.number} ${12} ${13?string}" + + "<#setting numberFormat='@loc'>${11?string.number} ${11} ${12?string.number} ${12} ${13?string}", + "11 b 12 c d" + + "11 11_en_US 12 12_en_US 13_en_US"); + } + + @Test + public void testUnformattableNumber() throws Exception { + getConfiguration().setNumberFormat("@hex"); + assertErrorContains("${1.1}", "hexadecimal int", "doesn't fit into an int"); + } + + @Test + public void testLocaleSensitive() throws Exception { + Configuration cfg = getConfiguration(); + cfg.setNumberFormat("@loc"); + assertOutput("${1.1}", "1.1_en_US"); + cfg.setLocale(Locale.GERMANY); + assertOutput("${1.1}", "1.1_de_DE"); + } + + @Test + public void testLocaleSensitive2() throws Exception { + Configuration cfg = getConfiguration(); + cfg.setNumberFormat("@loc"); + assertOutput("${1.1} <#setting locale='de_DE'>${1.1}", "1.1_en_US 1.1_de_DE"); + } + + @Test + public void testCustomParameterized() throws Exception { + Configuration cfg = getConfiguration(); + cfg.setNumberFormat("@base 2"); + assertOutput("${11}", "1011"); + assertOutput("${11?string}", "1011"); + assertOutput("${11?string.@base_3}", "102"); + + assertErrorContains("${11?string.@base_xyz}", "\"@base_xyz\"", "\"xyz\""); + cfg.setNumberFormat("@base"); + assertErrorContains("${11}", "\"@base\"", "format parameter is required"); + } + + @Test + public void testCustomWithFallback() throws Exception { + Configuration cfg = getConfiguration(); + cfg.setNumberFormat("@base 2|0.0#"); + assertOutput("${11}", "1011"); + assertOutput("${11.34}", "11.34"); + assertOutput("${11?string('@base 3|0.00')}", "102"); + assertOutput("${11.2?string('@base 3|0.00')}", "11.20"); + } + + @Test + public void testEnvironmentGetters() throws Exception { + Template t = new Template(null, "", getConfiguration()); + Environment env = t.createProcessingEnvironment(null, null); + + TemplateNumberFormat defF = env.getTemplateNumberFormat(); + // + TemplateNumberFormat explF = env.getTemplateNumberFormat("0.00"); + assertEquals("1.25", explF.formatToPlainText(new SimpleNumber(1.25))); + // + TemplateNumberFormat expl2F = env.getTemplateNumberFormat("@loc"); + assertEquals("1.25_en_US", expl2F.formatToPlainText(new SimpleNumber(1.25))); + + TemplateNumberFormat explFFr = env.getTemplateNumberFormat("0.00", Locale.FRANCE); + assertNotSame(explF, explFFr); + assertEquals("1,25", explFFr.formatToPlainText(new SimpleNumber(1.25))); + // + TemplateNumberFormat expl2FFr = env.getTemplateNumberFormat("@loc", Locale.FRANCE); + assertEquals("1.25_fr_FR", expl2FFr.formatToPlainText(new SimpleNumber(1.25))); + + assertSame(env.getTemplateNumberFormat(), defF); + // + assertSame(env.getTemplateNumberFormat("0.00"), explF); + // + assertSame(env.getTemplateNumberFormat("@loc"), expl2F); + } + + /** + * ?string formats lazily (at least in 2.3.x), so it must make a snapshot of the format inputs when it's called. + */ + @Test + @Ignore // [FM3] We want to rework BI-s so that lazy evaluation won't be needed. Then this will go away too. + public void testStringBIDoesSnapshot() throws Exception { + // TemplateNumberModel-s shouldn't change, but we have to keep BC when that still happens. + final MutableTemplateNumberModel nm = new MutableTemplateNumberModel(); + nm.setNumber(123); + addToDataModel("n", nm); + addToDataModel("incN", new TemplateDirectiveModel() { + + @Override + public void execute(Environment env, Map params, TemplateModel[] loopVars, TemplateDirectiveBody body) + throws TemplateException, IOException { + nm.setNumber(nm.getAsNumber().intValue() + 1); + } + }); + assertOutput( + "<#assign s1 = n?string>" + + "<#setting numberFormat='@loc'>" + + "<#assign s2 = n?string>" + + "<#setting numberFormat='@hex'>" + + "<#assign s3 = n?string>" + + "${s1} ${s2} ${s3}", + "123 123_en_US 7b"); + assertOutput( + "<#assign s1 = n?string>" + + "<@incN />" + + "<#assign s2 = n?string>" + + "${s1} ${s2}", + "123 124"); + } + + @Test + public void testNullInModel() throws Exception { + addToDataModel("n", new MutableTemplateNumberModel()); + assertErrorContains("${n}", "nothing inside it"); + assertErrorContains("${n?string}", "nothing inside it"); + } + + @Test + public void testAtPrefix() throws Exception { + Configuration cfg = getConfiguration(); + + cfg.setNumberFormat("@hex"); + assertOutput("${10}", "a"); + cfg.setNumberFormat("'@'0"); + assertOutput("${10}", "@10"); + cfg.setNumberFormat("@@0"); + assertOutput("${10}", "@@10"); + + cfg.setCustomNumberFormats(Collections.<String, TemplateNumberFormatFactory>emptyMap()); + cfg.setNumberFormat("@hex"); + assertErrorContains("${10}", "custom", "\"hex\""); + cfg.setNumberFormat("'@'0"); + assertOutput("${10}", "@10"); + cfg.setNumberFormat("@@0"); + assertOutput("${10}", "@@10"); + } + + @Test + public void testAlieses() throws Exception { + Configuration cfg = getConfiguration(); + cfg.setCustomNumberFormats(ImmutableMap.of( + "f", new AliasTemplateNumberFormatFactory("0.#'f'"), + "d", new AliasTemplateNumberFormatFactory("0.0#"), + "hex", HexTemplateNumberFormatFactory.INSTANCE)); + + TemplateConfiguration tc = new TemplateConfiguration(); + tc.setCustomNumberFormats(ImmutableMap.of( + "d", new AliasTemplateNumberFormatFactory("0.#'d'"), + "i", new AliasTemplateNumberFormatFactory("@hex"))); + cfg.setTemplateConfigurations(new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("*2*"), tc)); + + String commonFtl = "${1?string.@f} ${1?string.@d} " + + "<#setting locale='fr_FR'>${1.5?string.@d} " + + "<#attempt>${10?string.@i}<#recover>E</#attempt>"; + addTemplate("t1.ftl", commonFtl); + addTemplate("t2.ftl", commonFtl); + + assertOutputForNamed("t1.ftl", "1f 1.0 1,5 E"); + assertOutputForNamed("t2.ftl", "1f 1d 1,5d a"); + } + + @Test + public void testAlieses2() throws Exception { + Configuration cfg = getConfiguration(); + cfg.setCustomNumberFormats(ImmutableMap.of( + "n", new AliasTemplateNumberFormatFactory("0.0", + ImmutableMap.of( + new Locale("en"), "0.0'_en'", + Locale.UK, "0.0'_en_GB'", + Locale.FRANCE, "0.0'_fr_FR'")))); + cfg.setNumberFormat("@n"); + assertOutput( + "<#setting locale='en_US'>${1} " + + "<#setting locale='en_GB'>${1} " + + "<#setting locale='en_GB_Win'>${1} " + + "<#setting locale='fr_FR'>${1} " + + "<#setting locale='hu_HU'>${1}", + "1.0_en 1.0_en_GB 1.0_en_GB 1,0_fr_FR 1,0"); + } + + @Test + public void testMarkupFormat() throws IOException, TemplateException { + getConfiguration().setNumberFormat("@printfG_3"); + + String commonFTL = "${1234567} ${'cat:' + 1234567} ${0.0000123}"; + String commonOutput = "1.23*10<sup>6</sup> cat:1.23*10<sup>6</sup> 1.23*10<sup>-5</sup>"; + assertOutput(commonFTL, commonOutput); + assertOutput("<#ftl outputFormat='HTML'>" + commonFTL, commonOutput); + assertOutput("<#escape x as x?html>" + commonFTL + "</#escape>", commonOutput); + assertOutput("<#escape x as x?xhtml>" + commonFTL + "</#escape>", commonOutput); + assertOutput("<#escape x as x?xml>" + commonFTL + "</#escape>", commonOutput); + assertOutput("${\"" + commonFTL + "\"}", "1.23*10<sup>6</sup> cat:1.23*10<sup>6</sup> 1.23*10<sup>-5</sup>"); + assertErrorContains("<#ftl outputFormat='plainText'>" + commonFTL, "HTML", "plainText", "conversion"); + } + + @Test + public void testPrintG() throws IOException, TemplateException { + for (Number n : new Number[] { + 1234567, 1234567L, 1234567d, 1234567f, BigInteger.valueOf(1234567), BigDecimal.valueOf(1234567) }) { + addToDataModel("n", n); + + assertOutput("${n?string.@printfG}", "1.23457E+06"); + assertOutput("${n?string.@printfG_3}", "1.23E+06"); + assertOutput("${n?string.@printfG_7}", "1234567"); + assertOutput("${0.0000123?string.@printfG}", "1.23000E-05"); + } + } + + private static class MutableTemplateNumberModel implements TemplateNumberModel { + + private Number number; + + public void setNumber(Number number) { + this.number = number; + } + + @Override + public Number getAsNumber() throws TemplateModelException { + return number; + } + + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ef968757/src/test/java/org/apache/freemarker/core/valueformat/impl/ExtendedDecimalFormatTest.java ---------------------------------------------------------------------- diff --git a/src/test/java/org/apache/freemarker/core/valueformat/impl/ExtendedDecimalFormatTest.java b/src/test/java/org/apache/freemarker/core/valueformat/impl/ExtendedDecimalFormatTest.java new file mode 100644 index 0000000..29bbc6a --- /dev/null +++ b/src/test/java/org/apache/freemarker/core/valueformat/impl/ExtendedDecimalFormatTest.java @@ -0,0 +1,343 @@ +/* + * 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 org.apache.freemarker.core.valueformat.impl; + +import static org.apache.freemarker.test.hamcerst.Matchers.containsStringIgnoringCase; +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.containsString; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; + +import java.io.IOException; +import java.text.DecimalFormat; +import java.text.ParseException; +import java.util.Locale; + +import org.apache.freemarker.core.Configuration; +import org.apache.freemarker.core.TemplateException; +import org.apache.freemarker.test.TemplateTest; +import org.junit.Test; + +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, "[email protected]@"); + + 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)); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ef968757/src/test/java/org/apache/freemarker/manualtest/CustomFormatsExample.java ---------------------------------------------------------------------- diff --git a/src/test/java/org/apache/freemarker/manualtest/CustomFormatsExample.java b/src/test/java/org/apache/freemarker/manualtest/CustomFormatsExample.java index 5441609..0f4cee5 100644 --- a/src/test/java/org/apache/freemarker/manualtest/CustomFormatsExample.java +++ b/src/test/java/org/apache/freemarker/manualtest/CustomFormatsExample.java @@ -24,12 +24,12 @@ import java.util.Date; import java.util.HashMap; import java.util.Map; -import org.apache.freemarker.core.AliasTemplateDateFormatFactory; -import org.apache.freemarker.core.AliasTemplateNumberFormatFactory; +import org.apache.freemarker.core.valueformat.impl.AliasTemplateDateFormatFactory; +import org.apache.freemarker.core.valueformat.impl.AliasTemplateNumberFormatFactory; import org.apache.freemarker.core.Configuration; -import org.apache.freemarker.core.TemplateDateFormatFactory; +import org.apache.freemarker.core.valueformat.TemplateDateFormatFactory; import org.apache.freemarker.core.TemplateException; -import org.apache.freemarker.core.TemplateNumberFormatFactory; +import org.apache.freemarker.core.valueformat.TemplateNumberFormatFactory; import org.apache.freemarker.core.userpkg.BaseNTemplateNumberFormatFactory; import org.junit.Test; http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ef968757/src/test/java/org/apache/freemarker/manualtest/UnitAwareTemplateNumberFormatFactory.java ---------------------------------------------------------------------- diff --git a/src/test/java/org/apache/freemarker/manualtest/UnitAwareTemplateNumberFormatFactory.java b/src/test/java/org/apache/freemarker/manualtest/UnitAwareTemplateNumberFormatFactory.java index 862c5f1..7013b30 100644 --- a/src/test/java/org/apache/freemarker/manualtest/UnitAwareTemplateNumberFormatFactory.java +++ b/src/test/java/org/apache/freemarker/manualtest/UnitAwareTemplateNumberFormatFactory.java @@ -21,9 +21,9 @@ package org.apache.freemarker.manualtest; import java.util.Locale; import org.apache.freemarker.core.Environment; -import org.apache.freemarker.core.TemplateNumberFormat; -import org.apache.freemarker.core.TemplateNumberFormatFactory; -import org.apache.freemarker.core.TemplateValueFormatException; +import org.apache.freemarker.core.valueformat.TemplateNumberFormat; +import org.apache.freemarker.core.valueformat.TemplateNumberFormatFactory; +import org.apache.freemarker.core.valueformat.TemplateValueFormatException; import org.apache.freemarker.core.model.TemplateModelException; import org.apache.freemarker.core.model.TemplateNumberModel;
