http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/test/java/org/apache/freemarker/core/CustomAttributeTest.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/test/java/org/apache/freemarker/core/CustomAttributeTest.java b/freemarker-core/src/test/java/org/apache/freemarker/core/CustomAttributeTest.java new file mode 100644 index 0000000..726a20c --- /dev/null +++ b/freemarker-core/src/test/java/org/apache/freemarker/core/CustomAttributeTest.java @@ -0,0 +1,163 @@ +/* + * 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.math.BigDecimal; +import java.util.Arrays; + +import org.junit.Test; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; + +@SuppressWarnings("boxing") +public class CustomAttributeTest { + + private static final String KEY_1 = "key1"; + private static final String KEY_2 = "key2"; + private static final String KEY_3 = "key3"; + private static final Integer KEY_4 = 4; + + private static final Integer VALUE_1 = 1; // Serializable + private static final Object VALUE_2 = new Object(); + private static final Object VALUE_3 = new Object(); + private static final Object VALUE_4 = new Object(); + private static final Object VALUE_LIST = ImmutableList.<Object>of( + "s", BigDecimal.valueOf(2), Boolean.TRUE, ImmutableMap.of("a", "A")); + private static final Object VALUE_BIGDECIMAL = BigDecimal.valueOf(22); + + private static final Object CUST_ATT_KEY = new Object(); + + @Test + public void testStringKey() throws Exception { + // Need some MutableProcessingConfiguration: + TemplateConfiguration.Builder mpc = new TemplateConfiguration.Builder(); + + assertEquals(0, mpc.getCustomAttributeNames().length); + assertNull(mpc.getCustomAttribute(KEY_1)); + + mpc.setCustomAttribute(KEY_1, VALUE_1); + assertArrayEquals(new String[] { KEY_1 }, mpc.getCustomAttributeNames()); + assertSame(VALUE_1, mpc.getCustomAttribute(KEY_1)); + + mpc.setCustomAttribute(KEY_2, VALUE_2); + assertArrayEquals(new String[] { KEY_1, KEY_2 }, sort(mpc.getCustomAttributeNames())); + assertSame(VALUE_1, mpc.getCustomAttribute(KEY_1)); + assertSame(VALUE_2, mpc.getCustomAttribute(KEY_2)); + + mpc.setCustomAttribute(KEY_1, VALUE_2); + assertArrayEquals(new String[] { KEY_1, KEY_2 }, sort(mpc.getCustomAttributeNames())); + assertSame(VALUE_2, mpc.getCustomAttribute(KEY_1)); + assertSame(VALUE_2, mpc.getCustomAttribute(KEY_2)); + + mpc.setCustomAttribute(KEY_1, null); + assertArrayEquals(new String[] { KEY_1, KEY_2 }, sort(mpc.getCustomAttributeNames())); + assertNull(mpc.getCustomAttribute(KEY_1)); + assertSame(VALUE_2, mpc.getCustomAttribute(KEY_2)); + + mpc.removeCustomAttribute(KEY_1); + assertArrayEquals(new String[] { KEY_2 }, mpc.getCustomAttributeNames()); + assertNull(mpc.getCustomAttribute(KEY_1)); + assertSame(VALUE_2, mpc.getCustomAttribute(KEY_2)); + } + + @Test + public void testRemoveFromEmptySet() throws Exception { + // Need some MutableProcessingConfiguration: + TemplateConfiguration.Builder mpc = new TemplateConfiguration.Builder(); + + mpc.removeCustomAttribute(KEY_1); + assertEquals(0, mpc.getCustomAttributeNames().length); + assertNull(mpc.getCustomAttribute(KEY_1)); + + mpc.setCustomAttribute(KEY_1, VALUE_1); + assertArrayEquals(new String[] { KEY_1 }, mpc.getCustomAttributeNames()); + assertSame(VALUE_1, mpc.getCustomAttribute(KEY_1)); + } + + @Test + public void testAttrsFromFtlHeaderOnly() throws Exception { + Template t = new Template(null, "<#ftl attributes={" + + "'" + KEY_1 + "': [ 's', 2, true, { 'a': 'A' } ], " + + "'" + KEY_2 + "': " + VALUE_BIGDECIMAL + " " + + "}>", + new Configuration.Builder(Configuration.VERSION_3_0_0).build()); + + assertEquals(ImmutableSet.of(KEY_1, KEY_2), t.getCustomAttributes().keySet()); + assertEquals(VALUE_LIST, t.getCustomAttribute(KEY_1)); + assertEquals(VALUE_BIGDECIMAL, t.getCustomAttribute(KEY_2)); + + t.setCustomAttribute(KEY_1, VALUE_1); + assertEquals(VALUE_1, t.getCustomAttribute(KEY_1)); + assertEquals(VALUE_BIGDECIMAL, t.getCustomAttribute(KEY_2)); + + t.setCustomAttribute(KEY_1, null); + assertEquals(ImmutableSet.of(KEY_1, KEY_2), t.getCustomAttributes().keySet()); + assertNull(t.getCustomAttribute(KEY_1)); + } + + @Test + public void testAttrsFromFtlHeaderAndFromTemplateConfiguration() throws Exception { + TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder(); + tcb.setCustomAttribute(KEY_3, VALUE_3); + tcb.setCustomAttribute(KEY_4, VALUE_4); + Template t = new Template(null, "<#ftl attributes={" + + "'" + KEY_1 + "': 'a', " + + "'" + KEY_2 + "': 'b', " + + "'" + KEY_3 + "': 'c' " + + "}>", + new Configuration.Builder(Configuration.VERSION_3_0_0).build(), + tcb.build()); + + assertEquals(ImmutableSet.of(KEY_1, KEY_2, KEY_3, KEY_4), t.getCustomAttributes().keySet()); + assertEquals("a", t.getCustomAttribute(KEY_1)); + assertEquals("b", t.getCustomAttribute(KEY_2)); + assertEquals("c", t.getCustomAttribute(KEY_3)); // Has overridden TC attribute + assertEquals(VALUE_4, t.getCustomAttribute(KEY_4)); // Inherited TC attribute + + t.setCustomAttribute(KEY_3, null); + assertEquals(ImmutableSet.of(KEY_1, KEY_2, KEY_3, KEY_4), t.getCustomAttributes().keySet()); + assertNull("null value shouldn't cause fallback to TC attribute", t.getCustomAttribute(KEY_3)); + } + + + @Test + public void testAttrsFromTemplateConfigurationOnly() throws Exception { + TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder(); + tcb.setCustomAttribute(KEY_3, VALUE_3); + tcb.setCustomAttribute(KEY_4, VALUE_4); + Template t = new Template(null, "", + new Configuration.Builder(Configuration.VERSION_3_0_0).build(), + tcb.build()); + + assertEquals(ImmutableSet.of(KEY_3, KEY_4), t.getCustomAttributes().keySet()); + assertEquals(VALUE_3, t.getCustomAttribute(KEY_3)); + assertEquals(VALUE_4, t.getCustomAttribute(KEY_4)); + } + + private Object[] sort(String[] customAttributeNames) { + Arrays.sort(customAttributeNames); + return customAttributeNames; + } + +}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/test/java/org/apache/freemarker/core/DateFormatTest.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/test/java/org/apache/freemarker/core/DateFormatTest.java b/freemarker-core/src/test/java/org/apache/freemarker/core/DateFormatTest.java new file mode 100644 index 0000000..4ad5937 --- /dev/null +++ b/freemarker-core/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/3fd56062/freemarker-core/src/test/java/org/apache/freemarker/core/DirectiveCallPlaceTest.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/test/java/org/apache/freemarker/core/DirectiveCallPlaceTest.java b/freemarker-core/src/test/java/org/apache/freemarker/core/DirectiveCallPlaceTest.java new file mode 100644 index 0000000..29220c4 --- /dev/null +++ b/freemarker-core/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", + "<@uc>Abc</@uc> <@uc>x=${x}</@uc> <@uc>Ab<#-- -->c</@uc> <@lc/><@lc></@lc> <@lc>Abc</@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>Abc</@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/3fd56062/freemarker-core/src/test/java/org/apache/freemarker/core/EncodingOverrideTest.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/test/java/org/apache/freemarker/core/EncodingOverrideTest.java b/freemarker-core/src/test/java/org/apache/freemarker/core/EncodingOverrideTest.java new file mode 100644 index 0000000..b1acead --- /dev/null +++ b/freemarker-core/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/3fd56062/freemarker-core/src/test/java/org/apache/freemarker/core/EnvironmentGetTemplateVariantsTest.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/test/java/org/apache/freemarker/core/EnvironmentGetTemplateVariantsTest.java b/freemarker-core/src/test/java/org/apache/freemarker/core/EnvironmentGetTemplateVariantsTest.java new file mode 100644 index 0000000..b79559c --- /dev/null +++ b/freemarker-core/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/3fd56062/freemarker-core/src/test/java/org/apache/freemarker/core/ExceptionTest.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/test/java/org/apache/freemarker/core/ExceptionTest.java b/freemarker-core/src/test/java/org/apache/freemarker/core/ExceptionTest.java new file mode 100644 index 0000000..19c3b6e --- /dev/null +++ b/freemarker-core/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/3fd56062/freemarker-core/src/test/java/org/apache/freemarker/core/GetSourceTest.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/test/java/org/apache/freemarker/core/GetSourceTest.java b/freemarker-core/src/test/java/org/apache/freemarker/core/GetSourceTest.java new file mode 100644 index 0000000..1462bfc --- /dev/null +++ b/freemarker-core/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/3fd56062/freemarker-core/src/test/java/org/apache/freemarker/core/HeaderParsingTest.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/test/java/org/apache/freemarker/core/HeaderParsingTest.java b/freemarker-core/src/test/java/org/apache/freemarker/core/HeaderParsingTest.java new file mode 100644 index 0000000..6f8851d --- /dev/null +++ b/freemarker-core/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/3fd56062/freemarker-core/src/test/java/org/apache/freemarker/core/IncludeAndImportConfigurableLayersTest.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/test/java/org/apache/freemarker/core/IncludeAndImportConfigurableLayersTest.java b/freemarker-core/src/test/java/org/apache/freemarker/core/IncludeAndImportConfigurableLayersTest.java new file mode 100644 index 0000000..738b4de --- /dev/null +++ b/freemarker-core/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); + } + +}
