http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/28a276c8/freemarker-core-test/src/test/java/org/apache/freemarker/core/OutputFormatTest.java ---------------------------------------------------------------------- diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/OutputFormatTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/OutputFormatTest.java new file mode 100644 index 0000000..eedb4d1 --- /dev/null +++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/OutputFormatTest.java @@ -0,0 +1,1068 @@ +/* + * 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.apache.freemarker.core.ParsingConfiguration.*; +import static org.junit.Assert.*; + +import java.io.IOException; +import java.io.StringWriter; +import java.io.Writer; +import java.util.Collections; + +import org.apache.freemarker.core.model.TemplateModelException; +import org.apache.freemarker.core.outputformat.OutputFormat; +import org.apache.freemarker.core.outputformat.impl.HTMLOutputFormat; +import org.apache.freemarker.core.outputformat.impl.PlainTextOutputFormat; +import org.apache.freemarker.core.outputformat.impl.RTFOutputFormat; +import org.apache.freemarker.core.outputformat.impl.UndefinedOutputFormat; +import org.apache.freemarker.core.outputformat.impl.XMLOutputFormat; +import org.apache.freemarker.core.templateresolver.ConditionalTemplateConfigurationFactory; +import org.apache.freemarker.core.templateresolver.FileNameGlobMatcher; +import org.apache.freemarker.core.templateresolver.OrMatcher; +import org.apache.freemarker.core.templateresolver.impl.NullCacheStorage; +import org.apache.freemarker.core.userpkg.CustomHTMLOutputFormat; +import org.apache.freemarker.core.userpkg.DummyOutputFormat; +import org.apache.freemarker.core.userpkg.SeldomEscapedOutputFormat; +import org.apache.freemarker.test.TemplateTest; +import org.apache.freemarker.test.TestConfigurationBuilder; +import org.junit.Before; +import org.junit.Test; + +import com.google.common.collect.ImmutableList; + +public class OutputFormatTest extends TemplateTest { + + @Test + public void testOutputFormatSettingLayers() throws Exception { + addTemplate("t", "${.outputFormat}"); + addTemplate("t.xml", "${.outputFormat}"); + addTemplate("tWithHeader", "<#ftl outputFormat='HTML'>${.outputFormat}"); + + TestConfigurationBuilder cfgB = createDefaultConfigurationBuilder(); + for (OutputFormat cfgOutputFormat + : new OutputFormat[] { UndefinedOutputFormat.INSTANCE, RTFOutputFormat.INSTANCE } ) { + if (!cfgOutputFormat.equals(UndefinedOutputFormat.INSTANCE)) { + cfgB.setOutputFormat(cfgOutputFormat); + } + setConfiguration(cfgB.build()); + + assertEquals(cfgOutputFormat, getConfiguration().getOutputFormat()); + + { + Template t = getConfiguration().getTemplate("t"); + assertEquals(cfgOutputFormat, t.getOutputFormat()); + assertOutput(t, t.getOutputFormat().getName()); + } + + { + Template t = getConfiguration().getTemplate("t.xml"); + assertEquals(XMLOutputFormat.INSTANCE, t.getOutputFormat()); + assertOutput(t, t.getOutputFormat().getName()); + } + + { + Template t = getConfiguration().getTemplate("tWithHeader"); + assertEquals(HTMLOutputFormat.INSTANCE, t.getOutputFormat()); + assertOutput(t, t.getOutputFormat().getName()); + } + + getConfiguration().clearTemplateCache(); + } + } + + @Test + public void testStandardFileExtensions() throws Exception { + String commonContent = "${.outputFormat}"; + addTemplate("t", commonContent); + addTemplate("t.ftl", commonContent); + addTemplate("t.ftlh", commonContent); + addTemplate("t.FTLH", commonContent); + addTemplate("t.fTlH", commonContent); + addTemplate("t.ftlx", commonContent); + addTemplate("t.FTLX", commonContent); + addTemplate("t.fTlX", commonContent); + addTemplate("tWithHeader.ftlx", "<#ftl outputFormat='HTML'>" + commonContent); + + TestConfigurationBuilder cfgB = createDefaultConfigurationBuilder(); + for (int setupNumber = 1; setupNumber <= 3; setupNumber++) { + final OutputFormat cfgOutputFormat; + final OutputFormat ftlhOutputFormat; + final OutputFormat ftlxOutputFormat; + switch (setupNumber) { + case 1: + cfgOutputFormat = UndefinedOutputFormat.INSTANCE; + ftlhOutputFormat = HTMLOutputFormat.INSTANCE; + ftlxOutputFormat = XMLOutputFormat.INSTANCE; + break; + case 2: + cfgOutputFormat = RTFOutputFormat.INSTANCE; + cfgB.setOutputFormat(cfgOutputFormat); + ftlhOutputFormat = HTMLOutputFormat.INSTANCE; + ftlxOutputFormat = XMLOutputFormat.INSTANCE; + break; + case 3: + cfgOutputFormat = UndefinedOutputFormat.INSTANCE; + cfgB.unsetOutputFormat(); + TemplateConfiguration.Builder tcbXML = new TemplateConfiguration.Builder(); + tcbXML.setOutputFormat(XMLOutputFormat.INSTANCE); + cfgB.setTemplateConfigurations( + new ConditionalTemplateConfigurationFactory( + new OrMatcher( + new FileNameGlobMatcher("*.ftlh"), + new FileNameGlobMatcher("*.FTLH"), + new FileNameGlobMatcher("*.fTlH")), + tcbXML.build())); + ftlhOutputFormat = HTMLOutputFormat.INSTANCE; // can't be overidden + ftlxOutputFormat = XMLOutputFormat.INSTANCE; + break; + default: + throw new AssertionError(); + } + + setConfiguration(cfgB.build()); + assertEquals(cfgOutputFormat, getConfiguration().getOutputFormat()); + + { + Template t = getConfiguration().getTemplate("t"); + assertEquals(cfgOutputFormat, t.getOutputFormat()); + assertOutput(t, t.getOutputFormat().getName()); + } + + { + Template t = getConfiguration().getTemplate("t.ftl"); + assertEquals(cfgOutputFormat, t.getOutputFormat()); + assertOutput(t, t.getOutputFormat().getName()); + } + + for (String name : new String[] { "t.ftlh", "t.FTLH", "t.fTlH" }) { + Template t = getConfiguration().getTemplate(name); + assertEquals(ftlhOutputFormat, t.getOutputFormat()); + assertOutput(t, t.getOutputFormat().getName()); + } + + for (String name : new String[] { "t.ftlx", "t.FTLX", "t.fTlX" }) { + Template t = getConfiguration().getTemplate(name); + assertEquals(ftlxOutputFormat, t.getOutputFormat()); + assertOutput(t, t.getOutputFormat().getName()); + } + + { + Template t = getConfiguration().getTemplate("tWithHeader.ftlx"); + assertEquals(HTMLOutputFormat.INSTANCE, t.getOutputFormat()); + assertOutput(t, t.getOutputFormat().getName()); + } + + getConfiguration().clearTemplateCache(); + } + } + + @Test + public void testStandardFileExtensionsSettingOverriding() throws Exception { + addTemplate("t.ftlx", + "${\"'\"} ${\"'\"?esc} ${\"'\"?noEsc}"); + addTemplate("t.ftl", + "${'{}'} ${'{}'?esc} ${'{}'?noEsc}"); + + ConditionalTemplateConfigurationFactory tcfHTML = new ConditionalTemplateConfigurationFactory( + new FileNameGlobMatcher("t.*"), + new TemplateConfiguration.Builder() + .outputFormat(HTMLOutputFormat.INSTANCE) + .build()); + + ConditionalTemplateConfigurationFactory tcfNoAutoEsc = new ConditionalTemplateConfigurationFactory( + new FileNameGlobMatcher("t.*"), + new TemplateConfiguration.Builder() + .autoEscapingPolicy(DISABLE_AUTO_ESCAPING_POLICY) + .build()); + + { + TestConfigurationBuilder cfgB = createDefaultConfigurationBuilder(); + + setConfiguration(cfgB.outputFormat(HTMLOutputFormat.INSTANCE).build()); + assertOutputForNamed("t.ftlx", "' ' '"); // Can't override it + setConfiguration(cfgB.templateConfigurations(tcfHTML).build()); + assertOutputForNamed("t.ftlx", "' ' '"); // Can't override it + setConfiguration(cfgB.templateConfigurations(tcfNoAutoEsc).build()); + assertOutputForNamed("t.ftlx", "' ' '"); // Can't override it + } + + { + TestConfigurationBuilder cfgB = createDefaultConfigurationBuilder(); + + setConfiguration(cfgB.recognizeStandardFileExtensions(false).build()); + assertErrorContainsForNamed("t.ftlx", UndefinedOutputFormat.INSTANCE.getName()); + setConfiguration(cfgB.outputFormat(HTMLOutputFormat.INSTANCE).build()); + assertOutputForNamed("t.ftlx", "' ' '"); + setConfiguration(cfgB.outputFormat(XMLOutputFormat.INSTANCE).build()); + assertOutputForNamed("t.ftlx", "' ' '"); + setConfiguration(cfgB.templateConfigurations(tcfHTML).build()); + assertOutputForNamed("t.ftlx", "' ' '"); + setConfiguration(cfgB.templateConfigurations(tcfNoAutoEsc).build()); + assertOutputForNamed("t.ftlx", "' ' '"); + } + + { + TestConfigurationBuilder cfgB = createDefaultConfigurationBuilder(); + cfgB.setRecognizeStandardFileExtensions(true); + + setConfiguration(cfgB.templateConfigurations(tcfHTML).build()); + assertOutputForNamed("t.ftlx", "' ' '"); // Can't override it + setConfiguration(cfgB.templateConfigurations(tcfNoAutoEsc).build()); + assertOutputForNamed("t.ftlx", "' ' '"); // Can't override it + } + + { + TestConfigurationBuilder cfgB = createDefaultConfigurationBuilder(); + + setConfiguration(cfgB.templateConfigurations(tcfHTML).build()); + assertOutputForNamed("t.ftlx", "' ' '"); // Can't override it + setConfiguration(cfgB.recognizeStandardFileExtensions(false).build()); + assertOutputForNamed("t.ftlx", "' ' '"); + } + } + + @Test + public void testStandardFileExtensionsWithConstructor() throws Exception { + Configuration cfg = getConfiguration(); + String commonFTL = "${'\\''}"; + { + Template t = new Template("foo.ftl", commonFTL, cfg); + assertSame(UndefinedOutputFormat.INSTANCE, t.getOutputFormat()); + StringWriter out = new StringWriter(); + t.process(null, out); + assertEquals("'", out.toString()); + } + { + Template t = new Template("foo.ftlx", commonFTL, cfg); + assertSame(XMLOutputFormat.INSTANCE, t.getOutputFormat()); + StringWriter out = new StringWriter(); + t.process(null, out); + assertEquals("'", out.toString()); + } + { + Template t = new Template("foo.ftlh", commonFTL, cfg); + assertSame(HTMLOutputFormat.INSTANCE, t.getOutputFormat()); + StringWriter out = new StringWriter(); + t.process(null, out); + assertEquals("'", out.toString()); + } + } + + @Test + public void testStandardFileExtensionsFormatterImplOverriding() throws Exception { + addTemplate("t.ftlh", "${'a&x'}"); + + assertOutputForNamed("t.ftlh", "a&x"); + + setConfiguration(new TestConfigurationBuilder() + .registeredCustomOutputFormats(Collections.<OutputFormat>singleton(CustomHTMLOutputFormat.INSTANCE)) + .build()); + assertOutputForNamed("t.ftlh", "a&X"); + + setConfiguration(new TestConfigurationBuilder() + .registeredCustomOutputFormats(Collections.<OutputFormat>emptyList()) + .build()); + assertOutputForNamed("t.ftlh", "a&x"); + } + + @Test + public void testAutoEscapingSettingLayers() throws Exception { + addTemplate("t", "${'a&b'}"); + addTemplate("tWithHeaderFalse", "<#ftl autoEsc=false>${'a&b'}"); + addTemplate("tWithHeaderTrue", "<#ftl autoEsc=true>${'a&b'}"); + + TestConfigurationBuilder cfgB = createDefaultConfigurationBuilder().outputFormat(XMLOutputFormat.INSTANCE); + assertEquals(ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY, cfgB.getAutoEscapingPolicy()); + + for (boolean cfgAutoEscaping : new boolean[] { true, false }) { + if (!cfgAutoEscaping) { + cfgB.setAutoEscapingPolicy(DISABLE_AUTO_ESCAPING_POLICY); + } + setConfiguration(cfgB.build()); + + { + Template t = getConfiguration().getTemplate("t"); + if (cfgAutoEscaping) { + assertEquals(ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY, t.getAutoEscapingPolicy()); + assertOutput(t, "a&b"); + } else { + assertEquals(DISABLE_AUTO_ESCAPING_POLICY, t.getAutoEscapingPolicy()); + assertOutput(t, "a&b"); + } + } + + { + Template t = getConfiguration().getTemplate("tWithHeaderFalse"); + assertEquals(DISABLE_AUTO_ESCAPING_POLICY, t.getAutoEscapingPolicy()); + assertOutput(t, "a&b"); + } + + { + Template t = getConfiguration().getTemplate("tWithHeaderTrue"); + assertEquals(ENABLE_IF_SUPPORTED_AUTO_ESCAPING_POLICY, t.getAutoEscapingPolicy()); + assertOutput(t, "a&b"); + } + + getConfiguration().clearTemplateCache(); + } + } + + @Test + public void testNumericalInterpolation() throws IOException, TemplateException { + setConfiguration(new TestConfigurationBuilder() + .registeredCustomOutputFormats(Collections.<OutputFormat>singleton(DummyOutputFormat.INSTANCE)) + .build()); + assertOutput( + "<#ftl outputFormat='dummy'>#{1.5}; #{1.5; m3}; ${'a.b'}", + "1\\.5; 1\\.500; a\\.b"); + assertOutput( + "<#ftl outputFormat='dummy' autoEsc=false>#{1.5}; #{1.5; m3}; ${'a.b'}; ${'a.b'?esc}", + "1.5; 1.500; a.b; a\\.b"); + assertOutput("<#ftl outputFormat='plainText'>#{1.5}", "1.5"); + assertOutput("<#ftl outputFormat='HTML'>#{1.5}", "1.5"); + assertOutput("#{1.5}", "1.5"); + } + + @Test + public void testUndefinedOutputFormat() throws IOException, TemplateException { + assertOutput("${'a < b'}; ${htmlPlain}; ${htmlMarkup}", "a < b; a < {h'}; <p>c"); + assertErrorContains("${'x'?esc}", "undefined", "escaping", "?esc"); + assertErrorContains("${'x'?noEsc}", "undefined", "escaping", "?noEsc"); + } + + @Test + public void testPlainTextOutputFormat() throws IOException, TemplateException { + assertOutput("<#ftl outputFormat='plainText'>${'a < b'}; ${htmlPlain}", "a < b; a < {h'}"); + assertErrorContains("<#ftl outputFormat='plainText'>${htmlMarkup}", "plainText", "HTML", "conversion"); + assertErrorContains("<#ftl outputFormat='plainText'>${'x'?esc}", "plainText", "escaping", "?esc"); + assertErrorContains("<#ftl outputFormat='plainText'>${'x'?noEsc}", "plainText", "escaping", "?noEsc"); + } + + @Test + public void testAutoEscapingOnMOs() throws IOException, TemplateException { + for (boolean cfgAutoEscaping : new boolean[] { true, false }) { + String commonAutoEscFtl = "<#ftl outputFormat='HTML'>${'&'}"; + if (cfgAutoEscaping) { + // Cfg default is autoEscaping true + assertOutput(commonAutoEscFtl, "&"); + } else { + setConfiguration(createDefaultConfigurationBuilder() + .autoEscapingPolicy(DISABLE_AUTO_ESCAPING_POLICY) + .build()); + assertOutput(commonAutoEscFtl, "&"); + } + + assertOutput( + "<#ftl outputFormat='RTF'>" + + "${rtfPlain} ${rtfMarkup} " + + "${htmlPlain} " + + "${xmlPlain}", + "\\\\par a & b \\par c " + + "a < \\{h'\\} " + + "a < \\{x'\\}"); + assertOutput( + "<#ftl outputFormat='HTML'>" + + "${htmlPlain} ${htmlMarkup} " + + "${xmlPlain} " + + "${rtfPlain}", + "a < {h'} <p>c " + + "a < {x'} " + + "\\par a & b"); + assertOutput( + "<#ftl outputFormat='XML'>" + + "${xmlPlain} ${xmlMarkup} " + + "${htmlPlain} " + + "${rtfPlain}", + "a < {x'} <p>c</p> " + + "a < {h'} " + + "\\par a & b"); + assertErrorContains("<#ftl outputFormat='RTF'>${htmlMarkup}", "output format", "RTF", "HTML"); + assertErrorContains("<#ftl outputFormat='RTF'>${xmlMarkup}", "output format", "RTF", "XML"); + assertErrorContains("<#ftl outputFormat='HTML'>${rtfMarkup}", "output format", "HTML", "RTF"); + assertErrorContains("<#ftl outputFormat='HTML'>${xmlMarkup}", "output format", "HTML", "XML"); + assertErrorContains("<#ftl outputFormat='XML'>${rtfMarkup}", "output format", "XML", "RTF"); + assertErrorContains("<#ftl outputFormat='XML'>${htmlMarkup}", "output format", "XML", "HTML"); + + for (int hasHeader = 0; hasHeader < 2; hasHeader++) { + assertOutput( + (hasHeader == 1 ? "<#ftl outputFormat='undefined'>" : "") + + "${xmlPlain} ${xmlMarkup} " + + "${htmlPlain} ${htmlMarkup} " + + "${rtfPlain} ${rtfMarkup}", + "a < {x'} <p>c</p> " + + "a < {h'} <p>c " + + "\\\\par a & b \\par c"); + } + } + } + + @Test + public void testStringLiteralsUseUndefinedOF() throws IOException, TemplateException { + String expectedOut = "& (&) &"; + String ftl = "<#ftl outputFormat='XML'>${'&'} ${\"(${'&'})\"?noEsc} ${'&'}"; + + assertOutput(ftl, expectedOut); + + addTemplate("t.xml", ftl); + assertOutputForNamed("t.xml", expectedOut); + } + + @Test + public void testUnparsedTemplate() throws IOException, TemplateException { + String content = "<#ftl>a<#foo>b${x}"; + { + Template t = Template.createPlainTextTemplate("x", content, getConfiguration()); + Writer sw = new StringWriter(); + t.process(null, sw); + assertEquals(content, sw.toString()); + assertEquals(UndefinedOutputFormat.INSTANCE, t.getOutputFormat()); + } + + { + setConfiguration(new TestConfigurationBuilder().outputFormat(HTMLOutputFormat.INSTANCE).build()); + Template t = Template.createPlainTextTemplate("x", content, getConfiguration()); + Writer sw = new StringWriter(); + t.process(null, sw); + assertEquals(content, sw.toString()); + assertEquals(HTMLOutputFormat.INSTANCE, t.getOutputFormat()); + } + } + + @Test + public void testStringLiteralInterpolation() throws IOException, TemplateException { + Template t = new Template(null, "<#ftl outputFormat='XML'>${'&'} ${\"(${'&'})\"?noEsc}", getConfiguration()); + assertEquals(XMLOutputFormat.INSTANCE, t.getOutputFormat()); + + assertOutput("${.outputFormat} ${'${.outputFormat}'} ${.outputFormat}", + "undefined undefined undefined"); + assertOutput("<#ftl outputFormat='HTML'>${.outputFormat} ${'${.outputFormat}'} ${.outputFormat}", + "HTML HTML HTML"); + assertOutput("${.outputFormat} <#outputFormat 'XML'>${'${.outputFormat}'}</#outputFormat> ${.outputFormat}", + "undefined XML undefined"); + assertOutput("${'foo ${xmlPlain}'}", "foo a < {x'}"); + assertOutput("${'${xmlMarkup}'}", "<p>c</p>"); + assertErrorContains("${'${\"x\"?esc}'}", "?esc", "undefined"); + assertOutput("<#ftl outputFormat='XML'>${'${xmlMarkup?esc} ${\"<\"?esc} ${\">\"} ${\"&\"?noEsc}'}", + "<p>c</p> < > &"); + } + + @Test + public void testStringBIsFail() { + assertErrorContains("<#ftl outputFormat='HTML'>${'<b>foo</b>'?esc?upperCase}", "string", "markup_output"); + } + + @Test + public void testConcatWithMOs() throws IOException, TemplateException { + assertOutput( + "${'\\'' + htmlMarkup} ${htmlMarkup + '\\''} ${htmlMarkup + htmlMarkup}", + "'<p>c <p>c' <p>c<p>c"); + assertOutput( + "${'\\'' + htmlPlain} ${htmlPlain + '\\''} ${htmlPlain + htmlPlain}", + "'a < {h'} a < {h'}' a < {h'}a < {h'}"); + assertErrorContains( + "<#ftl outputFormat='XML'>${'\\'' + htmlMarkup}", + "HTML", "XML", "conversion"); + assertErrorContains( + "${xmlMarkup + htmlMarkup}", + "HTML", "XML", "Conversion", "common"); + assertOutput( + "${xmlMarkup + htmlPlain}", + "<p>c</p>a < {h'}"); + assertOutput( + "${xmlPlain + htmlMarkup}", + "a < {x'}<p>c"); + assertOutput( + "${xmlPlain + htmlPlain}", + "a < {x'}a < {h'}"); + assertOutput( + "${xmlPlain + htmlPlain + '\\''}", + "a < {x'}a < {h'}'"); + assertOutput( + "${htmlPlain + xmlPlain + '\\''}", + "a < {h'}a < {x'}'"); + assertOutput( + "${xmlPlain + htmlPlain + '\\''}", + "a < {x'}a < {h'}'"); + assertOutput( + "<#ftl outputFormat='XML'>${htmlPlain + xmlPlain + '\\''}", + "a < {h'}a < {x'}'"); + assertOutput( + "<#ftl outputFormat='RTF'>${htmlPlain + xmlPlain + '\\''}", + "a < \\{h'\\}a < \\{x'\\}'"); + assertOutput( + "<#ftl outputFormat='XML'>${'\\'' + htmlPlain}", + "'a < {h'}"); + assertOutput( + "<#ftl outputFormat='HTML'>${'\\'' + htmlPlain}", + "'a < {h'}"); + assertOutput( + "<#ftl outputFormat='HTML'>${'\\'' + xmlPlain}", + "'a < {x'}"); + assertOutput( + "<#ftl outputFormat='RTF'>${'\\'' + xmlPlain}", + "'a < \\{x'\\}"); + + assertOutput( + "<#assign x = '\\''><#assign x += xmlMarkup>${x}", + "'<p>c</p>"); + assertOutput( + "<#assign x = xmlMarkup><#assign x += '\\''>${x}", + "<p>c</p>'"); + assertOutput( + "<#assign x = xmlMarkup><#assign x += htmlPlain>${x}", + "<p>c</p>a < {h'}"); + assertErrorContains( + "<#assign x = xmlMarkup><#assign x += htmlMarkup>${x}", + "HTML", "XML", "Conversion", "common"); + } + + @Test + public void testBlockAssignment() throws Exception { + for (String d : new String[] { "assign", "global", "local" }) { + String commonFTL = + "<#macro m>" + + "<#" + d + " x><p>${'&'}</#" + d + ">${x?isString?c} ${x} ${'&'} " + + "<#" + d + " x></#" + d + ">${x?isString?c}" + + "</#macro><@m />"; + assertOutput( + commonFTL, + "true <p>& & true"); + assertOutput( + "<#ftl outputFormat='HTML'>" + commonFTL, + "false <p>& & false"); + } + } + + @Test + public void testSpecialVariables() throws Exception { + String commonFTL = "${.outputFormat} ${.autoEsc?c}"; + + addTemplate("t.ftlx", commonFTL); + assertOutputForNamed("t.ftlx", "XML true"); + + addTemplate("t.ftlh", commonFTL); + assertOutputForNamed("t.ftlh", "HTML true"); + + addTemplate("t.ftl", commonFTL); + assertOutputForNamed("t.ftl", "undefined false"); + + addTemplate("tX.ftl", "<#ftl outputFormat='XML'>" + commonFTL); + addTemplate("tX.ftlx", commonFTL); + assertOutputForNamed("t.ftlx", "XML true"); + + addTemplate("tN.ftl", "<#ftl outputFormat='RTF' autoEsc=false>" + commonFTL); + assertOutputForNamed("tN.ftl", "RTF false"); + + assertOutput("${.output_format} ${.auto_esc?c}", "undefined false"); + } + + @Test + public void testEscAndNoEscBIBasics() throws IOException, TemplateException { + String commonFTL = "${'<x>'} ${'<x>'?esc} ${'<x>'?noEsc}"; + addTemplate("t.ftlh", commonFTL); + addTemplate("t-noAuto.ftlh", "<#ftl autoEsc=false>" + commonFTL); + addTemplate("t.ftl", commonFTL); + assertOutputForNamed("t.ftlh", "<x> <x> <x>"); + assertOutputForNamed("t-noAuto.ftlh", "<x> <x> <x>"); + assertErrorContainsForNamed("t.ftl", "output format", "undefined"); + } + + @Test + public void testEscAndNoEscBIsOnMOs() throws IOException, TemplateException { + String xmlHdr = "<#ftl outputFormat='XML'>"; + + assertOutput( + xmlHdr + "${'&'?esc?esc} ${'&'?esc?noEsc} ${'&'?noEsc?esc} ${'&'?noEsc?noEsc}", + "& & & &"); + + for (String bi : new String[] { "esc", "noEsc" } ) { + assertOutput( + xmlHdr + "${rtfPlain?" + bi + "}", + "\\par a & b"); + assertOutput( + xmlHdr + "<#setting numberFormat='0.0'>${1?" + bi + "}", + "1.0"); + assertOutput( + xmlHdr + "<#setting booleanFormat='&y,&n'>${true?" + bi + "}", + bi.equals("esc") ? "&y" : "&y"); + assertErrorContains( + xmlHdr + "${rtfMarkup?" + bi + "}", + "?" + bi, "output format", "RTF", "XML"); + assertErrorContains( + xmlHdr + "${noSuchVar?" + bi + "}", + "noSuchVar", "null or missing"); + assertErrorContains( + xmlHdr + "${[]?" + bi + "}", + "?" + bi, "xpected", "string", "sequence"); + } + } + + @Test + public void testMarkupStringBI() throws Exception { + assertOutput( + "${htmlPlain?markupString} ${htmlMarkup?markupString}", + "a < {h'} <p>c"); + assertErrorContains( + "${noSuchVar?markupString}", + "noSuchVar", "null or missing"); + assertErrorContains( + "${'x'?markupString}", + "xpected", "markup output", "string"); + } + + @Test + public void testOutputFormatDirective() throws Exception { + assertOutput( + "${.outputFormat}${'\\''} " + + "<#outputFormat 'HTML'>" + + "${.outputFormat}${'\\''} " + + "<#outputFormat 'XML'>${.outputFormat}${'\\''}</#outputFormat> " + + "${.outputFormat}${'\\''} " + + "</#outputFormat>" + + "${.outputFormat}${'\\''}", + "undefined' HTML' XML' HTML' undefined'"); + assertOutput( + "<#ftl output_format='XML'>" + + "${.output_format}${'\\''} " + + "<#outputformat 'HTML'>${.output_format}${'\\''}</#outputformat> " + + "${.output_format}${'\\''}", + "XML' HTML' XML'"); + + // Custom format: + assertErrorContains( + "<#outputFormat 'dummy'></#outputFormat>", + "dummy", "nregistered"); + setConfiguration(new TestConfigurationBuilder() + .registeredCustomOutputFormats(Collections.<OutputFormat>singleton(DummyOutputFormat.INSTANCE)) + .build()); + assertOutput( + "<#outputFormat 'dummy'>${.outputFormat}</#outputFormat>", + "dummy"); + + // Parse-time param expression: + assertOutput( + "<#outputFormat 'plain' + 'Text'>${.outputFormat}</#outputFormat>", + "plainText"); + assertErrorContains( + "<#outputFormat 'plain' + someVar + 'Text'></#outputFormat>", + "someVar", "parse-time"); + assertErrorContains( + "<#outputFormat 'plainText'?upperCase></#outputFormat>", + "?upperCase", "parse-time"); + assertErrorContains( + "<#outputFormat true></#outputFormat>", + "string", "boolean"); + + // Naming convention: + assertErrorContains( + "<#outputFormat 'HTML'></#outputformat>", + "convention", "#outputFormat", "#outputformat"); + assertErrorContains( + "<#outputformat 'HTML'></#outputFormat>", + "convention", "#outputFormat", "#outputformat"); + + // Empty block: + assertOutput( + "${.output_format} " + + "<#outputformat 'HTML'></#outputformat>" + + "${.output_format}", + "undefined undefined"); + + // WS stripping: + assertOutput( + "${.output_format}\n" + + "<#outputformat 'HTML'>\n" + + " x\n" + + "</#outputformat>\n" + + "${.output_format}", + "undefined\n x\nundefined"); + } + + @Test + public void testAutoEscAndNoAutoEscDirectives() throws Exception { + assertOutput( + "<#ftl outputFormat='XML'>" + + "${.autoEsc?c}${'&'} " + + "<#noAutoEsc>" + + "${.autoEsc?c}${'&'} " + + "<#autoEsc>${.autoEsc?c}${'&'}</#autoEsc> " + + "${.autoEsc?c}${'&'} " + + "</#noAutoEsc>" + + "${.autoEsc?c}${'&'}", + "true& false& true& false& true&"); + assertOutput( + "<#ftl auto_esc=false output_format='XML'>" + + "${.auto_esc?c}${'&'} " + + "<#autoesc>${.auto_esc?c}${'&'}</#autoesc> " + + "${.auto_esc?c}${'&'}", + "false& true& false&"); + + // Bad came case: + assertErrorContains( + "<#noAutoesc></#noAutoesc>", + "Unknown directive"); + assertErrorContains( + "<#noautoEsc></#noautoEsc>", + "Unknown directive"); + + setConfiguration(new TestConfigurationBuilder() + .outputFormat(XMLOutputFormat.INSTANCE) + .build()); + + // Empty block: + assertOutput( + "${.auto_esc?c} " + + "<#noautoesc></#noautoesc>" + + "${.auto_esc?c}", + "true true"); + + // WS stripping: + assertOutput( + "${.auto_esc?c}\n" + + "<#noautoesc>\n" + + " x\n" + + "</#noautoesc>\n" + + "${.auto_esc?c}", + "true\n x\ntrue"); + + + // Naming convention: + assertErrorContains( + "<#autoEsc></#autoesc>", + "convention", "#autoEsc", "#autoesc"); + assertErrorContains( + "<#autoesc></#autoEsc>", + "convention", "#autoEsc", "#autoesc"); + assertErrorContains( + "<#noAutoEsc></#noautoesc>", + "convention", "#noAutoEsc", "#noautoesc"); + assertErrorContains( + "<#noautoesc></#noAutoEsc>", + "convention", "#noAutoEsc", "#noautoesc"); + } + + @Test + public void testExplicitAutoEscBannedForNonMarkup() throws Exception { + // While this restriction is technically unnecessary, we can catch a dangerous and probably common user + // misunderstanding. + assertErrorContains("<#ftl autoEsc=true>", "can't do escaping", "undefined"); + assertErrorContains("<#ftl outputFormat='plainText' autoEsc=true>", "can't do escaping", "plainText"); + assertErrorContains("<#ftl autoEsc=true outputFormat='plainText'>", "can't do escaping", "plainText"); + assertOutput("<#ftl autoEsc=true outputFormat='HTML'>", ""); + assertOutput("<#ftl outputFormat='HTML' autoEsc=true>", ""); + assertOutput("<#ftl autoEsc=false>", ""); + + assertErrorContains("<#autoEsc></#autoEsc>", "can't do escaping", "undefined"); + assertErrorContains("<#ftl outputFormat='plainText'><#autoEsc></#autoEsc>", "can't do escaping", "plainText"); + assertOutput("<#ftl outputFormat='plainText'><#outputFormat 'XML'><#autoEsc></#autoEsc></#outputFormat>", ""); + assertOutput("<#ftl outputFormat='HTML'><#autoEsc></#autoEsc>", ""); + assertOutput("<#noAutoEsc></#noAutoEsc>", ""); + } + + @Test + public void testAutoEscPolicy() throws Exception { + TestConfigurationBuilder cfgB = createDefaultConfigurationBuilder(); + cfgB.setRegisteredCustomOutputFormats(ImmutableList.<OutputFormat>of( + SeldomEscapedOutputFormat.INSTANCE, DummyOutputFormat.INSTANCE)); + assertEquals(ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY, cfgB.getAutoEscapingPolicy()); + + String commonFTL = "${'.'} ${.autoEsc?c}"; + String notEsced = ". false"; + String esced = "\\. true"; + + for (int autoEscPolicy : new int[] { + ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY, + ENABLE_IF_SUPPORTED_AUTO_ESCAPING_POLICY, + DISABLE_AUTO_ESCAPING_POLICY }) { + cfgB.setAutoEscapingPolicy(autoEscPolicy); + + String sExpted = autoEscPolicy == ENABLE_IF_SUPPORTED_AUTO_ESCAPING_POLICY ? esced : notEsced; + cfgB.setOutputFormat(SeldomEscapedOutputFormat.INSTANCE); + setConfiguration(cfgB.build()); + assertOutput(commonFTL, sExpted); + cfgB.setOutputFormat(UndefinedOutputFormat.INSTANCE); + setConfiguration(cfgB.build()); + assertOutput("<#ftl outputFormat='seldomEscaped'>" + commonFTL, sExpted); + assertOutput("<#outputFormat 'seldomEscaped'>" + commonFTL + "</#outputFormat>", sExpted); + + String dExpted = autoEscPolicy == DISABLE_AUTO_ESCAPING_POLICY ? notEsced : esced; + cfgB.setOutputFormat(DummyOutputFormat.INSTANCE); + setConfiguration(cfgB.build()); + assertOutput(commonFTL, dExpted); + cfgB.setOutputFormat(UndefinedOutputFormat.INSTANCE); + setConfiguration(cfgB.build()); + assertOutput("<#ftl outputFormat='dummy'>" + commonFTL, dExpted); + assertOutput("<#outputFormat 'dummy'>" + commonFTL + "</#outputFormat>", dExpted); + + cfgB.setOutputFormat(DummyOutputFormat.INSTANCE); + setConfiguration(cfgB.build()); + assertOutput( + commonFTL + + "<#outputFormat 'seldomEscaped'>" + + commonFTL + + "<#outputFormat 'dummy'>" + + commonFTL + + "</#outputFormat>" + + commonFTL + + "<#outputFormat 'plainText'>" + + commonFTL + + "</#outputFormat>" + + commonFTL + + "<#noAutoEsc>" + + commonFTL + + "</#noAutoEsc>" + + commonFTL + + "<#autoEsc>" + + commonFTL + + "</#autoEsc>" + + commonFTL + + "</#outputFormat>" + + commonFTL + + "<#noAutoEsc>" + + commonFTL + + "</#noAutoEsc>" + + commonFTL + + "<#autoEsc>" + + commonFTL + + "</#autoEsc>" + + commonFTL + , + dExpted + + sExpted + + dExpted + + sExpted + + notEsced + + sExpted + + notEsced + + sExpted + + esced + + sExpted + + dExpted + + notEsced + + dExpted + + esced + + dExpted); + } + } + + @Test + public void testDynamicParsingBIsInherticContextOutputFormat() throws Exception { + // Dynamic parser BI-s are supposed to use the ParsingConfiguration of the calling template, and ignore anything + // inside the calling template itself. Except, the outputFormat and autoEscapingPolicy has to come from the + // calling lexical context. + + String commonFTL + = "Eval: ${'.outputFormat'?eval}; " + + "Interpret: <#assign ipd = r\"${.outputFormat} ${'{&}'}\"?interpret><@ipd/>"; + addTemplate("t.ftlh", commonFTL); + addTemplate("t2.ftlh", "<#outputFormat 'RTF'>" + commonFTL + "</#outputFormat>"); + + assertOutputForNamed( + "t.ftlh", + "Eval: HTML; Interpret: HTML {&}"); + assertOutputForNamed( + "t2.ftlh", + "Eval: RTF; Interpret: RTF \\{&\\}"); + assertOutput( + commonFTL, + "Eval: undefined; Interpret: undefined {&}"); + assertOutput( + "<#ftl outputFormat='RTF'>" + commonFTL + "\n" + + "<#outputFormat 'XML'>" + commonFTL + "</#outputFormat>", + "Eval: RTF; Interpret: RTF \\{&\\}\n" + + "Eval: XML; Interpret: XML {&}"); + + // parser.autoEscapingPolicy is inherited too: + assertOutput( + "<#ftl autoEsc=false outputFormat='XML'>" + + commonFTL + " ${'.autoEsc'?eval?c}", + "Eval: XML; Interpret: XML {&} false"); + assertOutput( + "<#ftl outputFormat='XML'>" + + "<#noAutoEsc>" + commonFTL + " ${'.autoEsc'?eval?c}</#noAutoEsc>", + "Eval: XML; Interpret: XML {&} false"); + assertOutput( + "<#ftl autoEsc=false outputFormat='XML'>" + + "<#noAutoEsc>" + commonFTL + " ${'.autoEsc'?eval?c}</#noAutoEsc>", + "Eval: XML; Interpret: XML {&} false"); + assertOutput( + "<#ftl autoEsc=false outputFormat='XML'>" + + "<#autoEsc>" + commonFTL + " ${'.autoEsc'?eval?c}</#autoEsc>", + "Eval: XML; Interpret: XML {&} true"); + assertOutput( + "${.outputFormat}<#assign ftl='<#ftl outputFormat=\\'RTF\\'>$\\{.outputFormat}'> <@ftl?interpret/>", + "undefined RTF"); + assertOutput( + "${.outputFormat}<#outputFormat 'RTF'>" + + "<#assign ftl='$\\{.outputFormat}'> <@ftl?interpret/> ${'.outputFormat'?eval}" + + "</#outputFormat>", + "undefined RTF RTF"); + } + + @Test + public void testBannedBIsWhenAutoEscaping() throws Exception { + for (String biName : new String[] { "html", "xhtml", "rtf", "xml" }) { + String commonFTL = "${'x'?" + biName + "}"; + assertOutput(commonFTL, "x"); + assertErrorContains("<#ftl outputFormat='HTML'>" + commonFTL, + "?" + biName, "HTML", "double-escaping"); + assertErrorContains("<#ftl outputFormat='HTML'>${'${\"x\"?" + biName + "}'}", + "?" + biName, "HTML", "double-escaping"); + assertOutput("<#ftl outputFormat='plainText'>" + commonFTL, "x"); + assertOutput("<#ftl outputFormat='HTML' autoEsc=false>" + commonFTL, "x"); + assertOutput("<#ftl outputFormat='HTML'><#noAutoEsc>" + commonFTL + "</#noAutoEsc>", "x"); + assertOutput("<#ftl outputFormat='HTML'><#outputFormat 'plainText'>" + commonFTL + "</#outputFormat>", + "x"); + } + } + + @Test + public void testLegacyEscaperBIsBypassMOs() throws Exception { + assertOutput("${htmlPlain?html} ${htmlMarkup?html}", "a < {h'} <p>c"); + assertErrorContains("${xmlPlain?html}", "?html", "string", "markup_output", "XML"); + assertErrorContains("${xmlMarkup?html}", "?html", "string", "markup_output", "XML"); + assertErrorContains("${rtfPlain?html}", "?html", "string", "markup_output", "RTF"); + assertErrorContains("${rtfMarkup?html}", "?html", "string", "markup_output", "RTF"); + + assertOutput("${htmlPlain?xhtml} ${htmlMarkup?xhtml}", "a < {h'} <p>c"); + assertErrorContains("${xmlPlain?xhtml}", "?xhtml", "string", "markup_output", "XML"); + assertErrorContains("${xmlMarkup?xhtml}", "?xhtml", "string", "markup_output", "XML"); + assertErrorContains("${rtfPlain?xhtml}", "?xhtml", "string", "markup_output", "RTF"); + assertErrorContains("${rtfMarkup?xhtml}", "?xhtml", "string", "markup_output", "RTF"); + + assertOutput("${xmlPlain?xml} ${xmlMarkup?xml}", "a < {x'} <p>c</p>"); + assertOutput("${htmlPlain?xml} ${htmlMarkup?xml}", "a < {h'} <p>c"); + assertErrorContains("${rtfPlain?xml}", "?xml", "string", "markup_output", "RTF"); + assertErrorContains("${rtfMarkup?xml}", "?xml", "string", "markup_output", "RTF"); + + assertOutput("${rtfPlain?rtf} ${rtfMarkup?rtf}", "\\\\par a & b \\par c"); + assertErrorContains("${xmlPlain?rtf}", "?rtf", "string", "markup_output", "XML"); + assertErrorContains("${xmlMarkup?rtf}", "?rtf", "string", "markup_output", "XML"); + assertErrorContains("${htmlPlain?rtf}", "?rtf", "string", "markup_output", "HTML"); + assertErrorContains("${htmlMarkup?rtf}", "?rtf", "string", "markup_output", "HTML"); + } + + @Test + public void testBannedDirectivesWhenAutoEscaping() throws Exception { + String commonFTL = "<#escape x as x?html>x</#escape>"; + assertOutput(commonFTL, "x"); + assertErrorContains("<#ftl outputFormat='HTML'>" + commonFTL, "escape", "HTML", "double-escaping"); + assertOutput("<#ftl outputFormat='plainText'>" + commonFTL, "x"); + assertOutput("<#ftl outputFormat='HTML' autoEsc=false>" + commonFTL, "x"); + assertOutput("<#ftl outputFormat='HTML'><#noAutoEsc>" + commonFTL + "</#noAutoEsc>", "x"); + assertOutput("<#ftl outputFormat='HTML'><#outputFormat 'plainText'>" + commonFTL + "</#outputFormat>", "x"); + } + + @Test + public void testCombinedOutputFormats() throws Exception { + assertOutput( + "<#outputFormat 'XML{HTML}'>${'\\''}</#outputFormat>", + "&#39;"); + assertOutput( + "<#outputFormat 'HTML{RTF{XML}}'>${'<a=\\'{}\\' />'}</#outputFormat>", + "&lt;a=&apos;\\{\\}&apos; /&gt;"); + + String commonFtl = "${'\\''} <#outputFormat '{HTML}'>${'\\''}</#outputFormat>"; + String commonOutput = "' &#39;"; + assertOutput( + "<#outputFormat 'XML'>" + commonFtl + "</#outputFormat>", + commonOutput); + assertOutput( + "<#ftl outputFormat='XML'>" + commonFtl, + commonOutput); + addTemplate("t.ftlx", commonFtl); + assertOutputForNamed( + "t.ftlx", + commonOutput); + + assertErrorContains( + commonFtl, + ParseException.class, "{...}", "markup", UndefinedOutputFormat.INSTANCE.getName()); + assertErrorContains( + "<#ftl outputFormat='plainText'>" + commonFtl, + ParseException.class, "{...}", "markup", PlainTextOutputFormat.INSTANCE.getName()); + assertErrorContains( + "<#ftl outputFormat='RTF'><#outputFormat '{plainText}'></#outputFormat>", + ParseException.class, "{...}", "markup", PlainTextOutputFormat.INSTANCE.getName()); + assertErrorContains( + "<#ftl outputFormat='RTF'><#outputFormat '{noSuchFormat}'></#outputFormat>", + ParseException.class, "noSuchFormat", "registered"); + assertErrorContains( + "<#outputFormat 'noSuchFormat{HTML}'></#outputFormat>", + ParseException.class, "noSuchFormat", "registered"); + assertErrorContains( + "<#outputFormat 'HTML{noSuchFormat}'></#outputFormat>", + ParseException.class, "noSuchFormat", "registered"); + } + + @Test + public void testHasContentBI() throws Exception { + assertOutput("${htmlMarkup?hasContent?c} ${htmlPlain?hasContent?c}", "true true"); + assertOutput("<#ftl outputFormat='HTML'>${''?esc?hasContent?c} ${''?noEsc?hasContent?c}", "false false"); + } + + @Test + public void testMissingVariables() throws Exception { + for (String ftl : new String[] { + "${noSuchVar}", + "<#ftl outputFormat='XML'>${noSuchVar}", + "<#ftl outputFormat='XML'>${noSuchVar?esc}", + "<#ftl outputFormat='XML'>${'x'?esc + noSuchVar}" + }) { + assertErrorContains(ftl, InvalidReferenceException.class, "noSuchVar", "null or missing"); + } + } + + @Test + public void testIsMarkupOutputBI() throws Exception { + addToDataModel("m1", HTMLOutputFormat.INSTANCE.fromPlainTextByEscaping("x")); + addToDataModel("m2", HTMLOutputFormat.INSTANCE.fromMarkup("x")); + addToDataModel("s", "x"); + assertOutput("${m1?isMarkupOutput?c} ${m2?isMarkupOutput?c} ${s?isMarkupOutput?c}", "true true false"); + assertOutput("${m1?is_markup_output?c}", "true"); + } + + private TestConfigurationBuilder createDefaultConfigurationBuilder() throws TemplateModelException { + return new TestConfigurationBuilder() + .templateConfigurations( + new ConditionalTemplateConfigurationFactory( + new FileNameGlobMatcher("*.xml"), + new TemplateConfiguration.Builder() + .outputFormat(XMLOutputFormat.INSTANCE) + .build())) + .cacheStorage(NullCacheStorage.INSTANCE); // Prevent caching as we change the cfgB between build(). + } + + @Before + public void addCommonDataModelVariables() throws TemplateModelException { + addToDataModel("rtfPlain", RTFOutputFormat.INSTANCE.fromPlainTextByEscaping("\\par a & b")); + addToDataModel("rtfMarkup", RTFOutputFormat.INSTANCE.fromMarkup("\\par c")); + addToDataModel("htmlPlain", HTMLOutputFormat.INSTANCE.fromPlainTextByEscaping("a < {h'}")); + addToDataModel("htmlMarkup", HTMLOutputFormat.INSTANCE.fromMarkup("<p>c")); + addToDataModel("xmlPlain", XMLOutputFormat.INSTANCE.fromPlainTextByEscaping("a < {x'}")); + addToDataModel("xmlMarkup", XMLOutputFormat.INSTANCE.fromMarkup("<p>c</p>")); + } + + @Override + protected Configuration createDefaultConfiguration() throws TemplateModelException { + return createDefaultConfigurationBuilder().build(); + } + +}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/28a276c8/freemarker-core-test/src/test/java/org/apache/freemarker/core/ParseTimeParameterBIErrorMessagesTest.java ---------------------------------------------------------------------- diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/ParseTimeParameterBIErrorMessagesTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/ParseTimeParameterBIErrorMessagesTest.java new file mode 100644 index 0000000..77b46de --- /dev/null +++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/ParseTimeParameterBIErrorMessagesTest.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.freemarker.core; + +import org.apache.freemarker.test.TemplateTest; +import org.junit.Test; + +public class ParseTimeParameterBIErrorMessagesTest extends TemplateTest { + + @Test + public void testThen() throws Exception { + assertErrorContains("${true?then}", "expecting", "\"(\""); + assertErrorContains("${true?then + 1}", "expecting", "\"(\""); + assertErrorContains("${true?then()}", "?then", "2 parameters"); + assertErrorContains("${true?then(1)}", "?then", "2 parameters"); + assertOutput("${true?then(1, 2)}", "1"); + assertErrorContains("${true?then(1, 2, 3)}", "?then", "2 parameters"); + } + + @Test + public void testSwitch() throws Exception { + assertErrorContains("${true?switch}", "expecting", "\"(\""); + assertErrorContains("${true?switch + 1}", "expecting", "\"(\""); + assertErrorContains("${true?switch()}", "at least 2 parameters"); + assertErrorContains("${true?switch(true)}", "at least 2 parameters"); + assertOutput("${true?switch(true, 1)}", "1"); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/28a276c8/freemarker-core-test/src/test/java/org/apache/freemarker/core/ParsingErrorMessagesTest.java ---------------------------------------------------------------------- diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/ParsingErrorMessagesTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/ParsingErrorMessagesTest.java new file mode 100644 index 0000000..8f20d6c --- /dev/null +++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/ParsingErrorMessagesTest.java @@ -0,0 +1,116 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.freemarker.core; + +import static org.junit.Assert.*; + +import java.io.IOException; + +import org.apache.freemarker.core.util._StringUtil; +import org.apache.freemarker.test.TestConfigurationBuilder; +import org.junit.Test; + +public class ParsingErrorMessagesTest { + + private Configuration cfg = new TestConfigurationBuilder() + .tagSyntax(ParsingConfiguration.AUTO_DETECT_TAG_SYNTAX) + .build(); + + @Test + public void testNeedlessInterpolation() { + assertErrorContains("<#if ${x} == 3></#if>", "instead of ${"); + assertErrorContains("<#if ${x == 3}></#if>", "instead of ${"); + assertErrorContains("<@foo ${x == 3} />", "instead of ${"); + } + + @Test + public void testWrongDirectiveNames() { + assertErrorContains("<#foo />", "nknown directive", "#foo"); + assertErrorContains("<#set x = 1 />", "nknown directive", "#set", "#assign"); + assertErrorContains("<#iterator></#iterator>", "nknown directive", "#iterator", "#list"); + } + + @Test + public void testBug402() { + assertErrorContains("<#list 1..i as k>${k}<#list>", "existing directive", "malformed", "#list"); + assertErrorContains("<#assign>", "existing directive", "malformed", "#assign"); + assertErrorContains("</#if x>", "existing directive", "malformed", "#if"); + assertErrorContains("<#compress x>", "existing directive", "malformed", "#compress"); + } + + @Test + public void testUnclosedDirectives() { + assertErrorContains("<#macro x>", "#macro", "unclosed"); + assertErrorContains("<#function x>", "#macro", "unclosed"); + assertErrorContains("<#assign x>", "#assign", "unclosed"); + assertErrorContains("<#macro m><#local x>", "#local", "unclosed"); + assertErrorContains("<#global x>", "#global", "unclosed"); + assertErrorContains("<@foo>", "@...", "unclosed"); + assertErrorContains("<#list xs as x>", "#list", "unclosed"); + assertErrorContains("<#list xs as x><#if x>", "#if", "unclosed"); + assertErrorContains("<#list xs as x><#if x><#if q><#else>", "#if", "unclosed"); + assertErrorContains("<#list xs as x><#if x><#if q><#else><#macro x>qwe", "#macro", "unclosed"); + assertErrorContains("${(blah", "\"(\"", "unclosed"); + assertErrorContains("${blah", "\"{\"", "unclosed"); + } + + @Test + public void testInterpolatingClosingsErrors() { + assertErrorContains("${x", "unclosed"); + assertErrorContains("<#assign x = x}>", "\"}\"", "open"); + // TODO assertErrorContains("<#assign x = '${x'>", "unclosed"); + } + + private void assertErrorContains(String ftl, String... expectedSubstrings) { + assertErrorContains(false, ftl, expectedSubstrings); + assertErrorContains(true, ftl, expectedSubstrings); + } + + private void assertErrorContains(boolean squareTags, String ftl, String... expectedSubstrings) { + try { + if (squareTags) { + ftl = ftl.replace('<', '[').replace('>', ']'); + } + new Template("adhoc", ftl, cfg); + fail("The template had to fail"); + } catch (ParseException e) { + String msg = e.getMessage(); + for (String needle: expectedSubstrings) { + if (needle.startsWith("\\!")) { + String netNeedle = needle.substring(2); + if (msg.contains(netNeedle)) { + fail("The message shouldn't contain substring " + _StringUtil.jQuote(netNeedle) + ":\n" + msg); + } + } else if (!msg.contains(needle)) { + fail("The message didn't contain substring " + _StringUtil.jQuote(needle) + ":\n" + msg); + } + } + showError(e); + } catch (IOException e) { + // Won't happen + throw new RuntimeException(e); + } + } + + private void showError(Throwable e) { + //System.out.println(e); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/28a276c8/freemarker-core-test/src/test/java/org/apache/freemarker/core/RestrictedObjectWrapperTest.java ---------------------------------------------------------------------- diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/RestrictedObjectWrapperTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/RestrictedObjectWrapperTest.java new file mode 100644 index 0000000..702a254 --- /dev/null +++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/RestrictedObjectWrapperTest.java @@ -0,0 +1,72 @@ +/* + * 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.apache.freemarker.test.hamcerst.Matchers.*; +import static org.junit.Assert.*; + +import java.util.Collections; +import java.util.Date; +import java.util.HashSet; + +import javax.annotation.PostConstruct; + +import org.apache.freemarker.core.model.TemplateBooleanModel; +import org.apache.freemarker.core.model.TemplateModelException; +import org.apache.freemarker.core.model.impl.DefaultArrayAdapter; +import org.apache.freemarker.core.model.impl.DefaultListAdapter; +import org.apache.freemarker.core.model.impl.DefaultMapAdapter; +import org.apache.freemarker.core.model.impl.DefaultNonListCollectionAdapter; +import org.apache.freemarker.core.model.impl.DefaultObjectWrapperTest.TestBean; +import org.apache.freemarker.core.model.impl.RestrictedObjectWrapper; +import org.apache.freemarker.core.model.impl.SimpleDate; +import org.apache.freemarker.core.model.impl.SimpleNumber; +import org.apache.freemarker.core.model.impl.SimpleScalar; +import org.junit.Test; + +public class RestrictedObjectWrapperTest { + + @Test + public void testBasics() throws TemplateModelException { + PostConstruct.class.toString(); + RestrictedObjectWrapper ow = new RestrictedObjectWrapper.Builder(Configuration.VERSION_3_0_0).build(); + testCustomizationCommonPart(ow); + assertTrue(ow.wrap(Collections.emptyMap()) instanceof DefaultMapAdapter); + assertTrue(ow.wrap(Collections.emptyList()) instanceof DefaultListAdapter); + assertTrue(ow.wrap(new boolean[] { }) instanceof DefaultArrayAdapter); + assertTrue(ow.wrap(new HashSet()) instanceof DefaultNonListCollectionAdapter); + } + + @SuppressWarnings("boxing") + private void testCustomizationCommonPart(RestrictedObjectWrapper ow) throws TemplateModelException { + assertTrue(ow.wrap("x") instanceof SimpleScalar); + assertTrue(ow.wrap(1.5) instanceof SimpleNumber); + assertTrue(ow.wrap(new Date()) instanceof SimpleDate); + assertEquals(TemplateBooleanModel.TRUE, ow.wrap(true)); + + try { + ow.wrap(new TestBean()); + fail(); + } catch (TemplateModelException e) { + assertThat(e.getMessage(), containsStringIgnoringCase("type")); + } + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/28a276c8/freemarker-core-test/src/test/java/org/apache/freemarker/core/RestrictedObjetWrapperTest.java ---------------------------------------------------------------------- diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/RestrictedObjetWrapperTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/RestrictedObjetWrapperTest.java new file mode 100644 index 0000000..43ff3bf --- /dev/null +++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/RestrictedObjetWrapperTest.java @@ -0,0 +1,112 @@ +/* + * 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.File; +import java.io.IOException; +import java.io.StringReader; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +import org.apache.freemarker.core.model.TemplateBooleanModel; +import org.apache.freemarker.core.model.TemplateCollectionModel; +import org.apache.freemarker.core.model.TemplateCollectionModelEx; +import org.apache.freemarker.core.model.TemplateDateModel; +import org.apache.freemarker.core.model.TemplateHashModelEx2; +import org.apache.freemarker.core.model.TemplateModelException; +import org.apache.freemarker.core.model.TemplateModelWithAPISupport; +import org.apache.freemarker.core.model.TemplateNumberModel; +import org.apache.freemarker.core.model.TemplateScalarModel; +import org.apache.freemarker.core.model.TemplateSequenceModel; +import org.apache.freemarker.core.model.impl.RestrictedObjectWrapper; +import org.junit.Test; +import org.w3c.dom.Document; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + +public class RestrictedObjetWrapperTest { + + @Test + public void testDoesNotAllowAPIBuiltin() throws TemplateModelException { + RestrictedObjectWrapper sow = new RestrictedObjectWrapper.Builder(Configuration.VERSION_3_0_0).build(); + + TemplateModelWithAPISupport map = (TemplateModelWithAPISupport) sow.wrap(new HashMap()); + try { + map.getAPI(); + fail(); + } catch (TemplateException e) { + assertThat(e.getMessage(), containsString("?api")); + } + } + + @SuppressWarnings("boxing") + @Test + public void testCanWrapBasicTypes() throws TemplateModelException { + RestrictedObjectWrapper sow = new RestrictedObjectWrapper.Builder(Configuration.VERSION_3_0_0).build(); + assertTrue(sow.wrap("s") instanceof TemplateScalarModel); + assertTrue(sow.wrap(1) instanceof TemplateNumberModel); + assertTrue(sow.wrap(true) instanceof TemplateBooleanModel); + assertTrue(sow.wrap(new Date()) instanceof TemplateDateModel); + assertTrue(sow.wrap(new ArrayList()) instanceof TemplateSequenceModel); + assertTrue(sow.wrap(new String[0]) instanceof TemplateSequenceModel); + assertTrue(sow.wrap(new ArrayList().iterator()) instanceof TemplateCollectionModel); + assertTrue(sow.wrap(new HashSet()) instanceof TemplateCollectionModelEx); + assertTrue(sow.wrap(new HashMap()) instanceof TemplateHashModelEx2); + assertNull(sow.wrap(null)); + } + + @Test + public void testWontWrapDOM() throws SAXException, IOException, ParserConfigurationException, + TemplateModelException { + DocumentBuilder db = DocumentBuilderFactory.newInstance().newDocumentBuilder(); + InputSource is = new InputSource(); + is.setCharacterStream(new StringReader("<doc><sub a='1' /></doc>")); + Document doc = db.parse(is); + + RestrictedObjectWrapper sow = new RestrictedObjectWrapper.Builder(Configuration.VERSION_3_0_0).build(); + try { + sow.wrap(doc); + fail(); + } catch (TemplateModelException e) { + assertThat(e.getMessage(), containsString("won't wrap")); + } + } + + @Test + public void testWontWrapGenericObjects() { + RestrictedObjectWrapper sow = new RestrictedObjectWrapper.Builder(Configuration.VERSION_3_0_0).build(); + try { + sow.wrap(new File("/x")); + fail(); + } catch (TemplateModelException e) { + assertThat(e.getMessage(), containsString("won't wrap")); + } + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/28a276c8/freemarker-core-test/src/test/java/org/apache/freemarker/core/SQLTimeZoneTest.java ---------------------------------------------------------------------- diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/SQLTimeZoneTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/SQLTimeZoneTest.java new file mode 100644 index 0000000..cf14b93 --- /dev/null +++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/SQLTimeZoneTest.java @@ -0,0 +1,371 @@ +/* + * 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.sql.Time; +import java.sql.Timestamp; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; +import java.util.TimeZone; + +import org.apache.freemarker.core.util._DateUtil; +import org.apache.freemarker.test.TemplateTest; +import org.junit.Test; + +public class SQLTimeZoneTest extends TemplateTest { + + private final static TimeZone GMT_P02 = TimeZone.getTimeZone("GMT+02"); + + private TimeZone lastDefaultTimeZone; + + private final DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.US); + { + df.setTimeZone(_DateUtil.UTC); + } + + // Values that JDBC in GMT+02 would produce + private final java.sql.Date sqlDate = new java.sql.Date(utcToLong("2014-07-11T22:00:00")); // 2014-07-12 + private final Time sqlTime = new Time(utcToLong("1970-01-01T10:30:05")); // 12:30:05 + private final Timestamp sqlTimestamp = new Timestamp(utcToLong("2014-07-12T10:30:05")); // 2014-07-12T12:30:05 + private final Date javaDate = new Date(utcToLong("2014-07-12T10:30:05")); // 2014-07-12T12:30:05 + private final Date javaDayErrorDate = new Date(utcToLong("2014-07-11T22:00:00")); // 2014-07-12T12:30:05 + + public TimeZone getLastDefaultTimeZone() { + return lastDefaultTimeZone; + } + + public void setLastDefaultTimeZone(TimeZone lastDefaultTimeZone) { + this.lastDefaultTimeZone = lastDefaultTimeZone; + } + + public java.sql.Date getSqlDate() { + return sqlDate; + } + + public Time getSqlTime() { + return sqlTime; + } + + public Timestamp getSqlTimestamp() { + return sqlTimestamp; + } + + public Date getJavaDate() { + return javaDate; + } + + public Date getJavaDayErrorDate() { + return javaDayErrorDate; + } + + private static final String FTL = + "${sqlDate} ${sqlTime} ${sqlTimestamp} ${javaDate?datetime}\n" + + "${sqlDate?string.iso_fz} ${sqlTime?string.iso_fz} " + + "${sqlTimestamp?string.iso_fz} ${javaDate?datetime?string.iso_fz}\n" + + "${sqlDate?string.xs_fz} ${sqlTime?string.xs_fz} " + + "${sqlTimestamp?string.xs_fz} ${javaDate?datetime?string.xs_fz}\n" + + "${sqlDate?string.xs} ${sqlTime?string.xs} " + + "${sqlTimestamp?string.xs} ${javaDate?datetime?string.xs}\n" + + "<#setting time_zone='GMT'>\n" + + "${sqlDate} ${sqlTime} ${sqlTimestamp} ${javaDate?datetime}\n" + + "${sqlDate?string.iso_fz} ${sqlTime?string.iso_fz} " + + "${sqlTimestamp?string.iso_fz} ${javaDate?datetime?string.iso_fz}\n" + + "${sqlDate?string.xs_fz} ${sqlTime?string.xs_fz} " + + "${sqlTimestamp?string.xs_fz} ${javaDate?datetime?string.xs_fz}\n" + + "${sqlDate?string.xs} ${sqlTime?string.xs} " + + "${sqlTimestamp?string.xs} ${javaDate?datetime?string.xs}\n"; + + private static final String OUTPUT_BEFORE_SETTING_GMT_CFG_GMT2 + = "2014-07-12 12:30:05 2014-07-12T12:30:05 2014-07-12T12:30:05\n" + + "2014-07-12 12:30:05+02:00 2014-07-12T12:30:05+02:00 2014-07-12T12:30:05+02:00\n" + + "2014-07-12+02:00 12:30:05+02:00 2014-07-12T12:30:05+02:00 2014-07-12T12:30:05+02:00\n" + + "2014-07-12 12:30:05 2014-07-12T12:30:05+02:00 2014-07-12T12:30:05+02:00\n"; + + private static final String OUTPUT_BEFORE_SETTING_GMT_CFG_GMT1_SQL_DIFFERENT + = "2014-07-12 12:30:05 2014-07-12T11:30:05 2014-07-12T11:30:05\n" + + "2014-07-12 12:30:05+02:00 2014-07-12T11:30:05+01:00 2014-07-12T11:30:05+01:00\n" + + "2014-07-12+02:00 12:30:05+02:00 2014-07-12T11:30:05+01:00 2014-07-12T11:30:05+01:00\n" + + "2014-07-12 12:30:05 2014-07-12T11:30:05+01:00 2014-07-12T11:30:05+01:00\n"; + + private static final String OUTPUT_BEFORE_SETTING_GMT_CFG_GMT1_SQL_SAME + = "2014-07-11 11:30:05 2014-07-12T11:30:05 2014-07-12T11:30:05\n" + + "2014-07-11 11:30:05+01:00 2014-07-12T11:30:05+01:00 2014-07-12T11:30:05+01:00\n" + + "2014-07-11+01:00 11:30:05+01:00 2014-07-12T11:30:05+01:00 2014-07-12T11:30:05+01:00\n" + + "2014-07-11 11:30:05 2014-07-12T11:30:05+01:00 2014-07-12T11:30:05+01:00\n"; + + private static final String OUTPUT_AFTER_SETTING_GMT_CFG_SQL_SAME + = "2014-07-11 10:30:05 2014-07-12T10:30:05 2014-07-12T10:30:05\n" + + "2014-07-11 10:30:05Z 2014-07-12T10:30:05Z 2014-07-12T10:30:05Z\n" + + "2014-07-11Z 10:30:05Z 2014-07-12T10:30:05Z 2014-07-12T10:30:05Z\n" + + "2014-07-11 10:30:05 2014-07-12T10:30:05Z 2014-07-12T10:30:05Z\n"; + + private static final String OUTPUT_AFTER_SETTING_GMT_CFG_SQL_DIFFERENT + = "2014-07-12 12:30:05 2014-07-12T10:30:05 2014-07-12T10:30:05\n" + + "2014-07-12 12:30:05+02:00 2014-07-12T10:30:05Z 2014-07-12T10:30:05Z\n" + + "2014-07-12+02:00 12:30:05+02:00 2014-07-12T10:30:05Z 2014-07-12T10:30:05Z\n" + + "2014-07-12 12:30:05 2014-07-12T10:30:05Z 2014-07-12T10:30:05Z\n"; + + @Test + public void testWithDefaultTZAndNullSQL() throws Exception { + TimeZone prevSysDefTz = TimeZone.getDefault(); + TimeZone.setDefault(GMT_P02); + try { + Configuration.ExtendableBuilder<?> cfgB = createConfigurationBuilder(); + cfgB.unsetTimeZone(); + setConfiguration(cfgB.build()); + + assertNull(getConfiguration().getSQLDateAndTimeTimeZone()); + assertEquals(TimeZone.getDefault(), getConfiguration().getTimeZone()); + + assertOutput(FTL, OUTPUT_BEFORE_SETTING_GMT_CFG_GMT2 + OUTPUT_AFTER_SETTING_GMT_CFG_SQL_SAME); + } finally { + TimeZone.setDefault(prevSysDefTz); + } + } + + @Test + public void testWithDefaultTZAndGMT2SQL() throws Exception { + TimeZone prevSysDefTz = TimeZone.getDefault(); + TimeZone.setDefault(GMT_P02); + try { + Configuration.ExtendableBuilder<?> cfgB = createConfigurationBuilder(); + cfgB.sqlDateAndTimeTimeZone(GMT_P02).unsetTimeZone(); + setConfiguration(cfgB.build()); + + assertOutput(FTL, OUTPUT_BEFORE_SETTING_GMT_CFG_GMT2 + OUTPUT_AFTER_SETTING_GMT_CFG_SQL_DIFFERENT); + } finally { + TimeZone.setDefault(prevSysDefTz); + } + } + + @Test + public void testWithGMT1AndNullSQL() throws Exception { + setConfiguration(createConfigurationBuilder() + .timeZone(TimeZone.getTimeZone("GMT+01:00")) + .build()); + assertNull(getConfiguration().getSQLDateAndTimeTimeZone()); + + assertOutput(FTL, OUTPUT_BEFORE_SETTING_GMT_CFG_GMT1_SQL_SAME + OUTPUT_AFTER_SETTING_GMT_CFG_SQL_SAME); + } + + @Test + public void testWithGMT1AndGMT2SQL() throws Exception { + setConfiguration(createConfigurationBuilder() + .sqlDateAndTimeTimeZone(GMT_P02) + .timeZone(TimeZone.getTimeZone("GMT+01:00")) + .build()); + + assertOutput(FTL, OUTPUT_BEFORE_SETTING_GMT_CFG_GMT1_SQL_DIFFERENT + OUTPUT_AFTER_SETTING_GMT_CFG_SQL_DIFFERENT); + } + + @Test + public void testWithGMT2AndNullSQL() throws Exception { + setConfiguration(createConfigurationBuilder() + .timeZone(TimeZone.getTimeZone("GMT+02")) + .build()); + assertNull(getConfiguration().getSQLDateAndTimeTimeZone()); + + assertOutput(FTL, OUTPUT_BEFORE_SETTING_GMT_CFG_GMT2 + OUTPUT_AFTER_SETTING_GMT_CFG_SQL_SAME); + } + + @Test + public void testWithGMT2AndGMT2SQL() throws Exception { + setConfiguration(createConfigurationBuilder() + .sqlDateAndTimeTimeZone(GMT_P02) + .timeZone(TimeZone.getTimeZone("GMT+02")) + .build()); + + assertOutput(FTL, OUTPUT_BEFORE_SETTING_GMT_CFG_GMT2 + OUTPUT_AFTER_SETTING_GMT_CFG_SQL_DIFFERENT); + } + + @Test + public void testCacheFlushings() throws Exception { + Configuration.ExtendableBuilder<?> cfgB = createConfigurationBuilder() + .timeZone(_DateUtil.UTC) + .dateFormat("yyyy-MM-dd E") + .timeFormat("HH:mm:ss E") + .dateTimeFormat("yyyy-MM-dd'T'HH:mm:ss E"); + + setConfiguration(cfgB.build()); + assertOutput( + "${sqlDate}, ${sqlTime}, ${sqlTimestamp}, ${javaDate?datetime}, ${javaDate?date}, ${javaDate?time}\n" + + "<#setting locale='de'>\n" + + "${sqlDate}, ${sqlTime}, ${sqlTimestamp}, ${javaDate?datetime}, ${javaDate?date}, ${javaDate?time}\n", + "2014-07-11 Fri, 10:30:05 Thu, 2014-07-12T10:30:05 Sat, 2014-07-12T10:30:05 Sat, 2014-07-12 Sat, 10:30:05 Sat\n" + + "2014-07-11 Fr, 10:30:05 Do, 2014-07-12T10:30:05 Sa, 2014-07-12T10:30:05 Sa, 2014-07-12 Sa, 10:30:05 Sa\n"); + assertOutput( + "${sqlDate}, ${sqlTime}, ${sqlTimestamp}, ${javaDate?datetime}, ${javaDate?date}, ${javaDate?time}\n" + + "<#setting date_format='yyyy-MM-dd'>\n" + + "${sqlDate}, ${sqlTime}, ${sqlTimestamp}, ${javaDate?datetime}, ${javaDate?date}, ${javaDate?time}\n", + "2014-07-11 Fri, 10:30:05 Thu, 2014-07-12T10:30:05 Sat, 2014-07-12T10:30:05 Sat, 2014-07-12 Sat, 10:30:05 Sat\n" + + "2014-07-11, 10:30:05 Thu, 2014-07-12T10:30:05 Sat, 2014-07-12T10:30:05 Sat, 2014-07-12, 10:30:05 Sat\n"); + assertOutput( + "${sqlDate}, ${sqlTime}, ${sqlTimestamp}, ${javaDate?datetime}, ${javaDate?date}, ${javaDate?time}\n" + + "<#setting time_format='HH:mm:ss'>\n" + + "${sqlDate}, ${sqlTime}, ${sqlTimestamp}, ${javaDate?datetime}, ${javaDate?date}, ${javaDate?time}\n", + "2014-07-11 Fri, 10:30:05 Thu, 2014-07-12T10:30:05 Sat, 2014-07-12T10:30:05 Sat, 2014-07-12 Sat, 10:30:05 Sat\n" + + "2014-07-11 Fri, 10:30:05, 2014-07-12T10:30:05 Sat, 2014-07-12T10:30:05 Sat, 2014-07-12 Sat, 10:30:05\n"); + assertOutput( + "${sqlDate}, ${sqlTime}, ${sqlTimestamp}, ${javaDate?datetime}, ${javaDate?date}, ${javaDate?time}\n" + + "<#setting datetime_format='yyyy-MM-dd\\'T\\'HH:mm:ss'>\n" + + "${sqlDate}, ${sqlTime}, ${sqlTimestamp}, ${javaDate?datetime}, ${javaDate?date}, ${javaDate?time}\n", + "2014-07-11 Fri, 10:30:05 Thu, 2014-07-12T10:30:05 Sat, 2014-07-12T10:30:05 Sat, 2014-07-12 Sat, 10:30:05 Sat\n" + + "2014-07-11 Fri, 10:30:05 Thu, 2014-07-12T10:30:05, 2014-07-12T10:30:05, 2014-07-12 Sat, 10:30:05 Sat\n"); + + setConfiguration(cfgB.sqlDateAndTimeTimeZone(GMT_P02).build()); + assertOutput( + "${sqlDate}, ${sqlTime}, ${sqlTimestamp}, ${javaDate?datetime}, ${javaDate?date}, ${javaDate?time}\n" + + "<#setting locale='de'>\n" + + "${sqlDate}, ${sqlTime}, ${sqlTimestamp}, ${javaDate?datetime}, ${javaDate?date}, ${javaDate?time}\n", + "2014-07-12 Sat, 12:30:05 Thu, 2014-07-12T10:30:05 Sat, 2014-07-12T10:30:05 Sat, 2014-07-12 Sat, 10:30:05 Sat\n" + + "2014-07-12 Sa, 12:30:05 Do, 2014-07-12T10:30:05 Sa, 2014-07-12T10:30:05 Sa, 2014-07-12 Sa, 10:30:05 Sa\n"); + assertOutput( + "${sqlDate}, ${sqlTime}, ${sqlTimestamp}, ${javaDate?datetime}, ${javaDate?date}, ${javaDate?time}\n" + + "<#setting date_format='yyyy-MM-dd'>\n" + + "${sqlDate}, ${sqlTime}, ${sqlTimestamp}, ${javaDate?datetime}, ${javaDate?date}, ${javaDate?time}\n", + "2014-07-12 Sat, 12:30:05 Thu, 2014-07-12T10:30:05 Sat, 2014-07-12T10:30:05 Sat, 2014-07-12 Sat, 10:30:05 Sat\n" + + "2014-07-12, 12:30:05 Thu, 2014-07-12T10:30:05 Sat, 2014-07-12T10:30:05 Sat, 2014-07-12, 10:30:05 Sat\n"); + assertOutput( + "${sqlDate}, ${sqlTime}, ${sqlTimestamp}, ${javaDate?datetime}, ${javaDate?date}, ${javaDate?time}\n" + + "<#setting time_format='HH:mm:ss'>\n" + + "${sqlDate}, ${sqlTime}, ${sqlTimestamp}, ${javaDate?datetime}, ${javaDate?date}, ${javaDate?time}\n", + "2014-07-12 Sat, 12:30:05 Thu, 2014-07-12T10:30:05 Sat, 2014-07-12T10:30:05 Sat, 2014-07-12 Sat, 10:30:05 Sat\n" + + "2014-07-12 Sat, 12:30:05, 2014-07-12T10:30:05 Sat, 2014-07-12T10:30:05 Sat, 2014-07-12 Sat, 10:30:05\n"); + assertOutput( + "${sqlDate}, ${sqlTime}, ${sqlTimestamp}, ${javaDate?datetime}, ${javaDate?date}, ${javaDate?time}\n" + + "<#setting datetime_format='yyyy-MM-dd\\'T\\'HH:mm:ss'>\n" + + "${sqlDate}, ${sqlTime}, ${sqlTimestamp}, ${javaDate?datetime}, ${javaDate?date}, ${javaDate?time}\n", + "2014-07-12 Sat, 12:30:05 Thu, 2014-07-12T10:30:05 Sat, 2014-07-12T10:30:05 Sat, 2014-07-12 Sat, 10:30:05 Sat\n" + + "2014-07-12 Sat, 12:30:05 Thu, 2014-07-12T10:30:05, 2014-07-12T10:30:05, 2014-07-12 Sat, 10:30:05 Sat\n"); + } + + @Test + public void testDateAndTimeBuiltInsHasNoEffect() throws Exception { + setConfiguration(createConfigurationBuilder() + .timeZone(_DateUtil.UTC) + .sqlDateAndTimeTimeZone(GMT_P02) + .build()); + + assertOutput( + "${javaDayErrorDate?date} ${javaDayErrorDate?time} ${sqlTimestamp?date} ${sqlTimestamp?time} " + + "${sqlDate?date} ${sqlTime?time}\n" + + "<#setting time_zone='GMT+02'>\n" + + "${javaDayErrorDate?date} ${javaDayErrorDate?time} ${sqlTimestamp?date} ${sqlTimestamp?time} " + + "${sqlDate?date} ${sqlTime?time}\n" + + "<#setting time_zone='GMT-11'>\n" + + "${javaDayErrorDate?date} ${javaDayErrorDate?time} ${sqlTimestamp?date} ${sqlTimestamp?time} " + + "${sqlDate?date} ${sqlTime?time}\n", + "2014-07-11 22:00:00 2014-07-12 10:30:05 2014-07-12 12:30:05\n" + + "2014-07-12 00:00:00 2014-07-12 12:30:05 2014-07-12 12:30:05\n" + + "2014-07-11 11:00:00 2014-07-11 23:30:05 2014-07-12 12:30:05\n"); + } + + @Test + public void testChangeSettingInTemplate() throws Exception { + setConfiguration(createConfigurationBuilder() + .timeZone(_DateUtil.UTC) + .build()); + + assertNull(getConfiguration().getSQLDateAndTimeTimeZone()); + + assertOutput( + "${sqlDate}, ${sqlTime}, ${sqlTimestamp}, ${javaDate?datetime}\n" + + "<#setting sql_date_and_time_time_zone='GMT+02'>\n" + + "${sqlDate}, ${sqlTime}, ${sqlTimestamp}, ${javaDate?datetime}\n" + + "<#setting sql_date_and_time_time_zone='null'>\n" + + "${sqlDate}, ${sqlTime}, ${sqlTimestamp}, ${javaDate?datetime}\n" + + "<#setting time_zone='GMT+03'>\n" + + "${sqlDate}, ${sqlTime}, ${sqlTimestamp}, ${javaDate?datetime}\n" + + "<#setting sql_date_and_time_time_zone='GMT+02'>\n" + + "${sqlDate}, ${sqlTime}, ${sqlTimestamp}, ${javaDate?datetime}\n" + + "<#setting sql_date_and_time_time_zone='GMT-11'>\n" + + "${sqlDate}, ${sqlTime}, ${sqlTimestamp}, ${javaDate?datetime}\n" + + "<#setting date_format='xs fz'>\n" + + "${sqlDate}, ${sqlTime}, ${sqlTimestamp}, ${javaDate?datetime}\n" + + "<#setting time_format='xs fz'>\n" + + "${sqlDate}, ${sqlTime}, ${sqlTimestamp}, ${javaDate?datetime}\n" + + "<#setting datetime_format='iso m'>\n" + + "${sqlDate}, ${sqlTime}, ${sqlTimestamp}, ${javaDate?datetime}\n", + "2014-07-11, 10:30:05, 2014-07-12T10:30:05, 2014-07-12T10:30:05\n" + + "2014-07-12, 12:30:05, 2014-07-12T10:30:05, 2014-07-12T10:30:05\n" + + "2014-07-11, 10:30:05, 2014-07-12T10:30:05, 2014-07-12T10:30:05\n" + + "2014-07-12, 13:30:05, 2014-07-12T13:30:05, 2014-07-12T13:30:05\n" + + "2014-07-12, 12:30:05, 2014-07-12T13:30:05, 2014-07-12T13:30:05\n" + + "2014-07-11, 23:30:05, 2014-07-12T13:30:05, 2014-07-12T13:30:05\n" + + "2014-07-11-11:00, 23:30:05, 2014-07-12T13:30:05, 2014-07-12T13:30:05\n" + + "2014-07-11-11:00, 23:30:05-11:00, 2014-07-12T13:30:05, 2014-07-12T13:30:05\n" + + "2014-07-11-11:00, 23:30:05-11:00, 2014-07-12T13:30+03:00, 2014-07-12T13:30+03:00\n"); + } + + @Test + public void testFormatUTCFlagHasNoEffect() throws Exception { + setConfiguration(createConfigurationBuilder() + .sqlDateAndTimeTimeZone(GMT_P02) + .timeZone(TimeZone.getTimeZone("GMT-01")) + .build()); + + assertOutput( + "<#setting date_format='xs fz'><#setting time_format='xs fz'>\n" + + "${sqlDate}, ${sqlTime}, ${javaDate?time}\n" + + "<#setting date_format='xs fz u'><#setting time_format='xs fz u'>\n" + + "${sqlDate}, ${sqlTime}, ${javaDate?time}\n" + + "<#setting sql_date_and_time_time_zone='GMT+03'>\n" + + "${sqlDate}, ${sqlTime}, ${javaDate?time}\n" + + "<#setting sql_date_and_time_time_zone='null'>\n" + + "${sqlDate}, ${sqlTime}, ${javaDate?time}\n" + + "<#setting date_format='xs fz'><#setting time_format='xs fz'>\n" + + "${sqlDate}, ${sqlTime}, ${javaDate?time}\n" + + "<#setting date_format='xs fz fu'><#setting time_format='xs fz fu'>\n" + + "${sqlDate}, ${sqlTime}, ${javaDate?time}\n", + "2014-07-12+02:00, 12:30:05+02:00, 09:30:05-01:00\n" + + "2014-07-12+02:00, 12:30:05+02:00, 10:30:05Z\n" + + "2014-07-12+03:00, 13:30:05+03:00, 10:30:05Z\n" + + "2014-07-11-01:00, 09:30:05-01:00, 10:30:05Z\n" + + "2014-07-11-01:00, 09:30:05-01:00, 09:30:05-01:00\n" + + "2014-07-11Z, 10:30:05Z, 10:30:05Z\n"); + } + + private Configuration.ExtendableBuilder<?> createConfigurationBuilder() { + return new Configuration.Builder(Configuration.VERSION_3_0_0) + .locale(Locale.US) + .dateFormat("yyyy-MM-dd") + .timeFormat("HH:mm:ss") + .dateTimeFormat("yyyy-MM-dd'T'HH:mm:ss"); + } + + @Override + protected Object createDataModel() { + return this; + } + + private long utcToLong(String isoDateTime) { + try { + return df.parse(isoDateTime).getTime(); + } catch (ParseException e) { + throw new RuntimeException(e); + } + } + +}
