http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/test/java/org/apache/freemarker/core/HeaderParsingTest.java ---------------------------------------------------------------------- diff --git a/src/test/java/org/apache/freemarker/core/HeaderParsingTest.java b/src/test/java/org/apache/freemarker/core/HeaderParsingTest.java new file mode 100644 index 0000000..b2f69ff --- /dev/null +++ b/src/test/java/org/apache/freemarker/core/HeaderParsingTest.java @@ -0,0 +1,64 @@ +/* + * 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; + +import java.io.IOException; + +import org.apache.freemarker.core.Configuration; +import org.apache.freemarker.core.TemplateException; +import org.apache.freemarker.test.TemplateTest; +import org.junit.Test; + +public class HeaderParsingTest extends TemplateTest { + + private final Configuration cfgStripWS = new Configuration(Configuration.VERSION_3_0_0); + private final Configuration cfgNoStripWS = new Configuration(Configuration.VERSION_3_0_0); + { + cfgNoStripWS.setWhitespaceStripping(false); + } + + @Test + public void test() throws IOException, TemplateException { + assertOutput("<#ftl>text", "text", "text"); + assertOutput(" <#ftl> text", " text", " text"); + assertOutput("\n<#ftl>\ntext", "text", "text"); + assertOutput("\n \n\n<#ftl> \ntext", "text", "text"); + assertOutput("\n \n\n<#ftl>\n\ntext", "\ntext", "\ntext"); + } + + private void assertOutput(final String ftl, String expectedOutStripped, String expectedOutNonStripped) + throws IOException, TemplateException { + for (int i = 0; i < 4; i++) { + String ftlPermutation = ftl; + if ((i & 1) == 1) { + ftlPermutation = ftlPermutation.replace("<#ftl>", "<#ftl encoding='utf-8'>"); + } + if ((i & 2) == 2) { + ftlPermutation = ftlPermutation.replace('<', '[').replace('>', ']'); + } + + setConfiguration(cfgStripWS); + assertOutput(ftlPermutation, expectedOutStripped); + setConfiguration(cfgNoStripWS); + assertOutput(ftlPermutation, expectedOutNonStripped); + } + } + +}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/test/java/org/apache/freemarker/core/HexTemplateNumberFormatFactory.java ---------------------------------------------------------------------- diff --git a/src/test/java/org/apache/freemarker/core/HexTemplateNumberFormatFactory.java b/src/test/java/org/apache/freemarker/core/HexTemplateNumberFormatFactory.java new file mode 100644 index 0000000..9e2020c --- /dev/null +++ b/src/test/java/org/apache/freemarker/core/HexTemplateNumberFormatFactory.java @@ -0,0 +1,77 @@ +/* + * 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; + +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.model.TemplateModelException; +import org.apache.freemarker.core.model.TemplateNumberModel; +import org.apache.freemarker.core.util._NumberUtil; + +public class HexTemplateNumberFormatFactory extends TemplateNumberFormatFactory { + + public static final HexTemplateNumberFormatFactory INSTANCE = new HexTemplateNumberFormatFactory(); + + private HexTemplateNumberFormatFactory() { + // Defined to decrease visibility + } + + @Override + public TemplateNumberFormat get(String params, Locale locale, Environment env) + throws InvalidFormatParametersException { + TemplateFormatUtil.checkHasNoParameters(params); + return HexTemplateNumberFormat.INSTANCE; + } + + private static class HexTemplateNumberFormat extends TemplateNumberFormat { + + private static final HexTemplateNumberFormat INSTANCE = new HexTemplateNumberFormat(); + + private HexTemplateNumberFormat() { } + + @Override + public String formatToPlainText(TemplateNumberModel numberModel) + throws UnformattableValueException, TemplateModelException { + Number n = TemplateFormatUtil.getNonNullNumber(numberModel); + try { + return Integer.toHexString(_NumberUtil.toIntExact(n)); + } catch (ArithmeticException e) { + throw new UnformattableValueException(n + " doesn't fit into an int"); + } + } + + @Override + public boolean isLocaleBound() { + return false; + } + + @Override + public String getDescription() { + return "hexadecimal int"; + } + + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/test/java/org/apache/freemarker/core/IncludeAndImportConfigurableLayersTest.java ---------------------------------------------------------------------- diff --git a/src/test/java/org/apache/freemarker/core/IncludeAndImportConfigurableLayersTest.java b/src/test/java/org/apache/freemarker/core/IncludeAndImportConfigurableLayersTest.java new file mode 100644 index 0000000..750a701 --- /dev/null +++ b/src/test/java/org/apache/freemarker/core/IncludeAndImportConfigurableLayersTest.java @@ -0,0 +1,336 @@ +/* + * 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; + +import static org.junit.Assert.assertEquals; + +import java.io.StringWriter; + +import org.apache.freemarker.core.Configurable; +import org.apache.freemarker.core.Configuration; +import org.apache.freemarker.core.Environment; +import org.apache.freemarker.core.Template; +import org.apache.freemarker.core.TemplateConfiguration; +import org.apache.freemarker.core.templateresolver.ConditionalTemplateConfigurationFactory; +import org.apache.freemarker.core.templateresolver.FileNameGlobMatcher; +import org.apache.freemarker.test.TemplateTest; +import org.junit.Test; + +public class IncludeAndImportConfigurableLayersTest extends TemplateTest { + + @Test + public void test3LayerImportNoClashes() throws Exception { + Configuration cfg = getConfiguration(); + cfg.addAutoImport("t1", "t1.ftl"); + + TemplateConfiguration tc = new TemplateConfiguration(); + tc.addAutoImport("t2", "t2.ftl"); + cfg.setTemplateConfigurations( + new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("main.ftl"), tc)); + + { + Template t = cfg.getTemplate("main.ftl"); + StringWriter sw = new StringWriter(); + Environment env = t.createProcessingEnvironment(null, sw); + env.addAutoImport("t3", "t3.ftl"); + + env.process(); + assertEquals("In main: t1;t2;t3;", sw.toString()); + } + + { + Template t = cfg.getTemplate("main.ftl"); + StringWriter sw = new StringWriter(); + Environment env = t.createProcessingEnvironment(null, sw); + + env.process(); + assertEquals("In main: t1;t2;", sw.toString()); + } + + { + Template t = cfg.getTemplate("main2.ftl"); + StringWriter sw = new StringWriter(); + Environment env = t.createProcessingEnvironment(null, sw); + env.addAutoImport("t3", "t3.ftl"); + + env.process(); + assertEquals("In main2: t1;t3;", sw.toString()); + } + + cfg.removeAutoImport("t1"); + + { + Template t = cfg.getTemplate("main.ftl"); + StringWriter sw = new StringWriter(); + Environment env = t.createProcessingEnvironment(null, sw); + env.addAutoImport("t3", "t3.ftl"); + + env.process(); + assertEquals("In main: t2;t3;", sw.toString()); + } + } + + @Test + public void test3LayerImportClashes() throws Exception { + Configuration cfg = getConfiguration(); + cfg.addAutoImport("t1", "t1.ftl"); + cfg.addAutoImport("t2", "t2.ftl"); + cfg.addAutoImport("t3", "t3.ftl"); + + TemplateConfiguration tc = new TemplateConfiguration(); + tc.addAutoImport("t2", "t2b.ftl"); + cfg.setTemplateConfigurations( + new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("main.ftl"), tc)); + + { + Template t = cfg.getTemplate("main.ftl"); + StringWriter sw = new StringWriter(); + Environment env = t.createProcessingEnvironment(null, sw); + env.addAutoImport("t3", "t3b.ftl"); + + env.process(); + assertEquals("In main: t1;t2b;t3b;", sw.toString()); + } + + { + Template t = cfg.getTemplate("main2.ftl"); + StringWriter sw = new StringWriter(); + Environment env = t.createProcessingEnvironment(null, sw); + env.addAutoImport("t3", "t3b.ftl"); + + env.process(); + assertEquals("In main2: t1;t2;t3b;", sw.toString()); + } + + { + Template t = cfg.getTemplate("main.ftl"); + StringWriter sw = new StringWriter(); + Environment env = t.createProcessingEnvironment(null, sw); + + env.process(); + assertEquals("In main: t1;t3;t2b;", sw.toString()); + } + } + + @Test + public void test3LayerIncludesNoClashes() throws Exception { + Configuration cfg = getConfiguration(); + cfg.addAutoInclude("t1.ftl"); + + TemplateConfiguration tc = new TemplateConfiguration(); + tc.addAutoInclude("t2.ftl"); + cfg.setTemplateConfigurations( + new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("main.ftl"), tc)); + + { + Template t = cfg.getTemplate("main.ftl"); + StringWriter sw = new StringWriter(); + Environment env = t.createProcessingEnvironment(null, sw); + env.addAutoInclude("t3.ftl"); + + env.process(); + assertEquals("T1;T2;T3;In main: t1;t2;t3;", sw.toString()); + } + + { + Template t = cfg.getTemplate("main.ftl"); + StringWriter sw = new StringWriter(); + Environment env = t.createProcessingEnvironment(null, sw); + + env.process(); + assertEquals("T1;T2;In main: t1;t2;", sw.toString()); + } + + { + Template t = cfg.getTemplate("main2.ftl"); + StringWriter sw = new StringWriter(); + Environment env = t.createProcessingEnvironment(null, sw); + env.addAutoInclude("t3.ftl"); + + env.process(); + assertEquals("T1;T3;In main2: t1;t3;", sw.toString()); + } + + cfg.removeAutoInclude("t1.ftl"); + + { + Template t = cfg.getTemplate("main.ftl"); + StringWriter sw = new StringWriter(); + Environment env = t.createProcessingEnvironment(null, sw); + env.addAutoInclude("t3.ftl"); + + env.process(); + assertEquals("T2;T3;In main: t2;t3;", sw.toString()); + } + } + + @Test + public void test3LayerIncludeClashes() throws Exception { + Configuration cfg = getConfiguration(); + cfg.addAutoInclude("t1.ftl"); + cfg.addAutoInclude("t2.ftl"); + cfg.addAutoInclude("t3.ftl"); + + TemplateConfiguration tc = new TemplateConfiguration(); + tc.addAutoInclude("t2.ftl"); + cfg.setTemplateConfigurations( + new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("main.ftl"), tc)); + + { + Template t = cfg.getTemplate("main.ftl"); + StringWriter sw = new StringWriter(); + Environment env = t.createProcessingEnvironment(null, sw); + env.addAutoInclude("t3.ftl"); + + env.process(); + assertEquals("T1;T2;T3;In main: t1;t2;t3;", sw.toString()); + } + + { + Template t = cfg.getTemplate("main2.ftl"); + StringWriter sw = new StringWriter(); + Environment env = t.createProcessingEnvironment(null, sw); + env.addAutoInclude("t3.ftl"); + + env.process(); + assertEquals("T1;T2;T3;In main2: t1;t2;t3;", sw.toString()); + } + + { + Template t = cfg.getTemplate("main.ftl"); + StringWriter sw = new StringWriter(); + Environment env = t.createProcessingEnvironment(null, sw); + + env.process(); + assertEquals("T1;T3;T2;In main: t1;t3;t2;", sw.toString()); + } + + { + Template t = cfg.getTemplate("main.ftl"); + StringWriter sw = new StringWriter(); + Environment env = t.createProcessingEnvironment(null, sw); + env.addAutoInclude("t1.ftl"); + + env.process(); + assertEquals("T3;T2;T1;In main: t3;t2;t1;", sw.toString()); + } + } + + @Test + public void test3LayerIncludesClashes2() throws Exception { + Configuration cfg = getConfiguration(); + cfg.addAutoInclude("t1.ftl"); + cfg.addAutoInclude("t1.ftl"); + + TemplateConfiguration tc = new TemplateConfiguration(); + tc.addAutoInclude("t2.ftl"); + tc.addAutoInclude("t2.ftl"); + cfg.setTemplateConfigurations( + new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("main.ftl"), tc)); + + { + Template t = cfg.getTemplate("main.ftl"); + StringWriter sw = new StringWriter(); + Environment env = t.createProcessingEnvironment(null, sw); + env.addAutoInclude("t3.ftl"); + env.addAutoInclude("t3.ftl"); + env.addAutoInclude("t1.ftl"); + env.addAutoInclude("t1.ftl"); + + env.process(); + assertEquals("T2;T3;T1;In main: t2;t3;t1;", sw.toString()); + } + } + + @Test + public void test3LayerLazyness() throws Exception { + for (Class<?> layer : new Class<?>[] { Configuration.class, Template.class, Environment.class }) { + test3LayerLazyness(layer, null, null, false, "t1;t2;"); + test3LayerLazyness(layer, null, null, true, "t1;t2;"); + test3LayerLazyness(layer, null, false, true, "t1;t2;"); + test3LayerLazyness(layer, null, true, true, "t2;"); + + test3LayerLazyness(layer, false, null, false, "t1;t2;"); + test3LayerLazyness(layer, false, null, true, "t1;t2;"); + test3LayerLazyness(layer, false, false, true, "t1;t2;"); + test3LayerLazyness(layer, false, true, true, "t2;"); + + test3LayerLazyness(layer, true, null, false, ""); + test3LayerLazyness(layer, true, null, true, ""); + test3LayerLazyness(layer, true, false, true, "t1;"); + test3LayerLazyness(layer, true, true, true, ""); + } + } + + private void test3LayerLazyness( + Class<?> layer, + Boolean lazyImports, + Boolean lazyAutoImports, boolean setLazyAutoImports, + String expectedOutput) + throws Exception { + dropConfiguration(); + Configuration cfg = getConfiguration(); + cfg.addAutoImport("t1", "t1.ftl"); + Template t = new Template(null, "<#import 't2.ftl' as t2>${loaded!}", cfg); + + StringWriter sw = new StringWriter(); + Environment env = t.createProcessingEnvironment(null, sw); + + if (layer == Configuration.class) { + setLazynessOfConfigurable(cfg, lazyImports, lazyAutoImports, setLazyAutoImports); + } else if (layer == Template.class) { + setLazynessOfConfigurable(t, lazyImports, lazyAutoImports, setLazyAutoImports); + } else if (layer == Environment.class) { + setLazynessOfConfigurable(env, lazyImports, lazyAutoImports, setLazyAutoImports); + } else { + throw new IllegalArgumentException(); + } + + env.process(); + assertEquals(expectedOutput, sw.toString()); + } + + private void setLazynessOfConfigurable(Configurable cfg, Boolean lazyImports, Boolean lazyAutoImports, + boolean setLazyAutoImports) { + if (lazyImports != null) { + cfg.setLazyImports(lazyImports); + } + if (setLazyAutoImports) { + cfg.setLazyAutoImports(lazyAutoImports); + } + } + + @Override + protected Configuration createConfiguration() throws Exception { + return new Configuration(Configuration.VERSION_3_0_0); + } + + @Override + protected void addCommonTemplates() { + addTemplate("main.ftl", "In main: ${loaded}"); + addTemplate("main2.ftl", "In main2: ${loaded}"); + addTemplate("t1.ftl", "<#global loaded = (loaded!) + 't1;'>T1;"); + addTemplate("t2.ftl", "<#global loaded = (loaded!) + 't2;'>T2;"); + addTemplate("t3.ftl", "<#global loaded = (loaded!) + 't3;'>T3;"); + addTemplate("t1b.ftl", "<#global loaded = (loaded!) + 't1b;'>T1b;"); + addTemplate("t2b.ftl", "<#global loaded = (loaded!) + 't2b;'>T2b;"); + addTemplate("t3b.ftl", "<#global loaded = (loaded!) + 't3b;'>T3b;"); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/test/java/org/apache/freemarker/core/IncludeAndImportTest.java ---------------------------------------------------------------------- diff --git a/src/test/java/org/apache/freemarker/core/IncludeAndImportTest.java b/src/test/java/org/apache/freemarker/core/IncludeAndImportTest.java new file mode 100644 index 0000000..7c1372b --- /dev/null +++ b/src/test/java/org/apache/freemarker/core/IncludeAndImportTest.java @@ -0,0 +1,255 @@ +/* + * 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; + +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.instanceOf; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; + +import java.io.IOException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; + +import org.apache.freemarker.core.Configuration; +import org.apache.freemarker.core.InvalidReferenceException; +import org.apache.freemarker.core.TemplateException; +import org.apache.freemarker.core.TemplateNotFoundException; +import org.apache.freemarker.core.Environment.LazilyInitializedNamespace; +import org.apache.freemarker.core.Environment.Namespace; +import org.apache.freemarker.core.model.WrappingTemplateModel; +import org.apache.freemarker.test.TemplateTest; +import org.junit.Before; +import org.junit.Test; + +@SuppressWarnings("boxing") +public class IncludeAndImportTest extends TemplateTest { + + @Before + public void setup() { + addTemplate("inc1.ftl", "[inc1]<#global inc1Cnt = (inc1Cnt!0) + 1><#global history = (history!) + 'I'>"); + addTemplate("inc2.ftl", "[inc2]"); + addTemplate("inc3.ftl", "[inc3]"); + addTemplate("lib1.ftl", "<#global lib1Cnt = (lib1Cnt!0) + 1><#global history = (history!) + 'L1'>" + + "<#macro m>In lib1</#macro>"); + addTemplate("lib2.ftl", "<#global history = (history!) + 'L2'>" + + "<#macro m>In lib2</#macro>"); + addTemplate("lib3.ftl", "<#global history = (history!) + 'L3'>" + + "<#macro m>In lib3</#macro>"); + + addTemplate("lib2CallsLib1.ftl", "<#global history = (history!) + 'L2'>" + + "<#macro m>In lib2 (<@lib1.m/>)</#macro>"); + addTemplate("lib3ImportsLib1.ftl", "<#import 'lib1.ftl' as lib1><#global history = (history!) + 'L3'>" + + "<#macro m>In lib3 (<@lib1.m/>)</#macro>"); + + addTemplate("lib_de.ftl", "<#global history = (history!) + 'LDe'><#assign initLocale=.locale>" + + "<#macro m>de</#macro>"); + addTemplate("lib_en.ftl", "<#global history = (history!) + 'LEn'><#assign initLocale=.locale>" + + "<#macro m>en</#macro>"); + } + + @Test + public void includeSameTwice() throws IOException, TemplateException { + assertOutput("<#include 'inc1.ftl'>${inc1Cnt}<#include 'inc1.ftl'>${inc1Cnt}", "[inc1]1[inc1]2"); + } + + @Test + public void importSameTwice() throws IOException, TemplateException { + assertOutput("<#import 'lib1.ftl' as i1>${lib1Cnt} <#import 'lib1.ftl' as i2>${lib1Cnt}", "1 1"); + } + + @Test + public void importInMainCreatesGlobal() throws IOException, TemplateException { + String ftl = "${.main.lib1???c} ${.globals.lib1???c}" + + "<#import 'lib1.ftl' as lib1> ${.main.lib1???c} ${.globals.lib1???c}"; + String expectedOut = "false false true true"; + assertOutput(ftl, expectedOut); + } + + @Test + public void importInMainCreatesGlobalBugfix() throws IOException, TemplateException { + // An import in the main namespace should create a global variable, even if the imported library was already + // initialized elsewhere. + String ftl = "<#import 'lib3ImportsLib1.ftl' as lib3>${lib1Cnt} ${.main.lib1???c} ${.globals.lib1???c}, " + + "<#import 'lib1.ftl' as lib1>${lib1Cnt} ${.main.lib1???c} ${.globals.lib1???c}"; + assertOutput(ftl, "1 false false, 1 true true"); + } + + /** + * Tests the order of auto-includes and auto-imports, also that they only effect the main template directly. + */ + @Test + public void autoIncludeAndAutoImport() throws IOException, TemplateException { + getConfiguration().addAutoInclude("inc1.ftl"); + getConfiguration().addAutoInclude("inc2.ftl"); + getConfiguration().addAutoImport("lib1", "lib1.ftl"); + getConfiguration().addAutoImport("lib2", "lib2CallsLib1.ftl"); + assertOutput( + "<#include 'inc3.ftl'>[main] ${inc1Cnt}, ${history}, <@lib1.m/>, <@lib2.m/>", + "[inc1][inc2][inc3][main] 1, L1L2I, In lib1, In lib2 (In lib1)"); + } + + /** + * Demonstrates design issue in FreeMarker 2.3.x where the lookupStrategy is not factored in when identifying + * already existing namespaces. + */ + @Test + public void lookupSrategiesAreNotConsideredProperly() throws IOException, TemplateException { + // As only the name of the template is used for the finding the already existing namespace, the settings that + // influence the lookup are erroneously ignored. + assertOutput( + "<#setting locale='en_US'><#import 'lib.ftl' as ns1>" + + "<#setting locale='de_DE'><#import 'lib.ftl' as ns2>" + + "<@ns1.m/> <@ns2.m/> ${history}", + "en en LEn"); + + // The opposite of the prevous, where differn names refer to the same template after a lookup: + assertOutput( + "<#setting locale='en_US'>" + + "<#import '*/lib.ftl' as ns1>" + + "<#import 'lib.ftl' as ns2>" + + "<@ns1.m/> <@ns2.m/> ${history}", + "en en LEnLEn"); + } + + @Test + public void lazyImportBasics() throws IOException, TemplateException { + String ftlImports = "<#import 'lib1.ftl' as l1><#import 'lib2.ftl' as l2><#import 'lib3ImportsLib1.ftl' as l3>"; + String ftlCalls = "<@l2.m/>, <@l1.m/>; ${history}"; + String ftl = ftlImports + ftlCalls; + + assertOutput(ftl, "In lib2, In lib1; L1L2L3"); + + getConfiguration().setLazyImports(true); + assertOutput(ftl, "In lib2, In lib1; L2L1"); + + assertOutput(ftlImports + "<@l3.m/>, " + ftlCalls, "In lib3 (In lib1), In lib2, In lib1; L3L1L2"); + } + + @Test + public void lazyImportAndLocale() throws IOException, TemplateException { + getConfiguration().setLazyImports(true); + assertOutput("<#setting locale = 'de_DE'><#import 'lib.ftl' as lib>" + + "[${history!}] " + + "<#setting locale = 'en'>" + + "<@lib.m/> ${lib.initLocale} [${history}]", + "[] de de_DE [LDe]"); + } + + @Test + public void lazyAutoImportSettings() throws IOException, TemplateException { + Configuration cfg = getConfiguration(); + cfg.addAutoImport("l1", "lib1.ftl"); + cfg.addAutoImport("l2", "lib2.ftl"); + cfg.addAutoImport("l3", "lib3.ftl"); + + String ftl = "<@l2.m/>, <@l1.m/>; ${history}"; + String expectedEagerOutput = "In lib2, In lib1; L1L2L3"; + String expecedLazyOutput = "In lib2, In lib1; L2L1"; + + assertOutput(ftl, expectedEagerOutput); + cfg.setLazyImports(true); + assertOutput(ftl, expecedLazyOutput); + cfg.setLazyImports(false); + assertOutput(ftl, expectedEagerOutput); + cfg.setLazyAutoImports(true); + assertOutput(ftl, expecedLazyOutput); + cfg.setLazyAutoImports(null); + assertOutput(ftl, expectedEagerOutput); + cfg.setLazyImports(true); + cfg.setLazyAutoImports(false); + assertOutput(ftl, expectedEagerOutput); + } + + @Test + public void lazyAutoImportMixedWithManualImport() throws IOException, TemplateException { + Configuration cfg = getConfiguration(); + cfg.addAutoImport("l1", "lib1.ftl"); + cfg.addAutoImport("l2", "/./lib2.ftl"); + cfg.addAutoImport("l3", "lib3.ftl"); + cfg.setLazyAutoImports(true); + + String ftl = "<@l2.m/>, <@l1.m/>; ${history}"; + String expectOutputWithoutHistory = "In lib2, In lib1; "; + String expecedOutput = expectOutputWithoutHistory + "L2L1"; + + assertOutput(ftl, expecedOutput); + assertOutput("<#import 'lib1.ftl' as l1>" + ftl, expectOutputWithoutHistory + "L1L2"); + assertOutput("<#import './x/../lib1.ftl' as l1>" + ftl, expectOutputWithoutHistory + "L1L2"); + assertOutput("<#import 'lib2.ftl' as l2>" + ftl, expecedOutput); + assertOutput("<#import 'lib3.ftl' as l3>" + ftl, expectOutputWithoutHistory + "L3L2L1"); + cfg.setLazyImports(true); + assertOutput("<#import 'lib1.ftl' as l1>" + ftl, expecedOutput); + assertOutput("<#import './x/../lib1.ftl' as l1>" + ftl, expecedOutput); + assertOutput("<#import 'lib2.ftl' as l2>" + ftl, expecedOutput); + assertOutput("<#import 'lib3.ftl' as l3>" + ftl, expecedOutput); + } + + @Test + public void lazyImportErrors() throws IOException, TemplateException { + Configuration cfg = getConfiguration(); + cfg.setLazyImports(true); + + assertOutput("<#import 'noSuchTemplate.ftl' as wrong>x", "x"); + + cfg.addAutoImport("wrong", "noSuchTemplate.ftl"); + assertOutput("x", "x"); + + try { + assertOutput("${wrong.x}", ""); + fail(); + } catch (TemplateException e) { + assertThat(e.getMessage(), + allOf(containsString("Lazy initialization"), containsString("noSuchTemplate.ftl"))); + assertThat(e.getCause(), instanceOf(TemplateNotFoundException.class)); + } + + addTemplate("containsError.ftl", "${noSuchVar}"); + try { + assertOutput("<#import 'containsError.ftl' as lib>${lib.x}", ""); + fail(); + } catch (TemplateException e) { + assertThat(e.getMessage(), + allOf(containsString("Lazy initialization"), containsString("containsError.ftl"))); + assertThat(e.getCause(), instanceOf(InvalidReferenceException.class)); + assertThat(e.getCause().getMessage(), containsString("noSuchVar")); + } + } + + /** + * Ensures that all methods are overridden so that they will do the lazy initialization. + */ + @Test + public void lazilyInitializingNamespaceOverridesAll() throws SecurityException, NoSuchMethodException { + for (Method m : Namespace.class.getMethods()) { + Class<?> declClass = m.getDeclaringClass(); + if (declClass == Object.class || declClass == WrappingTemplateModel.class + || (m.getModifiers() & Modifier.STATIC) != 0 + || m.getName().equals("synchronizedWrapper")) { + continue; + } + Method lazyM = LazilyInitializedNamespace.class.getMethod(m.getName(), m.getParameterTypes()); + if (lazyM.getDeclaringClass() != LazilyInitializedNamespace.class) { + fail("The " + lazyM + " method wasn't overidden in " + LazilyInitializedNamespace.class.getName()); + } + } + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/test/java/org/apache/freemarker/core/InterpretAndEvalTemplateNameTest.java ---------------------------------------------------------------------- diff --git a/src/test/java/org/apache/freemarker/core/InterpretAndEvalTemplateNameTest.java b/src/test/java/org/apache/freemarker/core/InterpretAndEvalTemplateNameTest.java new file mode 100644 index 0000000..7c08ec3 --- /dev/null +++ b/src/test/java/org/apache/freemarker/core/InterpretAndEvalTemplateNameTest.java @@ -0,0 +1,70 @@ +/* + * 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; + +import java.io.IOException; + +import org.apache.freemarker.core.TemplateException; +import org.apache.freemarker.core.templateresolver.impl.StringTemplateLoader; +import org.apache.freemarker.test.TemplateTest; +import org.junit.Test; + +/** + * Test template names returned by special variables and relative path resolution in {@code ?interpret}-ed and + * {@code ?eval}-ed parts. + */ +public class InterpretAndEvalTemplateNameTest extends TemplateTest { + + @Test + public void testInterpret() throws IOException, TemplateException { + for (String getTemplateNames : new String[] { + "c=${.current_template_name}, m=${.main_template_name}", + "c=${\".current_template_name\"?eval}, m=${\".main_template_name\"?eval}" + }) { + StringTemplateLoader tl = new StringTemplateLoader(); + tl.putTemplate( + "main.ftl", + getTemplateNames + " " + + "{<#include 'sub/t.ftl'>}"); + tl.putTemplate( + "sub/t.ftl", + getTemplateNames + " " + + "i{<@r'" + getTemplateNames + " {<#include \"a.ftl\">'?interpret />}} " + + "i{<@[r'" + getTemplateNames + " {<#include \"a.ftl\">','named_interpreted']?interpret />}}"); + tl.putTemplate("sub/a.ftl", "In sub/a.ftl, " + getTemplateNames); + tl.putTemplate("a.ftl", "In a.ftl"); + + getConfiguration().setTemplateLoader(tl); + + assertOutputForNamed("main.ftl", + "c=main.ftl, m=main.ftl " + + "{" + + "c=sub/t.ftl, m=main.ftl " + + "i{c=sub/t.ftl->anonymous_interpreted, m=main.ftl {In sub/a.ftl, c=sub/a.ftl, m=main.ftl}} " + + "i{c=sub/t.ftl->named_interpreted, m=main.ftl {In sub/a.ftl, c=sub/a.ftl, m=main.ftl}}" + + "}"); + + assertOutputForNamed("sub/t.ftl", + "c=sub/t.ftl, m=sub/t.ftl " + + "i{c=sub/t.ftl->anonymous_interpreted, m=sub/t.ftl {In sub/a.ftl, c=sub/a.ftl, m=sub/t.ftl}} " + + "i{c=sub/t.ftl->named_interpreted, m=sub/t.ftl {In sub/a.ftl, c=sub/a.ftl, m=sub/t.ftl}}"); + } + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/test/java/org/apache/freemarker/core/InterpretSettingInheritanceTest.java ---------------------------------------------------------------------- diff --git a/src/test/java/org/apache/freemarker/core/InterpretSettingInheritanceTest.java b/src/test/java/org/apache/freemarker/core/InterpretSettingInheritanceTest.java new file mode 100644 index 0000000..0e8f2a2 --- /dev/null +++ b/src/test/java/org/apache/freemarker/core/InterpretSettingInheritanceTest.java @@ -0,0 +1,95 @@ +/* + * 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; + +import java.io.IOException; + +import org.apache.freemarker.core.Configuration; +import org.apache.freemarker.core.TemplateException; +import org.apache.freemarker.test.TemplateTest; +import org.junit.Test; + +/** + * The {@code interpret} built-in must not consider the settings or established auto-detected syntax of the surrounding + * template. It can only depend on the {@link Configuration}. + */ +public class InterpretSettingInheritanceTest extends TemplateTest { + + private static final String FTL_A_S_A = "<#ftl><@'[#if true]s[/#if]<#if true>a</#if>'?interpret />"; + private static final String FTL_A_A_S = "<#ftl><@'<#if true>a</#if>[#if true]s[/#if]'?interpret />"; + private static final String FTL_S_S_A = "[#ftl][@'[#if true]s[/#if]<#if true>a</#if>'?interpret /]"; + private static final String FTL_S_A_S = "[#ftl][@'<#if true>a</#if>[#if true]s[/#if]'?interpret /]"; + private static final String OUT_S_A_WHEN_SYNTAX_IS_S = "s<#if true>a</#if>"; + private static final String OUT_S_A_WHEN_SYNTAX_IS_A = "[#if true]s[/#if]a"; + private static final String OUT_A_S_WHEN_SYNTAX_IS_A = "a[#if true]s[/#if]"; + private static final String OUT_A_S_WHEN_SYNTAX_IS_S = "<#if true>a</#if>s"; + + @Test + public void tagSyntaxTest() throws IOException, TemplateException { + Configuration cfg = getConfiguration(); + + cfg.setTagSyntax(Configuration.ANGLE_BRACKET_TAG_SYNTAX); + assertOutput(FTL_S_A_S, OUT_A_S_WHEN_SYNTAX_IS_A); + assertOutput(FTL_S_S_A, OUT_S_A_WHEN_SYNTAX_IS_A); + assertOutput(FTL_A_A_S, OUT_A_S_WHEN_SYNTAX_IS_A); + assertOutput(FTL_A_S_A, OUT_S_A_WHEN_SYNTAX_IS_A); + + cfg.setTagSyntax(Configuration.SQUARE_BRACKET_TAG_SYNTAX); + assertOutput(FTL_S_A_S, OUT_A_S_WHEN_SYNTAX_IS_S); + assertOutput(FTL_S_S_A, OUT_S_A_WHEN_SYNTAX_IS_S); + assertOutput(FTL_A_A_S, OUT_A_S_WHEN_SYNTAX_IS_S); + assertOutput(FTL_A_S_A, OUT_S_A_WHEN_SYNTAX_IS_S); + + cfg.setTagSyntax(Configuration.AUTO_DETECT_TAG_SYNTAX); + assertOutput(FTL_S_A_S, OUT_A_S_WHEN_SYNTAX_IS_A); + assertOutput(FTL_S_S_A, OUT_S_A_WHEN_SYNTAX_IS_S); + assertOutput(FTL_A_A_S, OUT_A_S_WHEN_SYNTAX_IS_A); + assertOutput(FTL_A_S_A, OUT_S_A_WHEN_SYNTAX_IS_S); + assertOutput("<@'[#ftl]x'?interpret />[#if true]y[/#if]", "x[#if true]y[/#if]"); + } + + @Test + public void whitespaceStrippingTest() throws IOException, TemplateException { + Configuration cfg = getConfiguration(); + + cfg.setWhitespaceStripping(true); + assertOutput("<#assign x = 1>\nX<@'<#assign x = 1>\\nY'?interpret />", "XY"); + assertOutput("<#ftl stripWhitespace=false><#assign x = 1>\nX<@'<#assign x = 1>\\nY'?interpret />", "\nXY"); + assertOutput("<#assign x = 1>\nX<@'<#ftl stripWhitespace=false><#assign x = 1>\\nY'?interpret />", "X\nY"); + + cfg.setWhitespaceStripping(false); + assertOutput("<#assign x = 1>\nX<@'<#assign x = 1>\\nY'?interpret />", "\nX\nY"); + assertOutput("<#ftl stripWhitespace=true><#assign x = 1>\nX<@'<#assign x = 1>\\nY'?interpret />", "X\nY"); + assertOutput("<#assign x = 1>\nX<@'<#ftl stripWhitespace=true><#assign x = 1>\\nY'?interpret />", "\nXY"); + } + + @Test + public void evalTest() throws IOException, TemplateException { + Configuration cfg = getConfiguration(); + + cfg.setTagSyntax(Configuration.ANGLE_BRACKET_TAG_SYNTAX); + assertOutput("<@'\"[#if true]s[/#if]<#if true>a</#if>\"?interpret'?eval />", OUT_S_A_WHEN_SYNTAX_IS_A); + assertOutput("[#ftl][@'\"[#if true]s[/#if]<#if true>a</#if>\"?interpret'?eval /]", OUT_S_A_WHEN_SYNTAX_IS_A); + + cfg.setTagSyntax(Configuration.SQUARE_BRACKET_TAG_SYNTAX); + assertOutput("[@'\"[#if true]s[/#if]<#if true>a</#if>\"?interpret'?eval /]", OUT_S_A_WHEN_SYNTAX_IS_S); + assertOutput("<#ftl><@'\"[#if true]s[/#if]<#if true>a</#if>\"?interpret'?eval />", OUT_S_A_WHEN_SYNTAX_IS_S); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/test/java/org/apache/freemarker/core/IteratorIssuesTest.java ---------------------------------------------------------------------- diff --git a/src/test/java/org/apache/freemarker/core/IteratorIssuesTest.java b/src/test/java/org/apache/freemarker/core/IteratorIssuesTest.java new file mode 100644 index 0000000..8eccc07 --- /dev/null +++ b/src/test/java/org/apache/freemarker/core/IteratorIssuesTest.java @@ -0,0 +1,89 @@ +/* + * 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; + +import java.util.Arrays; +import java.util.Iterator; + +import org.apache.freemarker.core.Configuration; +import org.apache.freemarker.core.model.impl.DefaultObjectWrapper; +import org.apache.freemarker.core.model.impl.DefaultObjectWrapperBuilder; +import org.apache.freemarker.core.model.impl.beans.BeansWrapper; +import org.apache.freemarker.core.model.impl.beans.BeansWrapperBuilder; +import org.apache.freemarker.test.TemplateTest; +import org.junit.Test; + +public class IteratorIssuesTest extends TemplateTest { + + private static final String FTL_HAS_CONTENT_AND_LIST + = "<#if it?hasContent><#list it as i>${i}</#list><#else>empty</#if>"; + private static final String OUT_HAS_CONTENT_AND_LIST_ABC = "abc"; + private static final String OUT_HAS_CONTENT_AND_LIST_EMPTY = "empty"; + + private static final String FTL_LIST_AND_HAS_CONTENT + = "<#list it as i>${i}${it?hasContent?then('+', '-')}</#list>"; + private static final String OUT_LIST_AND_HAS_CONTENT_BW_GOOD = "a+b+c-"; + + @Test + public void testHasContentAndListDOW() throws Exception { + addToDataModel("it", getDOW300().wrap(getAbcIt())); + assertOutput(FTL_HAS_CONTENT_AND_LIST, OUT_HAS_CONTENT_AND_LIST_ABC); + + addToDataModel("it", getDOW300().wrap(getEmptyIt())); + assertOutput(FTL_HAS_CONTENT_AND_LIST, OUT_HAS_CONTENT_AND_LIST_EMPTY); + } + + @Test + public void testHasContentAndListBW() throws Exception { + addToDataModel("it", getBW300().wrap(getAbcIt())); + assertOutput(FTL_HAS_CONTENT_AND_LIST, OUT_HAS_CONTENT_AND_LIST_ABC); + + addToDataModel("it", getBW300().wrap(getEmptyIt())); + assertOutput(FTL_HAS_CONTENT_AND_LIST, OUT_HAS_CONTENT_AND_LIST_EMPTY); + } + + @Test + public void testListAndHasContentDOW() throws Exception { + addToDataModel("it", getDOW300().wrap(getAbcIt())); + assertErrorContains(FTL_LIST_AND_HAS_CONTENT, "can be listed only once"); + } + + @Test + public void testListAndHasContentBW() throws Exception { + addToDataModel("it", getBW300().wrap(getAbcIt())); + assertOutput(FTL_LIST_AND_HAS_CONTENT, OUT_LIST_AND_HAS_CONTENT_BW_GOOD); + } + + private Iterator getAbcIt() { + return Arrays.asList(new String[] { "a", "b", "c" }).iterator(); + } + + private Iterator getEmptyIt() { + return Arrays.asList(new String[] { }).iterator(); + } + + private DefaultObjectWrapper getDOW300() { + return new DefaultObjectWrapperBuilder(Configuration.VERSION_3_0_0).build(); + } + + private BeansWrapper getBW300() { + return new BeansWrapperBuilder(Configuration.VERSION_3_0_0).build(); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/test/java/org/apache/freemarker/core/ListErrorsTest.java ---------------------------------------------------------------------- diff --git a/src/test/java/org/apache/freemarker/core/ListErrorsTest.java b/src/test/java/org/apache/freemarker/core/ListErrorsTest.java new file mode 100644 index 0000000..db0965a --- /dev/null +++ b/src/test/java/org/apache/freemarker/core/ListErrorsTest.java @@ -0,0 +1,138 @@ +/* + * 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; + +import java.io.IOException; + +import org.apache.freemarker.core.Configuration; +import org.apache.freemarker.core.TemplateException; +import org.apache.freemarker.core.model.impl.DefaultObjectWrapper; +import org.apache.freemarker.test.TemplateTest; +import org.apache.freemarker.test.templatesuite.models.Listables; +import org.junit.Test; + +import com.google.common.collect.ImmutableMap; + +public class ListErrorsTest extends TemplateTest { + + @Test + public void testValid() throws IOException, TemplateException { + assertOutput("<#list 1..2 as x><#list 3..4>${x}:<#items as x>${x}</#items></#list>;</#list>", "1:34;2:34;"); + assertOutput("<#list [] as x>${x}<#else><#list 1..2 as x>${x}<#sep>, </#list></#list>", "1, 2"); + assertOutput("<#macro m>[<#nested 3>]</#macro>" + + "<#list 1..2 as x>" + + "${x}@${x?index}" + + "<@m ; x>" + + "${x}," + + "<#list 4..4 as x>${x}@${x?index}</#list>" + + "</@>" + + "${x}@${x?index}; " + + "</#list>", + "1@0[3,4@0]1@0; 2@1[3,4@0]2@1; "); + } + + @Test + public void testInvalidItemsParseTime() throws IOException, TemplateException { + assertErrorContains("<#items as x>${x}</#items>", + "#items", "must be inside", "#list"); + assertErrorContains("<#list xs><#macro m><#items as x></#items></#macro></#list>", + "#items", "must be inside", "#list"); + assertErrorContains("<#list xs><#forEach x in xs><#items as x></#items></#forEach></#list>", + "#forEach", "doesn't support", "#items"); + assertErrorContains("<#list xs as x><#items as x>${x}</#items></#list>", + "#list", "must not have", "#items", "as loopVar"); + assertErrorContains("<#list xs><#list xs as x><#items as x>${x}</#items></#list></#list>", + "#list", "must not have", "#items", "as loopVar"); + assertErrorContains("<#list xs></#list>", + "#list", "must have", "#items", "as loopVar"); + assertErrorContains("<#forEach x in xs><#items as x></#items></#forEach>", + "#forEach", "doesn't support", "#items"); + assertErrorContains("<#list xs><#forEach x in xs><#items as x></#items></#forEach></#list>", + "#forEach", "doesn't support", "#items"); + } + + @Test + public void testInvalidSepParseTime() throws IOException, TemplateException { + assertErrorContains("<#sep>, </#sep>", + "#sep", "must be inside", "#list", "#foreach"); + assertErrorContains("<#sep>, ", + "#sep", "must be inside", "#list", "#foreach"); + assertErrorContains("<#list xs as x><#else><#sep>, </#list>", + "#sep", "must be inside", "#list", "#foreach"); + assertErrorContains("<#list xs as x><#macro m><#sep>, </#macro></#list>", + "#sep", "must be inside", "#list", "#foreach"); + } + + @Test + public void testInvalidItemsRuntime() throws IOException, TemplateException { + assertErrorContains("<#list 1..1><#items as x></#items><#items as x></#items></#list>", + "#items", "already entered earlier"); + assertErrorContains("<#list 1..1><#items as x><#items as y>${x}/${y}</#items></#items></#list>", + "#items", "Can't nest #items into each other"); + } + + @Test + public void testInvalidLoopVarBuiltinLHO() { + assertErrorContains("<#list foos>${foo?index}</#list>", + "?index", "foo", "no loop variable"); + assertErrorContains("<#list foos as foo></#list>${foo?index}", + "?index", "foo" , "no loop variable"); + assertErrorContains("<#list foos as foo><#macro m>${foo?index}</#macro></#list>", + "?index", "foo" , "no loop variable"); + assertErrorContains("<#list foos as foo><#function f>${foo?index}</#function></#list>", + "?index", "foo" , "no loop variable"); + assertErrorContains("<#list xs as x>${foo?index}</#list>", + "?index", "foo" , "no loop variable"); + assertErrorContains("<#list foos as foo><@m; foo>${foo?index}</@></#list>", + "?index", "foo" , "user defined directive"); + assertErrorContains( + "<#list foos as foo><@m; foo><@m; foo>${foo?index}</@></@></#list>", + "?index", "foo" , "user defined directive"); + assertErrorContains( + "<#list foos as foo><@m; foo>" + + "<#list foos as foo><@m; foo>${foo?index}</@></#list>" + + "</@></#list>", + "?index", "foo" , "user defined directive"); + } + + @Test + public void testKeyValueSameName() { + assertErrorContains("<#list {} as foo, foo></#list>", + "key", "value", "both" , "foo"); + } + + @Test + public void testCollectionVersusHash() { + assertErrorContains("<#list {} as i></#list>", + "as k, v"); + assertErrorContains("<#list [] as k, v></#list>", + "only one loop variable"); + } + + @Test + public void testNonEx2NonStringKey() throws IOException, TemplateException { + addToDataModel("m", new Listables.NonEx2MapAdapter(ImmutableMap.of("k1", "v1", 2, "v2"), + new DefaultObjectWrapper(Configuration.VERSION_3_0_0))); + assertOutput("<#list m?keys as k>${k};</#list>", "k1;2;"); + assertErrorContains("<#list m as k, v></#list>", + "string", "number", ".TemplateHashModelEx2"); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/test/java/org/apache/freemarker/core/LocAndTZSensitiveTemplateDateFormatFactory.java ---------------------------------------------------------------------- diff --git a/src/test/java/org/apache/freemarker/core/LocAndTZSensitiveTemplateDateFormatFactory.java b/src/test/java/org/apache/freemarker/core/LocAndTZSensitiveTemplateDateFormatFactory.java new file mode 100644 index 0000000..e2fc454 --- /dev/null +++ b/src/test/java/org/apache/freemarker/core/LocAndTZSensitiveTemplateDateFormatFactory.java @@ -0,0 +1,97 @@ +/* + * 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; + +import java.util.Date; +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.model.TemplateDateModel; +import org.apache.freemarker.core.model.TemplateModelException; + +public class LocAndTZSensitiveTemplateDateFormatFactory extends TemplateDateFormatFactory { + + public static final LocAndTZSensitiveTemplateDateFormatFactory INSTANCE = new LocAndTZSensitiveTemplateDateFormatFactory(); + + private LocAndTZSensitiveTemplateDateFormatFactory() { + // Defined to decrease visibility + } + + @Override + public TemplateDateFormat get(String params, int dateType, Locale locale, TimeZone timeZone, boolean zonelessInput, + Environment env) throws UnknownDateTypeFormattingUnsupportedException, InvalidFormatParametersException { + TemplateFormatUtil.checkHasNoParameters(params); + return new LocAndTZSensitiveTemplateDateFormat(locale, timeZone); + } + + private static class LocAndTZSensitiveTemplateDateFormat extends TemplateDateFormat { + + private final Locale locale; + private final TimeZone timeZone; + + public LocAndTZSensitiveTemplateDateFormat(Locale locale, TimeZone timeZone) { + this.locale = locale; + this.timeZone = timeZone; + } + + @Override + public String formatToPlainText(TemplateDateModel dateModel) + throws UnformattableValueException, TemplateModelException { + return String.valueOf(TemplateFormatUtil.getNonNullDate(dateModel).getTime() + "@" + locale + ":" + timeZone.getID()); + } + + @Override + public boolean isLocaleBound() { + return true; + } + + @Override + public boolean isTimeZoneBound() { + return true; + } + + @Override + public Date parse(String s, int dateType) throws UnparsableValueException { + try { + int atIdx = s.indexOf("@"); + if (atIdx == -1) { + throw new UnparsableValueException("Missing @"); + } + return new Date(Long.parseLong(s.substring(0, atIdx))); + } catch (NumberFormatException e) { + throw new UnparsableValueException("Malformed long"); + } + } + + @Override + public String getDescription() { + return "millis since the epoch"; + } + + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/test/java/org/apache/freemarker/core/LocaleSensitiveTemplateNumberFormatFactory.java ---------------------------------------------------------------------- diff --git a/src/test/java/org/apache/freemarker/core/LocaleSensitiveTemplateNumberFormatFactory.java b/src/test/java/org/apache/freemarker/core/LocaleSensitiveTemplateNumberFormatFactory.java new file mode 100644 index 0000000..3f4b7a1 --- /dev/null +++ b/src/test/java/org/apache/freemarker/core/LocaleSensitiveTemplateNumberFormatFactory.java @@ -0,0 +1,78 @@ +/* + * 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; + +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.model.TemplateModelException; +import org.apache.freemarker.core.model.TemplateNumberModel; + +public class LocaleSensitiveTemplateNumberFormatFactory extends TemplateNumberFormatFactory { + + public static final LocaleSensitiveTemplateNumberFormatFactory INSTANCE = new LocaleSensitiveTemplateNumberFormatFactory(); + + private LocaleSensitiveTemplateNumberFormatFactory() { + // Defined to decrease visibility + } + + @Override + public TemplateNumberFormat get(String params, Locale locale, Environment env) + throws InvalidFormatParametersException { + TemplateFormatUtil.checkHasNoParameters(params); + return new LocaleSensitiveTemplateNumberFormat(locale); + } + + private static class LocaleSensitiveTemplateNumberFormat extends TemplateNumberFormat { + + private final Locale locale; + + private LocaleSensitiveTemplateNumberFormat(Locale locale) { + this.locale = locale; + } + + @Override + public String formatToPlainText(TemplateNumberModel numberModel) + throws UnformattableValueException, TemplateModelException { + Number n = numberModel.getAsNumber(); + try { + return n + "_" + locale; + } catch (ArithmeticException e) { + throw new UnformattableValueException(n + " doesn't fit into an int"); + } + } + + @Override + public boolean isLocaleBound() { + return true; + } + + @Override + public String getDescription() { + return "test locale sensitive"; + } + + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/test/java/org/apache/freemarker/core/MiscErrorMessagesTest.java ---------------------------------------------------------------------- diff --git a/src/test/java/org/apache/freemarker/core/MiscErrorMessagesTest.java b/src/test/java/org/apache/freemarker/core/MiscErrorMessagesTest.java new file mode 100644 index 0000000..7d3dccd --- /dev/null +++ b/src/test/java/org/apache/freemarker/core/MiscErrorMessagesTest.java @@ -0,0 +1,47 @@ +/* + * 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; + +import org.apache.freemarker.core.templateresolver.impl.DefaultTemplateNameFormat; +import org.apache.freemarker.test.TemplateTest; +import org.junit.Test; + +public class MiscErrorMessagesTest extends TemplateTest { + + @Test + public void stringIndexOutOfBounds() { + assertErrorContains("${'foo'[10]}", "length", "3", "10", "String index out of"); + } + + @Test + public void wrongTemplateNameFormat() { + getConfiguration().setTemplateNameFormat(DefaultTemplateNameFormat.INSTANCE); + + assertErrorContains("<#include 'foo:/bar:baaz'>", "Malformed template name", "':'"); + assertErrorContains("<#include '../baaz'>", "Malformed template name", "root"); + assertErrorContains("<#include '\u0000'>", "Malformed template name", "\\u0000"); + } + + @Test + public void numericalKeyHint() { + assertErrorContains("${{}[10]}", "[]", "?api"); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/test/java/org/apache/freemarker/core/MistakenlyPublicImportAPIsTest.java ---------------------------------------------------------------------- diff --git a/src/test/java/org/apache/freemarker/core/MistakenlyPublicImportAPIsTest.java b/src/test/java/org/apache/freemarker/core/MistakenlyPublicImportAPIsTest.java index 04054ad..0b9e0b6 100644 --- a/src/test/java/org/apache/freemarker/core/MistakenlyPublicImportAPIsTest.java +++ b/src/test/java/org/apache/freemarker/core/MistakenlyPublicImportAPIsTest.java @@ -28,10 +28,7 @@ import java.io.IOException; import java.io.StringWriter; import java.util.List; -import org.apache.freemarker.core.ast.Environment; -import org.apache.freemarker.core.ast.Environment.Namespace; -import org.apache.freemarker.core.ast.InvalidReferenceException; -import org.apache.freemarker.core.ast.LibraryLoad; +import org.apache.freemarker.core.Environment.Namespace; import org.apache.freemarker.core.model.TemplateModel; import org.apache.freemarker.core.templateresolver.impl.StringTemplateLoader; import org.apache.freemarker.core.util._NullWriter; @@ -51,12 +48,12 @@ public class MistakenlyPublicImportAPIsTest { cfg.setTemplateLoader(tl); Template t1 = new Template(null, "<#import 'imp1' as i1><#import 'imp2' as i2>", cfg); - List<LibraryLoad> imports = t1.getImports(); + List<ASTDirImport> imports = t1.getImports(); assertEquals(2, imports.size()); { Template t2 = new Template(null, "<@i1.m/><@i2.m/>", cfg); - for (LibraryLoad libLoad : imports) { + for (ASTDirImport libLoad : imports) { t2.addImport(libLoad); } http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/test/java/org/apache/freemarker/core/MistakenlyPublicMacroAPIsTest.java ---------------------------------------------------------------------- diff --git a/src/test/java/org/apache/freemarker/core/MistakenlyPublicMacroAPIsTest.java b/src/test/java/org/apache/freemarker/core/MistakenlyPublicMacroAPIsTest.java index 21e80c2..0445789 100644 --- a/src/test/java/org/apache/freemarker/core/MistakenlyPublicMacroAPIsTest.java +++ b/src/test/java/org/apache/freemarker/core/MistakenlyPublicMacroAPIsTest.java @@ -27,8 +27,6 @@ import java.io.IOException; import java.io.StringWriter; import java.util.Map; -import org.apache.freemarker.core.ast.Environment; -import org.apache.freemarker.core.ast.Macro; import org.apache.freemarker.core.model.TemplateModel; import org.apache.freemarker.core.util._NullWriter; import org.junit.Test; @@ -46,7 +44,7 @@ public class MistakenlyPublicMacroAPIsTest { @Test public void testMacroCopyingExploit() throws IOException, TemplateException { Template tMacros = new Template(null, "<#macro m1>1</#macro><#macro m2>2</#macro>", cfg); - Map<String, Macro> macros = tMacros.getMacros(); + Map<String, ASTDirMacro> macros = tMacros.getMacros(); Template t = new Template(null, "<@m1/><@m2/><@m3/>" @@ -62,7 +60,7 @@ public class MistakenlyPublicMacroAPIsTest { public void testMacroCopyingExploitAndNamespaces() throws IOException, TemplateException { Template tMacros = new Template(null, "<#assign x = 0><#macro m1>${x}</#macro>", cfg); Template t = new Template(null, "<#assign x = 1><@m1/>", cfg); - t.addMacro((Macro) tMacros.getMacros().get("m1")); + t.addMacro((ASTDirMacro) tMacros.getMacros().get("m1")); assertEquals("1", getTemplateOutput(t)); } @@ -73,10 +71,10 @@ public class MistakenlyPublicMacroAPIsTest { Environment env = tMacros.createProcessingEnvironment(null, _NullWriter.INSTANCE); env.process(); TemplateModel m1 = env.getVariable("m1"); - assertThat(m1, instanceOf(Macro.class)); + assertThat(m1, instanceOf(ASTDirMacro.class)); Template t = new Template(null, "<#assign x = 1><@m1/>", cfg); - t.addMacro((Macro) m1); + t.addMacro((ASTDirMacro) m1); assertEquals("1", getTemplateOutput(t)); } http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/test/java/org/apache/freemarker/core/NumberFormatTest.java ---------------------------------------------------------------------- diff --git a/src/test/java/org/apache/freemarker/core/NumberFormatTest.java b/src/test/java/org/apache/freemarker/core/NumberFormatTest.java new file mode 100644 index 0000000..18293ee --- /dev/null +++ b/src/test/java/org/apache/freemarker/core/NumberFormatTest.java @@ -0,0 +1,326 @@ +/* + * 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; + +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.AliasTemplateNumberFormatFactory; +import org.apache.freemarker.core.Configuration; +import org.apache.freemarker.core.Environment; +import org.apache.freemarker.core.Template; +import org.apache.freemarker.core.TemplateConfiguration; +import org.apache.freemarker.core.TemplateException; +import org.apache.freemarker.core.TemplateNumberFormat; +import org.apache.freemarker.core.TemplateNumberFormatFactory; +import org.apache.freemarker.core.UndefinedCustomFormatException; +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.test.TemplateTest; +import org.junit.Before; +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 + 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; + } + + } + +}
