You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@freemarker.apache.org by dd...@apache.org on 2017/05/15 21:24:03 UTC
[47/51] [abbrv] [partial] incubator-freemarker git commit:
Restructured project so that freemarker-test-utils depends on freemarker-core
(and hence can provide common classes for testing templates,
and can use utility classes defined in the core). As a c
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/28a276c8/freemarker-core-test/src/test/java/org/apache/freemarker/core/DateFormatTest.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/DateFormatTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/DateFormatTest.java
new file mode 100644
index 0000000..4ad5937
--- /dev/null
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/DateFormatTest.java
@@ -0,0 +1,464 @@
+/*
+ * 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.sql.Time;
+import java.sql.Timestamp;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+
+import org.apache.freemarker.core.model.TemplateDateModel;
+import org.apache.freemarker.core.model.TemplateModelException;
+import org.apache.freemarker.core.model.impl.SimpleDate;
+import org.apache.freemarker.core.templateresolver.ConditionalTemplateConfigurationFactory;
+import org.apache.freemarker.core.templateresolver.FileNameGlobMatcher;
+import org.apache.freemarker.core.userpkg.AppMetaTemplateDateFormatFactory;
+import org.apache.freemarker.core.userpkg.EpochMillisDivTemplateDateFormatFactory;
+import org.apache.freemarker.core.userpkg.EpochMillisTemplateDateFormatFactory;
+import org.apache.freemarker.core.userpkg.HTMLISOTemplateDateFormatFactory;
+import org.apache.freemarker.core.userpkg.LocAndTZSensitiveTemplateDateFormatFactory;
+import org.apache.freemarker.core.valueformat.TemplateDateFormat;
+import org.apache.freemarker.core.valueformat.TemplateDateFormatFactory;
+import org.apache.freemarker.core.valueformat.UndefinedCustomFormatException;
+import org.apache.freemarker.core.valueformat.impl.AliasTemplateDateFormatFactory;
+import org.apache.freemarker.test.TemplateTest;
+import org.apache.freemarker.test.TestConfigurationBuilder;
+import org.junit.Test;
+
+import com.google.common.collect.ImmutableMap;
+
+public class DateFormatTest extends TemplateTest {
+
+ /** 2015-09-06T12:00:00Z */
+ private static long T = 1441540800000L;
+ private static TemplateDateModel TM = new SimpleDate(new Date(T), TemplateDateModel.DATETIME);
+
+ private TestConfigurationBuilder createConfigurationBuilder() {
+ return new TestConfigurationBuilder()
+ .locale(Locale.US)
+ .timeZone(TimeZone.getTimeZone("GMT+01:00"))
+ .sqlDateAndTimeTimeZone(TimeZone.getTimeZone("UTC"))
+ .customDateFormats(ImmutableMap.of(
+ "epoch", EpochMillisTemplateDateFormatFactory.INSTANCE,
+ "loc", LocAndTZSensitiveTemplateDateFormatFactory.INSTANCE,
+ "div", EpochMillisDivTemplateDateFormatFactory.INSTANCE,
+ "appMeta", AppMetaTemplateDateFormatFactory.INSTANCE,
+ "htmlIso", HTMLISOTemplateDateFormatFactory.INSTANCE));
+ }
+
+ @Override
+ protected Configuration createDefaultConfiguration() throws Exception {
+ return createConfigurationBuilder().build();
+ }
+
+ @Test
+ public void testCustomFormat() throws Exception {
+ addToDataModel("d", new Date(123456789));
+ assertOutput(
+ "${d?string.@epoch} ${d?string.@epoch} <#setting locale='de_DE'>${d?string.@epoch}",
+ "123456789 123456789 123456789");
+
+ setConfigurationWithDateTimeFormat("@epoch");
+ assertOutput(
+ "<#assign d = d?datetime>"
+ + "${d} ${d?string} <#setting locale='de_DE'>${d}",
+ "123456789 123456789 123456789");
+
+ setConfigurationWithDateTimeFormat("@htmlIso");
+ assertOutput(
+ "<#assign d = d?datetime>"
+ + "${d} ${d?string} <#setting locale='de_DE'>${d}",
+ "1970-01-02<span class='T'>T</span>10:17:36Z "
+ + "1970-01-02T10:17:36Z "
+ + "1970-01-02<span class='T'>T</span>10:17:36Z");
+ }
+
+ @Test
+ public void testLocaleChange() throws Exception {
+ addToDataModel("d", new Date(123456789));
+ assertOutput(
+ "${d?string.@loc} ${d?string.@loc} "
+ + "<#setting locale='de_DE'>"
+ + "${d?string.@loc} ${d?string.@loc} "
+ + "<#setting locale='en_US'>"
+ + "${d?string.@loc} ${d?string.@loc}",
+ "123456789@en_US:GMT+01:00 123456789@en_US:GMT+01:00 "
+ + "123456789@de_DE:GMT+01:00 123456789@de_DE:GMT+01:00 "
+ + "123456789@en_US:GMT+01:00 123456789@en_US:GMT+01:00");
+
+ setConfigurationWithDateTimeFormat("@loc");
+ assertOutput(
+ "<#assign d = d?datetime>"
+ + "${d} ${d?string} "
+ + "<#setting locale='de_DE'>"
+ + "${d} ${d?string} "
+ + "<#setting locale='en_US'>"
+ + "${d} ${d?string}",
+ "123456789@en_US:GMT+01:00 123456789@en_US:GMT+01:00 "
+ + "123456789@de_DE:GMT+01:00 123456789@de_DE:GMT+01:00 "
+ + "123456789@en_US:GMT+01:00 123456789@en_US:GMT+01:00");
+ }
+
+ @Test
+ public void testTimeZoneChange() throws Exception {
+ addToDataModel("d", new Date(123456789));
+ setConfigurationWithDateTimeFormat("iso");
+ assertOutput(
+ "${d?string.@loc} ${d?string.@loc} ${d?datetime?isoLocal} "
+ + "<#setting timeZone='GMT+02:00'>"
+ + "${d?string.@loc} ${d?string.@loc} ${d?datetime?isoLocal} "
+ + "<#setting timeZone='GMT+01:00'>"
+ + "${d?string.@loc} ${d?string.@loc} ${d?datetime?isoLocal}",
+ "123456789@en_US:GMT+01:00 123456789@en_US:GMT+01:00 1970-01-02T11:17:36+01:00 "
+ + "123456789@en_US:GMT+02:00 123456789@en_US:GMT+02:00 1970-01-02T12:17:36+02:00 "
+ + "123456789@en_US:GMT+01:00 123456789@en_US:GMT+01:00 1970-01-02T11:17:36+01:00");
+
+ setConfigurationWithDateTimeFormat("@loc");
+ assertOutput(
+ "<#assign d = d?datetime>"
+ + "${d} ${d?string} "
+ + "<#setting timeZone='GMT+02:00'>"
+ + "${d} ${d?string} "
+ + "<#setting timeZone='GMT+01:00'>"
+ + "${d} ${d?string}",
+ "123456789@en_US:GMT+01:00 123456789@en_US:GMT+01:00 "
+ + "123456789@en_US:GMT+02:00 123456789@en_US:GMT+02:00 "
+ + "123456789@en_US:GMT+01:00 123456789@en_US:GMT+01:00");
+ }
+
+ @Test
+ public void testWrongFormatStrings() throws Exception {
+ setConfigurationWithDateTimeFormat("x1");
+ assertErrorContains("${.now}", "\"x1\"", "'x'");
+ assertErrorContains("${.now?string}", "\"x1\"", "'x'");
+ setConfigurationWithDateTimeFormat("short");
+ assertErrorContains("${.now?string('x2')}", "\"x2\"", "'x'");
+ assertErrorContains("${.now?string('[wrong]')}", "format string", "[wrong]");
+
+ setConfiguration(createConfigurationBuilder()
+ .dateFormat("[wrong d]")
+ .dateTimeFormat("[wrong dt]")
+ .timeFormat("[wrong t]")
+ .build());
+ assertErrorContains("${.now?date}", "\"date_format\"", "[wrong d]");
+ assertErrorContains("${.now?datetime}", "\"datetime_format\"", "[wrong dt]");
+ assertErrorContains("${.now?time}", "\"time_format\"", "[wrong t]");
+ }
+
+ @Test
+ public void testCustomParameterized() throws Exception {
+ Configuration cfg = getConfiguration();
+ addToDataModel("d", new SimpleDate(new Date(12345678L), TemplateDateModel.DATETIME));
+ setConfigurationWithDateTimeFormat("@div 1000");
+ assertOutput("${d}", "12345");
+ assertOutput("${d?string}", "12345");
+ assertOutput("${d?string.@div_100}", "123456");
+
+ assertErrorContains("${d?string.@div_xyz}", "\"@div_xyz\"", "\"xyz\"");
+ setConfigurationWithDateTimeFormat("@div");
+ assertErrorContains("${d}", "\"datetime_format\"", "\"@div\"", "format parameter is required");
+ }
+
+ @Test
+ public void testUnknownCustomFormat() throws Exception {
+ {
+ setConfigurationWithDateTimeFormat("@noSuchFormat");
+ Throwable exc = assertErrorContains(
+ "${.now}",
+ "\"@noSuchFormat\"", "\"noSuchFormat\"", "\"datetime_format\"");
+ assertThat(exc.getCause(), instanceOf(UndefinedCustomFormatException.class));
+
+ }
+ {
+ setConfiguration(createConfigurationBuilder().dateFormat("@noSuchFormatD").build());
+ assertErrorContains(
+ "${.now?date}",
+ "\"@noSuchFormatD\"", "\"noSuchFormatD\"", "\"date_format\"");
+ }
+ {
+ setConfiguration(createConfigurationBuilder().timeFormat("@noSuchFormatT").build());
+ assertErrorContains(
+ "${.now?time}",
+ "\"@noSuchFormatT\"", "\"noSuchFormatT\"", "\"time_format\"");
+ }
+
+ {
+ setConfigurationWithDateTimeFormat("");
+ Throwable exc = assertErrorContains("${.now?string('@noSuchFormat2')}",
+ "\"@noSuchFormat2\"", "\"noSuchFormat2\"");
+ assertThat(exc.getCause(), instanceOf(UndefinedCustomFormatException.class));
+ }
+ }
+
+ private void setConfigurationWithDateTimeFormat(String formatString) {
+ setConfiguration(createConfigurationBuilder().dateTimeFormat(formatString).build());
+ }
+
+ @Test
+ public void testNullInModel() throws Exception {
+ addToDataModel("d", new MutableTemplateDateModel());
+ assertErrorContains("${d}", "nothing inside it");
+ assertErrorContains("${d?string}", "nothing inside it");
+ }
+
+ @Test
+ public void testIcIAndEscaping() throws Exception {
+ addToDataModel("d", new SimpleDate(new Date(12345678L), TemplateDateModel.DATETIME));
+
+ setConfigurationWithDateTimeFormat("@epoch");
+ assertOutput("${d}", "12345678");
+ setConfigurationWithDateTimeFormat("'@'yyyy");
+ assertOutput("${d}", "@1970");
+ setConfigurationWithDateTimeFormat("@@yyyy");
+ assertOutput("${d}", "@@1970");
+
+ setConfiguration(createConfigurationBuilder()
+ .customDateFormats(Collections.<String, TemplateDateFormatFactory>emptyMap())
+ .dateTimeFormat("@epoch")
+ .build());
+ assertErrorContains("${d}", "custom", "\"epoch\"");
+ }
+
+ @Test
+ public void testEnvironmentGetters() throws Exception {
+ String dateFormatStr = "yyyy.MM.dd. (Z)";
+ String timeFormatStr = "HH:mm";
+ String dateTimeFormatStr = "yyyy.MM.dd. HH:mm";
+
+ setConfiguration(createConfigurationBuilder()
+ .dateFormat(dateFormatStr)
+ .timeFormat(timeFormatStr)
+ .dateTimeFormat(dateTimeFormatStr)
+ .build());
+
+ Configuration cfg = getConfiguration();
+
+ Template t = new Template(null, "", cfg);
+ Environment env = t.createProcessingEnvironment(null, null);
+
+ // Test that values are coming from the cache if possible
+ for (Class dateClass : new Class[] { Date.class, Timestamp.class, java.sql.Date.class, Time.class } ) {
+ for (int dateType
+ : new int[] { TemplateDateModel.DATE, TemplateDateModel.TIME, TemplateDateModel.DATETIME }) {
+ String formatString =
+ dateType == TemplateDateModel.DATE ? cfg.getDateFormat() :
+ (dateType == TemplateDateModel.TIME ? cfg.getTimeFormat()
+ : cfg.getDateTimeFormat());
+ TemplateDateFormat expectedF = env.getTemplateDateFormat(formatString, dateType, dateClass);
+ assertSame(expectedF, env.getTemplateDateFormat(dateType, dateClass)); // Note: Only reads the cache
+ assertSame(expectedF, env.getTemplateDateFormat(formatString, dateType, dateClass));
+ assertSame(expectedF, env.getTemplateDateFormat(formatString, dateType, dateClass, cfg.getLocale()));
+ assertSame(expectedF, env.getTemplateDateFormat(formatString, dateType, dateClass, cfg.getLocale(),
+ cfg.getTimeZone(), cfg.getSQLDateAndTimeTimeZone()));
+ }
+ }
+
+ String dateFormatStr2 = dateFormatStr + "'!'";
+ String timeFormatStr2 = timeFormatStr + "'!'";
+ String dateTimeFormatStr2 = dateTimeFormatStr + "'!'";
+
+ assertEquals("2015.09.06. 13:00",
+ env.getTemplateDateFormat(TemplateDateModel.DATETIME, Date.class).formatToPlainText(TM));
+ assertEquals("2015.09.06. 13:00!",
+ env.getTemplateDateFormat(dateTimeFormatStr2, TemplateDateModel.DATETIME, Date.class).formatToPlainText(TM));
+
+ assertEquals("2015.09.06. (+0100)",
+ env.getTemplateDateFormat(TemplateDateModel.DATE, Date.class).formatToPlainText(TM));
+ assertEquals("2015.09.06. (+0100)!",
+ env.getTemplateDateFormat(dateFormatStr2, TemplateDateModel.DATE, Date.class).formatToPlainText(TM));
+
+ assertEquals("13:00",
+ env.getTemplateDateFormat(TemplateDateModel.TIME, Date.class).formatToPlainText(TM));
+ assertEquals("13:00!",
+ env.getTemplateDateFormat(timeFormatStr2, TemplateDateModel.TIME, Date.class).formatToPlainText(TM));
+
+ assertEquals("2015.09.06. 13:00",
+ env.getTemplateDateFormat(TemplateDateModel.DATETIME, Timestamp.class).formatToPlainText(TM));
+ assertEquals("2015.09.06. 13:00!",
+ env.getTemplateDateFormat(dateTimeFormatStr2, TemplateDateModel.DATETIME, Timestamp.class).formatToPlainText(TM));
+
+ assertEquals("2015.09.06. (+0000)",
+ env.getTemplateDateFormat(TemplateDateModel.DATE, java.sql.Date.class).formatToPlainText(TM));
+ assertEquals("2015.09.06. (+0000)!",
+ env.getTemplateDateFormat(dateFormatStr2, TemplateDateModel.DATE, java.sql.Date.class).formatToPlainText(TM));
+
+ assertEquals("12:00",
+ env.getTemplateDateFormat(TemplateDateModel.TIME, Time.class).formatToPlainText(TM));
+ assertEquals("12:00!",
+ env.getTemplateDateFormat(timeFormatStr2, TemplateDateModel.TIME, Time.class).formatToPlainText(TM));
+
+ {
+ String dateTimeFormatStrLoc = dateTimeFormatStr + " EEEE";
+ // Gets into cache:
+ TemplateDateFormat format1
+ = env.getTemplateDateFormat(dateTimeFormatStrLoc, TemplateDateModel.DATETIME, Date.class);
+ assertEquals("2015.09.06. 13:00 Sunday", format1.formatToPlainText(TM));
+ // Different locale (not cached):
+ assertEquals("2015.09.06. 13:00 Sonntag",
+ env.getTemplateDateFormat(dateTimeFormatStrLoc, TemplateDateModel.DATETIME, Date.class,
+ Locale.GERMANY).formatToPlainText(TM));
+ // Different locale and zone (not cached):
+ assertEquals("2015.09.06. 14:00 Sonntag",
+ env.getTemplateDateFormat(dateTimeFormatStrLoc, TemplateDateModel.DATETIME, Date.class,
+ Locale.GERMANY, TimeZone.getTimeZone("GMT+02"), TimeZone.getTimeZone("GMT+03")).formatToPlainText(TM));
+ // Different locale and zone (not cached):
+ assertEquals("2015.09.06. 15:00 Sonntag",
+ env.getTemplateDateFormat(dateTimeFormatStrLoc, TemplateDateModel.DATETIME, java.sql.Date.class,
+ Locale.GERMANY, TimeZone.getTimeZone("GMT+02"), TimeZone.getTimeZone("GMT+03")).formatToPlainText(TM));
+ // Check for corrupted cache:
+ TemplateDateFormat format2
+ = env.getTemplateDateFormat(dateTimeFormatStrLoc, TemplateDateModel.DATETIME, Date.class);
+ assertEquals("2015.09.06. 13:00 Sunday", format2.formatToPlainText(TM));
+ assertSame(format1, format2);
+ }
+ }
+
+ @Test
+ public void testAliases() throws Exception {
+ setConfiguration(createConfigurationBuilder()
+ .customDateFormats(ImmutableMap.of(
+ "d", new AliasTemplateDateFormatFactory("yyyy-MMM-dd"),
+ "m", new AliasTemplateDateFormatFactory("yyyy-MMM"),
+ "epoch", EpochMillisTemplateDateFormatFactory.INSTANCE))
+ .templateConfigurations(
+ new ConditionalTemplateConfigurationFactory(
+ new FileNameGlobMatcher("*2*"),
+ new TemplateConfiguration.Builder()
+ .customDateFormats(ImmutableMap.<String, TemplateDateFormatFactory>of(
+ "m", new AliasTemplateDateFormatFactory("yyyy-MMMM"),
+ "i", new AliasTemplateDateFormatFactory("@epoch")))
+ .build()))
+ .build());
+
+ addToDataModel("d", TM);
+ String commonFtl = "${d?string.@d} ${d?string.@m} "
+ + "<#setting locale='fr_FR'>${d?string.@m} "
+ + "<#attempt>${d?string.@i}<#recover>E</#attempt>";
+ addTemplate("t1.ftl", commonFtl);
+ addTemplate("t2.ftl", commonFtl);
+
+ // 2015-09-06T12:00:00Z
+ assertOutputForNamed("t1.ftl", "2015-Sep-06 2015-Sep 2015-sept. E");
+ assertOutputForNamed("t2.ftl", "2015-Sep-06 2015-September 2015-septembre " + T);
+ }
+
+ @Test
+ public void testAliases2() throws Exception {
+ setConfiguration(
+ createConfigurationBuilder()
+ .customDateFormats(ImmutableMap.<String, TemplateDateFormatFactory>of(
+ "d", new AliasTemplateDateFormatFactory("yyyy-MMM",
+ ImmutableMap.of(
+ new Locale("en"), "yyyy-MMM'_en'",
+ Locale.UK, "yyyy-MMM'_en_GB'",
+ Locale.FRANCE, "yyyy-MMM'_fr_FR'"))))
+ .dateTimeFormat("@d")
+ .build());
+ addToDataModel("d", TM);
+ assertOutput(
+ "<#setting locale='en_US'>${d} "
+ + "<#setting locale='en_GB'>${d} "
+ + "<#setting locale='en_GB_Win'>${d} "
+ + "<#setting locale='fr_FR'>${d} "
+ + "<#setting locale='hu_HU'>${d}",
+ "2015-Sep_en 2015-Sep_en_GB 2015-Sep_en_GB 2015-sept._fr_FR 2015-szept.");
+ }
+
+ /**
+ * ?date() and such are new in 2.3.24.
+ */
+ @Test
+ public void testZeroArgDateBI() throws IOException, TemplateException {
+ setConfiguration(
+ createConfigurationBuilder()
+ .dateFormat("@epoch")
+ .dateTimeFormat("@epoch")
+ .timeFormat("@epoch")
+ .build());
+
+ addToDataModel("t", String.valueOf(T));
+
+ assertOutput(
+ "${t?date?string.xs_u} ${t?date()?string.xs_u}",
+ "2015-09-06Z 2015-09-06Z");
+ assertOutput(
+ "${t?time?string.xs_u} ${t?time()?string.xs_u}",
+ "12:00:00Z 12:00:00Z");
+ assertOutput(
+ "${t?datetime?string.xs_u} ${t?datetime()?string.xs_u}",
+ "2015-09-06T12:00:00Z 2015-09-06T12:00:00Z");
+ }
+
+ @Test
+ public void testAppMetaRoundtrip() throws IOException, TemplateException {
+ setConfiguration(
+ createConfigurationBuilder()
+ .dateFormat("@appMeta")
+ .dateTimeFormat("@appMeta")
+ .timeFormat("@appMeta")
+ .build());
+
+ addToDataModel("t", String.valueOf(T) + "/foo");
+
+ assertOutput(
+ "${t?date} ${t?date()}",
+ T + " " + T + "/foo");
+ assertOutput(
+ "${t?time} ${t?time()}",
+ T + " " + T + "/foo");
+ assertOutput(
+ "${t?datetime} ${t?datetime()}",
+ T + " " + T + "/foo");
+ }
+
+ @Test
+ public void testUnknownDateType() throws IOException, TemplateException {
+ addToDataModel("u", new Date(T));
+ assertErrorContains("${u?string}", "isn't known");
+ assertOutput("${u?string('yyyy')}", "2015");
+ assertOutput("<#assign s = u?string>${s('yyyy')}", "2015");
+ }
+
+ private static class MutableTemplateDateModel implements TemplateDateModel {
+
+ private Date date;
+
+ public void setDate(Date date) {
+ this.date = date;
+ }
+
+ @Override
+ public Date getAsDate() throws TemplateModelException {
+ return date;
+ }
+
+ @Override
+ public int getDateType() {
+ return DATETIME;
+ }
+
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/28a276c8/freemarker-core-test/src/test/java/org/apache/freemarker/core/DirectiveCallPlaceTest.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/DirectiveCallPlaceTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/DirectiveCallPlaceTest.java
new file mode 100644
index 0000000..29220c4
--- /dev/null
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/DirectiveCallPlaceTest.java
@@ -0,0 +1,249 @@
+/*
+ * 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.StringWriter;
+import java.io.Writer;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+
+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.TemplateScalarModel;
+import org.apache.freemarker.core.util.ObjectFactory;
+import org.apache.freemarker.test.TemplateTest;
+import org.junit.Test;
+
+public class DirectiveCallPlaceTest extends TemplateTest {
+
+ @Test
+ public void testCustomDataBasics() throws IOException, TemplateException {
+ addTemplate(
+ "customDataBasics.ftl",
+ "<@u...@uc> <@u...@uc> <@uc>Ab<#-- -->c</...@uc> <@l...@lc> <@l...@lc>");
+
+ CachingTextConverterDirective.resetCacheRecreationCount();
+ for (int i = 0; i < 3; i++) {
+ assertOutputForNamed(
+ "customDataBasics.ftl",
+ "ABC[cached 1] X=123 ABC[cached 2] abc[cached 3]");
+ }
+ }
+
+ @Test
+ public void testCustomDataProviderMismatch() throws IOException, TemplateException {
+ addTemplate(
+ "customDataProviderMismatch.ftl",
+ "<#list [uc, lc, uc] as d><#list 1..2 as _><@d...@d></#list></#list>");
+
+ CachingTextConverterDirective.resetCacheRecreationCount();
+ assertOutputForNamed(
+ "customDataProviderMismatch.ftl",
+ "ABC[cached 1]ABC[cached 1]abc[cached 2]abc[cached 2]ABC[cached 3]ABC[cached 3]");
+ assertOutputForNamed(
+ "customDataProviderMismatch.ftl",
+ "ABC[cached 3]ABC[cached 3]abc[cached 4]abc[cached 4]ABC[cached 5]ABC[cached 5]");
+ }
+
+ @Test
+ public void testPositions() throws IOException, TemplateException {
+ addTemplate(
+ "positions.ftl",
+ "<@pa />\n"
+ + "..<@pa\n"
+ + "/><@pa>xxx</@>\n"
+ + "<@pa>{<@pa/> <@pa/>}</@>\n"
+ + "${curDirLine}<@argP p=curDirLine?string>${curDirLine}</...@argP>${curDirLine}\n"
+ + "<#macro m p>(p=${p}){<#nested>}</#macro>\n"
+ + "${curDirLine}<@m p=curDirLine?string>${curDirLine}</...@m>${curDirLine}");
+
+ assertOutputForNamed(
+ "positions.ftl",
+ "[positions.ftl:1:1-1:7]"
+ + "..[positions.ftl:2:3-3:2]"
+ + "[positions.ftl:3:3-3:14]xxx\n"
+ + "[positions.ftl:4:1-4:24]{[positions.ftl:4:7-4:12] [positions.ftl:4:14-4:19]}\n"
+ + "-(p=5){-}-\n"
+ + "-(p=7){-}-"
+ );
+ }
+
+ @SuppressWarnings("boxing")
+ @Override
+ protected Object createDataModel() {
+ Map<String, Object> dm = new HashMap<>();
+ dm.put("uc", new CachingUpperCaseDirective());
+ dm.put("lc", new CachingLowerCaseDirective());
+ dm.put("pa", new PositionAwareDirective());
+ dm.put("argP", new ArgPrinterDirective());
+ dm.put("curDirLine", new CurDirLineScalar());
+ dm.put("x", 123);
+ return dm;
+ }
+
+ private abstract static class CachingTextConverterDirective implements TemplateDirectiveModel {
+
+ /** Only needed for testing. */
+ private static AtomicInteger cacheRecreationCount = new AtomicInteger();
+
+ /** Only needed for testing. */
+ static void resetCacheRecreationCount() {
+ cacheRecreationCount.set(0);
+ }
+
+ @Override
+ public void execute(Environment env, Map params, TemplateModel[] loopVars, final TemplateDirectiveBody body)
+ throws TemplateException, IOException {
+ if (body == null) {
+ return;
+ }
+
+ final String convertedText;
+
+ final DirectiveCallPlace callPlace = env.getCurrentDirectiveCallPlace();
+ if (callPlace.isNestedOutputCacheable()) {
+ try {
+ convertedText = (String) callPlace.getOrCreateCustomData(
+ getTextConversionIdentity(), new ObjectFactory<String>() {
+
+ @Override
+ public String createObject() throws TemplateException, IOException {
+ return convertBodyText(body)
+ + "[cached " + cacheRecreationCount.incrementAndGet() + "]";
+ }
+
+ });
+ } catch (CallPlaceCustomDataInitializationException e) {
+ throw new TemplateModelException("Failed to pre-render nested content", e);
+ }
+ } else {
+ convertedText = convertBodyText(body);
+ }
+
+ env.getOut().write(convertedText);
+ }
+
+ protected abstract Class getTextConversionIdentity();
+
+ private String convertBodyText(TemplateDirectiveBody body) throws TemplateException,
+ IOException {
+ StringWriter sw = new StringWriter();
+ body.render(sw);
+ return convertText(sw.toString());
+ }
+
+ protected abstract String convertText(String s);
+
+ }
+
+ private static class CachingUpperCaseDirective extends CachingTextConverterDirective {
+
+ @Override
+ protected String convertText(String s) {
+ return s.toUpperCase();
+ }
+
+ @Override
+ protected Class getTextConversionIdentity() {
+ return CachingUpperCaseDirective.class;
+ }
+
+ }
+
+ private static class CachingLowerCaseDirective extends CachingTextConverterDirective {
+
+ @Override
+ protected String convertText(String s) {
+ return s.toLowerCase();
+ }
+
+ @Override
+ protected Class getTextConversionIdentity() {
+ return CachingLowerCaseDirective.class;
+ }
+
+ }
+
+ private static class PositionAwareDirective implements TemplateDirectiveModel {
+
+ @Override
+ public void execute(Environment env, Map params, TemplateModel[] loopVars, TemplateDirectiveBody body)
+ throws TemplateException, IOException {
+ Writer out = env.getOut();
+ DirectiveCallPlace callPlace = env.getCurrentDirectiveCallPlace();
+ out.write("[");
+ out.write(getTemplateSourceName(callPlace));
+ out.write(":");
+ out.write(Integer.toString(callPlace.getBeginLine()));
+ out.write(":");
+ out.write(Integer.toString(callPlace.getBeginColumn()));
+ out.write("-");
+ out.write(Integer.toString(callPlace.getEndLine()));
+ out.write(":");
+ out.write(Integer.toString(callPlace.getEndColumn()));
+ out.write("]");
+ if (body != null) {
+ body.render(out);
+ }
+ }
+
+ private String getTemplateSourceName(DirectiveCallPlace callPlace) {
+ return ((ASTDirUserDefined) callPlace).getTemplate().getSourceName();
+ }
+
+ }
+
+ private static class ArgPrinterDirective implements TemplateDirectiveModel {
+
+ @Override
+ public void execute(Environment env, Map params, TemplateModel[] loopVars, TemplateDirectiveBody body)
+ throws TemplateException, IOException {
+ final Writer out = env.getOut();
+ if (params.size() > 0) {
+ out.write("(p=");
+ out.write(((TemplateScalarModel) params.get("p")).getAsString());
+ out.write(")");
+ }
+ if (body != null) {
+ out.write("{");
+ body.render(out);
+ out.write("}");
+ }
+ }
+
+ }
+
+ private static class CurDirLineScalar implements TemplateScalarModel {
+
+ @Override
+ public String getAsString() throws TemplateModelException {
+ DirectiveCallPlace callPlace = Environment.getCurrentEnvironment().getCurrentDirectiveCallPlace();
+ return callPlace != null
+ ? String.valueOf(Environment.getCurrentEnvironment().getCurrentDirectiveCallPlace().getBeginLine())
+ : "-";
+ }
+
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/28a276c8/freemarker-core-test/src/test/java/org/apache/freemarker/core/EncodingOverrideTest.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/EncodingOverrideTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/EncodingOverrideTest.java
new file mode 100644
index 0000000..b1acead
--- /dev/null
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/EncodingOverrideTest.java
@@ -0,0 +1,62 @@
+/*
+ * 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.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.Collections;
+
+import org.apache.freemarker.core.templateresolver.impl.ClassTemplateLoader;
+import org.junit.Test;
+
+public class EncodingOverrideTest {
+
+ @Test
+ public void testMarchingCharset() throws Exception {
+ Template t = createConfig(StandardCharsets.UTF_8).getTemplate("encodingOverride-UTF-8.ftl");
+ assertEquals(StandardCharsets.UTF_8, t.getActualSourceEncoding());
+ checkTemplateOutput(t);
+ }
+
+ @Test
+ public void testDifferentCharset() throws Exception {
+ Template t = createConfig(StandardCharsets.UTF_8).getTemplate("encodingOverride-ISO-8859-1.ftl");
+ assertEquals(StandardCharsets.ISO_8859_1, t.getActualSourceEncoding());
+ checkTemplateOutput(t);
+ }
+
+ private void checkTemplateOutput(Template t) throws TemplateException, IOException {
+ StringWriter out = new StringWriter();
+ t.process(Collections.emptyMap(), out);
+ assertEquals("Béka", out.toString());
+ }
+
+ private Configuration createConfig(Charset charset) {
+ return new Configuration.Builder(Configuration.VERSION_3_0_0)
+ .templateLoader(new ClassTemplateLoader(EncodingOverrideTest.class, ""))
+ .sourceEncoding(charset)
+ .build();
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/28a276c8/freemarker-core-test/src/test/java/org/apache/freemarker/core/EnvironmentGetTemplateVariantsTest.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/EnvironmentGetTemplateVariantsTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/EnvironmentGetTemplateVariantsTest.java
new file mode 100644
index 0000000..b79559c
--- /dev/null
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/EnvironmentGetTemplateVariantsTest.java
@@ -0,0 +1,214 @@
+/*
+ * 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.Writer;
+import java.util.Collections;
+import java.util.Map;
+
+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.impl.SimpleScalar;
+import org.apache.freemarker.core.templateresolver.impl.StringTemplateLoader;
+import org.apache.freemarker.test.TemplateTest;
+import org.apache.freemarker.test.TestConfigurationBuilder;
+import org.junit.Test;
+
+public class EnvironmentGetTemplateVariantsTest extends TemplateTest {
+
+ private static final StringTemplateLoader TEMPLATES = new StringTemplateLoader();
+ static {
+ TEMPLATES.putTemplate("main",
+ "<@tNames />\n"
+ + "---1---\n"
+ + "[imp: <#import 'imp' as i>${i.impIni}]\n"
+ + "---2---\n"
+ + "<@i.impM>"
+ + "<@tNames />"
+ + "</@>\n"
+ + "---3---\n"
+ + "[inc: <#include 'inc'>]\n"
+ + "---4---\n"
+ + "<@incM>"
+ + "<@tNames />"
+ + "</@>\n"
+ + "---5---\n"
+ + "[inc2: <#include 'inc2'>]\n"
+ + "---6---\n"
+ + "<#import 'imp2' as i2>"
+ + "<@i.impM2><@tNames /></@>\n"
+ + "---7---\n"
+ + "<#macro mainM>"
+ + "[mainM: <@tNames /> {<#nested>} <@tNames />]"
+ + "</#macro>"
+ + "[inc3: <#include 'inc3'>]\n"
+ + "<@mainM><@tNames /> <#include 'inc4'> <@tNames /></@>\n"
+ + "<@tNames />\n"
+ + "---8---\n"
+ + "<#function mainF>"
+ + "<@tNames />"
+ + "<#return lastTNamesResult>"
+ + "</#function>"
+ + "mainF: ${mainF()}, impF: ${i.impF()}, incF: ${incF()}\n"
+ );
+ TEMPLATES.putTemplate("inc",
+ "<@tNames />\n"
+ + "<#macro incM>"
+ + "[incM: <@tNames /> {<#nested>}]"
+ + "</#macro>"
+ + "<#function incF>"
+ + "<@tNames />"
+ + "<#return lastTNamesResult>"
+ + "</#function>"
+ + "<@incM><@tNames /></@>\n"
+ + "<#if !included!false>[incInc: <#assign included=true><#include 'inc'>]\n</#if>"
+ );
+ TEMPLATES.putTemplate("imp",
+ "<#assign impIni><@tNames /></#assign>\n"
+ + "<#macro impM>"
+ + "[impM: <@tNames />\n"
+ + "{<#nested>}\n"
+ + "[inc: <#include 'inc'>]\n"
+ + "<@incM><@tNames /></@>\n"
+ + "]"
+ + "</#macro>"
+ + "<#macro impM2>"
+ + "[impM2: <@tNames />\n"
+ + "{<#nested>}\n"
+ + "<@i2.imp2M><@tNames /></@>\n"
+ + "]"
+ + "</#macro>"
+ + "<#function impF>"
+ + "<@tNames />"
+ + "<#return lastTNamesResult>"
+ + "</#function>"
+ );
+ TEMPLATES.putTemplate("inc2",
+ "<@tNames />\n"
+ + "<@i.impM><@tNames /></@>\n"
+ );
+ TEMPLATES.putTemplate("imp2",
+ "<#macro imp2M>"
+ + "[imp2M: <@tNames /> {<#nested>}]"
+ + "</#macro>");
+ TEMPLATES.putTemplate("inc3",
+ "<@tNames />\n"
+ + "<@mainM><@tNames /></@>\n"
+ );
+ TEMPLATES.putTemplate("inc4",
+ "<@tNames />"
+ );
+ }
+
+ @Test
+ public void test() throws IOException, TemplateException {
+ setConfiguration(createConfiguration(Configuration.VERSION_3_0_0));
+ assertOutputForNamed(
+ "main",
+ "<ct=main mt=main>\n"
+ + "---1---\n"
+ + "[imp: <ct=imp mt=main>]\n"
+ + "---2---\n"
+ + "[impM: <ct=imp mt=main>\n"
+ + "{<ct=main mt=main>}\n"
+ + "[inc: <ct=inc mt=main>\n"
+ + "[incM: <ct=inc mt=main> {<ct=inc mt=main>}]\n"
+ + "[incInc: <ct=inc mt=main>\n"
+ + "[incM: <ct=inc mt=main> {<ct=inc mt=main>}]\n"
+ + "]\n"
+ + "]\n"
+ + "[incM: <ct=inc mt=main> {<ct=imp mt=main>}]\n"
+ + "]\n"
+ + "---3---\n"
+ + "[inc: <ct=inc mt=main>\n"
+ + "[incM: <ct=inc mt=main> {<ct=inc mt=main>}]\n"
+ + "[incInc: <ct=inc mt=main>\n"
+ + "[incM: <ct=inc mt=main> {<ct=inc mt=main>}]\n"
+ + "]\n"
+ + "]\n"
+ + "---4---\n"
+ + "[incM: <ct=inc mt=main> {<ct=main mt=main>}]\n"
+ + "---5---\n"
+ + "[inc2: <ct=inc2 mt=main>\n"
+ + "[impM: <ct=imp mt=main>\n"
+ + "{<ct=inc2 mt=main>}\n"
+ + "[inc: <ct=inc mt=main>\n"
+ + "[incM: <ct=inc mt=main> {<ct=inc mt=main>}]\n"
+ + "]\n"
+ + "[incM: <ct=inc mt=main> {<ct=imp mt=main>}]\n"
+ + "]\n"
+ + "]\n"
+ + "---6---\n"
+ + "[impM2: <ct=imp mt=main>\n"
+ + "{<ct=main mt=main>}\n"
+ + "[imp2M: <ct=imp2 mt=main> {<ct=imp mt=main>}]\n"
+ + "]\n"
+ + "---7---\n"
+ + "[inc3: <ct=inc3 mt=main>\n"
+ + "[mainM: <ct=main mt=main> {<ct=inc3 mt=main>} <ct=main mt=main>]\n"
+ + "]\n"
+ + "[mainM: "
+ + "<ct=main mt=main> "
+ + "{<ct=main mt=main> <ct=inc4 mt=main> <ct=main mt=main>} "
+ + "<ct=main mt=main>"
+ + "]\n"
+ + "<ct=main mt=main>\n"
+ + "---8---\n"
+ + "mainF: <ct=main mt=main>, impF: <ct=imp mt=main>, incF: <ct=inc mt=main>\n"
+ .replaceAll("<t=\\w+", "<t=main"));
+ }
+
+ @Test
+ public void testNotStarted() throws IOException, TemplateException {
+ Template t = new Template("foo", "", createConfiguration(Configuration.VERSION_3_0_0));
+ final Environment env = t.createProcessingEnvironment(null, null);
+ assertSame(t, env.getMainTemplate());
+ assertSame(t, env.getCurrentTemplate());
+ }
+
+ private Configuration createConfiguration(Version iciVersion) {
+ return new TestConfigurationBuilder(iciVersion)
+ .templateLoader(TEMPLATES)
+ .whitespaceStripping(false)
+ .build();
+ }
+
+ @Override
+ protected Object createDataModel() {
+ return Collections.singletonMap("tNames", new TemplateDirectiveModel() {
+
+ @Override
+ public void execute(Environment env, Map params, TemplateModel[] loopVars, TemplateDirectiveBody body)
+ throws TemplateException, IOException {
+ Writer out = env.getOut();
+ final String r = "<ct=" + env.getCurrentTemplate().getLookupName() + " mt="
+ + env.getMainTemplate().getLookupName() + ">";
+ out.write(r);
+ env.setGlobalVariable("lastTNamesResult", new SimpleScalar(r));
+ }
+
+ });
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/28a276c8/freemarker-core-test/src/test/java/org/apache/freemarker/core/ExceptionTest.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/ExceptionTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/ExceptionTest.java
new file mode 100644
index 0000000..19c3b6e
--- /dev/null
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/ExceptionTest.java
@@ -0,0 +1,115 @@
+/*
+ * 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.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.util.Collections;
+import java.util.Locale;
+
+import org.apache.freemarker.core.templateresolver.impl.StringTemplateLoader;
+import org.apache.freemarker.core.util._NullWriter;
+import org.apache.freemarker.test.TestConfigurationBuilder;
+
+import junit.framework.TestCase;
+public class ExceptionTest extends TestCase {
+
+ public ExceptionTest(String name) {
+ super(name);
+ }
+
+ public void testParseExceptionSerializable() throws IOException, ClassNotFoundException {
+ try {
+ new Template(null, new StringReader("<@>"), new TestConfigurationBuilder().build());
+ fail();
+ } catch (ParseException e) {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ new ObjectOutputStream(out).writeObject(e);
+ new ObjectInputStream(new ByteArrayInputStream(out.toByteArray())).readObject();
+ }
+ }
+
+ public void testTemplateErrorSerializable() throws IOException, ClassNotFoundException {
+ Template tmp = new Template(null, new StringReader("${noSuchVar}"),
+ new TestConfigurationBuilder().build());
+ try {
+ tmp.process(Collections.EMPTY_MAP, new StringWriter());
+ fail();
+ } catch (TemplateException e) {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ new ObjectOutputStream(out).writeObject(e);
+ new ObjectInputStream(new ByteArrayInputStream(out.toByteArray())).readObject();
+ }
+ }
+
+ @SuppressWarnings("boxing")
+ public void testTemplateExceptionLocationInformation() throws IOException {
+ StringTemplateLoader tl = new StringTemplateLoader();
+ tl.putTemplate("foo_en.ftl", "\n\nxxx${noSuchVariable}");
+
+ Template t = new TestConfigurationBuilder().templateLoader(tl).build()
+ .getTemplate("foo.ftl", Locale.US);
+ try {
+ t.process(null, _NullWriter.INSTANCE);
+ fail();
+ } catch (TemplateException e) {
+ assertEquals("foo.ftl", t.getLookupName());
+ assertEquals("foo.ftl", e.getTemplateLookupName());
+ assertEquals("foo_en.ftl", e.getTemplateSourceName());
+ assertEquals(3, (int) e.getLineNumber());
+ assertEquals(6, (int) e.getColumnNumber());
+ assertEquals(3, (int) e.getEndLineNumber());
+ assertEquals(19, (int) e.getEndColumnNumber());
+ assertThat(e.getMessage(), containsString("foo_en.ftl"));
+ assertThat(e.getMessage(), containsString("noSuchVariable"));
+ }
+ }
+
+ @SuppressWarnings("cast")
+ public void testParseExceptionLocationInformation() throws IOException {
+ StringTemplateLoader tl = new StringTemplateLoader();
+ tl.putTemplate("foo_en.ftl", "\n\nxxx<#noSuchDirective>");
+
+ try {
+ new TestConfigurationBuilder().templateLoader(tl).build()
+ .getTemplate("foo.ftl", Locale.US);
+ fail();
+ } catch (ParseException e) {
+ System.out.println(e.getMessage());
+ assertEquals("foo_en.ftl", e.getTemplateSourceName());
+ assertEquals("foo.ftl", e.getTemplateLookupName());
+ assertEquals(3, e.getLineNumber());
+ assertEquals(5, e.getColumnNumber());
+ assertEquals(3, e.getEndLineNumber());
+ assertEquals(20, e.getEndColumnNumber());
+ assertThat(e.getMessage(), containsString("foo_en.ftl"));
+ assertThat(e.getMessage(), containsString("noSuchDirective"));
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/28a276c8/freemarker-core-test/src/test/java/org/apache/freemarker/core/GetSourceTest.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/GetSourceTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/GetSourceTest.java
new file mode 100644
index 0000000..1462bfc
--- /dev/null
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/GetSourceTest.java
@@ -0,0 +1,52 @@
+/*
+ * 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 org.apache.freemarker.test.TestConfigurationBuilder;
+import org.junit.Test;
+
+public class GetSourceTest {
+
+ @Test
+ public void testGetSource() throws Exception {
+ {
+ // Note: Default tab size is 8.
+ Template t = new Template(null, "a\n\tb\nc",
+ new TestConfigurationBuilder().build());
+ // A historical quirk we keep for B.C.: it repaces tabs with spaces.
+ assertEquals("a\n b\nc", t.getSource(1, 1, 1, 3));
+ }
+
+ {
+ Template t = new Template(null, "a\n\tb\nc",
+ new TestConfigurationBuilder().tabSize(4).build());
+ assertEquals("a\n b\nc", t.getSource(1, 1, 1, 3));
+ }
+
+ {
+ Template t = new Template(null, "a\n\tb\nc",
+ new TestConfigurationBuilder().tabSize(1).build());
+ // If tab size is 1, it behaves as it always should have: it keeps the tab.
+ assertEquals("a\n\tb\nc", t.getSource(1, 1, 1, 3));
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/28a276c8/freemarker-core-test/src/test/java/org/apache/freemarker/core/HeaderParsingTest.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/HeaderParsingTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/HeaderParsingTest.java
new file mode 100644
index 0000000..6f8851d
--- /dev/null
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/HeaderParsingTest.java
@@ -0,0 +1,60 @@
+/*
+ * 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.test.TemplateTest;
+import org.apache.freemarker.test.TestConfigurationBuilder;
+import org.junit.Test;
+
+public class HeaderParsingTest extends TemplateTest {
+
+ private final Configuration cfgStripWS = new TestConfigurationBuilder().build();
+ private final Configuration cfgNoStripWS = new TestConfigurationBuilder().whitespaceStripping(false).build();
+
+ @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/28a276c8/freemarker-core-test/src/test/java/org/apache/freemarker/core/IncludeAndImportConfigurableLayersTest.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/IncludeAndImportConfigurableLayersTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/IncludeAndImportConfigurableLayersTest.java
new file mode 100644
index 0000000..738b4de
--- /dev/null
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/IncludeAndImportConfigurableLayersTest.java
@@ -0,0 +1,354 @@
+/*
+ * 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.StringWriter;
+
+import org.apache.freemarker.core.templateresolver.ConditionalTemplateConfigurationFactory;
+import org.apache.freemarker.core.templateresolver.FileNameGlobMatcher;
+import org.apache.freemarker.core.templateresolver.impl.StringTemplateLoader;
+import org.apache.freemarker.test.TestConfigurationBuilder;
+import org.junit.Test;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+public class IncludeAndImportConfigurableLayersTest {
+
+ @Test
+ public void test3LayerImportNoClashes() throws Exception {
+ TestConfigurationBuilder cfgB = createConfigurationBuilder()
+ .autoImports(ImmutableMap.of("t1", "t1.ftl"))
+ .templateConfigurations(
+ new ConditionalTemplateConfigurationFactory(
+ new FileNameGlobMatcher("main.ftl"),
+ new TemplateConfiguration.Builder()
+ .autoImports(ImmutableMap.of("t2", "t2.ftl"))
+ .build()));
+ Configuration cfg = cfgB.build();
+
+ {
+ 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());
+ }
+
+ cfgB.removeAutoImport("t1");
+ cfg = cfgB.build();
+
+ {
+ 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 = createConfigurationBuilder()
+ .autoImports(ImmutableMap.of(
+ "t1", "t1.ftl",
+ "t2", "t2.ftl",
+ "t3", "t3.ftl"))
+ .templateConfigurations(
+ new ConditionalTemplateConfigurationFactory(
+ new FileNameGlobMatcher("main.ftl"),
+ new TemplateConfiguration.Builder()
+ .autoImports(ImmutableMap.of("t2", "t2b.ftl"))
+ .build()))
+ .build();
+
+ {
+ 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 {
+ TestConfigurationBuilder cfgB = createConfigurationBuilder()
+ .autoIncludes(ImmutableList.of("t1.ftl"))
+ .templateConfigurations(
+ new ConditionalTemplateConfigurationFactory(
+ new FileNameGlobMatcher("main.ftl"),
+ new TemplateConfiguration.Builder()
+ .autoIncludes(ImmutableList.of("t2.ftl"))
+ .build()));
+
+ Configuration cfg = cfgB.build();
+
+ {
+ 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());
+ }
+
+ cfgB.removeAutoInclude("t1.ftl");
+ cfg = cfgB.build();
+
+ {
+ 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 = createConfigurationBuilder()
+ .autoIncludes(ImmutableList.of(
+ "t1.ftl",
+ "t2.ftl",
+ "t3.ftl"))
+ .templateConfigurations(new ConditionalTemplateConfigurationFactory(
+ new FileNameGlobMatcher("main.ftl"),
+ new TemplateConfiguration.Builder()
+ .autoIncludes(ImmutableList.of("t2.ftl"))
+ .build()))
+ .build();
+
+ {
+ 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 = createConfigurationBuilder()
+ .autoIncludes(ImmutableList.of("t1.ftl", "t1.ftl"))
+ .templateConfigurations(
+ new ConditionalTemplateConfigurationFactory(
+ new FileNameGlobMatcher("main.ftl"),
+ new TemplateConfiguration.Builder()
+ .autoIncludes(ImmutableList.of("t2.ftl", "t2.ftl"))
+ .build()))
+ .build();
+
+ {
+ 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 test3LayerLaziness() throws Exception {
+ for (Class<?> layer : new Class<?>[] { Configuration.class, Template.class, Environment.class }) {
+ test3LayerLaziness(layer, null, null, false, "t1;t2;");
+ test3LayerLaziness(layer, null, null, true, "t1;t2;");
+ test3LayerLaziness(layer, null, false, true, "t1;t2;");
+ test3LayerLaziness(layer, null, true, true, "t2;");
+
+ test3LayerLaziness(layer, false, null, false, "t1;t2;");
+ test3LayerLaziness(layer, false, null, true, "t1;t2;");
+ test3LayerLaziness(layer, false, false, true, "t1;t2;");
+ test3LayerLaziness(layer, false, true, true, "t2;");
+
+ test3LayerLaziness(layer, true, null, false, "");
+ test3LayerLaziness(layer, true, null, true, "");
+ test3LayerLaziness(layer, true, false, true, "t1;");
+ test3LayerLaziness(layer, true, true, true, "");
+ }
+ }
+
+ private void test3LayerLaziness(
+ Class<?> layer,
+ Boolean lazyImports,
+ Boolean lazyAutoImports, boolean setLazyAutoImports,
+ String expectedOutput)
+ throws Exception {
+ Configuration cfg;
+ {
+ TestConfigurationBuilder cfgB = createConfigurationBuilder()
+ .autoImports(ImmutableMap.of("t1", "t1.ftl"));
+ if (layer == Configuration.class) {
+ setLazinessOfConfigurable(cfgB, lazyImports, lazyAutoImports, setLazyAutoImports);
+ }
+ cfg = cfgB.build();
+ }
+
+ TemplateConfiguration tc;
+ if (layer == Template.class) {
+ TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder();
+ setLazinessOfConfigurable(tcb, lazyImports, lazyAutoImports, setLazyAutoImports);
+ tc = tcb.build();
+ } else {
+ tc = null;
+ }
+
+ Template t = new Template(null, "<#import 't2.ftl' as t2>${loaded!}", cfg, tc);
+ StringWriter sw = new StringWriter();
+
+ Environment env = t.createProcessingEnvironment(null, sw);
+ if (layer == Environment.class) {
+ setLazinessOfConfigurable(env, lazyImports, lazyAutoImports, setLazyAutoImports);
+ }
+
+ env.process();
+ assertEquals(expectedOutput, sw.toString());
+ }
+
+ private void setLazinessOfConfigurable(
+ MutableProcessingConfiguration<?> cfg,
+ Boolean lazyImports, Boolean lazyAutoImports, boolean setLazyAutoImports) {
+ if (lazyImports != null) {
+ cfg.setLazyImports(lazyImports);
+ }
+ if (setLazyAutoImports) {
+ cfg.setLazyAutoImports(lazyAutoImports);
+ }
+ }
+
+ private TestConfigurationBuilder createConfigurationBuilder() {
+ StringTemplateLoader loader = new StringTemplateLoader();
+ loader.putTemplate("main.ftl", "In main: ${loaded}");
+ loader.putTemplate("main2.ftl", "In main2: ${loaded}");
+ loader.putTemplate("t1.ftl", "<#global loaded = (loaded!) + 't1;'>T1;");
+ loader.putTemplate("t2.ftl", "<#global loaded = (loaded!) + 't2;'>T2;");
+ loader.putTemplate("t3.ftl", "<#global loaded = (loaded!) + 't3;'>T3;");
+ loader.putTemplate("t1b.ftl", "<#global loaded = (loaded!) + 't1b;'>T1b;");
+ loader.putTemplate("t2b.ftl", "<#global loaded = (loaded!) + 't2b;'>T2b;");
+ loader.putTemplate("t3b.ftl", "<#global loaded = (loaded!) + 't3b;'>T3b;");
+
+ return new TestConfigurationBuilder().templateLoader(loader);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/28a276c8/freemarker-core-test/src/test/java/org/apache/freemarker/core/IncludeAndImportTest.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/IncludeAndImportTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/IncludeAndImportTest.java
new file mode 100644
index 0000000..fa767ff
--- /dev/null
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/IncludeAndImportTest.java
@@ -0,0 +1,270 @@
+/*
+ * 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.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+
+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.apache.freemarker.test.TestConfigurationBuilder;
+import org.junit.Test;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+@SuppressWarnings("boxing")
+public class IncludeAndImportTest extends TemplateTest {
+
+ @Override
+ protected void addCommonTemplates() {
+ 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 invoke 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 {
+ setConfiguration(new TestConfigurationBuilder()
+ .autoImports(ImmutableMap.of(
+ "lib1", "lib1.ftl",
+ "lib2", "lib2CallsLib1.ftl"
+ ))
+ .autoIncludes(ImmutableList.of(
+ "inc1.ftl",
+ "inc2.ftl"))
+ .build());
+ 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");
+
+ setConfiguration(new TestConfigurationBuilder().lazyImports(true).build());
+ 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 {
+ setConfiguration(new TestConfigurationBuilder().lazyImports(true).build());
+ 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 {
+ TestConfigurationBuilder cfgB = new TestConfigurationBuilder()
+ .autoImports(ImmutableMap.of(
+ "l1", "lib1.ftl",
+ "l2", "lib2.ftl",
+ "l3", "lib3.ftl"
+ ));
+
+ String ftl = "<@l2.m/>, <@l1.m/>; ${history}";
+ String expectedEagerOutput = "In lib2, In lib1; L1L2L3";
+ String expecedLazyOutput = "In lib2, In lib1; L2L1";
+
+ setConfiguration(cfgB.build());
+ assertOutput(ftl, expectedEagerOutput);
+ cfgB.setLazyImports(true);
+ setConfiguration(cfgB.build());
+ assertOutput(ftl, expecedLazyOutput);
+ cfgB.setLazyImports(false);
+ setConfiguration(cfgB.build());
+ assertOutput(ftl, expectedEagerOutput);
+ cfgB.setLazyAutoImports(true);
+ setConfiguration(cfgB.build());
+ assertOutput(ftl, expecedLazyOutput);
+ cfgB.setLazyAutoImports(null);
+ setConfiguration(cfgB.build());
+ assertOutput(ftl, expectedEagerOutput);
+ cfgB.setLazyImports(true);
+ cfgB.setLazyAutoImports(false);
+ setConfiguration(cfgB.build());
+ assertOutput(ftl, expectedEagerOutput);
+ }
+
+ @Test
+ public void lazyAutoImportMixedWithManualImport() throws IOException, TemplateException {
+ TestConfigurationBuilder cfgB = new TestConfigurationBuilder()
+ .autoImports(ImmutableMap.of(
+ "l1", "lib1.ftl",
+ "l2", "/./lib2.ftl",
+ "l3", "lib3.ftl"))
+ .lazyAutoImports(true);
+
+ String ftl = "<@l2.m/>, <@l1.m/>; ${history}";
+ String expectOutputWithoutHistory = "In lib2, In lib1; ";
+ String expecedOutput = expectOutputWithoutHistory + "L2L1";
+
+ setConfiguration(cfgB.build());
+ 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");
+
+ cfgB.setLazyImports(true);
+ setConfiguration(cfgB.build());
+ 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 {
+ TestConfigurationBuilder cfgB = new TestConfigurationBuilder();
+ cfgB.setLazyImports(true);
+
+ setConfiguration(cfgB.build());
+ assertOutput("<#import 'noSuchTemplate.ftl' as wrong>x", "x");
+
+ cfgB.addAutoImport("wrong", "noSuchTemplate.ftl");
+ setConfiguration(cfgB.build());
+ 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/28a276c8/freemarker-core-test/src/test/java/org/apache/freemarker/core/IncudeFromNamelessTest.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/IncudeFromNamelessTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/IncudeFromNamelessTest.java
new file mode 100644
index 0000000..f4908b8
--- /dev/null
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/IncudeFromNamelessTest.java
@@ -0,0 +1,58 @@
+/*
+ * 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.templateresolver.impl.StringTemplateLoader;
+import org.apache.freemarker.test.TestConfigurationBuilder;
+
+import junit.framework.TestCase;
+
+public class IncudeFromNamelessTest extends TestCase {
+
+ public IncudeFromNamelessTest(String name) {
+ super(name);
+ }
+
+ public void test() throws IOException, TemplateException {
+ StringTemplateLoader loader = new StringTemplateLoader();
+ loader.putTemplate("i.ftl", "[i]");
+ loader.putTemplate("sub/i.ftl", "[sub/i]");
+ loader.putTemplate("import.ftl", "<#assign x = 1>");
+
+ Configuration cfg = new TestConfigurationBuilder().templateLoader(loader).build();
+
+ Template t = new Template(null, new StringReader(
+ "<#include 'i.ftl'>\n"
+ + "<#include '/i.ftl'>\n"
+ + "<#include 'sub/i.ftl'>\n"
+ + "<#include '/sub/i.ftl'>"
+ + "<#import 'import.ftl' as i>${i.x}"
+ ),
+ cfg);
+ StringWriter out = new StringWriter();
+ t.process(null, out);
+ assertEquals("[i][i][sub/i][sub/i]1", out.toString());
+ }
+
+}