http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/test/java/org/apache/freemarker/core/ASTTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/ASTTest.java 
b/src/test/java/org/apache/freemarker/core/ASTTest.java
new file mode 100644
index 0000000..9a4222c
--- /dev/null
+++ b/src/test/java/org/apache/freemarker/core/ASTTest.java
@@ -0,0 +1,98 @@
+/*
+ * 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.FileNotFoundException;
+import java.io.IOException;
+
+import org.apache.freemarker.core.ASTPrinter.Options;
+import org.apache.freemarker.core.util._StringUtil;
+import org.apache.freemarker.test.util.FileTestCase;
+import org.apache.freemarker.test.util.TestUtil;
+
+public class ASTTest extends FileTestCase {
+
+    public ASTTest(String name) {
+        super(name);
+    }
+    
+    public void test1() throws Exception {
+        testAST("ast-1");
+    }
+
+    public void testRange() throws Exception {
+        testAST("ast-range");
+    }
+    
+    public void testAssignments() throws Exception {
+        testAST("ast-assignments");
+    }
+    
+    public void testBuiltins() throws Exception {
+        testAST("ast-builtins");
+    }
+    
+    public void testStringLiteralInterpolation() throws Exception {
+        testAST("ast-strlitinterpolation");
+    }
+    
+    public void testWhitespaceStripping() throws Exception {
+        testAST("ast-whitespacestripping");
+    }
+
+    public void testMixedContentSimplifications() throws Exception {
+        testAST("ast-mixedcontentsimplifications");
+    }
+
+    public void testMultipleIgnoredChildren() throws Exception {
+        testAST("ast-multipleignoredchildren");
+    }
+    
+    public void testNestedIgnoredChildren() throws Exception {
+        testAST("ast-nestedignoredchildren");
+    }
+
+    public void testLocations() throws Exception {
+        testASTWithLocations("ast-locations");
+    }
+    
+    private void testAST(String testName) throws FileNotFoundException, 
IOException {
+        testAST(testName, null);
+    }
+
+    private void testASTWithLocations(String testName) throws 
FileNotFoundException, IOException {
+        Options options = new Options();
+        options.setShowLocation(true);
+        testAST(testName, options);
+    }
+
+    private void testAST(String testName, Options ops) throws 
FileNotFoundException, IOException {
+        final String templateName = testName + ".ftl";
+        assertExpectedFileEqualsString(
+                testName + ".ast",
+                ASTPrinter.getASTAsString(templateName,
+                        
TestUtil.removeFTLCopyrightComment(normalizeLineBreaks(loadResource(templateName))),
 ops));
+    }
+    
+    private String normalizeLineBreaks(final String s) throws 
FileNotFoundException, IOException {
+        return _StringUtil.replace(s, "\r\n", "\n").replace('\r', '\n');
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/test/java/org/apache/freemarker/core/AppMetaTemplateDateFormatFactory.java
----------------------------------------------------------------------
diff --git 
a/src/test/java/org/apache/freemarker/core/AppMetaTemplateDateFormatFactory.java
 
b/src/test/java/org/apache/freemarker/core/AppMetaTemplateDateFormatFactory.java
new file mode 100644
index 0000000..94c8dff
--- /dev/null
+++ 
b/src/test/java/org/apache/freemarker/core/AppMetaTemplateDateFormatFactory.java
@@ -0,0 +1,129 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.freemarker.core;
+
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+
+import org.apache.freemarker.core.Environment;
+import org.apache.freemarker.core.InvalidFormatParametersException;
+import org.apache.freemarker.core.TemplateDateFormat;
+import org.apache.freemarker.core.TemplateDateFormatFactory;
+import org.apache.freemarker.core.TemplateFormatUtil;
+import org.apache.freemarker.core.UnformattableValueException;
+import 
org.apache.freemarker.core.UnknownDateTypeFormattingUnsupportedException;
+import org.apache.freemarker.core.UnparsableValueException;
+import org.apache.freemarker.core.model.TemplateDateModel;
+import org.apache.freemarker.core.model.TemplateModelException;
+
+public class AppMetaTemplateDateFormatFactory extends 
TemplateDateFormatFactory {
+
+    public static final AppMetaTemplateDateFormatFactory INSTANCE = new 
AppMetaTemplateDateFormatFactory();
+    
+    private AppMetaTemplateDateFormatFactory() {
+        // Defined to decrease visibility
+    }
+    
+    @Override
+    public TemplateDateFormat get(String params, int dateType, Locale locale, 
TimeZone timeZone, boolean zonelessInput,
+            Environment env) throws 
UnknownDateTypeFormattingUnsupportedException, InvalidFormatParametersException 
{
+        TemplateFormatUtil.checkHasNoParameters(params);
+        return AppMetaTemplateDateFormat.INSTANCE;
+    }
+
+    private static class AppMetaTemplateDateFormat extends TemplateDateFormat {
+
+        private static final AppMetaTemplateDateFormat INSTANCE = new 
AppMetaTemplateDateFormat();
+        
+        private AppMetaTemplateDateFormat() { }
+        
+        @Override
+        public String formatToPlainText(TemplateDateModel dateModel)
+                throws UnformattableValueException, TemplateModelException {
+            String result = 
String.valueOf(TemplateFormatUtil.getNonNullDate(dateModel).getTime());
+            if (dateModel instanceof AppMetaTemplateDateModel) {
+                result += "/" + ((AppMetaTemplateDateModel) 
dateModel).getAppMeta(); 
+            }
+            return result;
+        }
+
+        @Override
+        public boolean isLocaleBound() {
+            return false;
+        }
+
+        @Override
+        public boolean isTimeZoneBound() {
+            return false;
+        }
+
+        @Override
+        public Object parse(String s, int dateType) throws 
UnparsableValueException {
+            int slashIdx = s.indexOf('/');
+            try {
+                if (slashIdx != -1) {
+                    return new AppMetaTemplateDateModel(
+                            new Date(Long.parseLong(s.substring(0,  
slashIdx))),
+                            dateType,
+                            s.substring(slashIdx +1));
+                } else {
+                    return new Date(Long.parseLong(s));
+                }
+            } catch (NumberFormatException e) {
+                throw new UnparsableValueException("Malformed long");
+            }
+        }
+
+        @Override
+        public String getDescription() {
+            return "millis since the epoch";
+        }
+        
+    }
+    
+    public static class AppMetaTemplateDateModel implements TemplateDateModel {
+        
+        private final Date date;
+        private final int dateType;
+        private final String appMeta;
+
+        public AppMetaTemplateDateModel(Date date, int dateType, String 
appMeta) {
+            this.date = date;
+            this.dateType = dateType;
+            this.appMeta = appMeta;
+        }
+
+        @Override
+        public Date getAsDate() throws TemplateModelException {
+            return date;
+        }
+
+        @Override
+        public int getDateType() {
+            return dateType;
+        }
+
+        public String getAppMeta() {
+            return appMeta;
+        }
+        
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/test/java/org/apache/freemarker/core/BaseNTemplateNumberFormatFactory.java
----------------------------------------------------------------------
diff --git 
a/src/test/java/org/apache/freemarker/core/BaseNTemplateNumberFormatFactory.java
 
b/src/test/java/org/apache/freemarker/core/BaseNTemplateNumberFormatFactory.java
new file mode 100644
index 0000000..576063b
--- /dev/null
+++ 
b/src/test/java/org/apache/freemarker/core/BaseNTemplateNumberFormatFactory.java
@@ -0,0 +1,128 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.freemarker.core;
+
+import java.util.Locale;
+
+import org.apache.freemarker.core.Environment;
+import org.apache.freemarker.core.InvalidFormatParametersException;
+import org.apache.freemarker.core.TemplateFormatUtil;
+import org.apache.freemarker.core.TemplateNumberFormat;
+import org.apache.freemarker.core.TemplateNumberFormatFactory;
+import org.apache.freemarker.core.TemplateValueFormatException;
+import org.apache.freemarker.core.UnformattableValueException;
+import org.apache.freemarker.core.model.TemplateModelException;
+import org.apache.freemarker.core.model.TemplateNumberModel;
+import org.apache.freemarker.core.util._NumberUtil;
+import org.apache.freemarker.core.util._StringUtil;
+
+/**
+ * Shows a number in base N number system. Can only format numbers that fit 
into an {@code int},
+ * however, optionally you can specify a fallback format. This format has one 
required parameter,
+ * the numerical system base. That can be optionally followed by "|" and a 
fallback format.
+ */
+public class BaseNTemplateNumberFormatFactory extends 
TemplateNumberFormatFactory {
+
+    public static final BaseNTemplateNumberFormatFactory INSTANCE
+            = new BaseNTemplateNumberFormatFactory();
+    
+    private BaseNTemplateNumberFormatFactory() {
+        // Defined to decrease visibility
+    }
+    
+    @Override
+    public TemplateNumberFormat get(String params, Locale locale, Environment 
env)
+            throws InvalidFormatParametersException {
+        TemplateNumberFormat fallbackFormat;
+        {
+            int barIdx = params.indexOf('|');
+            if (barIdx != -1) {
+                String fallbackFormatStr = params.substring(barIdx + 1);
+                params = params.substring(0, barIdx);
+                try {
+                    fallbackFormat = 
env.getTemplateNumberFormat(fallbackFormatStr, locale);
+                } catch (TemplateValueFormatException e) {
+                    throw new InvalidFormatParametersException(
+                            "Couldn't get the fallback number format 
(specified after the \"|\"), "
+                            + _StringUtil.jQuote(fallbackFormatStr) + ". 
Reason: " + e.getMessage(),
+                            e);
+                }
+            } else {
+                fallbackFormat = null;
+            }
+        }
+        
+        int base;
+        try {
+            base = Integer.parseInt(params);
+        } catch (NumberFormatException e) {
+            if (params.length() == 0) {
+                throw new InvalidFormatParametersException(
+                        "A format parameter is required to specify the 
numerical system base.");
+            }
+            throw new InvalidFormatParametersException(
+                    "The format paramter must be an integer, but was (shown 
quoted): "
+                    + _StringUtil.jQuote(params));
+        }
+        if (base < 2) {
+            throw new InvalidFormatParametersException("A base must be at 
least 2.");
+        }
+        return new BaseNTemplateNumberFormat(base, fallbackFormat);
+    }
+
+    private static class BaseNTemplateNumberFormat extends 
TemplateNumberFormat {
+
+        private final int base;
+        private final TemplateNumberFormat fallbackFormat;
+        
+        private BaseNTemplateNumberFormat(int base, TemplateNumberFormat 
fallbackFormat) {
+            this.base = base;
+            this.fallbackFormat = fallbackFormat;
+        }
+        
+        @Override
+        public String formatToPlainText(TemplateNumberModel numberModel)
+                throws TemplateModelException, TemplateValueFormatException {
+            Number n = TemplateFormatUtil.getNonNullNumber(numberModel);
+            try {
+                return Integer.toString(_NumberUtil.toIntExact(n), base);
+            } catch (ArithmeticException e) {
+                if (fallbackFormat == null) {
+                    throw new UnformattableValueException(
+                            n + " doesn't fit into an int, and there was no 
fallback format "
+                            + "specified.");
+                } else {
+                    return fallbackFormat.formatToPlainText(numberModel);
+                }
+            }
+        }
+
+        @Override
+        public boolean isLocaleBound() {
+            return false;
+        }
+
+        @Override
+        public String getDescription() {
+            return "base " + base;
+        }
+        
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/test/java/org/apache/freemarker/core/BreakPlacementTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/BreakPlacementTest.java 
b/src/test/java/org/apache/freemarker/core/BreakPlacementTest.java
new file mode 100644
index 0000000..0278a40
--- /dev/null
+++ b/src/test/java/org/apache/freemarker/core/BreakPlacementTest.java
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core;
+
+import java.io.IOException;
+
+import org.apache.freemarker.core.Configuration;
+import org.apache.freemarker.core.TemplateException;
+import org.apache.freemarker.test.TemplateTest;
+import org.junit.Test;
+
+public class BreakPlacementTest extends TemplateTest {
+    
+    private static final String BREAK_NESTING_ERROR_MESSAGE_PART = "<#break> 
must be nested";
+
+    @Test
+    public void testValidPlacements() throws IOException, TemplateException {
+        assertOutput("<#assign x = 1><#switch x><#case 1>one<#break><#case 
2>two</#switch>", "one");
+        assertOutput("<#list 1..2 as x>${x}<#break></#list>", "1");
+        assertOutput("<#list 1..2>[<#items as 
x>${x}<#break></#items>]</#list>", "[1]");
+        assertOutput("<#list 1..2 as x>${x}<#list 1..3>B<#break>E<#items as 
y></#items></#list>E</#list>.", "1B.");
+        assertOutput("<#list 1..2 as x>${x}<#list 3..4 as 
x>${x}<#break></#list>;</#list>", "13;23;");
+        assertOutput("<#list [1..2, 3..4, [], 5..6] as xs>[<#list xs as 
x>${x}<#else><#break></#list>]</#list>.",
+                "[12][34][.");
+        assertOutput("<#list [1..2, 3..4, [], 5..6] as xs>"
+                + "<#list xs>[<#items as 
x>${x}</#items>]<#else><#break></#list>"
+                + "</#list>.",
+                "[12][34].");
+        assertOutput("<#forEach x in 1..2>${x}<#break></#forEach>", "1");
+    }
+
+    @Test
+    public void testInvalidPlacements() throws IOException, TemplateException {
+        assertErrorContains("<#break>", BREAK_NESTING_ERROR_MESSAGE_PART);
+        assertErrorContains("<#list 1..2 as x>${x}</#list><#break>", 
BREAK_NESTING_ERROR_MESSAGE_PART);
+        assertErrorContains("<#if false><#break></#if>", 
BREAK_NESTING_ERROR_MESSAGE_PART);
+        assertErrorContains("<#list xs><#break></#list>", 
BREAK_NESTING_ERROR_MESSAGE_PART);
+        assertErrorContains("<#list 1..2 as x>${x}<#else><#break></#list>", 
BREAK_NESTING_ERROR_MESSAGE_PART);
+    }
+
+    @Test
+    public void testInvalidPlacementInsideMacro() throws IOException, 
TemplateException {
+        final String ftl = "<#list 1..2 as x>${x}<#macro 
m><#break></#macro></#list>";
+        
getConfiguration().setIncompatibleImprovements(Configuration.VERSION_3_0_0);
+        assertErrorContains(ftl, BREAK_NESTING_ERROR_MESSAGE_PART);
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/test/java/org/apache/freemarker/core/CamelCaseTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/CamelCaseTest.java 
b/src/test/java/org/apache/freemarker/core/CamelCaseTest.java
new file mode 100644
index 0000000..859442c
--- /dev/null
+++ b/src/test/java/org/apache/freemarker/core/CamelCaseTest.java
@@ -0,0 +1,493 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.freemarker.core;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.Set;
+
+import org.apache.freemarker.core.ASTExpBuiltIn;
+import org.apache.freemarker.core.ASTExpBuiltInVariable;
+import org.apache.freemarker.core.Configuration;
+import org.apache.freemarker.core.HTMLOutputFormat;
+import org.apache.freemarker.core.TemplateException;
+import org.apache.freemarker.core.UndefinedOutputFormat;
+import org.apache.freemarker.core.util._StringUtil;
+import org.apache.freemarker.test.TemplateTest;
+import org.junit.Test;
+
+public class CamelCaseTest extends TemplateTest {
+
+    @Test
+    public void camelCaseSpecialVars() throws IOException, TemplateException {
+        getConfiguration().setOutputEncoding("utf-8");
+        getConfiguration().setURLEscapingCharset("iso-8859-1");
+        getConfiguration().setLocale(Locale.GERMANY);
+        assertOutput("${.dataModel?isHash?c}", "true");
+        assertOutput("${.data_model?is_hash?c}", "true");
+        assertOutput("${.localeObject.toString()}", "de_DE");
+        assertOutput("${.locale_object.toString()}", "de_DE");
+        assertOutput("${.templateName!'null'}", "null");
+        assertOutput("${.template_name!'null'}", "null");
+        assertOutput("${.currentTemplateName!'null'}", "null");
+        assertOutput("${.current_template_name!'null'}", "null");
+        assertOutput("${.mainTemplateName!'null'}", "null");
+        assertOutput("${.main_template_name!'null'}", "null");
+        assertOutput("${.outputEncoding}", "utf-8");
+        assertOutput("${.output_encoding}", "utf-8");
+        assertOutput("${.outputFormat}", 
UndefinedOutputFormat.INSTANCE.getName());
+        assertOutput("${.output_format}", 
UndefinedOutputFormat.INSTANCE.getName());
+        assertOutput("${.urlEscapingCharset}", "iso-8859-1");
+        assertOutput("${.url_escaping_charset}", "iso-8859-1");
+        assertOutput("${.currentNode!'-'}", "-");
+        assertOutput("${.current_node!'-'}", "-");
+    }
+
+    @Test
+    public void camelCaseSpecialVarsInErrorMessage() throws IOException, 
TemplateException {
+        assertErrorContains("${.fooBar}", "dataModel", "\\!data_model");
+        assertErrorContains("${.foo_bar}", "data_model", "\\!dataModel");
+        // [2.4] If camel case will be the recommended style, then this need 
to be inverted:
+        assertErrorContains("${.foo}", "data_model", "\\!dataModel");
+        
+        assertErrorContains("<#if x><#elseIf y></#if>${.foo}", "dataModel", 
"\\!data_model");
+        assertErrorContains("<#if x><#elseif y></#if>${.foo}", "data_model", 
"\\!dataModel");
+        
+        
getConfiguration().setNamingConvention(Configuration.CAMEL_CASE_NAMING_CONVENTION);
+        assertErrorContains("${.foo}", "dataModel", "\\!data_model");
+        
getConfiguration().setNamingConvention(Configuration.LEGACY_NAMING_CONVENTION);
+        assertErrorContains("${.foo}", "data_model", "\\!dataModel");
+    }
+    
+    @Test
+    public void camelCaseSettingNames() throws IOException, TemplateException {
+        assertOutput("<#setting booleanFormat='Y,N'>${true} <#setting 
booleanFormat='+,-'>${true}", "Y +");
+        assertOutput("<#setting boolean_format='Y,N'>${true} <#setting 
boolean_format='+,-'>${true}", "Y +");
+        
+        // Still works inside ?interpret
+        assertOutput("<@r\"<#setting booleanFormat='Y,N'>${true}\"?interpret 
/>", "Y");
+    }
+    
+    @Test
+    public void camelCaseFtlHeaderParameters() throws IOException, 
TemplateException {
+        getConfiguration().setOutputEncoding("utf-8");
+        
+        assertOutput(
+                "<#ftl "
+                + "stripWhitespace=false "
+                + "stripText=true "
+                + "outputFormat='" + HTMLOutputFormat.INSTANCE.getName() + "' "
+                + "autoEsc=true "
+                + "nsPrefixes={} "
+                + ">\nx\n<#if true>\n${.outputFormat}\n</#if>\n",
+                "\nHTML\n");
+
+        assertOutput(
+                "<#ftl "
+                + "strip_whitespace=false "
+                + "strip_text=true "
+                + "output_format='" + HTMLOutputFormat.INSTANCE.getName() + "' 
"
+                + "auto_esc=true "
+                + "ns_prefixes={} "
+                + ">\nx\n<#if true>\n${.output_format}\n</#if>\n",
+                "\nHTML\n");
+
+        assertErrorContains("<#ftl strip_text=true xmlns={}>", "ns_prefixes", 
"\\!nsPrefixes");
+        assertErrorContains("<#ftl stripText=true xmlns={}>", "nsPrefixes");
+        
+        assertErrorContains("<#ftl stripWhitespace=true strip_text=true>", 
"naming convention");
+        assertErrorContains("<#ftl strip_whitespace=true stripText=true>", 
"naming convention");
+        assertErrorContains("<#ftl stripWhitespace=true>${.foo_bar}", "naming 
convention");
+        assertErrorContains("<#ftl strip_whitespace=true>${.fooBar}", "naming 
convention");
+        
+        
getConfiguration().setNamingConvention(Configuration.CAMEL_CASE_NAMING_CONVENTION);
+        assertErrorContains("<#ftl strip_whitespace=true>", "naming 
convention");
+        assertOutput("<#ftl stripWhitespace=true>${.outputEncoding}", "utf-8");
+        
+        
getConfiguration().setNamingConvention(Configuration.LEGACY_NAMING_CONVENTION);
+        assertErrorContains("<#ftl stripWhitespace=true>", "naming 
convention");
+        assertOutput("<#ftl strip_whitespace=true>${.output_encoding}", 
"utf-8");
+        
+        
getConfiguration().setNamingConvention(Configuration.AUTO_DETECT_NAMING_CONVENTION);
+        assertOutput("<#ftl stripWhitespace=true>${.outputEncoding}", "utf-8");
+        assertOutput("<#ftl encoding='iso-8859-1' 
stripWhitespace=true>${.outputEncoding}", "utf-8");
+        assertOutput("<#ftl stripWhitespace=true 
encoding='iso-8859-1'>${.outputEncoding}", "utf-8");
+        assertOutput("<#ftl encoding='iso-8859-1' 
strip_whitespace=true>${.output_encoding}", "utf-8");
+        assertOutput("<#ftl strip_whitespace=true 
encoding='iso-8859-1'>${.output_encoding}", "utf-8");
+    }
+    
+    @Test
+    public void camelCaseSettingNamesInErrorMessages() throws IOException, 
TemplateException {
+        assertErrorContains("<#setting fooBar=1>", "booleanFormat", 
"\\!boolean_format");
+        assertErrorContains("<#setting foo_bar=1>", "boolean_format", 
"\\!booleanFormat");
+        // [2.4] If camel case will be the recommended style, then this need 
to be inverted:
+        assertErrorContains("<#setting foo=1>", "boolean_format", 
"\\!booleanFormat");
+
+        assertErrorContains("<#if x><#elseIf y></#if><#setting foo=1>", 
"booleanFormat", "\\!boolean_format");
+        assertErrorContains("<#if x><#elseif y></#if><#setting foo=1>", 
"boolean_format", "\\!booleanFormat");
+
+        
getConfiguration().setNamingConvention(Configuration.CAMEL_CASE_NAMING_CONVENTION);
+        assertErrorContains("<#setting foo=1>", "booleanFormat", 
"\\!boolean_format");
+        
getConfiguration().setNamingConvention(Configuration.LEGACY_NAMING_CONVENTION);
+        assertErrorContains("<#setting foo=1>", "boolean_format", 
"\\!booleanFormat");
+    }
+    
+    @Test
+    public void camelCaseIncludeParameters() throws IOException, 
TemplateException {
+        assertOutput("<#ftl stripWhitespace=true>[<#include 'noSuchTemplate' 
ignoreMissing=true>]", "[]");
+        assertOutput("<#ftl strip_whitespace=true>[<#include 'noSuchTemplate' 
ignore_missing=true>]", "[]");
+        assertErrorContains("<#ftl stripWhitespace=true>[<#include 
'noSuchTemplate' ignore_missing=true>]",
+                "naming convention", "ignore_missing");
+        assertErrorContains("<#ftl strip_whitespace=true>[<#include 
'noSuchTemplate' ignoreMissing=true>]",
+                "naming convention", "ignoreMissing");
+    }
+    
+    @Test
+    public void specialVarsHasBothNamingStyle() throws IOException, 
TemplateException {
+        assertContainsBothNamingStyles(
+                new 
HashSet(Arrays.asList(ASTExpBuiltInVariable.SPEC_VAR_NAMES)),
+                new NamePairAssertion() { @Override
+                public void assertPair(String name1, String name2) { } });
+    }
+    
+    @Test
+    public void camelCaseBuiltIns() throws IOException, TemplateException {
+        assertOutput("${'x'?upperCase}", "X");
+        assertOutput("${'x'?upper_case}", "X");
+    }
+
+    @Test
+    public void stringLiteralInterpolation() throws IOException, 
TemplateException {
+        assertEquals(Configuration.AUTO_DETECT_NAMING_CONVENTION, 
getConfiguration().getNamingConvention());
+        getConfiguration().setSharedVariable("x", "x");
+        
+        assertOutput("${'-${x?upperCase}-'} ${x?upperCase}", "-X- X");
+        assertOutput("${x?upperCase} ${'-${x?upperCase}-'}", "X -X-");
+        assertOutput("${'-${x?upper_case}-'} ${x?upper_case}", "-X- X");
+        assertOutput("${x?upper_case} ${'-${x?upper_case}-'}", "X -X-");
+
+        assertErrorContains("${'-${x?upper_case}-'} ${x?upperCase}",
+                "naming convention", "legacy", "upperCase", "detection", "9");
+        assertErrorContains("${x?upper_case} ${'-${x?upperCase}-'}",
+                "naming convention", "legacy", "upperCase", "detection", "5");
+        assertErrorContains("${'-${x?upperCase}-'} ${x?upper_case}",
+                "naming convention", "camel", "upper_case");
+        assertErrorContains("${x?upperCase} ${'-${x?upper_case}-'}",
+                "naming convention", "camel", "upper_case");
+        
+        
getConfiguration().setNamingConvention(Configuration.CAMEL_CASE_NAMING_CONVENTION);
+        assertOutput("${'-${x?upperCase}-'} ${x?upperCase}", "-X- X");
+        assertErrorContains("${'-${x?upper_case}-'}",
+                "naming convention", "camel", "upper_case", "\\!detection");
+        
+        
getConfiguration().setNamingConvention(Configuration.LEGACY_NAMING_CONVENTION);
+        assertOutput("${'-${x?upper_case}-'} ${x?upper_case}", "-X- X");
+        assertErrorContains("${'-${x?upperCase}-'}",
+                "naming convention", "legacy", "upperCase", "\\!detection");
+    }
+    
+    @Test
+    public void evalAndInterpret() throws IOException, TemplateException {
+        assertEquals(Configuration.AUTO_DETECT_NAMING_CONVENTION, 
getConfiguration().getNamingConvention());
+        // The naming convention detected doesn't affect the enclosing 
template's naming convention.
+        // - ?eval:
+        assertOutput("${\"'x'?upperCase\"?eval}${'x'?upper_case}", "XX");
+        assertOutput("${\"'x'?upper_case\"?eval}${'x'?upperCase}", "XX");
+        assertOutput("${'x'?upperCase}${\"'x'?upper_case\"?eval}", "XX");
+        assertErrorContains("${\"'x'\n?upperCase\n?is_string\"?eval}",
+                "naming convention", "camel", "upperCase", "is_string", "line 
2", "line 3");
+        // - ?interpret:
+        assertOutput("<@r\"${'x'?upperCase}\"?interpret />${'x'?upper_case}", 
"XX");
+        assertOutput("<@r\"${'x'?upper_case}\"?interpret />${'x'?upperCase}", 
"XX");
+        assertOutput("${'x'?upper_case}<@r\"${'x'?upperCase}\"?interpret />", 
"XX");
+        assertErrorContains("<@r\"${'x'\n?upperCase\n?is_string}\"?interpret 
/>",
+                "naming convention", "camel", "upperCase", "is_string", "line 
2", "line 3");
+        
+        // Will be inherited by ?eval-ed/?interpreted fragments:
+        
getConfiguration().setNamingConvention(Configuration.CAMEL_CASE_NAMING_CONVENTION);
+        // - ?eval:
+        assertErrorContains("${\"'x'?upper_case\"?eval}", "naming convention", 
"camel", "upper_case");
+        assertOutput("${\"'x'?upperCase\"?eval}", "X");
+        // - ?interpret:
+        assertErrorContains("<@r\"${'x'?upper_case}\"?interpret />", "naming 
convention", "camel", "upper_case");
+        assertOutput("<@r\"${'x'?upperCase}\"?interpret />", "X");
+        
+        // Again, will be inherited by ?eval-ed/?interpreted fragments:
+        
getConfiguration().setNamingConvention(Configuration.LEGACY_NAMING_CONVENTION);
+        // - ?eval:
+        assertErrorContains("${\"'x'?upperCase\"?eval}", "naming convention", 
"legacy", "upperCase");
+        assertOutput("${\"'x'?upper_case\"?eval}", "X");
+        // - ?interpret:
+        assertErrorContains("<@r\"${'x'?upperCase}\"?interpret />", "naming 
convention", "legacy", "upperCase");
+        assertOutput("<@r\"${'x'?upper_case}\"?interpret />", "X");
+    }
+    
+    @Test
+    public void camelCaseBuiltInErrorMessage() throws IOException, 
TemplateException {
+        assertErrorContains("${'x'?upperCasw}", "upperCase", "\\!upper_case");
+        assertErrorContains("${'x'?upper_casw}", "upper_case", "\\!upperCase");
+        // [2.4] If camel case will be the recommended style, then this need 
to be inverted:
+        assertErrorContains("${'x'?foo}", "upper_case", "\\!upperCase");
+        
+        assertErrorContains("<#if x><#elseIf y></#if> ${'x'?foo}", 
"upperCase", "\\!upper_case");
+        assertErrorContains("<#if x><#elseif y></#if>${'x'?foo}", 
"upper_case", "\\!upperCase");
+        
+        
getConfiguration().setNamingConvention(Configuration.CAMEL_CASE_NAMING_CONVENTION);
+        assertErrorContains("${'x'?foo}", "upperCase", "\\!upper_case");
+        
getConfiguration().setNamingConvention(Configuration.LEGACY_NAMING_CONVENTION);
+        assertErrorContains("${'x'?foo}", "upper_case", "\\!upperCase");
+    }
+    
+    @Test
+    public void builtInsHasBothNamingStyle() throws IOException, 
TemplateException {
+        
assertContainsBothNamingStyles(getConfiguration().getSupportedBuiltInNames(), 
new NamePairAssertion() {
+
+            @Override
+            public void assertPair(String name1, String name2) {
+                ASTExpBuiltIn bi1  = 
ASTExpBuiltIn.BUILT_INS_BY_NAME.get(name1);
+                ASTExpBuiltIn bi2 = ASTExpBuiltIn.BUILT_INS_BY_NAME.get(name2);
+                assertTrue("\"" + name1 + "\" and \"" + name2 + "\" doesn't 
belong to the same BI object.",
+                        bi1 == bi2);
+            }
+            
+        });
+    }
+
+    private void assertContainsBothNamingStyles(Set<String> names, 
NamePairAssertion namePairAssertion) {
+        Set<String> underscoredNamesWithCamelCasePair = new HashSet<>();
+        for (String name : names) {
+            if (_StringUtil.getIdentifierNamingConvention(name) == 
Configuration.CAMEL_CASE_NAMING_CONVENTION) {
+                String underscoredName = 
correctIsoBIExceptions(_StringUtil.camelCaseToUnderscored(name)); 
+                assertTrue(
+                        "Missing underscored variation \"" + underscoredName + 
"\" for \"" + name + "\".",
+                        names.contains(underscoredName));
+                
assertTrue(underscoredNamesWithCamelCasePair.add(underscoredName));
+                
+                namePairAssertion.assertPair(name, underscoredName);
+            }
+        }
+        for (String name : names) {
+            if (_StringUtil.getIdentifierNamingConvention(name) == 
Configuration.LEGACY_NAMING_CONVENTION) {
+                assertTrue("Missing camel case variation for \"" + name + 
"\".",
+                        underscoredNamesWithCamelCasePair.contains(name));
+            }
+        }
+    }
+    
+    private String correctIsoBIExceptions(String underscoredName) {
+        return underscoredName.replace("_n_z", "_nz").replace("_f_z", "_fz");
+    }
+    
+    @Test
+    public void camelCaseDirectives() throws IOException, TemplateException {
+        camelCaseDirectives(false);
+        getConfiguration().setTagSyntax(Configuration.AUTO_DETECT_TAG_SYNTAX);
+        camelCaseDirectives(true);
+    }
+
+    private void camelCaseDirectives(boolean squared) throws IOException, 
TemplateException {
+        assertOutput(
+                squared("<#list 1..4 as x><#if x == 1>one <#elseIf x == 2>two 
<#elseIf x == 3>three "
+                        + "<#else>more</#if></#list>", squared),
+                "one two three more");
+        assertOutput(
+                squared("<#list 1..4 as x><#if x == 1>one <#elseif x == 2>two 
<#elseif x == 3>three "
+                        + "<#else>more</#if></#list>", squared),
+                "one two three more");
+        
+        assertOutput(
+                squared("<#escape x as 
x?upperCase>${'a'}<#noEscape>${'b'}</#noEscape></#escape>", squared),
+                "Ab");
+        assertOutput(
+                squared("<#escape x as 
x?upper_case>${'a'}<#noescape>${'b'}</#noescape></#escape>", squared),
+                "Ab");
+        
+        assertOutput(
+                squared("<#noParse></#noparse></#noParse>", squared),
+                squared("</#noparse>", squared));
+        assertOutput(
+                squared("<#noparse></#noParse></#noparse>", squared),
+                squared("</#noParse>", squared));
+        
+        assertOutput(
+                squared("<#forEach x in 1..3>${x}</#forEach>", squared),
+                "123");
+        assertOutput(
+                squared("<#foreach x in 1..3>${x}</#foreach>", squared),
+                "123");
+    }
+    
+    private String squared(String ftl, boolean squared) {
+        return squared ? ftl.replace('<', '[').replace('>', ']') : ftl;
+    }
+
+    @Test
+    public void explicitNamingConvention() throws IOException, 
TemplateException {
+        explicitNamingConvention(false);
+        explicitNamingConvention(true);
+    }
+    
+    private void explicitNamingConvention(boolean squared) throws IOException, 
TemplateException {
+        if (squared) {
+            
getConfiguration().setTagSyntax(Configuration.AUTO_DETECT_TAG_SYNTAX);
+        }
+        
+        
getConfiguration().setNamingConvention(Configuration.CAMEL_CASE_NAMING_CONVENTION);
+        
+        assertErrorContains(
+                squared("<#if true>t<#elseif false>f</#if>", squared),
+                "naming convention", "camel", "#elseif");
+        assertOutput(
+                squared("<#if true>t<#elseIf false>f</#if>", squared),
+                "t");
+        
+        assertErrorContains(
+                squared("<#noparse>${x}</#noparse>", squared),
+                "naming convention", "camel", "#noparse");
+        assertOutput(
+                squared("<#noParse>${x}</#noParse>", squared),
+                "${x}");
+        
+        assertErrorContains(
+                squared("<#escape x as 
-x><#noescape>${1}</#noescape></#escape>", squared),
+                "naming convention", "camel", "#noescape");
+        assertOutput(
+                squared("<#escape x as 
-x><#noEscape>${1}</#noEscape></#escape>", squared),
+                "1");
+        
+        assertErrorContains(
+                squared("<#foreach x in 1..3>${x}</#foreach>", squared),
+                "naming convention", "camel", "#foreach");
+        assertOutput(
+                squared("<#forEach x in 1..3>${x}</#forEach>", squared),
+                "123");
+        
+        // ---
+        
+        
getConfiguration().setNamingConvention(Configuration.LEGACY_NAMING_CONVENTION);
+        
+        assertErrorContains(
+                squared("<#if true>t<#elseIf false>f</#if>", squared),
+                "naming convention", "legacy", "#elseIf");
+        assertOutput(
+                squared("<#if true>t<#elseif false>f</#if>", squared),
+                "t");
+        
+        assertErrorContains(
+                squared("<#noParse>${x}</#noParse>", squared),
+                "naming convention", "legacy", "#noParse");
+        assertOutput(
+                squared("<#noparse>${x}</#noparse>", squared),
+                "${x}");
+        
+        assertErrorContains(
+                squared("<#escape x as 
-x><#noEscape>${1}</#noEscape></#escape>", squared),
+                "naming convention", "legacy", "#noEscape");
+        assertOutput(
+                squared("<#escape x as 
-x><#noescape>${1}</#noescape></#escape>", squared),
+                "1");
+        
+        assertErrorContains(
+                squared("<#forEach x in 1..3>${x}</#forEach>", squared),
+                "naming convention", "legacy", "#forEach");
+        assertOutput(
+                squared("<#foreach x in 1..3>${x}</#foreach>", squared),
+                "123");
+    }
+    
+    @Test
+    public void inconsistentAutoDetectedNamingConvention() {
+        assertErrorContains(
+                "<#if x><#elseIf y><#elseif z></#if>",
+                "naming convention", "camel");
+        assertErrorContains(
+                "<#if x><#elseif y><#elseIf z></#if>",
+                "naming convention", "legacy");
+        assertErrorContains(
+                "<#if x><#elseIf y></#if><#noparse></#noparse>",
+                "naming convention", "camel");
+        assertErrorContains(
+                "<#if x><#elseif y></#if><#noParse></#noParse>",
+                "naming convention", "legacy");
+        assertErrorContains(
+                "<#if x><#elseif y><#elseIf z></#if>",
+                "naming convention", "legacy");
+        assertErrorContains(
+                "<#escape x as x + 1><#noEscape></#noescape></#escape>",
+                "naming convention", "camel");
+        assertErrorContains(
+                "<#escape x as x + 
1><#noEscape></#noEscape><#noescape></#noescape></#escape>",
+                "naming convention", "camel");
+        assertErrorContains(
+                "<#escape x as x + 1><#noescape></#noEscape></#escape>",
+                "naming convention", "legacy");
+        assertErrorContains(
+                "<#escape x as x + 
1><#noescape></#noescape><#noEscape></#noEscape></#escape>",
+                "naming convention", "legacy");
+        assertErrorContains(
+                "<#forEach x in 1..3>${x}</#foreach>",
+                "naming convention", "camel");
+        assertErrorContains(
+                "<#forEach x in 1..3>${x}</#forEach><#foreach x in 
1..3>${x}</#foreach>",
+                "naming convention", "camel");
+        assertErrorContains(
+                "<#foreach x in 1..3>${x}</#forEach>",
+                "naming convention", "legacy");
+        assertErrorContains(
+                "<#foreach x in 1..3>${x}</#foreach><#forEach x in 
1..3>${x}</#forEach>",
+                "naming convention", "legacy");
+        
+        assertErrorContains("${x?upperCase?is_string}",
+                "naming convention", "camel", "upperCase", "is_string");
+        assertErrorContains("${x?upper_case?isString}",
+                "naming convention", "legacy", "upper_case", "isString");
+
+        assertErrorContains("<#setting outputEncoding='utf-8'>${x?is_string}",
+                "naming convention", "camel", "outputEncoding", "is_string");
+        assertErrorContains("<#setting output_encoding='utf-8'>${x?isString}",
+                "naming convention", "legacy", "output_encoding", "isString");
+        
+        assertErrorContains("${x?isString}<#setting output_encoding='utf-8'>",
+                "naming convention", "camel", "isString", "output_encoding");
+        assertErrorContains("${x?is_string}<#setting outputEncoding='utf-8'>",
+                "naming convention", "legacy", "is_string", "outputEncoding");
+        
+        assertErrorContains("${.outputEncoding}${x?is_string}",
+                "naming convention", "camel", "outputEncoding", "is_string");
+        assertErrorContains("${.output_encoding}${x?isString}",
+                "naming convention", "legacy", "output_encoding", "isString");
+        
+        assertErrorContains("${x?upperCase}<#noparse></#noparse>",
+                "naming convention", "camel", "upperCase", "noparse");
+        assertErrorContains("${x?upper_case}<#noParse></#noParse>",
+                "naming convention", "legacy", "upper_case", "noParse");
+    }
+    
+    private interface NamePairAssertion {
+        
+        void assertPair(String name1, String name2);
+        
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/test/java/org/apache/freemarker/core/CanonicalFormTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/CanonicalFormTest.java 
b/src/test/java/org/apache/freemarker/core/CanonicalFormTest.java
new file mode 100644
index 0000000..b0be788
--- /dev/null
+++ b/src/test/java/org/apache/freemarker/core/CanonicalFormTest.java
@@ -0,0 +1,71 @@
+/*
+ * 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 org.apache.freemarker.core.Configuration;
+import org.apache.freemarker.core.ParseException;
+import org.apache.freemarker.core.TemplateNotFoundException;
+import 
org.apache.freemarker.core.templateresolver.MalformedTemplateNameException;
+import org.apache.freemarker.core.templateresolver.impl.ClassTemplateLoader;
+import org.apache.freemarker.test.CopyrightCommentRemoverTemplateLoader;
+import org.apache.freemarker.test.util.FileTestCase;
+
+public class CanonicalFormTest extends FileTestCase {
+
+    public CanonicalFormTest(String name) {
+        super(name);
+    }
+
+    public void testMacrosCanonicalForm() throws Exception {
+        assertCanonicalFormOf("cano-macros.ftl");
+    }
+    
+    public void testIdentifierEscapingCanonicalForm() throws Exception {
+        assertCanonicalFormOf("cano-identifier-escaping.ftl");
+    }
+
+    public void testAssignmentCanonicalForm() throws Exception {
+        assertCanonicalFormOf("cano-assignments.ftl");
+    }
+
+    public void testBuiltInCanonicalForm() throws Exception {
+        assertCanonicalFormOf("cano-builtins.ftl");
+    }
+
+    public void testStringLiteralInterpolationCanonicalForm() throws Exception 
{
+        assertCanonicalFormOf("cano-strlitinterpolation.ftl");
+    }
+    
+    private void assertCanonicalFormOf(String ftlFileName)
+            throws TemplateNotFoundException, MalformedTemplateNameException, 
ParseException, IOException {
+        Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
+        cfg.setTemplateLoader(
+                new CopyrightCommentRemoverTemplateLoader(
+                        new ClassTemplateLoader(CanonicalFormTest.class, "")));
+        StringWriter sw = new StringWriter();
+        cfg.getTemplate(ftlFileName).dump(sw);
+
+        assertExpectedFileEqualsString(ftlFileName + ".out", sw.toString());
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/test/java/org/apache/freemarker/core/CoercionToTextualTest.java
----------------------------------------------------------------------
diff --git 
a/src/test/java/org/apache/freemarker/core/CoercionToTextualTest.java 
b/src/test/java/org/apache/freemarker/core/CoercionToTextualTest.java
new file mode 100644
index 0000000..fe8da82
--- /dev/null
+++ b/src/test/java/org/apache/freemarker/core/CoercionToTextualTest.java
@@ -0,0 +1,137 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.freemarker.core;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Date;
+
+import org.apache.freemarker.core.Configuration;
+import org.apache.freemarker.core.HTMLOutputFormat;
+import org.apache.freemarker.core.TemplateException;
+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.test.TemplateTest;
+import org.junit.Before;
+import org.junit.Test;
+
+@SuppressWarnings("boxing")
+public class CoercionToTextualTest extends TemplateTest {
+    
+    /** 2015-09-06T12:00:00Z */
+    private static long T = 1441540800000L;
+    private static TemplateDateModel TM = new SimpleDate(new Date(T), 
TemplateDateModel.DATETIME);
+    
+    @Test
+    public void testBasicStringBuiltins() throws IOException, 
TemplateException {
+        assertOutput("${s?upperCase}", "ABC");
+        assertOutput("${n?string?lowerCase}", "1.50e+03");
+        assertErrorContains("${n?lowerCase}", "convert", "string", "markup", 
"text/html");
+        assertOutput("${dt?string?lowerCase}", "2015-09-06t12:00:00z");
+        assertErrorContains("${dt?lowerCase}", "convert", "string", "markup", 
"text/html");
+        assertOutput("${b?upperCase}", "Y");
+        assertErrorContains("${m?upperCase}", "convertible to string", 
"HTMLOutputModel");
+    }
+
+    @Test
+    public void testEscBuiltin() throws IOException, TemplateException {
+        Configuration cfg = getConfiguration();
+        cfg.setOutputFormat(HTMLOutputFormat.INSTANCE);
+        cfg.setAutoEscapingPolicy(Configuration.DISABLE_AUTO_ESCAPING_POLICY);
+        cfg.setBooleanFormat("<y>,<n>");
+        
+        assertOutput("${'a<b'?esc}", "a&lt;b");
+        assertOutput("${n?string?esc}", "1.50E+03");
+        assertOutput("${n?esc}", "1.50*10<sup>3</sup>");
+        assertOutput("${dt?string?esc}", "2015-09-06T12:00:00Z");
+        assertOutput("${dt?esc}", "2015-09-06<span 
class='T'>T</span>12:00:00Z");
+        assertOutput("${b?esc}", "&lt;y&gt;");
+        assertOutput("${m?esc}", "<p>M</p>");
+    }
+    
+    @Test
+    public void testStringOverloadedBuiltIns() throws IOException, 
TemplateException {
+        assertOutput("${s?contains('b')}", "y");
+        assertOutput("${n?string?contains('E')}", "y");
+        assertErrorContains("${n?contains('E')}", "convert", "string", 
"markup", "text/html");
+        assertErrorContains("${n?indexOf('E')}", "convert", "string", 
"markup", "text/html");
+        assertOutput("${dt?string?contains('0')}", "y");
+        assertErrorContains("${dt?contains('0')}", "convert", "string", 
"markup", "text/html");
+        assertErrorContains("${m?contains('0')}", "convertible to string", 
"HTMLOutputModel");
+        assertErrorContains("${m?indexOf('0')}", "convertible to string", 
"HTMLOutputModel");
+    }
+    
+    @Test
+    public void testMarkupStringBuiltIns() throws IOException, 
TemplateException {
+        assertErrorContains("${n?string?markupString}", "Expected", "markup", 
"string");
+        assertErrorContains("${n?markupString}", "Expected", "markup", 
"number");
+        assertErrorContains("${dt?markupString}", "Expected", "markup", 
"date");
+    }
+    
+    @Test
+    public void testSimpleInterpolation() throws IOException, 
TemplateException {
+        assertOutput("${s}", "abc");
+        assertOutput("${n?string}", "1.50E+03");
+        assertOutput("${n}", "1.50*10<sup>3</sup>");
+        assertOutput("${dt?string}", "2015-09-06T12:00:00Z");
+        assertOutput("${dt}", "2015-09-06<span class='T'>T</span>12:00:00Z");
+        assertOutput("${b}", "y");
+        assertOutput("${m}", "<p>M</p>");
+    }
+    
+    @Test
+    public void testConcatenation() throws IOException, TemplateException {
+        assertOutput("${s + '&'}", "abc&");
+        assertOutput("${n?string + '&'}", "1.50E+03&");
+        assertOutput("${n + '&'}", "1.50*10<sup>3</sup>&amp;");
+        assertOutput("${dt?string + '&'}", "2015-09-06T12:00:00Z&");
+        assertOutput("${dt + '&'}", "2015-09-06<span 
class='T'>T</span>12:00:00Z&amp;");
+        assertOutput("${b + '&'}", "y&");
+        assertOutput("${m + '&'}", "<p>M</p>&amp;");
+    }
+
+    @Test
+    public void testConcatenation2() throws IOException, TemplateException {
+        assertOutput("${'&' + s}", "&abc");
+        assertOutput("${'&' + n?string}", "&1.50E+03");
+        assertOutput("${'&' + n}", "&amp;1.50*10<sup>3</sup>");
+        assertOutput("${'&' + dt?string}", "&2015-09-06T12:00:00Z");
+        assertOutput("${'&' + dt}", "&amp;2015-09-06<span 
class='T'>T</span>12:00:00Z");
+        assertOutput("${'&' + b}", "&y");
+        assertOutput("${'&' + m}", "&amp;<p>M</p>");
+    }
+    
+    @Before
+    public void setup() throws TemplateModelException {
+        Configuration cfg = getConfiguration();
+        cfg.setCustomNumberFormats(Collections.singletonMap("G", 
PrintfGTemplateNumberFormatFactory.INSTANCE));
+        cfg.setCustomDateFormats(Collections.singletonMap("HI", 
HTMLISOTemplateDateFormatFactory.INSTANCE));
+        cfg.setNumberFormat("@G 3");
+        cfg.setDateTimeFormat("@HI");
+        cfg.setBooleanFormat("y,n");
+        
+        addToDataModel("s", "abc");
+        addToDataModel("n", 1500);
+        addToDataModel("dt", TM);
+        addToDataModel("b", Boolean.TRUE);
+        addToDataModel("m", HTMLOutputFormat.INSTANCE.fromMarkup("<p>M</p>"));
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/test/java/org/apache/freemarker/core/CombinedMarkupOutputFormatTest.java
----------------------------------------------------------------------
diff --git 
a/src/test/java/org/apache/freemarker/core/CombinedMarkupOutputFormatTest.java 
b/src/test/java/org/apache/freemarker/core/CombinedMarkupOutputFormatTest.java
new file mode 100644
index 0000000..783c437
--- /dev/null
+++ 
b/src/test/java/org/apache/freemarker/core/CombinedMarkupOutputFormatTest.java
@@ -0,0 +1,198 @@
+/*
+ * 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 org.apache.freemarker.core.CombinedMarkupOutputFormat;
+import org.apache.freemarker.core.HTMLOutputFormat;
+import org.apache.freemarker.core.MarkupOutputFormat;
+import org.apache.freemarker.core.RTFOutputFormat;
+import org.apache.freemarker.core.TemplateCombinedMarkupOutputModel;
+import org.apache.freemarker.core.XMLOutputFormat;
+import org.apache.freemarker.core.model.TemplateModelException;
+import org.junit.Test; 
+
+public class CombinedMarkupOutputFormatTest {
+    
+    private static final CombinedMarkupOutputFormat HTML_RTF = new 
CombinedMarkupOutputFormat(
+            HTMLOutputFormat.INSTANCE, RTFOutputFormat.INSTANCE);
+    private static final CombinedMarkupOutputFormat XML_XML = new 
CombinedMarkupOutputFormat(
+            XMLOutputFormat.INSTANCE, XMLOutputFormat.INSTANCE);
+
+    @Test
+    public void testName() {
+        assertEquals("HTML{RTF}", HTML_RTF.getName());
+        assertEquals("XML{XML}", XML_XML.getName());
+    }
+    
+    @Test
+    public void testOutputMO() throws TemplateModelException, IOException {
+       StringWriter out = new StringWriter();
+       
+       HTML_RTF.output(HTML_RTF.fromMarkup("<pre>\\par Test "), out);
+       HTML_RTF.output(HTML_RTF.fromPlainTextByEscaping("foo { bar } \\ "), 
out);
+       HTML_RTF.output(HTML_RTF.fromPlainTextByEscaping("& baaz "), out);
+       HTML_RTF.output(HTML_RTF.fromPlainTextByEscaping("\\par & qwe"), out);
+       HTML_RTF.output(HTML_RTF.fromMarkup("\\par{0} End</pre>"), out);
+       
+       assertEquals(
+               "<pre>\\par Test "
+               + "foo \\{ bar \\} \\\\ "
+               + "&amp; baaz "
+               + "\\\\par &amp; qwe"
+               + "\\par{0} End</pre>",
+               out.toString());
+    }
+
+    @Test
+    public void testOutputMO2() throws TemplateModelException, IOException {
+       StringWriter out = new StringWriter();
+       
+       XML_XML.output(XML_XML.fromMarkup("<pre>&lt;p&gt; Test "), out);
+       XML_XML.output(XML_XML.fromPlainTextByEscaping("a & b < c"), out);
+       XML_XML.output(XML_XML.fromMarkup(" End</pre>"), out);
+       
+       assertEquals(
+               "<pre>&lt;p&gt; Test "
+               + "a &amp;amp; b &amp;lt; c"
+               + " End</pre>",
+               out.toString());
+    }
+
+    @Test
+    public void testOutputMO3() throws TemplateModelException, IOException {
+        MarkupOutputFormat outputFormat = new CombinedMarkupOutputFormat(
+                RTFOutputFormat.INSTANCE,
+                new CombinedMarkupOutputFormat(RTFOutputFormat.INSTANCE, 
RTFOutputFormat.INSTANCE));
+        StringWriter out = new StringWriter();
+        
+        outputFormat.output(outputFormat.fromPlainTextByEscaping("b{}"), out);
+        outputFormat.output(outputFormat.fromMarkup("a{}"), out);
+        
+        assertEquals(
+                "b\\\\\\\\\\\\\\{\\\\\\\\\\\\\\}"
+                + "a{}",
+                out.toString());
+    }
+    
+    @Test
+    public void testOutputString() throws TemplateModelException, IOException {
+        StringWriter out = new StringWriter();
+        
+        HTML_RTF.output("a", out);
+        HTML_RTF.output("{", out);
+        HTML_RTF.output("<b>}c", out);
+        
+        assertEquals("a\\{&lt;b&gt;\\}c", out.toString());
+    }
+    
+    @Test
+    public void testOutputString2() throws TemplateModelException, IOException 
{
+        StringWriter out = new StringWriter();
+        
+        XML_XML.output("a", out);
+        XML_XML.output("&", out);
+        XML_XML.output("<b>", out);
+        
+        assertEquals("a&amp;amp;&amp;lt;b&amp;gt;", out.toString());
+    }
+    
+    @Test
+    public void testFromPlainTextByEscaping() throws TemplateModelException {
+        String plainText = "a\\b&c";
+        TemplateCombinedMarkupOutputModel mo = 
HTML_RTF.fromPlainTextByEscaping(plainText);
+        assertSame(plainText, mo.getPlainTextContent());
+        assertNull(mo.getMarkupContent()); // Not the MO's duty to calculate 
it!
+    }
+
+    @Test
+    public void testFromMarkup() throws TemplateModelException {
+        String markup = "a \\par <b>";
+        TemplateCombinedMarkupOutputModel mo = HTML_RTF.fromMarkup(markup);
+        assertSame(markup, mo.getMarkupContent());
+        assertNull(mo.getPlainTextContent()); // Not the MO's duty to 
calculate it!
+    }
+    
+    @Test
+    public void testGetMarkup() throws TemplateModelException {
+        {
+            String markup = "a \\par <b>";
+            TemplateCombinedMarkupOutputModel mo = HTML_RTF.fromMarkup(markup);
+            assertSame(markup, HTML_RTF.getMarkupString(mo));
+        }
+        
+        {
+            String safe = "abc";
+            TemplateCombinedMarkupOutputModel mo = 
HTML_RTF.fromPlainTextByEscaping(safe);
+            assertSame(safe, HTML_RTF.getMarkupString(mo));
+        }
+    }
+    
+    @Test
+    public void testConcat() throws Exception {
+        assertMO(
+                "ab", null,
+                HTML_RTF.concat(
+                        new TemplateCombinedMarkupOutputModel("a", null, 
HTML_RTF),
+                        new TemplateCombinedMarkupOutputModel("b", null, 
HTML_RTF)));
+        assertMO(
+                null, "ab",
+                HTML_RTF.concat(
+                        new TemplateCombinedMarkupOutputModel(null, "a", 
HTML_RTF),
+                        new TemplateCombinedMarkupOutputModel(null, "b", 
HTML_RTF)));
+        assertMO(
+                null, "{<a>}\\{&lt;b&gt;\\}",
+                HTML_RTF.concat(
+                        new TemplateCombinedMarkupOutputModel(null, "{<a>}", 
HTML_RTF),
+                        new TemplateCombinedMarkupOutputModel("{<b>}", null, 
HTML_RTF)));
+        assertMO(
+                null, "\\{&lt;a&gt;\\}{<b>}",
+                HTML_RTF.concat(
+                        new TemplateCombinedMarkupOutputModel("{<a>}", null, 
HTML_RTF),
+                        new TemplateCombinedMarkupOutputModel(null, "{<b>}", 
HTML_RTF)));
+    }
+    
+    @Test
+    public void testEscaplePlainText() throws TemplateModelException {
+        assertEquals("", HTML_RTF.escapePlainText(""));
+        assertEquals("a", HTML_RTF.escapePlainText("a"));
+        assertEquals("\\{a\\\\b&amp;\\}", HTML_RTF.escapePlainText("{a\\b&}"));
+        assertEquals("a\\\\b&amp;", HTML_RTF.escapePlainText("a\\b&"));
+        assertEquals("\\{\\}&amp;", HTML_RTF.escapePlainText("{}&"));
+        
+        assertEquals("a", XML_XML.escapePlainText("a"));
+        assertEquals("a&amp;apos;b", XML_XML.escapePlainText("a'b"));
+    }
+    
+    private void assertMO(String pc, String mc, 
TemplateCombinedMarkupOutputModel mo) {
+        assertEquals(pc, mo.getPlainTextContent());
+        assertEquals(mc, mo.getMarkupContent());
+    }
+    
+    @Test
+    public void testGetMimeType() {
+        assertEquals("text/html", HTML_RTF.getMimeType());
+        assertEquals("application/xml", XML_XML.getMimeType());
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/test/java/org/apache/freemarker/core/ConfigurableTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/ConfigurableTest.java 
b/src/test/java/org/apache/freemarker/core/ConfigurableTest.java
new file mode 100644
index 0000000..8e2abd8
--- /dev/null
+++ b/src/test/java/org/apache/freemarker/core/ConfigurableTest.java
@@ -0,0 +1,183 @@
+/*
+ * 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.greaterThan;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.freemarker.core.Configurable;
+import org.apache.freemarker.core.Configuration;
+import org.apache.freemarker.core.Template;
+import org.apache.freemarker.core.util._StringUtil;
+import org.junit.Test;
+
+public class ConfigurableTest {
+
+    @Test
+    public void testGetSettingNamesAreSorted() throws Exception {
+        Configurable cfgable = createConfigurable();
+        for (boolean camelCase : new boolean[] { false, true }) {
+            Collection<String> names = cfgable.getSettingNames(camelCase);
+            String prevName = null;
+            for (String name : names) {
+                if (prevName != null) {
+                    assertThat(name, greaterThan(prevName));
+                }
+                prevName = name;
+            }
+        }
+    }
+
+    @Test
+    public void testStaticFieldKeysCoverAllGetSettingNames() throws Exception {
+        Configurable cfgable = createConfigurable();
+        Collection<String> names = cfgable.getSettingNames(false);
+        for (String name : names) {
+                assertTrue("No field was found for " + name, 
keyFieldExists(name));
+        }
+    }
+    
+    @Test
+    public void testGetSettingNamesCoversAllStaticKeyFields() throws Exception 
{
+        Configurable cfgable = createConfigurable();
+        Collection<String> names = cfgable.getSettingNames(false);
+        
+        for (Field f : Configurable.class.getFields()) {
+            if (f.getName().endsWith("_KEY")) {
+                final Object name = f.get(null);
+                assertTrue("Missing setting name: " + name, 
names.contains(name));
+            }
+        }
+    }
+
+    @Test
+    public void testKeyStaticFieldsHasAllVariationsAndCorrectFormat() throws 
IllegalArgumentException, IllegalAccessException {
+        
ConfigurableTest.testKeyStaticFieldsHasAllVariationsAndCorrectFormat(Configurable.class);
+    }
+    
+    @Test
+    public void testGetSettingNamesNameConventionsContainTheSame() throws 
Exception {
+        Configurable cfgable = createConfigurable();
+        ConfigurableTest.testGetSettingNamesNameConventionsContainTheSame(
+                new ArrayList<>(cfgable.getSettingNames(false)),
+                new ArrayList<>(cfgable.getSettingNames(true)));
+    }
+
+    public static void testKeyStaticFieldsHasAllVariationsAndCorrectFormat(
+            Class<? extends Configurable> confClass) throws 
IllegalArgumentException, IllegalAccessException {
+        // For all _KEY fields there must be a _KEY_CAMEL_CASE and a 
_KEY_SNAKE_CASE field.
+        // Their content must not contradict the expected naming convention.
+        // They _KEY filed value must be deducable from the field name
+        // The _KEY value must be the same as _KEY_SNAKE_CASE field.
+        // The _KEY_CAMEL_CASE converted to snake case must give the value of 
the _KEY_SNAKE_CASE.
+        for (Field field : confClass.getFields()) {
+            String fieldName = field.getName();
+            if (fieldName.endsWith("_KEY")) {
+                String keyFieldValue = (String) field.get(null);
+                assertNotEquals(Configuration.CAMEL_CASE_NAMING_CONVENTION,
+                        
_StringUtil.getIdentifierNamingConvention(keyFieldValue));
+                assertEquals(fieldName.substring(0, fieldName.length() - 
4).toLowerCase(), keyFieldValue);
+                
+                try {
+                    String keySCFieldValue = (String) 
confClass.getField(fieldName + "_SNAKE_CASE").get(null);
+                    assertEquals(keyFieldValue, keySCFieldValue);
+                } catch (NoSuchFieldException e) {
+                    fail("Missing ..._SNAKE_CASE field for " + fieldName);
+                }
+                
+                try {
+                    String keyCCFieldValue = (String) 
confClass.getField(fieldName + "_CAMEL_CASE").get(null);
+                    assertNotEquals(Configuration.LEGACY_NAMING_CONVENTION,
+                            
_StringUtil.getIdentifierNamingConvention(keyCCFieldValue));
+                    assertEquals(keyFieldValue, 
_StringUtil.camelCaseToUnderscored(keyCCFieldValue));
+                } catch (NoSuchFieldException e) {
+                    fail("Missing ..._CAMEL_CASE field for " + fieldName);
+                }
+            }
+        }
+        
+        // For each _KEY_SNAKE_CASE field there must be a _KEY field.
+        for (Field field : confClass.getFields()) {
+            String fieldName = field.getName();
+            if (fieldName.endsWith("_KEY_SNAKE_CASE")) {
+                try {
+                    confClass.getField(fieldName.substring(0, 
fieldName.length() - 11)).get(null);
+                } catch (NoSuchFieldException e) {
+                    fail("Missing ..._KEY field for " + fieldName);
+                }
+            }
+        }
+        
+        // For each _KEY_CAMEL_CASE field there must be a _KEY field.
+        for (Field field : confClass.getFields()) {
+            String fieldName = field.getName();
+            if (fieldName.endsWith("_KEY_CAMEL_CASE")) {
+                try {
+                    confClass.getField(fieldName.substring(0, 
fieldName.length() - 11)).get(null);
+                } catch (NoSuchFieldException e) {
+                    fail("Missing ..._KEY field for " + fieldName);
+                }
+            }
+        }
+    }
+    
+    public static void 
testGetSettingNamesNameConventionsContainTheSame(List<String> namesSCList, 
List<String> namesCCList) {
+        Set<String> namesSC = new HashSet<>(namesSCList);
+        assertEquals(namesSCList.size(), namesSC.size());
+        
+        Set<String> namesCC = new HashSet<>(namesCCList);
+        assertEquals(namesCCList.size(), namesCC.size());
+
+        assertEquals(namesSC.size(), namesCC.size());
+        
+        for (String nameCC : namesCC) {
+            final String nameSC = _StringUtil.camelCaseToUnderscored(nameCC);
+            if (!namesSC.contains(nameSC)) {
+                fail("\"" + nameCC + "\" misses corresponding snake case name, 
\"" + nameSC + "\".");
+            }
+        }
+    }
+    
+    private Configurable createConfigurable() throws IOException {
+        return new Template(null, "", new 
Configuration(Configuration.VERSION_3_0_0));
+    }
+
+    private boolean keyFieldExists(String name) throws Exception {
+        try {
+            Configurable.class.getField(name.toUpperCase() + "_KEY");
+        } catch (NoSuchFieldException e) {
+            return false;
+        }
+        return true;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/test/java/org/apache/freemarker/core/ConfigurationTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/ConfigurationTest.java 
b/src/test/java/org/apache/freemarker/core/ConfigurationTest.java
index 6a7b4d9..08132ad 100644
--- a/src/test/java/org/apache/freemarker/core/ConfigurationTest.java
+++ b/src/test/java/org/apache/freemarker/core/ConfigurationTest.java
@@ -45,28 +45,8 @@ import java.util.Map;
 import java.util.Set;
 import java.util.TimeZone;
 
-import org.apache.freemarker.core.ast.BaseNTemplateNumberFormatFactory;
-import org.apache.freemarker.core.ast.CombinedMarkupOutputFormat;
-import org.apache.freemarker.core.ast.Configurable;
-import 
org.apache.freemarker.core.ast.Configurable.SettingValueAssignmentException;
-import org.apache.freemarker.core.ast.Configurable.UnknownSettingException;
-import org.apache.freemarker.core.ast.ConfigurableTest;
-import org.apache.freemarker.core.ast.CustomHTMLOutputFormat;
-import org.apache.freemarker.core.ast.DummyOutputFormat;
-import org.apache.freemarker.core.ast.Environment;
-import org.apache.freemarker.core.ast.EpochMillisDivTemplateDateFormatFactory;
-import org.apache.freemarker.core.ast.EpochMillisTemplateDateFormatFactory;
-import org.apache.freemarker.core.ast.HTMLOutputFormat;
-import org.apache.freemarker.core.ast.HexTemplateNumberFormatFactory;
-import org.apache.freemarker.core.ast.MarkupOutputFormat;
-import org.apache.freemarker.core.ast.OutputFormat;
-import org.apache.freemarker.core.ast.ParseException;
-import org.apache.freemarker.core.ast.RTFOutputFormat;
-import org.apache.freemarker.core.ast.TemplateDateFormatFactory;
-import org.apache.freemarker.core.ast.TemplateNumberFormatFactory;
-import org.apache.freemarker.core.ast.UndefinedOutputFormat;
-import org.apache.freemarker.core.ast.UnregisteredOutputFormatException;
-import org.apache.freemarker.core.ast.XMLOutputFormat;
+import org.apache.freemarker.core.Configurable.SettingValueAssignmentException;
+import org.apache.freemarker.core.Configurable.UnknownSettingException;
 import org.apache.freemarker.core.model.TemplateModelException;
 import org.apache.freemarker.core.model.TemplateScalarModel;
 import org.apache.freemarker.core.model.impl.DefaultObjectWrapper;
@@ -911,7 +891,7 @@ public class ConfigurationTest extends TestCase {
         assertTrue(cfg.getRegisteredCustomOutputFormats().isEmpty());
         
         
cfg.setSetting(Configuration.REGISTERED_CUSTOM_OUTPUT_FORMATS_KEY_CAMEL_CASE,
-                "[org.apache.freemarker.core.ast.CustomHTMLOutputFormat(), 
org.apache.freemarker.core.ast.DummyOutputFormat()]");
+                "[org.apache.freemarker.core.CustomHTMLOutputFormat(), 
org.apache.freemarker.core.DummyOutputFormat()]");
         assertEquals(
                 ImmutableList.of(CustomHTMLOutputFormat.INSTANCE, 
DummyOutputFormat.INSTANCE),
                 new ArrayList(cfg.getRegisteredCustomOutputFormats()));

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/test/java/org/apache/freemarker/core/CoreLocaleUtilsTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/CoreLocaleUtilsTest.java 
b/src/test/java/org/apache/freemarker/core/CoreLocaleUtilsTest.java
new file mode 100644
index 0000000..3993c1e
--- /dev/null
+++ b/src/test/java/org/apache/freemarker/core/CoreLocaleUtilsTest.java
@@ -0,0 +1,73 @@
+/*
+ * 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.util.Locale;
+
+import org.apache.freemarker.core.util._LocaleUtil;
+import org.junit.Test;
+
+public class CoreLocaleUtilsTest {
+
+    @Test
+    public void testGetLessSpecificLocale() {
+        Locale locale;
+        
+        locale = new Locale("ru", "RU", "Linux");
+        assertEquals("ru_RU_Linux", locale.toString());
+        locale = _LocaleUtil.getLessSpecificLocale(locale);
+        assertEquals("ru_RU", locale.toString());
+        locale = _LocaleUtil.getLessSpecificLocale(locale);
+        assertEquals("ru", locale.toString());
+        locale = _LocaleUtil.getLessSpecificLocale(locale);
+        assertNull(locale);
+        
+        locale = new Locale("ch", "CH");
+        assertEquals("ch_CH", locale.toString());
+        locale = _LocaleUtil.getLessSpecificLocale(locale);
+        assertEquals("ch", locale.toString());
+        locale = _LocaleUtil.getLessSpecificLocale(locale);
+        assertNull(locale);
+        
+        locale = new Locale("ja");
+        assertEquals("ja", locale.toString());
+        locale = _LocaleUtil.getLessSpecificLocale(locale);
+        assertNull(locale);
+
+        locale = new Locale("ja", "", "");
+        assertEquals("ja", locale.toString());
+        locale = _LocaleUtil.getLessSpecificLocale(locale);
+        assertNull(locale);
+        
+        locale = new Locale("");
+        assertEquals("", locale.toString());
+        locale = _LocaleUtil.getLessSpecificLocale(locale);
+        assertNull(locale);
+        
+        locale = new Locale("hu", "", "Linux");
+        assertEquals("hu__Linux", locale.toString());
+        locale = _LocaleUtil.getLessSpecificLocale(locale);
+        assertEquals("hu", locale.toString());
+        locale = _LocaleUtil.getLessSpecificLocale(locale);
+        assertNull(locale);
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/test/java/org/apache/freemarker/core/CustomAttributeTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/CustomAttributeTest.java 
b/src/test/java/org/apache/freemarker/core/CustomAttributeTest.java
index 6ee16cc..1778c60 100644
--- a/src/test/java/org/apache/freemarker/core/CustomAttributeTest.java
+++ b/src/test/java/org/apache/freemarker/core/CustomAttributeTest.java
@@ -28,8 +28,6 @@ import static org.junit.Assert.fail;
 import java.math.BigDecimal;
 import java.util.Arrays;
 
-import org.apache.freemarker.core.ast.CustomAttribute;
-import org.apache.freemarker.core.ast.Environment;
 import org.apache.freemarker.core.util._NullWriter;
 import org.junit.Test;
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/test/java/org/apache/freemarker/core/CustomHTMLOutputFormat.java
----------------------------------------------------------------------
diff --git 
a/src/test/java/org/apache/freemarker/core/CustomHTMLOutputFormat.java 
b/src/test/java/org/apache/freemarker/core/CustomHTMLOutputFormat.java
new file mode 100644
index 0000000..86e69a4
--- /dev/null
+++ b/src/test/java/org/apache/freemarker/core/CustomHTMLOutputFormat.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 java.io.IOException;
+import java.io.Writer;
+
+import org.apache.freemarker.core.CommonMarkupOutputFormat;
+import org.apache.freemarker.core.model.TemplateModelException;
+import org.apache.freemarker.core.util._StringUtil;
+
+/**
+ * Represents the HTML output format.
+ * 
+ * @since 2.3.24
+ */
+public final class CustomHTMLOutputFormat extends 
CommonMarkupOutputFormat<CustomTemplateHTMLModel> {
+
+    public static final CustomHTMLOutputFormat INSTANCE = new 
CustomHTMLOutputFormat();
+    
+    private CustomHTMLOutputFormat() {
+        // Only to decrease visibility
+    }
+    
+    @Override
+    public String getName() {
+        return "HTML";
+    }
+
+    @Override
+    public String getMimeType() {
+        return "text/html";
+    }
+
+    @Override
+    public void output(String textToEsc, Writer out) throws IOException, 
TemplateModelException {
+        // This is lazy - don't do it in reality.
+        out.write(escapePlainText(textToEsc));
+    }
+
+    @Override
+    public String escapePlainText(String plainTextContent) {
+        return _StringUtil.XHTMLEnc(plainTextContent.replace('x', 'X'));
+    }
+
+    @Override
+    public boolean isLegacyBuiltInBypassed(String builtInName) {
+        return builtInName.equals("html") || builtInName.equals("xml") || 
builtInName.equals("xhtml");
+    }
+
+    @Override
+    protected CustomTemplateHTMLModel newTemplateMarkupOutputModel(String 
plainTextContent, String markupContent) {
+        return new CustomTemplateHTMLModel(plainTextContent, markupContent);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/test/java/org/apache/freemarker/core/CustomTemplateHTMLModel.java
----------------------------------------------------------------------
diff --git 
a/src/test/java/org/apache/freemarker/core/CustomTemplateHTMLModel.java 
b/src/test/java/org/apache/freemarker/core/CustomTemplateHTMLModel.java
new file mode 100644
index 0000000..60a419a
--- /dev/null
+++ b/src/test/java/org/apache/freemarker/core/CustomTemplateHTMLModel.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.freemarker.core;
+
+import org.apache.freemarker.core.CommonTemplateMarkupOutputModel;
+
+public final class CustomTemplateHTMLModel extends 
CommonTemplateMarkupOutputModel<CustomTemplateHTMLModel> {
+    
+    CustomTemplateHTMLModel(String plainTextContent, String markupContent) {
+        super(plainTextContent, markupContent);
+    }
+
+    @Override
+    public CustomHTMLOutputFormat getOutputFormat() {
+        return CustomHTMLOutputFormat.INSTANCE;
+    }
+
+}

Reply via email to