http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/28a276c8/freemarker-core-test/src/test/java/org/apache/freemarker/core/SettingDirectiveTest.java ---------------------------------------------------------------------- diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/SettingDirectiveTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/SettingDirectiveTest.java new file mode 100644 index 0000000..3b59e78 --- /dev/null +++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/SettingDirectiveTest.java @@ -0,0 +1,40 @@ +/* + * 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.*; +import static org.junit.Assert.*; + +import org.junit.Test; + +public class SettingDirectiveTest { + + @Test + public void testGetSettingNamesSorted() throws Exception { + String prevName = null; + for (String name : ASTDirSetting.SETTING_NAMES) { + if (prevName != null) { + assertThat(name, greaterThan(prevName)); + } + prevName = name; + } + } + +}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/28a276c8/freemarker-core-test/src/test/java/org/apache/freemarker/core/SpecialVariableTest.java ---------------------------------------------------------------------- diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/SpecialVariableTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/SpecialVariableTest.java new file mode 100644 index 0000000..7e17fc7 --- /dev/null +++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/SpecialVariableTest.java @@ -0,0 +1,114 @@ +/* + * 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.*; +import static org.junit.Assert.*; + +import org.apache.freemarker.core.outputformat.impl.HTMLOutputFormat; +import org.apache.freemarker.core.outputformat.impl.PlainTextOutputFormat; +import org.apache.freemarker.core.outputformat.impl.UndefinedOutputFormat; +import org.apache.freemarker.test.TemplateTest; +import org.junit.Test; + +public class SpecialVariableTest extends TemplateTest { + + @Test + public void testNamesSorted() throws Exception { + String prevName = null; + for (String name : ASTExpBuiltInVariable.SPEC_VAR_NAMES) { + if (prevName != null) { + assertThat(name, greaterThan(prevName)); + } + prevName = name; + } + } + + @Test + public void testVersion() throws Exception { + String versionStr = Configuration.getVersion().toString(); + assertOutput("${.version}", versionStr); + } + + @Test + public void testIncompationImprovements() throws Exception { + setConfiguration(new Configuration.Builder(Configuration.VERSION_3_0_0).build()); + assertOutput( + "${.incompatibleImprovements}", + getConfiguration().getIncompatibleImprovements().toString()); + + setConfiguration(new Configuration.Builder(Configuration.getVersion()).build()); + assertOutput( + "${.incompatible_improvements}", + getConfiguration().getIncompatibleImprovements().toString()); + } + + @Test + public void testAutoEsc() throws Exception { + Configuration.Builder cfgB = new Configuration.Builder(Configuration.VERSION_3_0_0); + + for (int autoEscaping : new int[] { + ParsingConfiguration.ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY, ParsingConfiguration.ENABLE_IF_SUPPORTED_AUTO_ESCAPING_POLICY }) { + cfgB.setAutoEscapingPolicy(autoEscaping); + cfgB.setOutputFormat(HTMLOutputFormat.INSTANCE); + setConfiguration(cfgB.build()); + assertOutput("${.autoEsc?c}", "true"); + assertOutput("<#ftl autoEsc=false>${.autoEsc?c}", "false"); + + cfgB.setOutputFormat(PlainTextOutputFormat.INSTANCE); + setConfiguration(cfgB.build()); + assertOutput("${.autoEsc?c}", "false"); + + cfgB.setOutputFormat(UndefinedOutputFormat.INSTANCE); + setConfiguration(cfgB.build()); + assertOutput("${.autoEsc?c}", "false"); + } + + cfgB.setAutoEscapingPolicy(ParsingConfiguration.DISABLE_AUTO_ESCAPING_POLICY); + cfgB.setOutputFormat(HTMLOutputFormat.INSTANCE); + setConfiguration(cfgB.build()); + assertOutput("${.autoEsc?c}", "false"); + assertOutput("<#ftl autoEsc=true>${.autoEsc?c}", "true"); + + cfgB.setOutputFormat(PlainTextOutputFormat.INSTANCE); + setConfiguration(cfgB.build()); + assertOutput("${.autoEsc?c}", "false"); + + cfgB.setOutputFormat(UndefinedOutputFormat.INSTANCE); + setConfiguration(cfgB.build()); + assertOutput("${.autoEsc?c}", "false"); + + cfgB.setAutoEscapingPolicy(ParsingConfiguration.ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY); + setConfiguration(cfgB.build()); + assertOutput( + "${.autoEsc?c} " + + "<#outputFormat 'HTML'>${.autoEsc?c}</#outputFormat> " + + "<#outputFormat 'undefined'>${.autoEsc?c}</#outputFormat> " + + "<#outputFormat 'HTML'>" + + "${.autoEsc?c} <#noAutoEsc>${.autoEsc?c} " + + "<#autoEsc>${.autoEsc?c}</#autoEsc> ${.autoEsc?c}</#noAutoEsc> ${.autoEsc?c}" + + "</#outputFormat>", + "false true false " + + "true false true false true"); + + assertErrorContains("${.autoEscaping}", "You may meant: \"autoEsc\""); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/28a276c8/freemarker-core-test/src/test/java/org/apache/freemarker/core/StringLiteralInterpolationTest.java ---------------------------------------------------------------------- diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/StringLiteralInterpolationTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/StringLiteralInterpolationTest.java new file mode 100644 index 0000000..ae72dac --- /dev/null +++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/StringLiteralInterpolationTest.java @@ -0,0 +1,135 @@ +/* + * 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 java.util.Collections; + +import org.apache.freemarker.core.outputformat.impl.RTFOutputFormat; +import org.apache.freemarker.core.userpkg.PrintfGTemplateNumberFormatFactory; +import org.apache.freemarker.core.valueformat.TemplateNumberFormatFactory; +import org.apache.freemarker.test.TemplateTest; +import org.apache.freemarker.test.TestConfigurationBuilder; +import org.junit.Test; + +@SuppressWarnings("boxing") +public class StringLiteralInterpolationTest extends TemplateTest { + + @Test + public void basics() throws IOException, TemplateException { + addToDataModel("x", 1); + assertOutput("${'${x}'}", "1"); + assertOutput("${'#{x}'}", "1"); + assertOutput("${'a${x}b${x*2}c'}", "a1b2c"); + assertOutput("${'a#{x}b#{x*2}c'}", "a1b2c"); + assertOutput("${'a#{x; m2}'}", "a1.00"); + assertOutput("${'${x} ${x}'}", "1 1"); + assertOutput("${'$\\{x}'}", "${x}"); + assertOutput("${'$\\{x} $\\{x}'}", "${x} ${x}"); + assertOutput("${'<#-- not a comment -->${x}'}", "<#-- not a comment -->1"); + assertOutput("${'<#-- not a comment -->$\\{x}'}", "<#-- not a comment -->${x}"); + assertOutput("${'<#assign x = 2> ${x} <#assign x = 2>'}", "<#assign x = 2> 1 <#assign x = 2>"); + assertOutput("${'<#assign x = 2> $\\{x} <#assign x = 2>'}", "<#assign x = 2> ${x} <#assign x = 2>"); + assertOutput("${'<@x/>${x}<@x/>'}", "<@x/>1<@x/>"); + assertOutput("${'<@x/>$\\{x}<@x/>'}", "<@x/>${x}<@x/>"); + assertOutput("${'<@ ${x}<@'}", "<@ 1<@"); + assertOutput("${'<@ $\\{x}<@'}", "<@ ${x}<@"); + assertOutput("${'</@x>${x}'}", "</@x>1"); + assertOutput("${'</@x>$\\{x}'}", "</@x>${x}"); + assertOutput("${'</@ ${x}</@'}", "</@ 1</@"); + assertOutput("${'</@ $\\{x}</@'}", "</@ ${x}</@"); + assertOutput("${'[@ ${x}'}", "[@ 1"); + assertOutput("${'[@ $\\{x}'}", "[@ ${x}"); + } + + /** + * Broken behavior for backward compatibility. + */ + @Test + public void legacyEscapingBugStillPresent() throws IOException, TemplateException { + addToDataModel("x", 1); + assertOutput("${'$\\{x} ${x}'}", "1 1"); + assertOutput("${'${x} $\\{x}'}", "1 1"); + } + + @Test + public void legacyLengthGlitch() throws IOException, TemplateException { + assertOutput("${'${'}", "${"); + assertOutput("${'${1'}", "${1"); + assertOutput("${'${}'}", "${}"); + assertOutput("${'${1}'}", "1"); + assertErrorContains("${'${ '}", ""); + } + + @Test + public void testErrors() { + addToDataModel("x", 1); + assertErrorContains("${'${noSuchVar}'}", InvalidReferenceException.class, "missing", "noSuchVar"); + assertErrorContains("${'${x/0}'}", ArithmeticException.class, "zero"); + } + + @Test + public void escaping() throws IOException, TemplateException { + assertOutput("<#escape x as x?html><#assign x = '&'>${x} ${'${x}'}</#escape> ${x}", "& & &"); + } + + // We couldn't test this on 3.0.0, as nothing was fixed there with IcI yet + /*- + @Test + public void iciInheritanceBugFixed() throws Exception { + // Broken behavior emulated: + getConfiguration().setIncompatibleImprovements(Configuration.VERSION_2_3_23); + assertOutput("${'&\\''?html} ${\"${'&\\\\\\''?html}\"}", "&' &'"); + + // Fix enabled: + getConfiguration().setIncompatibleImprovements(Configuration.VERSION_2_3_24); + assertOutput("${'&\\''?html} ${\"${'&\\\\\\''?html}\"}", "&' &'"); + } + */ + + @Test + public void markup() throws IOException, TemplateException { + setConfiguration(new TestConfigurationBuilder() + .customNumberFormats(Collections.<String, TemplateNumberFormatFactory>singletonMap( + "G", PrintfGTemplateNumberFormatFactory.INSTANCE)) + .numberFormat("@G 3") + .build()); + + assertOutput("${\"${1000}\"}", "1.00*10<sup>3</sup>"); + assertOutput("${\"&_${1000}\"}", "&_1.00*10<sup>3</sup>"); + assertOutput("${\"${1000}_&\"}", "1.00*10<sup>3</sup>_&"); + assertOutput("${\"${1000}, ${2000}\"}", "1.00*10<sup>3</sup>, 2.00*10<sup>3</sup>"); + assertOutput("${\"& ${'x'}, ${2000}\"}", "& x, 2.00*10<sup>3</sup>"); + assertOutput("${\"& ${'x'}, #{2000}\"}", "& x, 2000"); + + assertOutput("${\"${2000}\"?isMarkupOutput?c}", "true"); + assertOutput("${\"x ${2000}\"?isMarkupOutput?c}", "true"); + assertOutput("${\"${2000} x\"?isMarkupOutput?c}", "true"); + assertOutput("${\"#{2000}\"?isMarkupOutput?c}", "false"); + assertOutput("${\"${'x'}\"?isMarkupOutput?c}", "false"); + assertOutput("${\"x ${'x'}\"?isMarkupOutput?c}", "false"); + assertOutput("${\"${'x'} x\"?isMarkupOutput?c}", "false"); + + addToDataModel("rtf", RTFOutputFormat.INSTANCE.fromMarkup("\\p")); + assertOutput("${\"${rtf}\"?isMarkupOutput?c}", "true"); + assertErrorContains("${\"${1000}${rtf}\"}", TemplateException.class, "HTML", "RTF", "onversion"); + assertErrorContains("x${\"${1000}${rtf}\"}", TemplateException.class, "HTML", "RTF", "onversion"); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/28a276c8/freemarker-core-test/src/test/java/org/apache/freemarker/core/TabSizeTest.java ---------------------------------------------------------------------- diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/TabSizeTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/TabSizeTest.java new file mode 100644 index 0000000..7945b5e --- /dev/null +++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/TabSizeTest.java @@ -0,0 +1,91 @@ +/* + * 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.*; + +import java.io.IOException; + +import org.apache.freemarker.test.TemplateTest; +import org.apache.freemarker.test.TestConfigurationBuilder; +import org.junit.Test; + +public class TabSizeTest extends TemplateTest { + + @Override + protected Configuration createDefaultConfiguration() throws Exception { + return super.createDefaultConfiguration(); + } + + @Test + public void testBasics() throws Exception { + assertErrorColumnNumber(3, "${*}"); + assertErrorColumnNumber(8 + 3, "\t${*}"); + assertErrorColumnNumber(16 + 3, "\t\t${*}"); + assertErrorColumnNumber(16 + 3, " \t \t${*}"); + + setConfiguration(new TestConfigurationBuilder().tabSize(1).build()); + assertErrorColumnNumber(3, "${*}"); + assertErrorColumnNumber(1 + 3, "\t${*}"); + assertErrorColumnNumber(2 + 3, "\t\t${*}"); + assertErrorColumnNumber(6 + 3, " \t \t${*}"); + } + + @Test + public void testEvalBI() throws Exception { + assertErrorContains("${r'\t~'?eval}", "column 9"); + setConfiguration(new TestConfigurationBuilder().tabSize(4).build()); + assertErrorContains("${r'\t~'?eval}", "column 5"); + } + + @Test + public void testInterpretBI() throws Exception { + assertErrorContains("<@'\\t$\\{*}'?interpret />", "column 11"); + setConfiguration(new TestConfigurationBuilder().tabSize(4).build()); + assertErrorContains("<@'\\t$\\{*}'?interpret />", "column 7"); + } + + @Test + public void testStringLiteralInterpolation() throws Exception { + assertErrorColumnNumber(6, "${'${*}'}"); + assertErrorColumnNumber(9, "${'${\t*}'}"); + setConfiguration(new TestConfigurationBuilder().tabSize(16).build()); + assertErrorColumnNumber(17, "${'${\t*}'}"); + } + + protected void assertErrorColumnNumber(int expectedColumn, String templateSource) + throws IOException { + addTemplate("t", templateSource); + try { + getConfiguration().getTemplate("t"); + fail(); + } catch (ParseException e) { + assertEquals(expectedColumn, e.getColumnNumber()); + } + getConfiguration().clearTemplateCache(); + + try { + new Template(null, templateSource, getConfiguration()); + fail(); + } catch (ParseException e) { + assertEquals(expectedColumn, e.getColumnNumber()); + } + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/28a276c8/freemarker-core-test/src/test/java/org/apache/freemarker/core/TagSyntaxVariationsTest.java ---------------------------------------------------------------------- diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/TagSyntaxVariationsTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/TagSyntaxVariationsTest.java new file mode 100644 index 0000000..fa21c76 --- /dev/null +++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/TagSyntaxVariationsTest.java @@ -0,0 +1,186 @@ +/* + * 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 java.io.StringReader; +import java.io.StringWriter; + +import org.apache.freemarker.core.util._StringUtil; +import org.apache.freemarker.test.TestConfigurationBuilder; + +import junit.framework.TestCase; + +/** + * Test various generated templates (permutations), including some deliberately + * wrong ones, with various tag_syntax settings. + */ +public class TagSyntaxVariationsTest extends TestCase { + + private static final String HDR_ANG = "<#ftl>"; + private static final String HDR_SQU = squarify(HDR_ANG); + private static final String IF_ANG = "<#if true>i</#if>"; + private static final String IF_SQU = squarify(IF_ANG); + private static final String IF_OUT = "i"; + private static final String ASSIGN_ANG = "<#assign x = 1>a"; + private static final String ASSIGN_SQU = squarify(ASSIGN_ANG); + private static final String ASSIGN_OUT = "a"; + private static final String WRONG_ANG = "<#wrong>"; + private static final String WRONG_SQU = squarify(WRONG_ANG); + private static final String WRONGC_ANG = "</#wrong>"; + private static final String WRONGC_SQU = squarify(WRONGC_ANG ); + private static final String CUST_ANG = "<@compress> z </@>"; + private static final String CUST_SQU = squarify(CUST_ANG); + private static final String CUST_OUT = "z"; + + public TagSyntaxVariationsTest(String name) { + super(name); + } + + private static String squarify(String s) { + return s.replace('<', '[').replace('>', ']'); + } + + public final void test() + throws TemplateException, IOException { + // Permutations + for (int ifOrAssign = 0; ifOrAssign < 2; ifOrAssign++) { + String dir_ang = ifOrAssign == 0 ? IF_ANG : ASSIGN_ANG; + String dir_squ = ifOrAssign == 0 ? IF_SQU : ASSIGN_SQU; + String dir_out = ifOrAssign == 0 ? IF_OUT : ASSIGN_OUT; + + // Permutations + for (int angOrSqu = 0; angOrSqu < 2; angOrSqu++) { + Configuration cfg = new TestConfigurationBuilder() + .tagSyntax(angOrSqu == 0 + ? ParsingConfiguration.ANGLE_BRACKET_TAG_SYNTAX + : ParsingConfiguration.SQUARE_BRACKET_TAG_SYNTAX) + .build(); + + String dir_xxx = angOrSqu == 0 ? dir_ang : dir_squ; + String cust_xxx = angOrSqu == 0 ? CUST_ANG : CUST_SQU; + String hdr_xxx = angOrSqu == 0 ? HDR_ANG : HDR_SQU; + String wrong_xxx = angOrSqu == 0 ? WRONG_ANG : WRONG_SQU; + String wrongc_xxx = angOrSqu == 0 ? WRONGC_ANG : WRONGC_SQU; + + test(cfg, + dir_xxx + cust_xxx, + dir_out + CUST_OUT); + + // Permutations + for (int wrongOrWrongc = 0; wrongOrWrongc < 2; wrongOrWrongc++) { + String wrongx_xxx = wrongOrWrongc == 0 ? wrong_xxx : wrongc_xxx; + + test(cfg, + wrongx_xxx + dir_xxx, + null); + + test(cfg, + dir_xxx + wrongx_xxx, + null); + + test(cfg, + hdr_xxx + wrongx_xxx, + null); + + test(cfg, + cust_xxx + wrongx_xxx + dir_xxx, + null); + } // for wrongc + } // for squ + + { + Configuration cfg = new TestConfigurationBuilder() + .tagSyntax(ParsingConfiguration.AUTO_DETECT_TAG_SYNTAX) + .build(); + for (int perm = 0; perm < 4; perm++) { + // All 4 permutations + String wrong_xxx = (perm & 1) == 0 ? WRONG_ANG : WRONG_SQU; + String dir_xxx = (perm & 2) == 0 ? dir_ang : dir_squ; + + test(cfg, + wrong_xxx + dir_xxx, + null); + } // for perm + } + + { + Configuration cfg = new TestConfigurationBuilder() + .tagSyntax(ParsingConfiguration.AUTO_DETECT_TAG_SYNTAX) + .build(); + // Permutations + for (int angOrSquStart = 0; angOrSquStart < 2; angOrSquStart++) { + String hdr_xxx = angOrSquStart == 0 ? HDR_ANG : HDR_SQU; + String cust_xxx = angOrSquStart == 0 ? CUST_ANG : CUST_SQU; + String wrong_yyy = angOrSquStart != 0 ? WRONG_ANG : WRONG_SQU; + String dir_xxx = angOrSquStart == 0 ? dir_ang : dir_squ; + String dir_yyy = angOrSquStart != 0 ? dir_ang : dir_squ; + + test(cfg, + cust_xxx + wrong_yyy + dir_xxx, + CUST_OUT + wrong_yyy + dir_out); + + test(cfg, + hdr_xxx + wrong_yyy + dir_xxx, + wrong_yyy + dir_out); + + test(cfg, + cust_xxx + wrong_yyy + dir_yyy, + CUST_OUT + wrong_yyy + dir_yyy); + + test(cfg, + hdr_xxx + wrong_yyy + dir_yyy, + wrong_yyy + dir_yyy); + + test(cfg, + dir_xxx + wrong_yyy + dir_yyy, + dir_out + wrong_yyy + dir_yyy); + } // for squStart + } // for assign + } + } + + /** + * @param expected the expected output or <tt>null</tt> if we expect + * a parsing error. + */ + private static void test( + Configuration cfg, String template, String expected) + throws TemplateException, IOException { + Template t = null; + try { + t = new Template("string", new StringReader(template), cfg); + } catch (ParseException e) { + if (expected != null) { + fail("Couldn't invoke Template from " + + _StringUtil.jQuote(template) + ": " + e); + } else { + return; + } + } + if (expected == null) fail("Template parsing should have fail for " + + _StringUtil.jQuote(template)); + + StringWriter out = new StringWriter(); + t.process(new Object(), out); + assertEquals(expected, out.toString()); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/28a276c8/freemarker-core-test/src/test/java/org/apache/freemarker/core/TemplateConfigurationTest.java ---------------------------------------------------------------------- diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/TemplateConfigurationTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/TemplateConfigurationTest.java new file mode 100644 index 0000000..5b1cda9 --- /dev/null +++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/TemplateConfigurationTest.java @@ -0,0 +1,909 @@ +/* + * 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.*; + +import java.beans.BeanInfo; +import java.beans.IntrospectionException; +import java.beans.Introspector; +import java.beans.PropertyDescriptor; +import java.io.IOException; +import java.io.StringReader; +import java.io.StringWriter; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.TimeZone; + +import org.apache.commons.collections.ListUtils; +import org.apache.freemarker.core.arithmetic.ArithmeticEngine; +import org.apache.freemarker.core.arithmetic.impl.ConservativeArithmeticEngine; +import org.apache.freemarker.core.model.impl.RestrictedObjectWrapper; +import org.apache.freemarker.core.outputformat.impl.HTMLOutputFormat; +import org.apache.freemarker.core.outputformat.impl.UndefinedOutputFormat; +import org.apache.freemarker.core.outputformat.impl.XMLOutputFormat; +import org.apache.freemarker.core.templateresolver.ConditionalTemplateConfigurationFactory; +import org.apache.freemarker.core.templateresolver.FileExtensionMatcher; +import org.apache.freemarker.core.templateresolver.FileNameGlobMatcher; +import org.apache.freemarker.core.templateresolver.impl.StringTemplateLoader; +import org.apache.freemarker.core.userpkg.BaseNTemplateNumberFormatFactory; +import org.apache.freemarker.core.userpkg.EpochMillisDivTemplateDateFormatFactory; +import org.apache.freemarker.core.userpkg.EpochMillisTemplateDateFormatFactory; +import org.apache.freemarker.core.userpkg.HexTemplateNumberFormatFactory; +import org.apache.freemarker.core.userpkg.LocAndTZSensitiveTemplateDateFormatFactory; +import org.apache.freemarker.core.userpkg.LocaleSensitiveTemplateNumberFormatFactory; +import org.apache.freemarker.core.valueformat.TemplateDateFormatFactory; +import org.apache.freemarker.core.valueformat.TemplateNumberFormatFactory; +import org.apache.freemarker.test.MonitoredTemplateLoader; +import org.apache.freemarker.test.TestConfigurationBuilder; +import org.junit.Test; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; + +@SuppressWarnings("boxing") +public class TemplateConfigurationTest { + + private static final Charset ISO_8859_2 = Charset.forName("ISO-8859-2"); + + private final class DummyArithmeticEngine extends ArithmeticEngine { + + @Override + public int compareNumbers(Number first, Number second) throws TemplateException { + return 0; + } + + @Override + public Number add(Number first, Number second) throws TemplateException { + return 22; + } + + @Override + public Number subtract(Number first, Number second) throws TemplateException { + return null; + } + + @Override + public Number multiply(Number first, Number second) throws TemplateException { + return 33; + } + + @Override + public Number divide(Number first, Number second) throws TemplateException { + return null; + } + + @Override + public Number modulus(Number first, Number second) throws TemplateException { + return null; + } + + @Override + public Number toNumber(String s) { + return 11; + } + } + + private static final Configuration DEFAULT_CFG; + static { + TestConfigurationBuilder cfgB = new TestConfigurationBuilder(); + StringTemplateLoader stl = new StringTemplateLoader(); + stl.putTemplate("t1.ftl", "<#global loaded = (loaded!) + 't1;'>In t1;"); + stl.putTemplate("t2.ftl", "<#global loaded = (loaded!) + 't2;'>In t2;"); + stl.putTemplate("t3.ftl", "<#global loaded = (loaded!) + 't3;'>In t3;"); + try { + DEFAULT_CFG = cfgB.templateLoader(stl).build(); + } catch (ConfigurationException e) { + throw new IllegalStateException("Faild to create default configuration", e); + } + } + + private static final TimeZone NON_DEFAULT_TZ; + static { + TimeZone defaultTZ = DEFAULT_CFG.getTimeZone(); + TimeZone tz = TimeZone.getTimeZone("UTC"); + if (tz.equals(defaultTZ)) { + tz = TimeZone.getTimeZone("GMT+01"); + if (tz.equals(defaultTZ)) { + throw new AssertionError("Couldn't chose a non-default time zone"); + } + } + NON_DEFAULT_TZ = tz; + } + + private static final Locale NON_DEFAULT_LOCALE = + DEFAULT_CFG.getLocale().equals(Locale.US) ? Locale.GERMAN : Locale.US; + + private static final Charset NON_DEFAULT_ENCODING = + DEFAULT_CFG.getSourceEncoding().equals(StandardCharsets.UTF_8) ? StandardCharsets.UTF_16LE + : StandardCharsets.UTF_8; + + private static final Map<String, Object> SETTING_ASSIGNMENTS; + + static { + SETTING_ASSIGNMENTS = new HashMap<>(); + + // "MutableProcessingConfiguration" settings: + SETTING_ASSIGNMENTS.put("APIBuiltinEnabled", true); + SETTING_ASSIGNMENTS.put("SQLDateAndTimeTimeZone", NON_DEFAULT_TZ); + SETTING_ASSIGNMENTS.put("URLEscapingCharset", StandardCharsets.UTF_16); + SETTING_ASSIGNMENTS.put("autoFlush", false); + SETTING_ASSIGNMENTS.put("booleanFormat", "J,N"); + SETTING_ASSIGNMENTS.put("dateFormat", "yyyy-#DDD"); + SETTING_ASSIGNMENTS.put("dateTimeFormat", "yyyy-#DDD-@HH:mm"); + SETTING_ASSIGNMENTS.put("locale", NON_DEFAULT_LOCALE); + SETTING_ASSIGNMENTS.put("logTemplateExceptions", true); + SETTING_ASSIGNMENTS.put("newBuiltinClassResolver", TemplateClassResolver.ALLOWS_NOTHING_RESOLVER); + SETTING_ASSIGNMENTS.put("numberFormat", "0.0000"); + SETTING_ASSIGNMENTS.put("objectWrapper", + new RestrictedObjectWrapper.Builder(Configuration.VERSION_3_0_0).build()); + SETTING_ASSIGNMENTS.put("outputEncoding", StandardCharsets.UTF_16); + SETTING_ASSIGNMENTS.put("showErrorTips", false); + SETTING_ASSIGNMENTS.put("templateExceptionHandler", TemplateExceptionHandler.IGNORE_HANDLER); + SETTING_ASSIGNMENTS.put("timeFormat", "@HH:mm"); + SETTING_ASSIGNMENTS.put("timeZone", NON_DEFAULT_TZ); + SETTING_ASSIGNMENTS.put("arithmeticEngine", ConservativeArithmeticEngine.INSTANCE); + SETTING_ASSIGNMENTS.put("customNumberFormats", + ImmutableMap.of("dummy", HexTemplateNumberFormatFactory.INSTANCE)); + SETTING_ASSIGNMENTS.put("customDateFormats", + ImmutableMap.of("dummy", EpochMillisTemplateDateFormatFactory.INSTANCE)); + SETTING_ASSIGNMENTS.put("customAttributes", ImmutableMap.of("dummy", 123)); + + // Parser-only settings: + SETTING_ASSIGNMENTS.put("templateLanguage", TemplateLanguage.STATIC_TEXT); + SETTING_ASSIGNMENTS.put("tagSyntax", ParsingConfiguration.SQUARE_BRACKET_TAG_SYNTAX); + SETTING_ASSIGNMENTS.put("namingConvention", ParsingConfiguration.LEGACY_NAMING_CONVENTION); + SETTING_ASSIGNMENTS.put("whitespaceStripping", false); + SETTING_ASSIGNMENTS.put("strictSyntaxMode", false); + SETTING_ASSIGNMENTS.put("autoEscapingPolicy", ParsingConfiguration.DISABLE_AUTO_ESCAPING_POLICY); + SETTING_ASSIGNMENTS.put("outputFormat", HTMLOutputFormat.INSTANCE); + SETTING_ASSIGNMENTS.put("recognizeStandardFileExtensions", false); + SETTING_ASSIGNMENTS.put("tabSize", 1); + SETTING_ASSIGNMENTS.put("lazyImports", Boolean.TRUE); + SETTING_ASSIGNMENTS.put("lazyAutoImports", Boolean.FALSE); + SETTING_ASSIGNMENTS.put("autoImports", ImmutableMap.of("a", "/lib/a.ftl")); + SETTING_ASSIGNMENTS.put("autoIncludes", ImmutableList.of("/lib/b.ftl")); + + // Special settings: + SETTING_ASSIGNMENTS.put("sourceEncoding", NON_DEFAULT_ENCODING); + } + + public static String getIsSetMethodName(String readMethodName) { + return (readMethodName.startsWith("get") ? "is" + readMethodName.substring(3) + : readMethodName) + + "Set"; + } + + public static List<PropertyDescriptor> getTemplateConfigurationSettingPropDescs( + Class<? extends ProcessingConfiguration> confClass, boolean includeCompilerSettings) + throws IntrospectionException { + List<PropertyDescriptor> settingPropDescs = new ArrayList<>(); + + BeanInfo beanInfo = Introspector.getBeanInfo(confClass); + for (PropertyDescriptor pd : beanInfo.getPropertyDescriptors()) { + String name = pd.getName(); + if (pd.getWriteMethod() != null && !IGNORED_PROP_NAMES.contains(name) + && (includeCompilerSettings + || (CONFIGURABLE_PROP_NAMES.contains(name) || !PARSER_PROP_NAMES.contains(name)))) { + if (pd.getReadMethod() == null) { + throw new AssertionError("Property has no read method: " + pd); + } + settingPropDescs.add(pd); + } + } + + Collections.sort(settingPropDescs, new Comparator<PropertyDescriptor>() { + @Override + public int compare(PropertyDescriptor o1, PropertyDescriptor o2) { + return o1.getName().compareToIgnoreCase(o2.getName()); + } + }); + + return settingPropDescs; + } + + private static final Set<String> IGNORED_PROP_NAMES; + + static { + IGNORED_PROP_NAMES = new HashSet(); + IGNORED_PROP_NAMES.add("class"); + IGNORED_PROP_NAMES.add("strictBeanModels"); + IGNORED_PROP_NAMES.add("parentConfiguration"); + IGNORED_PROP_NAMES.add("settings"); + } + + private static final Set<String> CONFIGURABLE_PROP_NAMES; + static { + CONFIGURABLE_PROP_NAMES = new HashSet<>(); + try { + for (PropertyDescriptor propDesc : Introspector.getBeanInfo(MutableProcessingConfiguration.class).getPropertyDescriptors()) { + String propName = propDesc.getName(); + if (!IGNORED_PROP_NAMES.contains(propName)) { + CONFIGURABLE_PROP_NAMES.add(propName); + } + } + } catch (IntrospectionException e) { + throw new IllegalStateException("Failed to init static field", e); + } + } + + private static final Set<String> PARSER_PROP_NAMES; + static { + PARSER_PROP_NAMES = new HashSet<>(); + // It's an interface; can't use standard Inrospector + for (Method m : ParsingConfiguration.class.getMethods()) { + String propertyName; + String name = m.getName(); + if (name.startsWith("get")) { + propertyName = name.substring(3); + } else if (name.startsWith("is") && !name.endsWith("Set")) { + propertyName = name.substring(2); + } else { + propertyName = null; + } + if (propertyName != null) { + if (!Character.isUpperCase(propertyName.charAt(1))) { + propertyName = Character.toLowerCase(propertyName.charAt(0)) + propertyName.substring(1); + } + PARSER_PROP_NAMES.add(propertyName); + } + } + } + + private static final Object CA1 = new Object(); + private static final String CA2 = "ca2"; + private static final String CA3 = "ca3"; + private static final String CA4 = "ca4"; + + @Test + public void testMergeBasicFunctionality() throws Exception { + for (PropertyDescriptor propDesc1 : getTemplateConfigurationSettingPropDescs( + TemplateConfiguration.Builder.class, true)) { + for (PropertyDescriptor propDesc2 : getTemplateConfigurationSettingPropDescs( + TemplateConfiguration.Builder.class, true)) { + TemplateConfiguration.Builder tcb1 = new TemplateConfiguration.Builder(); + TemplateConfiguration.Builder tcb2 = new TemplateConfiguration.Builder(); + + Object value1 = SETTING_ASSIGNMENTS.get(propDesc1.getName()); + propDesc1.getWriteMethod().invoke(tcb1, value1); + Object value2 = SETTING_ASSIGNMENTS.get(propDesc2.getName()); + propDesc2.getWriteMethod().invoke(tcb2, value2); + + tcb1.merge(tcb2); + if (propDesc1.getName().equals(propDesc2.getName()) && value1 instanceof List + && !propDesc1.getName().equals("autoIncludes")) { + assertEquals("For " + propDesc1.getName(), + ListUtils.union((List) value1, (List) value1), propDesc1.getReadMethod().invoke(tcb1)); + } else { // Values of the same setting merged + assertEquals("For " + propDesc1.getName(), value1, propDesc1.getReadMethod().invoke(tcb1)); + assertEquals("For " + propDesc2.getName(), value2, propDesc2.getReadMethod().invoke(tcb1)); + } + } + } + } + + @Test + public void testMergeMapSettings() throws Exception { + TemplateConfiguration.Builder tc1 = new TemplateConfiguration.Builder(); + tc1.setCustomDateFormats(ImmutableMap.of( + "epoch", EpochMillisTemplateDateFormatFactory.INSTANCE, + "x", LocAndTZSensitiveTemplateDateFormatFactory.INSTANCE)); + tc1.setCustomNumberFormats(ImmutableMap.of( + "hex", HexTemplateNumberFormatFactory.INSTANCE, + "x", LocaleSensitiveTemplateNumberFormatFactory.INSTANCE)); + tc1.setAutoImports(ImmutableMap.of("a", "a1.ftl", "b", "b1.ftl")); + + TemplateConfiguration.Builder tc2 = new TemplateConfiguration.Builder(); + tc2.setCustomDateFormats(ImmutableMap.of( + "loc", LocAndTZSensitiveTemplateDateFormatFactory.INSTANCE, + "x", EpochMillisDivTemplateDateFormatFactory.INSTANCE)); + tc2.setCustomNumberFormats(ImmutableMap.of( + "loc", LocaleSensitiveTemplateNumberFormatFactory.INSTANCE, + "x", BaseNTemplateNumberFormatFactory.INSTANCE)); + tc2.setAutoImports(ImmutableMap.of("b", "b2.ftl", "c", "c2.ftl")); + + tc1.merge(tc2); + + Map<String, ? extends TemplateDateFormatFactory> mergedCustomDateFormats = tc1.getCustomDateFormats(); + assertEquals(EpochMillisTemplateDateFormatFactory.INSTANCE, mergedCustomDateFormats.get("epoch")); + assertEquals(LocAndTZSensitiveTemplateDateFormatFactory.INSTANCE, mergedCustomDateFormats.get("loc")); + assertEquals(EpochMillisDivTemplateDateFormatFactory.INSTANCE, mergedCustomDateFormats.get("x")); + + Map<String, ? extends TemplateNumberFormatFactory> mergedCustomNumberFormats = tc1.getCustomNumberFormats(); + assertEquals(HexTemplateNumberFormatFactory.INSTANCE, mergedCustomNumberFormats.get("hex")); + assertEquals(LocaleSensitiveTemplateNumberFormatFactory.INSTANCE, mergedCustomNumberFormats.get("loc")); + assertEquals(BaseNTemplateNumberFormatFactory.INSTANCE, mergedCustomNumberFormats.get("x")); + + Map<String, String> mergedAutoImports = tc1.getAutoImports(); + assertEquals("a1.ftl", mergedAutoImports.get("a")); + assertEquals("b2.ftl", mergedAutoImports.get("b")); + assertEquals("c2.ftl", mergedAutoImports.get("c")); + + // Empty map merging optimization: + tc1.merge(new TemplateConfiguration.Builder()); + assertSame(mergedCustomDateFormats, tc1.getCustomDateFormats()); + assertSame(mergedCustomNumberFormats, tc1.getCustomNumberFormats()); + + // Empty map merging optimization: + TemplateConfiguration.Builder tc3 = new TemplateConfiguration.Builder(); + tc3.merge(tc1); + assertSame(mergedCustomDateFormats, tc3.getCustomDateFormats()); + assertSame(mergedCustomNumberFormats, tc3.getCustomNumberFormats()); + } + + @Test + public void testMergeListSettings() throws Exception { + TemplateConfiguration.Builder tc1 = new TemplateConfiguration.Builder(); + tc1.setAutoIncludes(ImmutableList.of("a.ftl", "x.ftl", "b.ftl")); + + TemplateConfiguration.Builder tc2 = new TemplateConfiguration.Builder(); + tc2.setAutoIncludes(ImmutableList.of("c.ftl", "x.ftl", "d.ftl")); + + tc1.merge(tc2); + + assertEquals(ImmutableList.of("a.ftl", "b.ftl", "c.ftl", "x.ftl", "d.ftl"), tc1.getAutoIncludes()); + } + + @Test + public void testMergePriority() throws Exception { + TemplateConfiguration.Builder tc1 = new TemplateConfiguration.Builder(); + tc1.setDateFormat("1"); + tc1.setTimeFormat("1"); + tc1.setDateTimeFormat("1"); + + TemplateConfiguration.Builder tc2 = new TemplateConfiguration.Builder(); + tc2.setDateFormat("2"); + tc2.setTimeFormat("2"); + + TemplateConfiguration.Builder tc3 = new TemplateConfiguration.Builder(); + tc3.setDateFormat("3"); + + tc1.merge(tc2); + tc1.merge(tc3); + + assertEquals("3", tc1.getDateFormat()); + assertEquals("2", tc1.getTimeFormat()); + assertEquals("1", tc1.getDateTimeFormat()); + } + + @Test + public void testMergeCustomAttributes() throws Exception { + TemplateConfiguration.Builder tc1 = new TemplateConfiguration.Builder(); + tc1.setCustomAttribute("k1", "v1"); + tc1.setCustomAttribute("k2", "v1"); + tc1.setCustomAttribute("k3", "v1"); + tc1.setCustomAttribute(CA1, "V1"); + tc1.setCustomAttribute(CA2, "V1"); + tc1.setCustomAttribute(CA3, "V1"); + + TemplateConfiguration.Builder tc2 = new TemplateConfiguration.Builder(); + tc2.setCustomAttribute("k1", "v2"); + tc2.setCustomAttribute("k2", "v2"); + tc2.setCustomAttribute(CA1, "V2"); + tc2.setCustomAttribute(CA2, "V2"); + + TemplateConfiguration.Builder tc3 = new TemplateConfiguration.Builder(); + tc3.setCustomAttribute("k1", "v3"); + tc3.setCustomAttribute(CA1, "V3"); + + tc1.merge(tc2); + tc1.merge(tc3); + + assertEquals("v3", tc1.getCustomAttribute("k1")); + assertEquals("v2", tc1.getCustomAttribute("k2")); + assertEquals("v1", tc1.getCustomAttribute("k3")); + assertEquals("V3", tc1.getCustomAttribute(CA1)); + assertEquals("V2", tc1.getCustomAttribute(CA2)); + assertEquals("V1", tc1.getCustomAttribute(CA3)); + } + + @Test + public void testMergeNullCustomAttributes() throws Exception { + TemplateConfiguration.Builder tc1 = new TemplateConfiguration.Builder(); + tc1.setCustomAttribute("k1", "v1"); + tc1.setCustomAttribute("k2", "v1"); + tc1.setCustomAttribute(CA1, "V1"); + tc1.setCustomAttribute(CA2,"V1"); + + assertEquals("v1", tc1.getCustomAttribute("k1")); + assertEquals("v1", tc1.getCustomAttribute("k2")); + assertNull("v1", tc1.getCustomAttribute("k3")); + assertEquals("V1", tc1.getCustomAttribute(CA1)); + assertEquals("V1", tc1.getCustomAttribute(CA2)); + assertNull(tc1.getCustomAttribute(CA3)); + + TemplateConfiguration.Builder tc2 = new TemplateConfiguration.Builder(); + tc2.setCustomAttribute("k1", "v2"); + tc2.setCustomAttribute("k2", null); + tc2.setCustomAttribute(CA1, "V2"); + tc2.setCustomAttribute(CA2, null); + + TemplateConfiguration.Builder tc3 = new TemplateConfiguration.Builder(); + tc3.setCustomAttribute("k1", null); + tc2.setCustomAttribute(CA1, null); + + tc1.merge(tc2); + tc1.merge(tc3); + + assertNull(tc1.getCustomAttribute("k1")); + assertNull(tc1.getCustomAttribute("k2")); + assertNull(tc1.getCustomAttribute("k3")); + assertNull(tc1.getCustomAttribute(CA1)); + assertNull(tc1.getCustomAttribute(CA2)); + assertNull(tc1.getCustomAttribute(CA3)); + + TemplateConfiguration.Builder tc4 = new TemplateConfiguration.Builder(); + tc4.setCustomAttribute("k1", "v4"); + tc4.setCustomAttribute(CA1, "V4"); + + tc1.merge(tc4); + + assertEquals("v4", tc1.getCustomAttribute("k1")); + assertNull(tc1.getCustomAttribute("k2")); + assertNull(tc1.getCustomAttribute("k3")); + assertEquals("V4", tc1.getCustomAttribute(CA1)); + assertNull(tc1.getCustomAttribute(CA2)); + assertNull(tc1.getCustomAttribute(CA3)); + } + + @Test + public void testConfigureNonParserConfig() throws Exception { + for (PropertyDescriptor pd : getTemplateConfigurationSettingPropDescs( + TemplateConfiguration.Builder.class, false)) { + TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder(); + + Object newValue = SETTING_ASSIGNMENTS.get(pd.getName()); + pd.getWriteMethod().invoke(tcb, newValue); + + TemplateConfiguration tc = tcb.build(); + + Method tReaderMethod = Template.class.getMethod(pd.getReadMethod().getName()); + + // Without TC + assertNotEquals("For \"" + pd.getName() + "\"", + tReaderMethod.invoke(new Template(null, "", DEFAULT_CFG))); + // With TC + assertEquals("For \"" + pd.getName() + "\"", newValue, + tReaderMethod.invoke(new Template(null, "", DEFAULT_CFG, tc))); + } + } + + @Test + public void testConfigureCustomAttributes() throws Exception { + Configuration cfg = new TestConfigurationBuilder() + .customAttribute("k1", "c") + .customAttribute("k2", "c") + .customAttribute("k3", "c") + .build(); + + TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder(); + tcb.setCustomAttribute("k2", "tc"); + tcb.setCustomAttribute("k3", null); + tcb.setCustomAttribute("k4", "tc"); + tcb.setCustomAttribute("k5", "tc"); + tcb.setCustomAttribute("k6", "tc"); + tcb.setCustomAttribute(CA1, "tc"); + tcb.setCustomAttribute(CA2,"tc"); + tcb.setCustomAttribute(CA3,"tc"); + + TemplateConfiguration tc = tcb.build(); + Template t = new Template(null, "", cfg, tc); + t.setCustomAttribute("k5", "t"); + t.setCustomAttribute("k6", null); + t.setCustomAttribute("k7", "t"); + t.setCustomAttribute(CA2, "t"); + t.setCustomAttribute(CA3, null); + t.setCustomAttribute(CA4, "t"); + + assertEquals("c", t.getCustomAttribute("k1")); + assertEquals("tc", t.getCustomAttribute("k2")); + assertNull(t.getCustomAttribute("k3")); + assertEquals("tc", t.getCustomAttribute("k4")); + assertEquals("t", t.getCustomAttribute("k5")); + assertNull(t.getCustomAttribute("k6")); + assertEquals("t", t.getCustomAttribute("k7")); + assertEquals("tc", t.getCustomAttribute(CA1)); + assertEquals("t", t.getCustomAttribute(CA2)); + assertNull(t.getCustomAttribute(CA3)); + assertEquals("t", t.getCustomAttribute(CA4)); + } + + @Test + public void testConfigureParser() throws Exception { + Set<String> testedProps = new HashSet<>(); + + { + TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder(); + tcb.setTagSyntax(ParsingConfiguration.SQUARE_BRACKET_TAG_SYNTAX); + TemplateConfiguration tc = tcb.build(); + assertOutputWithoutAndWithTC(tc, "[#if true]y[/#if]", "[#if true]y[/#if]", "y"); + testedProps.add(Configuration.ExtendableBuilder.TAG_SYNTAX_KEY_CAMEL_CASE); + } + + { + TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder(); + tcb.setNamingConvention(ParsingConfiguration.CAMEL_CASE_NAMING_CONVENTION); + TemplateConfiguration tc = tcb.build(); + assertOutputWithoutAndWithTC(tc, "<#if true>y<#elseif false>n</#if>", "y", null); + testedProps.add(Configuration.ExtendableBuilder.NAMING_CONVENTION_KEY_CAMEL_CASE); + } + + { + TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder(); + tcb.setWhitespaceStripping(false); + TemplateConfiguration tc = tcb.build(); + assertOutputWithoutAndWithTC(tc, "<#if true>\nx\n</#if>\n", "x\n", "\nx\n\n"); + testedProps.add(Configuration.ExtendableBuilder.WHITESPACE_STRIPPING_KEY_CAMEL_CASE); + } + + { + TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder(); + tcb.setArithmeticEngine(new DummyArithmeticEngine()); + TemplateConfiguration tc = tcb.build(); + assertOutputWithoutAndWithTC(tc, "${1} ${1+1}", "1 2", "11 22"); + testedProps.add(Configuration.ExtendableBuilder.ARITHMETIC_ENGINE_KEY_CAMEL_CASE); + } + + { + TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder(); + tcb.setOutputFormat(XMLOutputFormat.INSTANCE); + TemplateConfiguration tc = tcb.build(); + assertOutputWithoutAndWithTC(tc, "${.outputFormat} ${\"a'b\"}", + UndefinedOutputFormat.INSTANCE.getName() + " a'b", + XMLOutputFormat.INSTANCE.getName() + " a'b"); + testedProps.add(Configuration.ExtendableBuilder.OUTPUT_FORMAT_KEY_CAMEL_CASE); + } + + { + TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder(); + tcb.setOutputFormat(XMLOutputFormat.INSTANCE); + tcb.setAutoEscapingPolicy(ParsingConfiguration.DISABLE_AUTO_ESCAPING_POLICY); + TemplateConfiguration tc = tcb.build(); + assertOutputWithoutAndWithTC(tc, "${'a&b'}", "a&b", "a&b"); + testedProps.add(Configuration.ExtendableBuilder.AUTO_ESCAPING_POLICY_KEY_CAMEL_CASE); + } + + { + TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder(); + /* Can't test this now, as the only valid value is 3.0.0. [FM3.0.1] + TemplateConfiguration tc = tcb.build(); + tc.setParentConfiguration(new Configuration(new Version(2, 3, 0))); + assertOutputWithoutAndWithTC(tc, "<#foo>", null, "<#foo>"); + */ + testedProps.add(Configuration.ExtendableBuilder.INCOMPATIBLE_IMPROVEMENTS_KEY_CAMEL_CASE); + } + + { + TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder(); + tcb.setRecognizeStandardFileExtensions(false); + TemplateConfiguration tc = tcb.build(); + assertOutputWithoutAndWithTC(tc, "adhoc.ftlh", "${.outputFormat}", + HTMLOutputFormat.INSTANCE.getName(), UndefinedOutputFormat.INSTANCE.getName()); + testedProps.add(Configuration.ExtendableBuilder.RECOGNIZE_STANDARD_FILE_EXTENSIONS_KEY_CAMEL_CASE); + } + + { + TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder(); + tcb.setLogTemplateExceptions(false); + tcb.setTabSize(3); + TemplateConfiguration tc = tcb.build(); + assertOutputWithoutAndWithTC(tc, + "<#attempt><@'\\t$\\{1+}'?interpret/><#recover>" + + "${.error?replace('(?s).*?column ([0-9]+).*', '$1', 'r')}" + + "</#attempt>", + "13", "8"); + testedProps.add(Configuration.ExtendableBuilder.TAB_SIZE_KEY_CAMEL_CASE); + } + + { + // As the TemplateLanguage-based parser selection happens in the TemplateResolver, we can't use + // assertOutput here, as that hard-coded to create an FTL Template. + + TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder(); + tcb.setTemplateLanguage(TemplateLanguage.STATIC_TEXT); + + TestConfigurationBuilder cfgB = new TestConfigurationBuilder(); + cfgB.setTemplateConfigurations( + new ConditionalTemplateConfigurationFactory(new FileExtensionMatcher("txt"), tcb.build())); + + StringTemplateLoader templateLoader = new StringTemplateLoader(); + templateLoader.putTemplate("adhoc.ftl", "${1+1}"); + templateLoader.putTemplate("adhoc.txt", "${1+1}"); + cfgB.setTemplateLoader(templateLoader); + + Configuration cfg = cfgB.build(); + + { + StringWriter out = new StringWriter(); + cfg.getTemplate("adhoc.ftl").process(null, out); + assertEquals("2", out.toString()); + } + { + StringWriter out = new StringWriter(); + cfg.getTemplate("adhoc.txt").process(null, out); + assertEquals("${1+1}", out.toString()); + } + + testedProps.add(Configuration.ExtendableBuilder.TEMPLATE_LANGUAGE_KEY_CAMEL_CASE); + } + + { + // As the TemplateLanguage-based parser selection happens in the TemplateResolver, we can't use + // assertOutput here, as that hard-coded to create an FTL Template. + + TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder(); + tcb.setSourceEncoding(StandardCharsets.ISO_8859_1); + + TestConfigurationBuilder cfgB = new TestConfigurationBuilder(); + cfgB.setSourceEncoding(StandardCharsets.UTF_8); + cfgB.setTemplateConfigurations(new ConditionalTemplateConfigurationFactory( + new FileNameGlobMatcher("latin1.ftl"), tcb.build())); + + MonitoredTemplateLoader templateLoader = new MonitoredTemplateLoader(); + templateLoader.putBinaryTemplate("utf8.ftl", "próba", StandardCharsets.UTF_8, 1); + templateLoader.putBinaryTemplate("latin1.ftl", "próba", StandardCharsets.ISO_8859_1, 1); + cfgB.setTemplateLoader(templateLoader); + + Configuration cfg = cfgB.build(); + + { + StringWriter out = new StringWriter(); + cfg.getTemplate("utf8.ftl").process(null, out); + assertEquals("próba", out.toString()); + } + { + StringWriter out = new StringWriter(); + cfg.getTemplate("latin1.ftl").process(null, out); + assertEquals("próba", out.toString()); + } + + testedProps.add(Configuration.ExtendableBuilder.SOURCE_ENCODING_KEY_CAMEL_CASE); + } + + if (!PARSER_PROP_NAMES.equals(testedProps)) { + Set<String> diff = new HashSet<>(PARSER_PROP_NAMES); + diff.removeAll(testedProps); + fail("Some settings weren't checked: " + diff); + } + } + + @Test + public void testArithmeticEngine() throws TemplateException, IOException { + TemplateConfiguration tc = new TemplateConfiguration.Builder() + .arithmeticEngine(new DummyArithmeticEngine()) + .build(); + assertOutputWithoutAndWithTC(tc, + "<#setting locale='en_US'>${1} ${1+1} ${1*3} <#assign x = 1>${x + x} ${x * 3}", + "1 2 3 2 3", "11 22 33 22 33"); + + // Does affect template.arithmeticEngine (unlike in FM2) + Template t = new Template(null, null, new StringReader(""), DEFAULT_CFG, tc, null); + assertEquals(tc.getArithmeticEngine(), t.getArithmeticEngine()); + } + + @Test + public void testAutoImport() throws TemplateException, IOException { + TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder(); + tcb.setAutoImports(ImmutableMap.of("t1", "t1.ftl", "t2", "t2.ftl")); + TemplateConfiguration tc = tcb.build(); + assertOutputWithoutAndWithTC(tc, "<#import 't3.ftl' as t3>${loaded}", "t3;", "t1;t2;t3;"); + } + + @Test + public void testAutoIncludes() throws TemplateException, IOException { + TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder(); + tcb.setAutoIncludes(ImmutableList.of("t1.ftl", "t2.ftl")); + TemplateConfiguration tc = tcb.build(); + assertOutputWithoutAndWithTC(tc, "<#include 't3.ftl'>", "In t3;", "In t1;In t2;In t3;"); + } + + @Test + public void testStringInterpolate() throws TemplateException, IOException { + TemplateConfiguration tc = new TemplateConfiguration.Builder() + .arithmeticEngine(new DummyArithmeticEngine()) + .build(); + assertOutputWithoutAndWithTC(tc, + "<#setting locale='en_US'>${'${1} ${1+1} ${1*3}'} <#assign x = 1>${'${x + x} ${x * 3}'}", + "1 2 3 2 3", "11 22 33 22 33"); + + // Does affect template.arithmeticEngine (unlike in FM2): + Template t = new Template(null, null, new StringReader(""), DEFAULT_CFG, tc, null); + assertEquals(tc.getArithmeticEngine(), t.getArithmeticEngine()); + } + + @Test + public void testInterpret() throws TemplateException, IOException { + TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder(); + tcb.setArithmeticEngine(new DummyArithmeticEngine()); + { + TemplateConfiguration tc = tcb.build(); + assertOutputWithoutAndWithTC(tc, + "<#setting locale='en_US'><#assign src = r'${1} <#assign x = 1>${x + x}'><@src?interpret />", + "1 2", "11 22"); + } + tcb.setWhitespaceStripping(false); + { + TemplateConfiguration tc = tcb.build(); + assertOutputWithoutAndWithTC(tc, + "<#if true>\nX</#if><#assign src = r'<#if true>\nY</#if>'><@src?interpret />", + "XY", "\nX\nY"); + } + } + + @Test + public void testEval() throws TemplateException, IOException { + { + TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder(); + tcb.setArithmeticEngine(new DummyArithmeticEngine()); + TemplateConfiguration tc = tcb.build(); + assertOutputWithoutAndWithTC(tc, + "<#assign x = 1>${r'1 + x'?eval?c}", + "2", "22"); + assertOutputWithoutAndWithTC(tc, + "${r'1?c'?eval}", + "1", "11"); + } + + { + TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder(); + Charset outputEncoding = ISO_8859_2; + tcb.setOutputEncoding(outputEncoding); + + String legacyNCFtl = "${r'.output_encoding!\"null\"'?eval}"; + String camelCaseNCFtl = "${r'.outputEncoding!\"null\"'?eval}"; + + { + TemplateConfiguration tc = tcb.build(); + + // Default is re-auto-detecting in ?eval: + assertOutputWithoutAndWithTC(tc, legacyNCFtl, "null", outputEncoding.name()); + assertOutputWithoutAndWithTC(tc, camelCaseNCFtl, "null", outputEncoding.name()); + } + + { + // Force camelCase: + tcb.setNamingConvention(ParsingConfiguration.CAMEL_CASE_NAMING_CONVENTION); + + TemplateConfiguration tc = tcb.build(); + + assertOutputWithoutAndWithTC(tc, legacyNCFtl, "null", null); + assertOutputWithoutAndWithTC(tc, camelCaseNCFtl, "null", outputEncoding.name()); + } + + { + // Force legacy: + tcb.setNamingConvention(ParsingConfiguration.LEGACY_NAMING_CONVENTION); + + TemplateConfiguration tc = tcb.build(); + + assertOutputWithoutAndWithTC(tc, legacyNCFtl, "null", outputEncoding.name()); + assertOutputWithoutAndWithTC(tc, camelCaseNCFtl, "null", null); + } + } + } + + private void assertOutputWithoutAndWithTC( + TemplateConfiguration tc, String ftl, String expectedDefaultOutput, + String expectedConfiguredOutput) throws TemplateException, IOException { + assertOutputWithoutAndWithTC(tc, null, ftl, expectedDefaultOutput, expectedConfiguredOutput); + } + + private void assertOutputWithoutAndWithTC( + TemplateConfiguration tc, String templateName, String ftl, String expectedDefaultOutput, + String expectedConfiguredOutput) throws TemplateException, IOException { + if (templateName == null) { + templateName = "adhoc.ftl"; + } + assertOutput(null, templateName, ftl, expectedDefaultOutput); + assertOutput(tc, templateName, ftl, expectedConfiguredOutput); + } + + private void assertOutput(TemplateConfiguration tc, String templateName, String ftl, String + expectedConfiguredOutput) + throws TemplateException, IOException { + StringWriter sw = new StringWriter(); + try { + Template t = new Template(templateName, null, new StringReader(ftl), DEFAULT_CFG, tc, null); + t.process(null, sw); + if (expectedConfiguredOutput == null) { + fail("Template should have fail."); + } + } catch (TemplateException|ParseException e) { + if (expectedConfiguredOutput != null) { + throw e; + } + } + if (expectedConfiguredOutput != null) { + assertEquals(expectedConfiguredOutput, sw.toString()); + } + } + + @Test + public void testIsSet() throws Exception { + for (PropertyDescriptor pd : getTemplateConfigurationSettingPropDescs( + TemplateConfiguration.Builder.class, true)) { + TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder(); + checkAllIsSetFalseExcept(tcb.build(), null); + pd.getWriteMethod().invoke(tcb, SETTING_ASSIGNMENTS.get(pd.getName())); + checkAllIsSetFalseExcept(tcb.build(), pd.getName()); + } + } + + private void checkAllIsSetFalseExcept(TemplateConfiguration tc, String setSetting) + throws SecurityException, IntrospectionException, + IllegalArgumentException, IllegalAccessException, InvocationTargetException { + for (PropertyDescriptor pd : getTemplateConfigurationSettingPropDescs(TemplateConfiguration.class, true)) { + String isSetMethodName = getIsSetMethodName(pd.getReadMethod().getName()); + Method isSetMethod; + try { + isSetMethod = tc.getClass().getMethod(isSetMethodName); + } catch (NoSuchMethodException e) { + fail("Missing " + isSetMethodName + " method for \"" + pd.getName() + "\"."); + return; + } + if (pd.getName().equals(setSetting)) { + assertTrue(isSetMethod + " should return true", (Boolean) (isSetMethod.invoke(tc))); + } else { + assertFalse(isSetMethod + " should return false", (Boolean) (isSetMethod.invoke(tc))); + } + } + } + + /** + * Test case self-check. + */ + @Test + public void checkTestAssignments() throws Exception { + for (PropertyDescriptor pd : getTemplateConfigurationSettingPropDescs( + TemplateConfiguration.Builder.class, true)) { + String propName = pd.getName(); + if (!SETTING_ASSIGNMENTS.containsKey(propName)) { + fail("Test case doesn't cover all settings in SETTING_ASSIGNMENTS. Missing: " + propName); + } + Method readMethod = pd.getReadMethod(); + String cfgMethodName = readMethod.getName(); + Method cfgMethod = DEFAULT_CFG.getClass().getMethod(cfgMethodName, readMethod.getParameterTypes()); + Object defaultSettingValue = cfgMethod.invoke(DEFAULT_CFG); + Object assignedValue = SETTING_ASSIGNMENTS.get(propName); + assertNotEquals("SETTING_ASSIGNMENTS must contain a non-default value for " + propName, + assignedValue, defaultSettingValue); + + TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder(); + try { + pd.getWriteMethod().invoke(tcb, assignedValue); + } catch (Exception e) { + throw new IllegalStateException("For setting \"" + propName + "\" and assigned value of type " + + (assignedValue != null ? assignedValue.getClass().getName() : "Null"), + e); + } + } + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/28a276c8/freemarker-core-test/src/test/java/org/apache/freemarker/core/TemplateConfigurationWithDefaultTemplateResolverTest.java ---------------------------------------------------------------------- diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/TemplateConfigurationWithDefaultTemplateResolverTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/TemplateConfigurationWithDefaultTemplateResolverTest.java new file mode 100644 index 0000000..4cd50eb --- /dev/null +++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/TemplateConfigurationWithDefaultTemplateResolverTest.java @@ -0,0 +1,267 @@ +/* + * 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.*; + +import java.io.IOException; +import java.io.StringWriter; +import java.io.UnsupportedEncodingException; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.Locale; + +import org.apache.freemarker.core.templateresolver.ConditionalTemplateConfigurationFactory; +import org.apache.freemarker.core.templateresolver.FileNameGlobMatcher; +import org.apache.freemarker.core.templateresolver.FirstMatchTemplateConfigurationFactory; +import org.apache.freemarker.core.templateresolver.MergingTemplateConfigurationFactory; +import org.apache.freemarker.core.templateresolver.impl.ByteArrayTemplateLoader; +import org.apache.freemarker.core.templateresolver.impl.StringTemplateLoader; +import org.apache.freemarker.test.TestConfigurationBuilder; +import org.junit.Test; + +public class TemplateConfigurationWithDefaultTemplateResolverTest { + + private static final String TEXT_WITH_ACCENTS = "pr\u00F3ba"; + + private static final Object CUST_ATT_1 = new Object(); + private static final Object CUST_ATT_2 = new Object(); + + private static final Charset ISO_8859_2 = Charset.forName("ISO-8859-2"); + + @Test + public void testEncoding() throws Exception { + Configuration cfg = createCommonEncodingTesterConfig(); + + { + Template t = cfg.getTemplate("utf8.ftl"); + assertEquals(StandardCharsets.UTF_8, t.getActualSourceEncoding()); + assertEquals(TEXT_WITH_ACCENTS, getTemplateOutput(t)); + } + { + Template t = cfg.getTemplate("utf16.ftl"); + assertEquals(StandardCharsets.UTF_16LE, t.getActualSourceEncoding()); + assertEquals(TEXT_WITH_ACCENTS, getTemplateOutput(t)); + } + { + Template t = cfg.getTemplate("default.ftl"); + assertEquals(StandardCharsets.ISO_8859_1, t.getActualSourceEncoding()); + assertEquals(TEXT_WITH_ACCENTS, getTemplateOutput(t)); + } + { + Template t = cfg.getTemplate("utf8-latin2.ftl"); + assertEquals(ISO_8859_2, t.getActualSourceEncoding()); + assertEquals(TEXT_WITH_ACCENTS, getTemplateOutput(t)); + } + { + Template t = cfg.getTemplate("default-latin2.ftl"); + assertEquals(ISO_8859_2, t.getActualSourceEncoding()); + assertEquals(TEXT_WITH_ACCENTS, getTemplateOutput(t)); + } + } + + @Test + public void testIncludeAndEncoding() throws Exception { + Configuration cfg = createCommonEncodingTesterConfig(); + ByteArrayTemplateLoader tl = (ByteArrayTemplateLoader) cfg.getTemplateLoader(); + tl.putTemplate("main.ftl", ( + "<#include 'utf8.ftl'>" + + "<#include 'utf16.ftl'>" + + "<#include 'default.ftl'>" + + "<#include 'utf8-latin2.ftl'>" + ).getBytes(StandardCharsets.ISO_8859_1)); + assertEquals( + TEXT_WITH_ACCENTS + TEXT_WITH_ACCENTS + TEXT_WITH_ACCENTS + TEXT_WITH_ACCENTS, + getTemplateOutput(cfg.getTemplate("main.ftl"))); + } + + @Test + public void testLocale() throws Exception { + StringTemplateLoader loader = new StringTemplateLoader(); + loader.putTemplate("(de).ftl", "${.locale}"); + loader.putTemplate("default.ftl", "${.locale}"); + loader.putTemplate("(de)-fr.ftl", + ("<#ftl locale='fr_FR'>${.locale}")); + loader.putTemplate("default-fr.ftl", + ("<#ftl locale='fr_FR'>${.locale}")); + + Configuration cfg = new TestConfigurationBuilder() + .templateLoader(loader) + .templateConfigurations( + new ConditionalTemplateConfigurationFactory( + new FileNameGlobMatcher("*(de)*"), + new TemplateConfiguration.Builder() + .locale(Locale.GERMANY) + .build())) + .build(); + + { + Template t = cfg.getTemplate("(de).ftl"); + assertEquals(Locale.GERMANY, t.getLocale()); + assertEquals("de_DE", getTemplateOutput(t)); + } + { + Template t = cfg.getTemplate("(de).ftl", Locale.ITALY); + assertEquals(Locale.GERMANY, t.getLocale()); + assertEquals("de_DE", getTemplateOutput(t)); + } + { + Template t = cfg.getTemplate("default.ftl"); + assertEquals(Locale.US, t.getLocale()); + assertEquals("en_US", getTemplateOutput(t)); + } + { + Template t = cfg.getTemplate("default.ftl", Locale.ITALY); + assertEquals(Locale.ITALY, t.getLocale()); + assertEquals("it_IT", getTemplateOutput(t)); + } + } + + @Test + public void testConfigurableSettings() throws Exception { + String commonFTL = "${.locale} ${true?string} ${1.2}"; + StringTemplateLoader loader = new StringTemplateLoader(); + loader.putTemplate("default", commonFTL); + loader.putTemplate("(fr)", commonFTL); + loader.putTemplate("(yn)(00)", commonFTL); + loader.putTemplate("(00)(fr)", commonFTL); + + Configuration cfg = new TestConfigurationBuilder() + .templateConfigurations( + new MergingTemplateConfigurationFactory( + new ConditionalTemplateConfigurationFactory( + new FileNameGlobMatcher("*(fr)*"), + new TemplateConfiguration.Builder().locale(Locale.FRANCE).build()), + new ConditionalTemplateConfigurationFactory( + new FileNameGlobMatcher("*(yn)*"), + new TemplateConfiguration.Builder().booleanFormat("Y,N").build()), + new ConditionalTemplateConfigurationFactory( + new FileNameGlobMatcher("*(00)*"), + new TemplateConfiguration.Builder().numberFormat("0.00").build()))) + .templateLoader(loader) + .build(); + + assertEquals("en_US true 1.2", getTemplateOutput(cfg.getTemplate("default"))); + assertEquals("fr_FR true 1,2", getTemplateOutput(cfg.getTemplate("(fr)"))); + assertEquals("en_US Y 1.20", getTemplateOutput(cfg.getTemplate("(yn)(00)"))); + assertEquals("fr_FR true 1,20", getTemplateOutput(cfg.getTemplate("(00)(fr)"))); + } + + @Test + public void testCustomAttributes() throws Exception { + String commonFTL = "<#ftl attributes={ 'a3': 'a3temp' }>"; + StringTemplateLoader tl = new StringTemplateLoader(); + tl.putTemplate("(tc1)", commonFTL); + tl.putTemplate("(tc1)noHeader", ""); + tl.putTemplate("(tc2)", commonFTL); + tl.putTemplate("(tc1)(tc2)", commonFTL); + + Configuration cfg = new TestConfigurationBuilder() + .templateConfigurations( + new MergingTemplateConfigurationFactory( + new ConditionalTemplateConfigurationFactory( + new FileNameGlobMatcher("*(tc1)*"), + new TemplateConfiguration.Builder() + .customAttribute("a1", "a1tc1") + .customAttribute("a2", "a2tc1") + .customAttribute("a3", "a3tc1") + .customAttribute(CUST_ATT_1, "ca1tc1") + .customAttribute(CUST_ATT_2, "ca2tc1") + .build()), + new ConditionalTemplateConfigurationFactory( + new FileNameGlobMatcher("*(tc2)*"), + new TemplateConfiguration.Builder() + .customAttribute("a1", "a1tc2") + .customAttribute(CUST_ATT_1, "ca1tc2") + .build()))) + .templateLoader(tl) + .build(); + + { + Template t = cfg.getTemplate("(tc1)"); + assertEquals("a1tc1", t.getCustomAttribute("a1")); + assertEquals("a2tc1", t.getCustomAttribute("a2")); + assertEquals("a3temp", t.getCustomAttribute("a3")); + assertEquals("ca1tc1", t.getCustomAttribute(CUST_ATT_1)); + assertEquals("ca2tc1", t.getCustomAttribute(CUST_ATT_2)); + } + { + Template t = cfg.getTemplate("(tc1)noHeader"); + assertEquals("a1tc1", t.getCustomAttribute("a1")); + assertEquals("a2tc1", t.getCustomAttribute("a2")); + assertEquals("a3tc1", t.getCustomAttribute("a3")); + assertEquals("ca1tc1", t.getCustomAttribute(CUST_ATT_1)); + assertEquals("ca2tc1", t.getCustomAttribute(CUST_ATT_2)); + } + { + Template t = cfg.getTemplate("(tc2)"); + assertEquals("a1tc2", t.getCustomAttribute("a1")); + assertNull(t.getCustomAttribute("a2")); + assertEquals("a3temp", t.getCustomAttribute("a3")); + assertEquals("ca1tc2", t.getCustomAttribute(CUST_ATT_1)); + assertNull(t.getCustomAttribute(CUST_ATT_2)); + } + { + Template t = cfg.getTemplate("(tc1)(tc2)"); + assertEquals("a1tc2", t.getCustomAttribute("a1")); + assertEquals("a2tc1", t.getCustomAttribute("a2")); + assertEquals("a3temp", t.getCustomAttribute("a3")); + assertEquals("ca1tc2", t.getCustomAttribute(CUST_ATT_1)); + assertEquals("ca2tc1", t.getCustomAttribute(CUST_ATT_2)); + } + } + + private String getTemplateOutput(Template t) throws TemplateException, IOException { + StringWriter sw = new StringWriter(); + t.process(null, sw); + return sw.toString(); + } + + private Configuration createCommonEncodingTesterConfig() throws UnsupportedEncodingException { + ByteArrayTemplateLoader tl = new ByteArrayTemplateLoader(); + tl.putTemplate("utf8.ftl", TEXT_WITH_ACCENTS.getBytes(StandardCharsets.UTF_8)); + tl.putTemplate("utf16.ftl", TEXT_WITH_ACCENTS.getBytes(StandardCharsets.UTF_16LE)); + tl.putTemplate("default.ftl", TEXT_WITH_ACCENTS.getBytes(ISO_8859_2)); + tl.putTemplate("utf8-latin2.ftl", + ("<#ftl encoding='iso-8859-2'>" + TEXT_WITH_ACCENTS).getBytes(ISO_8859_2)); + tl.putTemplate("default-latin2.ftl", + ("<#ftl encoding='iso-8859-2'>" + TEXT_WITH_ACCENTS).getBytes(ISO_8859_2)); + + return new TestConfigurationBuilder() + .sourceEncoding(StandardCharsets.ISO_8859_1) + .locale(Locale.US) + .templateLoader(tl) + .templateConfigurations( + new FirstMatchTemplateConfigurationFactory( + new ConditionalTemplateConfigurationFactory( + new FileNameGlobMatcher("*utf8*"), + new TemplateConfiguration.Builder() + .sourceEncoding(StandardCharsets.UTF_8) + .build()), + new ConditionalTemplateConfigurationFactory( + new FileNameGlobMatcher("*utf16*"), + new TemplateConfiguration.Builder() + .sourceEncoding(StandardCharsets.UTF_16LE) + .build()) + ) + .allowNoMatch(true)) + .build(); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/28a276c8/freemarker-core-test/src/test/java/org/apache/freemarker/core/TemplateConstructorsTest.java ---------------------------------------------------------------------- diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/TemplateConstructorsTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/TemplateConstructorsTest.java new file mode 100644 index 0000000..97c43ad --- /dev/null +++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/TemplateConstructorsTest.java @@ -0,0 +1,113 @@ +/* + * 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.*; +import static org.junit.Assert.*; + +import java.io.IOException; +import java.io.Reader; +import java.io.StringReader; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; + +import org.apache.freemarker.test.TestConfigurationBuilder; +import org.junit.Test; +public class TemplateConstructorsTest { + + private static final String CONTENT = "From a reader..."; + private static final String CONTENT_FORCE_UTF8 = "<#ftl encoding='utf-8'>From a reader..."; + + @Test + public void test() throws IOException { + final Configuration cfg = new TestConfigurationBuilder().sourceEncoding(StandardCharsets.ISO_8859_1).build(); + + final String name = "foo/bar.ftl"; + final String sourceName = "foo/bar_de.ftl"; + final Charset sourceEncoding = StandardCharsets.UTF_16LE; + { + Template t = new Template(name, createReader(), cfg); + assertEquals(name, t.getLookupName()); + assertNull(t.getSourceName()); + assertEquals(CONTENT, t.toString()); + assertNull(t.getActualSourceEncoding()); + } + { + Template t = new Template(name, CONTENT, cfg); + assertEquals(name, t.getLookupName()); + assertNull(t.getSourceName()); + assertEquals(CONTENT, t.toString()); + assertNull(t.getActualSourceEncoding()); + } + { + Template t = new Template(name, CONTENT_FORCE_UTF8, cfg); + assertEquals(name, t.getLookupName()); + assertNull(t.getSourceName()); + // assertEquals(CONTENT_FORCE_UTF8, t.toString()); // FIXME the #ftl header is missing from the dump, why? + assertNull(t.getActualSourceEncoding()); // Because it was created from a String + } + { + Template t = new Template(name, createReader(), cfg, sourceEncoding); + assertEquals(name, t.getLookupName()); + assertNull(t.getSourceName()); + assertEquals(CONTENT, t.toString()); + assertEquals(StandardCharsets.UTF_16LE, t.getActualSourceEncoding()); + } + { + Template t = new Template(name, sourceName, createReader(), cfg); + assertEquals(name, t.getLookupName()); + assertEquals(sourceName, t.getSourceName()); + assertEquals(CONTENT, t.toString()); + assertNull(t.getActualSourceEncoding()); + } + { + Template t = new Template(name, sourceName, createReader(), cfg, sourceEncoding); + assertEquals(name, t.getLookupName()); + assertEquals(sourceName, t.getSourceName()); + assertEquals(CONTENT, t.toString()); + assertEquals(StandardCharsets.UTF_16LE, t.getActualSourceEncoding()); + } + { + Template t = Template.createPlainTextTemplate(name, CONTENT, cfg); + assertEquals(name, t.getLookupName()); + assertNull(t.getSourceName()); + assertEquals(CONTENT, t.toString()); + assertNull(t.getActualSourceEncoding()); + } + { + try { + new Template(name, sourceName, createReaderForceUTF8(), cfg, sourceEncoding); + fail(); + } catch (WrongTemplateCharsetException e) { + assertThat(e.getMessage(), containsString(StandardCharsets.UTF_8.name())); + assertThat(e.getMessage(), containsString(sourceEncoding.name())); + } + } + } + + private Reader createReader() { + return new StringReader(CONTENT); + } + + private Reader createReaderForceUTF8() { + return new StringReader(CONTENT_FORCE_UTF8); + } + +}
