freemarker-notifications mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ddek...@apache.org
Subject [10/54] [partial] incubator-freemarker git commit: Unifying the o.a.f.core and o.a.f.core.ast
Date Thu, 23 Feb 2017 21:35:37 GMT
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/test/java/org/apache/freemarker/core/HeaderParsingTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/HeaderParsingTest.java b/src/test/java/org/apache/freemarker/core/HeaderParsingTest.java
new file mode 100644
index 0000000..b2f69ff
--- /dev/null
+++ b/src/test/java/org/apache/freemarker/core/HeaderParsingTest.java
@@ -0,0 +1,64 @@
+/*
+ * 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 HeaderParsingTest extends TemplateTest {
+
+    private final Configuration cfgStripWS = new Configuration(Configuration.VERSION_3_0_0);
+    private final Configuration cfgNoStripWS = new Configuration(Configuration.VERSION_3_0_0);
+    {
+        cfgNoStripWS.setWhitespaceStripping(false);
+    }
+    
+    @Test
+    public void test() throws IOException, TemplateException {
+        assertOutput("<#ftl>text", "text", "text");
+        assertOutput(" <#ftl> text", " text", " text");
+        assertOutput("\n<#ftl>\ntext", "text", "text");
+        assertOutput("\n \n\n<#ftl> \ntext", "text", "text");
+        assertOutput("\n \n\n<#ftl>\n\ntext", "\ntext", "\ntext");
+    }
+    
+    private void assertOutput(final String ftl, String expectedOutStripped, String expectedOutNonStripped)
+            throws IOException, TemplateException {
+        for (int i = 0; i < 4; i++) {
+            String ftlPermutation = ftl;
+            if ((i & 1) == 1) {
+                ftlPermutation = ftlPermutation.replace("<#ftl>", "<#ftl encoding='utf-8'>");
+            }
+            if ((i & 2) == 2) {
+                ftlPermutation = ftlPermutation.replace('<', '[').replace('>', ']');
+            }
+            
+            setConfiguration(cfgStripWS);
+            assertOutput(ftlPermutation, expectedOutStripped);
+            setConfiguration(cfgNoStripWS);
+            assertOutput(ftlPermutation, expectedOutNonStripped);
+        }
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/test/java/org/apache/freemarker/core/HexTemplateNumberFormatFactory.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/HexTemplateNumberFormatFactory.java b/src/test/java/org/apache/freemarker/core/HexTemplateNumberFormatFactory.java
new file mode 100644
index 0000000..9e2020c
--- /dev/null
+++ b/src/test/java/org/apache/freemarker/core/HexTemplateNumberFormatFactory.java
@@ -0,0 +1,77 @@
+/*
+ * 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.UnformattableValueException;
+import org.apache.freemarker.core.model.TemplateModelException;
+import org.apache.freemarker.core.model.TemplateNumberModel;
+import org.apache.freemarker.core.util._NumberUtil;
+
+public class HexTemplateNumberFormatFactory extends TemplateNumberFormatFactory {
+
+    public static final HexTemplateNumberFormatFactory INSTANCE = new HexTemplateNumberFormatFactory();
+    
+    private HexTemplateNumberFormatFactory() {
+        // Defined to decrease visibility
+    }
+    
+    @Override
+    public TemplateNumberFormat get(String params, Locale locale, Environment env)
+            throws InvalidFormatParametersException {
+        TemplateFormatUtil.checkHasNoParameters(params);
+        return HexTemplateNumberFormat.INSTANCE;
+    }
+
+    private static class HexTemplateNumberFormat extends TemplateNumberFormat {
+
+        private static final HexTemplateNumberFormat INSTANCE = new HexTemplateNumberFormat();
+        
+        private HexTemplateNumberFormat() { }
+        
+        @Override
+        public String formatToPlainText(TemplateNumberModel numberModel)
+                throws UnformattableValueException, TemplateModelException {
+            Number n = TemplateFormatUtil.getNonNullNumber(numberModel);
+            try {
+                return Integer.toHexString(_NumberUtil.toIntExact(n));
+            } catch (ArithmeticException e) {
+                throw new UnformattableValueException(n + " doesn't fit into an int");
+            }
+        }
+
+        @Override
+        public boolean isLocaleBound() {
+            return false;
+        }
+
+        @Override
+        public String getDescription() {
+            return "hexadecimal int";
+        }
+        
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/test/java/org/apache/freemarker/core/IncludeAndImportConfigurableLayersTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/IncludeAndImportConfigurableLayersTest.java b/src/test/java/org/apache/freemarker/core/IncludeAndImportConfigurableLayersTest.java
new file mode 100644
index 0000000..750a701
--- /dev/null
+++ b/src/test/java/org/apache/freemarker/core/IncludeAndImportConfigurableLayersTest.java
@@ -0,0 +1,336 @@
+/*
+ * 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 java.io.StringWriter;
+
+import org.apache.freemarker.core.Configurable;
+import org.apache.freemarker.core.Configuration;
+import org.apache.freemarker.core.Environment;
+import org.apache.freemarker.core.Template;
+import org.apache.freemarker.core.TemplateConfiguration;
+import org.apache.freemarker.core.templateresolver.ConditionalTemplateConfigurationFactory;
+import org.apache.freemarker.core.templateresolver.FileNameGlobMatcher;
+import org.apache.freemarker.test.TemplateTest;
+import org.junit.Test;
+
+public class IncludeAndImportConfigurableLayersTest extends TemplateTest {
+
+    @Test
+    public void test3LayerImportNoClashes() throws Exception {
+        Configuration cfg = getConfiguration();
+        cfg.addAutoImport("t1", "t1.ftl");
+
+        TemplateConfiguration tc = new TemplateConfiguration();
+        tc.addAutoImport("t2", "t2.ftl");
+        cfg.setTemplateConfigurations(
+                new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("main.ftl"), tc));
+
+        {
+            Template t = cfg.getTemplate("main.ftl");
+            StringWriter sw = new StringWriter();
+            Environment env = t.createProcessingEnvironment(null, sw);
+            env.addAutoImport("t3", "t3.ftl");
+    
+            env.process();
+            assertEquals("In main: t1;t2;t3;", sw.toString());
+        }
+
+        {
+            Template t = cfg.getTemplate("main.ftl");
+            StringWriter sw = new StringWriter();
+            Environment env = t.createProcessingEnvironment(null, sw);
+    
+            env.process();
+            assertEquals("In main: t1;t2;", sw.toString());
+        }
+        
+        {
+            Template t = cfg.getTemplate("main2.ftl");
+            StringWriter sw = new StringWriter();
+            Environment env = t.createProcessingEnvironment(null, sw);
+            env.addAutoImport("t3", "t3.ftl");
+    
+            env.process();
+            assertEquals("In main2: t1;t3;", sw.toString());
+        }
+        
+        cfg.removeAutoImport("t1");
+        
+        {
+            Template t = cfg.getTemplate("main.ftl");
+            StringWriter sw = new StringWriter();
+            Environment env = t.createProcessingEnvironment(null, sw);
+            env.addAutoImport("t3", "t3.ftl");
+    
+            env.process();
+            assertEquals("In main: t2;t3;", sw.toString());
+        }
+    }
+    
+    @Test
+    public void test3LayerImportClashes() throws Exception {
+        Configuration cfg = getConfiguration();
+        cfg.addAutoImport("t1", "t1.ftl");
+        cfg.addAutoImport("t2", "t2.ftl");
+        cfg.addAutoImport("t3", "t3.ftl");
+
+        TemplateConfiguration tc = new TemplateConfiguration();
+        tc.addAutoImport("t2", "t2b.ftl");
+        cfg.setTemplateConfigurations(
+                new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("main.ftl"), tc));
+
+        {
+            Template t = cfg.getTemplate("main.ftl");
+            StringWriter sw = new StringWriter();
+            Environment env = t.createProcessingEnvironment(null, sw);
+            env.addAutoImport("t3", "t3b.ftl");
+    
+            env.process();
+            assertEquals("In main: t1;t2b;t3b;", sw.toString());
+        }
+        
+        {
+            Template t = cfg.getTemplate("main2.ftl");
+            StringWriter sw = new StringWriter();
+            Environment env = t.createProcessingEnvironment(null, sw);
+            env.addAutoImport("t3", "t3b.ftl");
+    
+            env.process();
+            assertEquals("In main2: t1;t2;t3b;", sw.toString());
+        }
+        
+        {
+            Template t = cfg.getTemplate("main.ftl");
+            StringWriter sw = new StringWriter();
+            Environment env = t.createProcessingEnvironment(null, sw);
+    
+            env.process();
+            assertEquals("In main: t1;t3;t2b;", sw.toString());
+        }
+    }
+
+    @Test
+    public void test3LayerIncludesNoClashes() throws Exception {
+        Configuration cfg = getConfiguration();
+        cfg.addAutoInclude("t1.ftl");
+
+        TemplateConfiguration tc = new TemplateConfiguration();
+        tc.addAutoInclude("t2.ftl");
+        cfg.setTemplateConfigurations(
+                new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("main.ftl"), tc));
+
+        {
+            Template t = cfg.getTemplate("main.ftl");
+            StringWriter sw = new StringWriter();
+            Environment env = t.createProcessingEnvironment(null, sw);
+            env.addAutoInclude("t3.ftl");
+    
+            env.process();
+            assertEquals("T1;T2;T3;In main: t1;t2;t3;", sw.toString());
+        }
+
+        {
+            Template t = cfg.getTemplate("main.ftl");
+            StringWriter sw = new StringWriter();
+            Environment env = t.createProcessingEnvironment(null, sw);
+    
+            env.process();
+            assertEquals("T1;T2;In main: t1;t2;", sw.toString());
+        }
+        
+        {
+            Template t = cfg.getTemplate("main2.ftl");
+            StringWriter sw = new StringWriter();
+            Environment env = t.createProcessingEnvironment(null, sw);
+            env.addAutoInclude("t3.ftl");
+    
+            env.process();
+            assertEquals("T1;T3;In main2: t1;t3;", sw.toString());
+        }
+        
+        cfg.removeAutoInclude("t1.ftl");
+        
+        {
+            Template t = cfg.getTemplate("main.ftl");
+            StringWriter sw = new StringWriter();
+            Environment env = t.createProcessingEnvironment(null, sw);
+            env.addAutoInclude("t3.ftl");
+    
+            env.process();
+            assertEquals("T2;T3;In main: t2;t3;", sw.toString());
+        }
+    }
+
+    @Test
+    public void test3LayerIncludeClashes() throws Exception {
+        Configuration cfg = getConfiguration();
+        cfg.addAutoInclude("t1.ftl");
+        cfg.addAutoInclude("t2.ftl");
+        cfg.addAutoInclude("t3.ftl");
+
+        TemplateConfiguration tc = new TemplateConfiguration();
+        tc.addAutoInclude("t2.ftl");
+        cfg.setTemplateConfigurations(
+                new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("main.ftl"), tc));
+
+        {
+            Template t = cfg.getTemplate("main.ftl");
+            StringWriter sw = new StringWriter();
+            Environment env = t.createProcessingEnvironment(null, sw);
+            env.addAutoInclude("t3.ftl");
+    
+            env.process();
+            assertEquals("T1;T2;T3;In main: t1;t2;t3;", sw.toString());
+        }
+        
+        {
+            Template t = cfg.getTemplate("main2.ftl");
+            StringWriter sw = new StringWriter();
+            Environment env = t.createProcessingEnvironment(null, sw);
+            env.addAutoInclude("t3.ftl");
+    
+            env.process();
+            assertEquals("T1;T2;T3;In main2: t1;t2;t3;", sw.toString());
+        }
+        
+        {
+            Template t = cfg.getTemplate("main.ftl");
+            StringWriter sw = new StringWriter();
+            Environment env = t.createProcessingEnvironment(null, sw);
+    
+            env.process();
+            assertEquals("T1;T3;T2;In main: t1;t3;t2;", sw.toString());
+        }
+        
+        {
+            Template t = cfg.getTemplate("main.ftl");
+            StringWriter sw = new StringWriter();
+            Environment env = t.createProcessingEnvironment(null, sw);
+            env.addAutoInclude("t1.ftl");
+    
+            env.process();
+            assertEquals("T3;T2;T1;In main: t3;t2;t1;", sw.toString());
+        }
+    }
+    
+    @Test
+    public void test3LayerIncludesClashes2() throws Exception {
+        Configuration cfg = getConfiguration();
+        cfg.addAutoInclude("t1.ftl");
+        cfg.addAutoInclude("t1.ftl");
+
+        TemplateConfiguration tc = new TemplateConfiguration();
+        tc.addAutoInclude("t2.ftl");
+        tc.addAutoInclude("t2.ftl");
+        cfg.setTemplateConfigurations(
+                new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("main.ftl"), tc));
+
+        {
+            Template t = cfg.getTemplate("main.ftl");
+            StringWriter sw = new StringWriter();
+            Environment env = t.createProcessingEnvironment(null, sw);
+            env.addAutoInclude("t3.ftl");
+            env.addAutoInclude("t3.ftl");
+            env.addAutoInclude("t1.ftl");
+            env.addAutoInclude("t1.ftl");
+    
+            env.process();
+            assertEquals("T2;T3;T1;In main: t2;t3;t1;", sw.toString());
+        }
+    }
+    
+    @Test
+    public void test3LayerLazyness() throws Exception {
+        for (Class<?> layer : new Class<?>[] { Configuration.class, Template.class, Environment.class }) {
+            test3LayerLazyness(layer, null, null, false, "t1;t2;");
+            test3LayerLazyness(layer, null, null, true, "t1;t2;");
+            test3LayerLazyness(layer, null, false, true, "t1;t2;");
+            test3LayerLazyness(layer, null, true, true, "t2;");
+            
+            test3LayerLazyness(layer, false, null, false, "t1;t2;");
+            test3LayerLazyness(layer, false, null, true, "t1;t2;");
+            test3LayerLazyness(layer, false, false, true, "t1;t2;");
+            test3LayerLazyness(layer, false, true, true, "t2;");
+
+            test3LayerLazyness(layer, true, null, false, "");
+            test3LayerLazyness(layer, true, null, true, "");
+            test3LayerLazyness(layer, true, false, true, "t1;");
+            test3LayerLazyness(layer, true, true, true, "");
+        }
+    }
+    
+    private void test3LayerLazyness(
+            Class<?> layer,
+            Boolean lazyImports,
+            Boolean lazyAutoImports, boolean setLazyAutoImports,
+            String expectedOutput)
+            throws Exception {
+        dropConfiguration();
+        Configuration cfg = getConfiguration();
+        cfg.addAutoImport("t1", "t1.ftl");
+        Template t = new Template(null, "<#import 't2.ftl' as t2>${loaded!}", cfg);
+
+        StringWriter sw = new StringWriter();
+        Environment env = t.createProcessingEnvironment(null, sw);
+        
+        if (layer == Configuration.class) {
+            setLazynessOfConfigurable(cfg, lazyImports, lazyAutoImports, setLazyAutoImports);
+        } else if (layer == Template.class) {
+            setLazynessOfConfigurable(t, lazyImports, lazyAutoImports, setLazyAutoImports);
+        } else if (layer == Environment.class) {
+            setLazynessOfConfigurable(env, lazyImports, lazyAutoImports, setLazyAutoImports);
+        } else {
+            throw new IllegalArgumentException();
+        }
+        
+        env.process();
+        assertEquals(expectedOutput, sw.toString());
+    }
+
+    private void setLazynessOfConfigurable(Configurable cfg, Boolean lazyImports, Boolean lazyAutoImports,
+            boolean setLazyAutoImports) {
+        if (lazyImports != null) {
+            cfg.setLazyImports(lazyImports);
+        }
+        if (setLazyAutoImports) {
+            cfg.setLazyAutoImports(lazyAutoImports);
+        }
+    }
+    
+    @Override
+    protected Configuration createConfiguration() throws Exception {
+        return new Configuration(Configuration.VERSION_3_0_0);
+    }
+
+    @Override
+    protected void addCommonTemplates() {
+        addTemplate("main.ftl", "In main: ${loaded}");
+        addTemplate("main2.ftl", "In main2: ${loaded}");
+        addTemplate("t1.ftl", "<#global loaded = (loaded!) + 't1;'>T1;");
+        addTemplate("t2.ftl", "<#global loaded = (loaded!) + 't2;'>T2;");
+        addTemplate("t3.ftl", "<#global loaded = (loaded!) + 't3;'>T3;");
+        addTemplate("t1b.ftl", "<#global loaded = (loaded!) + 't1b;'>T1b;");
+        addTemplate("t2b.ftl", "<#global loaded = (loaded!) + 't2b;'>T2b;");
+        addTemplate("t3b.ftl", "<#global loaded = (loaded!) + 't3b;'>T3b;");
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/test/java/org/apache/freemarker/core/IncludeAndImportTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/IncludeAndImportTest.java b/src/test/java/org/apache/freemarker/core/IncludeAndImportTest.java
new file mode 100644
index 0000000..7c1372b
--- /dev/null
+++ b/src/test/java/org/apache/freemarker/core/IncludeAndImportTest.java
@@ -0,0 +1,255 @@
+/*
+ * 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.allOf;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.instanceOf;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
+
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+
+import org.apache.freemarker.core.Configuration;
+import org.apache.freemarker.core.InvalidReferenceException;
+import org.apache.freemarker.core.TemplateException;
+import org.apache.freemarker.core.TemplateNotFoundException;
+import org.apache.freemarker.core.Environment.LazilyInitializedNamespace;
+import org.apache.freemarker.core.Environment.Namespace;
+import org.apache.freemarker.core.model.WrappingTemplateModel;
+import org.apache.freemarker.test.TemplateTest;
+import org.junit.Before;
+import org.junit.Test;
+
+@SuppressWarnings("boxing")
+public class IncludeAndImportTest extends TemplateTest {
+
+    @Before
+    public void setup() {
+        addTemplate("inc1.ftl", "[inc1]<#global inc1Cnt = (inc1Cnt!0) + 1><#global history = (history!) + 'I'>");
+        addTemplate("inc2.ftl", "[inc2]");
+        addTemplate("inc3.ftl", "[inc3]");
+        addTemplate("lib1.ftl", "<#global lib1Cnt = (lib1Cnt!0) + 1><#global history = (history!) + 'L1'>"
+                + "<#macro m>In lib1</#macro>");
+        addTemplate("lib2.ftl", "<#global history = (history!) + 'L2'>"
+                + "<#macro m>In lib2</#macro>");
+        addTemplate("lib3.ftl", "<#global history = (history!) + 'L3'>"
+                + "<#macro m>In lib3</#macro>");
+        
+        addTemplate("lib2CallsLib1.ftl", "<#global history = (history!) + 'L2'>"
+                + "<#macro m>In lib2 (<@lib1.m/>)</#macro>");
+        addTemplate("lib3ImportsLib1.ftl", "<#import 'lib1.ftl' as lib1><#global history = (history!) + 'L3'>"
+                + "<#macro m>In lib3 (<@lib1.m/>)</#macro>");
+        
+        addTemplate("lib_de.ftl", "<#global history = (history!) + 'LDe'><#assign initLocale=.locale>"
+                + "<#macro m>de</#macro>");
+        addTemplate("lib_en.ftl", "<#global history = (history!) + 'LEn'><#assign initLocale=.locale>"
+                + "<#macro m>en</#macro>");
+    }
+
+    @Test
+    public void includeSameTwice() throws IOException, TemplateException {
+        assertOutput("<#include 'inc1.ftl'>${inc1Cnt}<#include 'inc1.ftl'>${inc1Cnt}", "[inc1]1[inc1]2");
+    }
+
+    @Test
+    public void importSameTwice() throws IOException, TemplateException {
+        assertOutput("<#import 'lib1.ftl' as i1>${lib1Cnt} <#import 'lib1.ftl' as i2>${lib1Cnt}", "1 1");
+    }
+
+    @Test
+    public void importInMainCreatesGlobal() throws IOException, TemplateException {
+        String ftl = "${.main.lib1???c} ${.globals.lib1???c}"
+                + "<#import 'lib1.ftl' as lib1> ${.main.lib1???c} ${.globals.lib1???c}";
+        String expectedOut = "false false true true";
+        assertOutput(ftl, expectedOut);
+    }
+    
+    @Test
+    public void importInMainCreatesGlobalBugfix() throws IOException, TemplateException {
+        // An import in the main namespace should create a global variable, even if the imported library was already
+        // initialized elsewhere.
+        String ftl = "<#import 'lib3ImportsLib1.ftl' as lib3>${lib1Cnt} ${.main.lib1???c} ${.globals.lib1???c}, "
+        + "<#import 'lib1.ftl' as lib1>${lib1Cnt} ${.main.lib1???c} ${.globals.lib1???c}";
+        assertOutput(ftl, "1 false false, 1 true true");
+    }
+
+    /**
+     * Tests the order of auto-includes and auto-imports, also that they only effect the main template directly.
+     */
+    @Test
+    public void autoIncludeAndAutoImport() throws IOException, TemplateException {
+        getConfiguration().addAutoInclude("inc1.ftl");
+        getConfiguration().addAutoInclude("inc2.ftl");
+        getConfiguration().addAutoImport("lib1", "lib1.ftl");
+        getConfiguration().addAutoImport("lib2", "lib2CallsLib1.ftl");
+        assertOutput(
+                "<#include 'inc3.ftl'>[main] ${inc1Cnt}, ${history}, <@lib1.m/>, <@lib2.m/>",
+                "[inc1][inc2][inc3][main] 1, L1L2I, In lib1, In lib2 (In lib1)");
+    }
+    
+    /**
+     * Demonstrates design issue in FreeMarker 2.3.x where the lookupStrategy is not factored in when identifying
+     * already existing namespaces.
+     */
+    @Test
+    public void lookupSrategiesAreNotConsideredProperly() throws IOException, TemplateException {
+        // As only the name of the template is used for the finding the already existing namespace, the settings that
+        // influence the lookup are erroneously ignored.
+        assertOutput(
+                "<#setting locale='en_US'><#import 'lib.ftl' as ns1>"
+                + "<#setting locale='de_DE'><#import 'lib.ftl' as ns2>"
+                + "<@ns1.m/> <@ns2.m/> ${history}",
+                "en en LEn");
+        
+        // The opposite of the prevous, where differn names refer to the same template after a lookup: 
+        assertOutput(
+                "<#setting locale='en_US'>"
+                + "<#import '*/lib.ftl' as ns1>"
+                + "<#import 'lib.ftl' as ns2>"
+                + "<@ns1.m/> <@ns2.m/> ${history}",
+                "en en LEnLEn");
+    }
+    
+    @Test
+    public void lazyImportBasics() throws IOException, TemplateException {
+        String ftlImports = "<#import 'lib1.ftl' as l1><#import 'lib2.ftl' as l2><#import 'lib3ImportsLib1.ftl' as l3>";
+        String ftlCalls = "<@l2.m/>, <@l1.m/>; ${history}";
+        String ftl = ftlImports + ftlCalls;
+        
+        assertOutput(ftl, "In lib2, In lib1; L1L2L3");
+        
+        getConfiguration().setLazyImports(true);
+        assertOutput(ftl, "In lib2, In lib1; L2L1");
+        
+        assertOutput(ftlImports + "<@l3.m/>, " + ftlCalls, "In lib3 (In lib1), In lib2, In lib1; L3L1L2");
+    }
+
+    @Test
+    public void lazyImportAndLocale() throws IOException, TemplateException {
+        getConfiguration().setLazyImports(true);
+        assertOutput("<#setting locale = 'de_DE'><#import 'lib.ftl' as lib>"
+                + "[${history!}] "
+                + "<#setting locale = 'en'>"
+                + "<@lib.m/> ${lib.initLocale} [${history}]",
+                "[] de de_DE [LDe]");
+    }
+
+    @Test
+    public void lazyAutoImportSettings() throws IOException, TemplateException {
+        Configuration cfg = getConfiguration();
+        cfg.addAutoImport("l1", "lib1.ftl");
+        cfg.addAutoImport("l2", "lib2.ftl");
+        cfg.addAutoImport("l3", "lib3.ftl");
+        
+        String ftl = "<@l2.m/>, <@l1.m/>; ${history}";
+        String expectedEagerOutput = "In lib2, In lib1; L1L2L3";
+        String expecedLazyOutput = "In lib2, In lib1; L2L1";
+        
+        assertOutput(ftl, expectedEagerOutput);
+        cfg.setLazyImports(true);
+        assertOutput(ftl, expecedLazyOutput);
+        cfg.setLazyImports(false);
+        assertOutput(ftl, expectedEagerOutput);
+        cfg.setLazyAutoImports(true);
+        assertOutput(ftl, expecedLazyOutput);
+        cfg.setLazyAutoImports(null);
+        assertOutput(ftl, expectedEagerOutput);
+        cfg.setLazyImports(true);
+        cfg.setLazyAutoImports(false);
+        assertOutput(ftl, expectedEagerOutput);
+    }
+    
+    @Test
+    public void lazyAutoImportMixedWithManualImport() throws IOException, TemplateException {
+        Configuration cfg = getConfiguration();
+        cfg.addAutoImport("l1", "lib1.ftl");
+        cfg.addAutoImport("l2", "/./lib2.ftl");
+        cfg.addAutoImport("l3", "lib3.ftl");
+        cfg.setLazyAutoImports(true);
+
+        String ftl = "<@l2.m/>, <@l1.m/>; ${history}";
+        String expectOutputWithoutHistory = "In lib2, In lib1; ";
+        String expecedOutput = expectOutputWithoutHistory + "L2L1";
+        
+        assertOutput(ftl, expecedOutput);
+        assertOutput("<#import 'lib1.ftl' as l1>" + ftl, expectOutputWithoutHistory + "L1L2");
+        assertOutput("<#import './x/../lib1.ftl' as l1>" + ftl, expectOutputWithoutHistory + "L1L2");
+        assertOutput("<#import 'lib2.ftl' as l2>" + ftl, expecedOutput);
+        assertOutput("<#import 'lib3.ftl' as l3>" + ftl, expectOutputWithoutHistory + "L3L2L1");
+        cfg.setLazyImports(true);
+        assertOutput("<#import 'lib1.ftl' as l1>" + ftl, expecedOutput);
+        assertOutput("<#import './x/../lib1.ftl' as l1>" + ftl, expecedOutput);
+        assertOutput("<#import 'lib2.ftl' as l2>" + ftl, expecedOutput);
+        assertOutput("<#import 'lib3.ftl' as l3>" + ftl, expecedOutput);
+    }
+
+    @Test
+    public void lazyImportErrors() throws IOException, TemplateException {
+        Configuration cfg = getConfiguration();
+        cfg.setLazyImports(true);
+        
+        assertOutput("<#import 'noSuchTemplate.ftl' as wrong>x", "x");
+        
+        cfg.addAutoImport("wrong", "noSuchTemplate.ftl");
+        assertOutput("x", "x");
+
+        try {
+            assertOutput("${wrong.x}", "");
+            fail();
+        } catch (TemplateException e) {
+            assertThat(e.getMessage(),
+                    allOf(containsString("Lazy initialization"), containsString("noSuchTemplate.ftl")));
+            assertThat(e.getCause(), instanceOf(TemplateNotFoundException.class));
+        }
+        
+        addTemplate("containsError.ftl", "${noSuchVar}");
+        try {
+            assertOutput("<#import 'containsError.ftl' as lib>${lib.x}", "");
+            fail();
+        } catch (TemplateException e) {
+            assertThat(e.getMessage(),
+                    allOf(containsString("Lazy initialization"), containsString("containsError.ftl")));
+            assertThat(e.getCause(), instanceOf(InvalidReferenceException.class));
+            assertThat(e.getCause().getMessage(), containsString("noSuchVar"));
+        }
+    }
+    
+    /**
+     * Ensures that all methods are overridden so that they will do the lazy initialization.
+     */
+    @Test
+    public void lazilyInitializingNamespaceOverridesAll() throws SecurityException, NoSuchMethodException {
+        for (Method m : Namespace.class.getMethods()) {
+            Class<?> declClass = m.getDeclaringClass();
+            if (declClass == Object.class || declClass == WrappingTemplateModel.class
+                    || (m.getModifiers() & Modifier.STATIC) != 0
+                    || m.getName().equals("synchronizedWrapper")) {
+                continue;
+            }
+            Method lazyM = LazilyInitializedNamespace.class.getMethod(m.getName(), m.getParameterTypes());
+            if (lazyM.getDeclaringClass() != LazilyInitializedNamespace.class) {
+                fail("The " + lazyM + " method wasn't overidden in " + LazilyInitializedNamespace.class.getName());
+            }
+        }
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/test/java/org/apache/freemarker/core/InterpretAndEvalTemplateNameTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/InterpretAndEvalTemplateNameTest.java b/src/test/java/org/apache/freemarker/core/InterpretAndEvalTemplateNameTest.java
new file mode 100644
index 0000000..7c08ec3
--- /dev/null
+++ b/src/test/java/org/apache/freemarker/core/InterpretAndEvalTemplateNameTest.java
@@ -0,0 +1,70 @@
+/*
+ * 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.TemplateException;
+import org.apache.freemarker.core.templateresolver.impl.StringTemplateLoader;
+import org.apache.freemarker.test.TemplateTest;
+import org.junit.Test;
+
+/**
+ * Test template names returned by special variables and relative path resolution in {@code ?interpret}-ed and
+ * {@code ?eval}-ed parts.  
+ */
+public class InterpretAndEvalTemplateNameTest extends TemplateTest {
+    
+    @Test
+    public void testInterpret() throws IOException, TemplateException {
+        for (String getTemplateNames : new String[] {
+                "c=${.current_template_name}, m=${.main_template_name}",
+                "c=${\".current_template_name\"?eval}, m=${\".main_template_name\"?eval}"
+                }) {
+            StringTemplateLoader tl = new StringTemplateLoader();
+            tl.putTemplate(
+                    "main.ftl",
+                    getTemplateNames + " "
+                    + "{<#include 'sub/t.ftl'>}");
+            tl.putTemplate(
+                    "sub/t.ftl",
+                    getTemplateNames + " "
+                    + "i{<@r'" + getTemplateNames + " {<#include \"a.ftl\">'?interpret />}} "
+                    + "i{<@[r'" + getTemplateNames + " {<#include \"a.ftl\">','named_interpreted']?interpret />}}");
+            tl.putTemplate("sub/a.ftl", "In sub/a.ftl, " + getTemplateNames);
+            tl.putTemplate("a.ftl", "In a.ftl");
+            
+            getConfiguration().setTemplateLoader(tl);
+            
+            assertOutputForNamed("main.ftl",
+                    "c=main.ftl, m=main.ftl "
+                    + "{"
+                        + "c=sub/t.ftl, m=main.ftl "
+                        + "i{c=sub/t.ftl->anonymous_interpreted, m=main.ftl {In sub/a.ftl, c=sub/a.ftl, m=main.ftl}} "
+                        + "i{c=sub/t.ftl->named_interpreted, m=main.ftl {In sub/a.ftl, c=sub/a.ftl, m=main.ftl}}"
+                    + "}");
+            
+            assertOutputForNamed("sub/t.ftl",
+                    "c=sub/t.ftl, m=sub/t.ftl "
+                    + "i{c=sub/t.ftl->anonymous_interpreted, m=sub/t.ftl {In sub/a.ftl, c=sub/a.ftl, m=sub/t.ftl}} "
+                    + "i{c=sub/t.ftl->named_interpreted, m=sub/t.ftl {In sub/a.ftl, c=sub/a.ftl, m=sub/t.ftl}}");
+        }
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/test/java/org/apache/freemarker/core/InterpretSettingInheritanceTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/InterpretSettingInheritanceTest.java b/src/test/java/org/apache/freemarker/core/InterpretSettingInheritanceTest.java
new file mode 100644
index 0000000..0e8f2a2
--- /dev/null
+++ b/src/test/java/org/apache/freemarker/core/InterpretSettingInheritanceTest.java
@@ -0,0 +1,95 @@
+/*
+ * 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;
+
+/**
+ * The {@code interpret} built-in must not consider the settings or established auto-detected syntax of the surrounding
+ * template. It can only depend on the {@link Configuration}.
+ */
+public class InterpretSettingInheritanceTest  extends TemplateTest {
+
+    private static final String FTL_A_S_A = "<#ftl><@'[#if true]s[/#if]<#if true>a</#if>'?interpret />";
+    private static final String FTL_A_A_S = "<#ftl><@'<#if true>a</#if>[#if true]s[/#if]'?interpret />";
+    private static final String FTL_S_S_A = "[#ftl][@'[#if true]s[/#if]<#if true>a</#if>'?interpret /]";
+    private static final String FTL_S_A_S = "[#ftl][@'<#if true>a</#if>[#if true]s[/#if]'?interpret /]";
+    private static final String OUT_S_A_WHEN_SYNTAX_IS_S = "s<#if true>a</#if>";
+    private static final String OUT_S_A_WHEN_SYNTAX_IS_A = "[#if true]s[/#if]a";
+    private static final String OUT_A_S_WHEN_SYNTAX_IS_A = "a[#if true]s[/#if]";
+    private static final String OUT_A_S_WHEN_SYNTAX_IS_S = "<#if true>a</#if>s";
+
+    @Test
+    public void tagSyntaxTest() throws IOException, TemplateException {
+        Configuration cfg = getConfiguration();
+        
+        cfg.setTagSyntax(Configuration.ANGLE_BRACKET_TAG_SYNTAX);
+        assertOutput(FTL_S_A_S, OUT_A_S_WHEN_SYNTAX_IS_A);
+        assertOutput(FTL_S_S_A, OUT_S_A_WHEN_SYNTAX_IS_A);
+        assertOutput(FTL_A_A_S, OUT_A_S_WHEN_SYNTAX_IS_A);
+        assertOutput(FTL_A_S_A, OUT_S_A_WHEN_SYNTAX_IS_A);
+        
+        cfg.setTagSyntax(Configuration.SQUARE_BRACKET_TAG_SYNTAX);
+        assertOutput(FTL_S_A_S, OUT_A_S_WHEN_SYNTAX_IS_S);
+        assertOutput(FTL_S_S_A, OUT_S_A_WHEN_SYNTAX_IS_S);
+        assertOutput(FTL_A_A_S, OUT_A_S_WHEN_SYNTAX_IS_S);
+        assertOutput(FTL_A_S_A, OUT_S_A_WHEN_SYNTAX_IS_S);
+        
+        cfg.setTagSyntax(Configuration.AUTO_DETECT_TAG_SYNTAX);
+        assertOutput(FTL_S_A_S, OUT_A_S_WHEN_SYNTAX_IS_A);
+        assertOutput(FTL_S_S_A, OUT_S_A_WHEN_SYNTAX_IS_S);
+        assertOutput(FTL_A_A_S, OUT_A_S_WHEN_SYNTAX_IS_A);
+        assertOutput(FTL_A_S_A, OUT_S_A_WHEN_SYNTAX_IS_S);
+        assertOutput("<@'[#ftl]x'?interpret />[#if true]y[/#if]", "x[#if true]y[/#if]");
+    }
+
+    @Test
+    public void whitespaceStrippingTest() throws IOException, TemplateException {
+        Configuration cfg = getConfiguration();
+        
+        cfg.setWhitespaceStripping(true);
+        assertOutput("<#assign x = 1>\nX<@'<#assign x = 1>\\nY'?interpret />", "XY");
+        assertOutput("<#ftl stripWhitespace=false><#assign x = 1>\nX<@'<#assign x = 1>\\nY'?interpret />", "\nXY");
+        assertOutput("<#assign x = 1>\nX<@'<#ftl stripWhitespace=false><#assign x = 1>\\nY'?interpret />", "X\nY");
+        
+        cfg.setWhitespaceStripping(false);
+        assertOutput("<#assign x = 1>\nX<@'<#assign x = 1>\\nY'?interpret />", "\nX\nY");
+        assertOutput("<#ftl stripWhitespace=true><#assign x = 1>\nX<@'<#assign x = 1>\\nY'?interpret />", "X\nY");
+        assertOutput("<#assign x = 1>\nX<@'<#ftl stripWhitespace=true><#assign x = 1>\\nY'?interpret />", "\nXY");
+    }
+
+    @Test
+    public void evalTest() throws IOException, TemplateException {
+        Configuration cfg = getConfiguration();
+        
+        cfg.setTagSyntax(Configuration.ANGLE_BRACKET_TAG_SYNTAX);
+        assertOutput("<@'\"[#if true]s[/#if]<#if true>a</#if>\"?interpret'?eval />", OUT_S_A_WHEN_SYNTAX_IS_A);
+        assertOutput("[#ftl][@'\"[#if true]s[/#if]<#if true>a</#if>\"?interpret'?eval /]", OUT_S_A_WHEN_SYNTAX_IS_A);
+        
+        cfg.setTagSyntax(Configuration.SQUARE_BRACKET_TAG_SYNTAX);
+        assertOutput("[@'\"[#if true]s[/#if]<#if true>a</#if>\"?interpret'?eval /]", OUT_S_A_WHEN_SYNTAX_IS_S);
+        assertOutput("<#ftl><@'\"[#if true]s[/#if]<#if true>a</#if>\"?interpret'?eval />", OUT_S_A_WHEN_SYNTAX_IS_S);
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/test/java/org/apache/freemarker/core/IteratorIssuesTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/IteratorIssuesTest.java b/src/test/java/org/apache/freemarker/core/IteratorIssuesTest.java
new file mode 100644
index 0000000..8eccc07
--- /dev/null
+++ b/src/test/java/org/apache/freemarker/core/IteratorIssuesTest.java
@@ -0,0 +1,89 @@
+/*
+ * 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.Arrays;
+import java.util.Iterator;
+
+import org.apache.freemarker.core.Configuration;
+import org.apache.freemarker.core.model.impl.DefaultObjectWrapper;
+import org.apache.freemarker.core.model.impl.DefaultObjectWrapperBuilder;
+import org.apache.freemarker.core.model.impl.beans.BeansWrapper;
+import org.apache.freemarker.core.model.impl.beans.BeansWrapperBuilder;
+import org.apache.freemarker.test.TemplateTest;
+import org.junit.Test;
+
+public class IteratorIssuesTest extends TemplateTest {
+    
+    private static final String FTL_HAS_CONTENT_AND_LIST
+            = "<#if it?hasContent><#list it as i>${i}</#list><#else>empty</#if>";
+    private static final String OUT_HAS_CONTENT_AND_LIST_ABC = "abc";
+    private static final String OUT_HAS_CONTENT_AND_LIST_EMPTY = "empty";
+    
+    private static final String FTL_LIST_AND_HAS_CONTENT
+            = "<#list it as i>${i}${it?hasContent?then('+', '-')}</#list>";
+    private static final String OUT_LIST_AND_HAS_CONTENT_BW_GOOD = "a+b+c-";
+
+    @Test
+    public void testHasContentAndListDOW() throws Exception {
+        addToDataModel("it", getDOW300().wrap(getAbcIt()));
+        assertOutput(FTL_HAS_CONTENT_AND_LIST, OUT_HAS_CONTENT_AND_LIST_ABC);
+        
+        addToDataModel("it", getDOW300().wrap(getEmptyIt()));
+        assertOutput(FTL_HAS_CONTENT_AND_LIST, OUT_HAS_CONTENT_AND_LIST_EMPTY);
+    }
+
+    @Test
+    public void testHasContentAndListBW() throws Exception {
+        addToDataModel("it", getBW300().wrap(getAbcIt()));
+        assertOutput(FTL_HAS_CONTENT_AND_LIST, OUT_HAS_CONTENT_AND_LIST_ABC);
+        
+        addToDataModel("it", getBW300().wrap(getEmptyIt()));
+        assertOutput(FTL_HAS_CONTENT_AND_LIST, OUT_HAS_CONTENT_AND_LIST_EMPTY);
+    }
+    
+    @Test
+    public void testListAndHasContentDOW() throws Exception {
+        addToDataModel("it", getDOW300().wrap(getAbcIt()));
+        assertErrorContains(FTL_LIST_AND_HAS_CONTENT, "can be listed only once");
+    }
+
+    @Test
+    public void testListAndHasContentBW() throws Exception {
+        addToDataModel("it", getBW300().wrap(getAbcIt()));
+        assertOutput(FTL_LIST_AND_HAS_CONTENT, OUT_LIST_AND_HAS_CONTENT_BW_GOOD);
+    }
+    
+    private Iterator getAbcIt() {
+        return Arrays.asList(new String[] { "a", "b", "c" }).iterator();
+    }
+
+    private Iterator getEmptyIt() {
+        return Arrays.asList(new String[] {  }).iterator();
+    }
+    
+    private DefaultObjectWrapper getDOW300() {
+        return new DefaultObjectWrapperBuilder(Configuration.VERSION_3_0_0).build();
+    }
+
+    private BeansWrapper getBW300() {
+        return new BeansWrapperBuilder(Configuration.VERSION_3_0_0).build();
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/test/java/org/apache/freemarker/core/ListErrorsTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/ListErrorsTest.java b/src/test/java/org/apache/freemarker/core/ListErrorsTest.java
new file mode 100644
index 0000000..db0965a
--- /dev/null
+++ b/src/test/java/org/apache/freemarker/core/ListErrorsTest.java
@@ -0,0 +1,138 @@
+/*
+ * 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.core.model.impl.DefaultObjectWrapper;
+import org.apache.freemarker.test.TemplateTest;
+import org.apache.freemarker.test.templatesuite.models.Listables;
+import org.junit.Test;
+
+import com.google.common.collect.ImmutableMap;
+
+public class ListErrorsTest extends TemplateTest {
+    
+    @Test
+    public void testValid() throws IOException, TemplateException {
+        assertOutput("<#list 1..2 as x><#list 3..4>${x}:<#items as x>${x}</#items></#list>;</#list>", "1:34;2:34;");
+        assertOutput("<#list [] as x>${x}<#else><#list 1..2 as x>${x}<#sep>, </#list></#list>", "1, 2");
+        assertOutput("<#macro m>[<#nested 3>]</#macro>"
+                + "<#list 1..2 as x>"
+                + "${x}@${x?index}"
+                + "<@m ; x>"
+                + "${x},"
+                + "<#list 4..4 as x>${x}@${x?index}</#list>"
+                + "</@>"
+                + "${x}@${x?index}; "
+                + "</#list>",
+                "1@0[3,4@0]1@0; 2@1[3,4@0]2@1; ");
+    }
+
+    @Test
+    public void testInvalidItemsParseTime() throws IOException, TemplateException {
+        assertErrorContains("<#items as x>${x}</#items>",
+                "#items", "must be inside", "#list");
+        assertErrorContains("<#list xs><#macro m><#items as x></#items></#macro></#list>",
+                "#items", "must be inside", "#list");
+        assertErrorContains("<#list xs><#forEach x in xs><#items as x></#items></#forEach></#list>",
+                "#forEach", "doesn't support", "#items");
+        assertErrorContains("<#list xs as x><#items as x>${x}</#items></#list>",
+                "#list", "must not have", "#items", "as loopVar");
+        assertErrorContains("<#list xs><#list xs as x><#items as x>${x}</#items></#list></#list>",
+                "#list", "must not have", "#items", "as loopVar");
+        assertErrorContains("<#list xs></#list>",
+                "#list", "must have", "#items", "as loopVar");
+        assertErrorContains("<#forEach x in xs><#items as x></#items></#forEach>",
+                "#forEach", "doesn't support", "#items");
+        assertErrorContains("<#list xs><#forEach x in xs><#items as x></#items></#forEach></#list>",
+                "#forEach", "doesn't support", "#items");
+    }
+
+    @Test
+    public void testInvalidSepParseTime() throws IOException, TemplateException {
+        assertErrorContains("<#sep>, </#sep>",
+                "#sep", "must be inside", "#list", "#foreach");
+        assertErrorContains("<#sep>, ",
+                "#sep", "must be inside", "#list", "#foreach");
+        assertErrorContains("<#list xs as x><#else><#sep>, </#list>",
+                "#sep", "must be inside", "#list", "#foreach");
+        assertErrorContains("<#list xs as x><#macro m><#sep>, </#macro></#list>",
+                "#sep", "must be inside", "#list", "#foreach");
+    }
+
+    @Test
+    public void testInvalidItemsRuntime() throws IOException, TemplateException {
+        assertErrorContains("<#list 1..1><#items as x></#items><#items as x></#items></#list>",
+                "#items", "already entered earlier");
+        assertErrorContains("<#list 1..1><#items as x><#items as y>${x}/${y}</#items></#items></#list>",
+                "#items", "Can't nest #items into each other");
+    }
+    
+    @Test
+    public void testInvalidLoopVarBuiltinLHO() {
+        assertErrorContains("<#list foos>${foo?index}</#list>",
+                "?index", "foo", "no loop variable");
+        assertErrorContains("<#list foos as foo></#list>${foo?index}",
+                "?index", "foo" , "no loop variable");
+        assertErrorContains("<#list foos as foo><#macro m>${foo?index}</#macro></#list>",
+                "?index", "foo" , "no loop variable");
+        assertErrorContains("<#list foos as foo><#function f>${foo?index}</#function></#list>",
+                "?index", "foo" , "no loop variable");
+        assertErrorContains("<#list xs as x>${foo?index}</#list>",
+                "?index", "foo" , "no loop variable");
+        assertErrorContains("<#list foos as foo><@m; foo>${foo?index}</@></#list>",
+                "?index", "foo" , "user defined directive");
+        assertErrorContains(
+                "<#list foos as foo><@m; foo><@m; foo>${foo?index}</@></@></#list>",
+                "?index", "foo" , "user defined directive");
+        assertErrorContains(
+                "<#list foos as foo><@m; foo>"
+                + "<#list foos as foo><@m; foo>${foo?index}</@></#list>"
+                + "</@></#list>",
+                "?index", "foo" , "user defined directive");
+    }
+
+    @Test
+    public void testKeyValueSameName() {
+        assertErrorContains("<#list {} as foo, foo></#list>",
+                "key", "value", "both" , "foo");
+    }
+
+    @Test
+    public void testCollectionVersusHash() {
+        assertErrorContains("<#list {} as i></#list>",
+                "as k, v");
+        assertErrorContains("<#list [] as k, v></#list>",
+                "only one loop variable");
+    }
+
+    @Test
+    public void testNonEx2NonStringKey() throws IOException, TemplateException {
+        addToDataModel("m", new Listables.NonEx2MapAdapter(ImmutableMap.of("k1", "v1", 2, "v2"),
+                new DefaultObjectWrapper(Configuration.VERSION_3_0_0)));
+        assertOutput("<#list m?keys as k>${k};</#list>", "k1;2;");
+        assertErrorContains("<#list m as k, v></#list>",
+                "string", "number", ".TemplateHashModelEx2");
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/test/java/org/apache/freemarker/core/LocAndTZSensitiveTemplateDateFormatFactory.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/LocAndTZSensitiveTemplateDateFormatFactory.java b/src/test/java/org/apache/freemarker/core/LocAndTZSensitiveTemplateDateFormatFactory.java
new file mode 100644
index 0000000..e2fc454
--- /dev/null
+++ b/src/test/java/org/apache/freemarker/core/LocAndTZSensitiveTemplateDateFormatFactory.java
@@ -0,0 +1,97 @@
+/*
+ * 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 LocAndTZSensitiveTemplateDateFormatFactory extends TemplateDateFormatFactory {
+
+    public static final LocAndTZSensitiveTemplateDateFormatFactory INSTANCE = new LocAndTZSensitiveTemplateDateFormatFactory();
+    
+    private LocAndTZSensitiveTemplateDateFormatFactory() {
+        // 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 new LocAndTZSensitiveTemplateDateFormat(locale, timeZone);
+    }
+
+    private static class LocAndTZSensitiveTemplateDateFormat extends TemplateDateFormat {
+
+        private final Locale locale;
+        private final TimeZone timeZone;
+        
+        public LocAndTZSensitiveTemplateDateFormat(Locale locale, TimeZone timeZone) {
+            this.locale = locale;
+            this.timeZone = timeZone;
+        }
+
+        @Override
+        public String formatToPlainText(TemplateDateModel dateModel)
+                throws UnformattableValueException, TemplateModelException {
+            return String.valueOf(TemplateFormatUtil.getNonNullDate(dateModel).getTime() + "@" + locale + ":" + timeZone.getID());
+        }
+
+        @Override
+        public boolean isLocaleBound() {
+            return true;
+        }
+
+        @Override
+        public boolean isTimeZoneBound() {
+            return true;
+        }
+
+        @Override
+        public Date parse(String s, int dateType) throws UnparsableValueException {
+            try {
+                int atIdx = s.indexOf("@");
+                if (atIdx == -1) {
+                    throw new UnparsableValueException("Missing @");
+                }
+                return new Date(Long.parseLong(s.substring(0, atIdx)));
+            } catch (NumberFormatException e) {
+                throw new UnparsableValueException("Malformed long");
+            }
+        }
+
+        @Override
+        public String getDescription() {
+            return "millis since the epoch";
+        }
+        
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/test/java/org/apache/freemarker/core/LocaleSensitiveTemplateNumberFormatFactory.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/LocaleSensitiveTemplateNumberFormatFactory.java b/src/test/java/org/apache/freemarker/core/LocaleSensitiveTemplateNumberFormatFactory.java
new file mode 100644
index 0000000..3f4b7a1
--- /dev/null
+++ b/src/test/java/org/apache/freemarker/core/LocaleSensitiveTemplateNumberFormatFactory.java
@@ -0,0 +1,78 @@
+/*
+ * 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.UnformattableValueException;
+import org.apache.freemarker.core.model.TemplateModelException;
+import org.apache.freemarker.core.model.TemplateNumberModel;
+
+public class LocaleSensitiveTemplateNumberFormatFactory extends TemplateNumberFormatFactory {
+
+    public static final LocaleSensitiveTemplateNumberFormatFactory INSTANCE = new LocaleSensitiveTemplateNumberFormatFactory();
+    
+    private LocaleSensitiveTemplateNumberFormatFactory() {
+        // Defined to decrease visibility
+    }
+    
+    @Override
+    public TemplateNumberFormat get(String params, Locale locale, Environment env)
+            throws InvalidFormatParametersException {
+        TemplateFormatUtil.checkHasNoParameters(params);
+        return new LocaleSensitiveTemplateNumberFormat(locale);
+    }
+
+    private static class LocaleSensitiveTemplateNumberFormat extends TemplateNumberFormat {
+    
+        private final Locale locale;
+        
+        private LocaleSensitiveTemplateNumberFormat(Locale locale) {
+            this.locale = locale;
+        }
+        
+        @Override
+        public String formatToPlainText(TemplateNumberModel numberModel)
+                throws UnformattableValueException, TemplateModelException {
+            Number n = numberModel.getAsNumber();
+            try {
+                return n + "_" + locale;
+            } catch (ArithmeticException e) {
+                throw new UnformattableValueException(n + " doesn't fit into an int");
+            }
+        }
+    
+        @Override
+        public boolean isLocaleBound() {
+            return true;
+        }
+    
+        @Override
+        public String getDescription() {
+            return "test locale sensitive";
+        }
+        
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/test/java/org/apache/freemarker/core/MiscErrorMessagesTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/MiscErrorMessagesTest.java b/src/test/java/org/apache/freemarker/core/MiscErrorMessagesTest.java
new file mode 100644
index 0000000..7d3dccd
--- /dev/null
+++ b/src/test/java/org/apache/freemarker/core/MiscErrorMessagesTest.java
@@ -0,0 +1,47 @@
+/*
+ * 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.templateresolver.impl.DefaultTemplateNameFormat;
+import org.apache.freemarker.test.TemplateTest;
+import org.junit.Test;
+
+public class MiscErrorMessagesTest extends TemplateTest {
+
+    @Test
+    public void stringIndexOutOfBounds() {
+        assertErrorContains("${'foo'[10]}", "length", "3", "10", "String index out of");
+    }
+    
+    @Test
+    public void wrongTemplateNameFormat() {
+        getConfiguration().setTemplateNameFormat(DefaultTemplateNameFormat.INSTANCE);
+        
+        assertErrorContains("<#include 'foo:/bar:baaz'>", "Malformed template name", "':'");
+        assertErrorContains("<#include '../baaz'>", "Malformed template name", "root");
+        assertErrorContains("<#include '\u0000'>", "Malformed template name", "\\u0000");
+    }
+
+    @Test
+    public void numericalKeyHint() {
+        assertErrorContains("${{}[10]}", "[]", "?api");
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/test/java/org/apache/freemarker/core/MistakenlyPublicImportAPIsTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/MistakenlyPublicImportAPIsTest.java b/src/test/java/org/apache/freemarker/core/MistakenlyPublicImportAPIsTest.java
index 04054ad..0b9e0b6 100644
--- a/src/test/java/org/apache/freemarker/core/MistakenlyPublicImportAPIsTest.java
+++ b/src/test/java/org/apache/freemarker/core/MistakenlyPublicImportAPIsTest.java
@@ -28,10 +28,7 @@ import java.io.IOException;
 import java.io.StringWriter;
 import java.util.List;
 
-import org.apache.freemarker.core.ast.Environment;
-import org.apache.freemarker.core.ast.Environment.Namespace;
-import org.apache.freemarker.core.ast.InvalidReferenceException;
-import org.apache.freemarker.core.ast.LibraryLoad;
+import org.apache.freemarker.core.Environment.Namespace;
 import org.apache.freemarker.core.model.TemplateModel;
 import org.apache.freemarker.core.templateresolver.impl.StringTemplateLoader;
 import org.apache.freemarker.core.util._NullWriter;
@@ -51,12 +48,12 @@ public class MistakenlyPublicImportAPIsTest {
         cfg.setTemplateLoader(tl);
         
         Template t1 = new Template(null, "<#import 'imp1' as i1><#import 'imp2' as i2>", cfg);
-        List<LibraryLoad> imports = t1.getImports();
+        List<ASTDirImport> imports = t1.getImports();
         assertEquals(2, imports.size());
         
         {
             Template t2 = new Template(null, "<@i1.m/><@i2.m/>", cfg);
-            for (LibraryLoad libLoad : imports) {
+            for (ASTDirImport libLoad : imports) {
                 t2.addImport(libLoad);
             }
             

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/test/java/org/apache/freemarker/core/MistakenlyPublicMacroAPIsTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/MistakenlyPublicMacroAPIsTest.java b/src/test/java/org/apache/freemarker/core/MistakenlyPublicMacroAPIsTest.java
index 21e80c2..0445789 100644
--- a/src/test/java/org/apache/freemarker/core/MistakenlyPublicMacroAPIsTest.java
+++ b/src/test/java/org/apache/freemarker/core/MistakenlyPublicMacroAPIsTest.java
@@ -27,8 +27,6 @@ import java.io.IOException;
 import java.io.StringWriter;
 import java.util.Map;
 
-import org.apache.freemarker.core.ast.Environment;
-import org.apache.freemarker.core.ast.Macro;
 import org.apache.freemarker.core.model.TemplateModel;
 import org.apache.freemarker.core.util._NullWriter;
 import org.junit.Test;
@@ -46,7 +44,7 @@ public class MistakenlyPublicMacroAPIsTest {
     @Test
     public void testMacroCopyingExploit() throws IOException, TemplateException {
         Template tMacros = new Template(null, "<#macro m1>1</#macro><#macro m2>2</#macro>", cfg);
-        Map<String, Macro> macros = tMacros.getMacros();
+        Map<String, ASTDirMacro> macros = tMacros.getMacros();
         
         Template t = new Template(null,
                 "<@m1/><@m2/><@m3/>"
@@ -62,7 +60,7 @@ public class MistakenlyPublicMacroAPIsTest {
     public void testMacroCopyingExploitAndNamespaces() throws IOException, TemplateException {
         Template tMacros = new Template(null, "<#assign x = 0><#macro m1>${x}</#macro>", cfg);
         Template t = new Template(null, "<#assign x = 1><@m1/>", cfg);
-        t.addMacro((Macro) tMacros.getMacros().get("m1"));
+        t.addMacro((ASTDirMacro) tMacros.getMacros().get("m1"));
         
         assertEquals("1", getTemplateOutput(t));
     }
@@ -73,10 +71,10 @@ public class MistakenlyPublicMacroAPIsTest {
         Environment env = tMacros.createProcessingEnvironment(null, _NullWriter.INSTANCE);
         env.process();
         TemplateModel m1 = env.getVariable("m1");
-        assertThat(m1, instanceOf(Macro.class));
+        assertThat(m1, instanceOf(ASTDirMacro.class));
         
         Template t = new Template(null, "<#assign x = 1><@m1/>", cfg);
-        t.addMacro((Macro) m1);
+        t.addMacro((ASTDirMacro) m1);
         
         assertEquals("1", getTemplateOutput(t));
     }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/test/java/org/apache/freemarker/core/NumberFormatTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/NumberFormatTest.java b/src/test/java/org/apache/freemarker/core/NumberFormatTest.java
new file mode 100644
index 0000000..18293ee
--- /dev/null
+++ b/src/test/java/org/apache/freemarker/core/NumberFormatTest.java
@@ -0,0 +1,326 @@
+/*
+ * 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.instanceOf;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertThat;
+
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.Collections;
+import java.util.Locale;
+import java.util.Map;
+
+import org.apache.freemarker.core.AliasTemplateNumberFormatFactory;
+import org.apache.freemarker.core.Configuration;
+import org.apache.freemarker.core.Environment;
+import org.apache.freemarker.core.Template;
+import org.apache.freemarker.core.TemplateConfiguration;
+import org.apache.freemarker.core.TemplateException;
+import org.apache.freemarker.core.TemplateNumberFormat;
+import org.apache.freemarker.core.TemplateNumberFormatFactory;
+import org.apache.freemarker.core.UndefinedCustomFormatException;
+import org.apache.freemarker.core.model.TemplateDirectiveBody;
+import org.apache.freemarker.core.model.TemplateDirectiveModel;
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateModelException;
+import org.apache.freemarker.core.model.TemplateNumberModel;
+import org.apache.freemarker.core.model.impl.SimpleNumber;
+import org.apache.freemarker.core.templateresolver.ConditionalTemplateConfigurationFactory;
+import org.apache.freemarker.core.templateresolver.FileNameGlobMatcher;
+import org.apache.freemarker.test.TemplateTest;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.google.common.collect.ImmutableMap;
+
+@SuppressWarnings("boxing")
+public class NumberFormatTest extends TemplateTest {
+    
+    @Before
+    public void setup() {
+        Configuration cfg = getConfiguration();
+        cfg.setLocale(Locale.US);
+        
+        cfg.setCustomNumberFormats(ImmutableMap.of(
+                "hex", HexTemplateNumberFormatFactory.INSTANCE,
+                "loc", LocaleSensitiveTemplateNumberFormatFactory.INSTANCE,
+                "base", BaseNTemplateNumberFormatFactory.INSTANCE,
+                "printfG", PrintfGTemplateNumberFormatFactory.INSTANCE));
+    }
+
+    @Test
+    public void testUnknownCustomFormat() throws Exception {
+        {
+            getConfiguration().setNumberFormat("@noSuchFormat");
+            Throwable exc = assertErrorContains("${1}", "\"@noSuchFormat\"", "\"noSuchFormat\"");
+            assertThat(exc.getCause(), instanceOf(UndefinedCustomFormatException.class));
+        }
+
+        {
+            getConfiguration().setNumberFormat("number");
+            Throwable exc = assertErrorContains("${1?string('@noSuchFormat2')}",
+                    "\"@noSuchFormat2\"", "\"noSuchFormat2\"");
+            assertThat(exc.getCause(), instanceOf(UndefinedCustomFormatException.class));
+        }
+    }
+    
+    @Test
+    public void testStringBI() throws Exception {
+        assertOutput("${11} ${11?string.@hex} ${12} ${12?string.@hex}", "11 b 12 c");
+    }
+
+    @Test
+    public void testSetting() throws Exception {
+        getConfiguration().setNumberFormat("@hex");
+        assertOutput("${11?string.number} ${11} ${12?string.number} ${12}", "11 b 12 c");
+    }
+
+    @Test
+    public void testSetting2() throws Exception {
+        assertOutput(
+                "<#setting numberFormat='@hex'>${11?string.number} ${11} ${12?string.number} ${12} ${13?string}"
+                + "<#setting numberFormat='@loc'>${11?string.number} ${11} ${12?string.number} ${12} ${13?string}",
+                "11 b 12 c d"
+                + "11 11_en_US 12 12_en_US 13_en_US");
+    }
+    
+    @Test
+    public void testUnformattableNumber() throws Exception {
+        getConfiguration().setNumberFormat("@hex");
+        assertErrorContains("${1.1}", "hexadecimal int", "doesn't fit into an int");
+    }
+
+    @Test
+    public void testLocaleSensitive() throws Exception {
+        Configuration cfg = getConfiguration();
+        cfg.setNumberFormat("@loc");
+        assertOutput("${1.1}", "1.1_en_US");
+        cfg.setLocale(Locale.GERMANY);
+        assertOutput("${1.1}", "1.1_de_DE");
+    }
+
+    @Test
+    public void testLocaleSensitive2() throws Exception {
+        Configuration cfg = getConfiguration();
+        cfg.setNumberFormat("@loc");
+        assertOutput("${1.1} <#setting locale='de_DE'>${1.1}", "1.1_en_US 1.1_de_DE");
+    }
+
+    @Test
+    public void testCustomParameterized() throws Exception {
+        Configuration cfg = getConfiguration();
+        cfg.setNumberFormat("@base 2");
+        assertOutput("${11}", "1011");
+        assertOutput("${11?string}", "1011");
+        assertOutput("${11?string.@base_3}", "102");
+        
+        assertErrorContains("${11?string.@base_xyz}", "\"@base_xyz\"", "\"xyz\"");
+        cfg.setNumberFormat("@base");
+        assertErrorContains("${11}", "\"@base\"", "format parameter is required");
+    }
+
+    @Test
+    public void testCustomWithFallback() throws Exception {
+        Configuration cfg = getConfiguration();
+        cfg.setNumberFormat("@base 2|0.0#");
+        assertOutput("${11}", "1011");
+        assertOutput("${11.34}", "11.34");
+        assertOutput("${11?string('@base 3|0.00')}", "102");
+        assertOutput("${11.2?string('@base 3|0.00')}", "11.20");
+    }
+
+    @Test
+    public void testEnvironmentGetters() throws Exception {
+        Template t = new Template(null, "", getConfiguration());
+        Environment env = t.createProcessingEnvironment(null, null);
+        
+        TemplateNumberFormat defF = env.getTemplateNumberFormat();
+        //
+        TemplateNumberFormat explF = env.getTemplateNumberFormat("0.00");
+        assertEquals("1.25", explF.formatToPlainText(new SimpleNumber(1.25)));
+        //
+        TemplateNumberFormat expl2F = env.getTemplateNumberFormat("@loc");
+        assertEquals("1.25_en_US", expl2F.formatToPlainText(new SimpleNumber(1.25)));
+        
+        TemplateNumberFormat explFFr = env.getTemplateNumberFormat("0.00", Locale.FRANCE);
+        assertNotSame(explF, explFFr);
+        assertEquals("1,25", explFFr.formatToPlainText(new SimpleNumber(1.25)));
+        //
+        TemplateNumberFormat expl2FFr = env.getTemplateNumberFormat("@loc", Locale.FRANCE);
+        assertEquals("1.25_fr_FR", expl2FFr.formatToPlainText(new SimpleNumber(1.25)));
+        
+        assertSame(env.getTemplateNumberFormat(), defF);
+        //
+        assertSame(env.getTemplateNumberFormat("0.00"), explF);
+        //
+        assertSame(env.getTemplateNumberFormat("@loc"), expl2F);
+    }
+    
+    /**
+     * ?string formats lazily (at least in 2.3.x), so it must make a snapshot of the format inputs when it's called.
+     */
+    @Test
+    public void testStringBIDoesSnapshot() throws Exception {
+        // TemplateNumberModel-s shouldn't change, but we have to keep BC when that still happens.
+        final MutableTemplateNumberModel nm = new MutableTemplateNumberModel();
+        nm.setNumber(123);
+        addToDataModel("n", nm);
+        addToDataModel("incN", new TemplateDirectiveModel() {
+            
+            @Override
+            public void execute(Environment env, Map params, TemplateModel[] loopVars, TemplateDirectiveBody body)
+                    throws TemplateException, IOException {
+                nm.setNumber(nm.getAsNumber().intValue() + 1);
+            }
+        });
+        assertOutput(
+                "<#assign s1 = n?string>"
+                + "<#setting numberFormat='@loc'>"
+                + "<#assign s2 = n?string>"
+                + "<#setting numberFormat='@hex'>"
+                + "<#assign s3 = n?string>"
+                + "${s1} ${s2} ${s3}",
+                "123 123_en_US 7b");
+        assertOutput(
+                "<#assign s1 = n?string>"
+                + "<@incN />"
+                + "<#assign s2 = n?string>"
+                + "${s1} ${s2}",
+                "123 124");
+    }
+
+    @Test
+    public void testNullInModel() throws Exception {
+        addToDataModel("n", new MutableTemplateNumberModel());
+        assertErrorContains("${n}", "nothing inside it");
+        assertErrorContains("${n?string}", "nothing inside it");
+    }
+    
+    @Test
+    public void testAtPrefix() throws Exception {
+        Configuration cfg = getConfiguration();
+        
+        cfg.setNumberFormat("@hex");
+        assertOutput("${10}", "a");
+        cfg.setNumberFormat("'@'0");
+        assertOutput("${10}", "@10");
+        cfg.setNumberFormat("@@0");
+        assertOutput("${10}", "@@10");
+        
+        cfg.setCustomNumberFormats(Collections.<String, TemplateNumberFormatFactory>emptyMap());
+        cfg.setNumberFormat("@hex");
+        assertErrorContains("${10}", "custom", "\"hex\"");
+        cfg.setNumberFormat("'@'0");
+        assertOutput("${10}", "@10");
+        cfg.setNumberFormat("@@0");
+        assertOutput("${10}", "@@10");
+    }
+
+    @Test
+    public void testAlieses() throws Exception {
+        Configuration cfg = getConfiguration();
+        cfg.setCustomNumberFormats(ImmutableMap.of(
+                "f", new AliasTemplateNumberFormatFactory("0.#'f'"),
+                "d", new AliasTemplateNumberFormatFactory("0.0#"),
+                "hex", HexTemplateNumberFormatFactory.INSTANCE));
+        
+        TemplateConfiguration tc = new TemplateConfiguration();
+        tc.setCustomNumberFormats(ImmutableMap.of(
+                "d", new AliasTemplateNumberFormatFactory("0.#'d'"),
+                "i", new AliasTemplateNumberFormatFactory("@hex")));
+        cfg.setTemplateConfigurations(new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("*2*"), tc));
+        
+        String commonFtl = "${1?string.@f} ${1?string.@d} "
+                + "<#setting locale='fr_FR'>${1.5?string.@d} "
+                + "<#attempt>${10?string.@i}<#recover>E</#attempt>";
+        addTemplate("t1.ftl", commonFtl);
+        addTemplate("t2.ftl", commonFtl);
+        
+        assertOutputForNamed("t1.ftl", "1f 1.0 1,5 E");
+        assertOutputForNamed("t2.ftl", "1f 1d 1,5d a");
+    }
+
+    @Test
+    public void testAlieses2() throws Exception {
+        Configuration cfg = getConfiguration();
+        cfg.setCustomNumberFormats(ImmutableMap.of(
+                "n", new AliasTemplateNumberFormatFactory("0.0",
+                        ImmutableMap.of(
+                                new Locale("en"), "0.0'_en'",
+                                Locale.UK, "0.0'_en_GB'",
+                                Locale.FRANCE, "0.0'_fr_FR'"))));
+        cfg.setNumberFormat("@n");
+        assertOutput(
+                "<#setting locale='en_US'>${1} "
+                + "<#setting locale='en_GB'>${1} "
+                + "<#setting locale='en_GB_Win'>${1} "
+                + "<#setting locale='fr_FR'>${1} "
+                + "<#setting locale='hu_HU'>${1}",
+                "1.0_en 1.0_en_GB 1.0_en_GB 1,0_fr_FR 1,0");
+    }
+    
+    @Test
+    public void testMarkupFormat() throws IOException, TemplateException {
+        getConfiguration().setNumberFormat("@printfG_3");
+
+        String commonFTL = "${1234567} ${'cat:' + 1234567} ${0.0000123}";
+        String commonOutput = "1.23*10<sup>6</sup> cat:1.23*10<sup>6</sup> 1.23*10<sup>-5</sup>";
+        assertOutput(commonFTL, commonOutput);
+        assertOutput("<#ftl outputFormat='HTML'>" + commonFTL, commonOutput);
+        assertOutput("<#escape x as x?html>" + commonFTL + "</#escape>", commonOutput);
+        assertOutput("<#escape x as x?xhtml>" + commonFTL + "</#escape>", commonOutput);
+        assertOutput("<#escape x as x?xml>" + commonFTL + "</#escape>", commonOutput);
+        assertOutput("${\"" + commonFTL + "\"}", "1.23*10<sup>6</sup> cat:1.23*10<sup>6</sup> 1.23*10<sup>-5</sup>");
+        assertErrorContains("<#ftl outputFormat='plainText'>" + commonFTL, "HTML", "plainText", "conversion");
+    }
+
+    @Test
+    public void testPrintG() throws IOException, TemplateException {
+        for (Number n : new Number[] {
+                1234567, 1234567L, 1234567d, 1234567f, BigInteger.valueOf(1234567), BigDecimal.valueOf(1234567) }) {
+            addToDataModel("n", n);
+            
+            assertOutput("${n?string.@printfG}", "1.23457E+06");
+            assertOutput("${n?string.@printfG_3}", "1.23E+06");
+            assertOutput("${n?string.@printfG_7}", "1234567");
+            assertOutput("${0.0000123?string.@printfG}", "1.23000E-05");
+        }
+    }
+    
+    private static class MutableTemplateNumberModel implements TemplateNumberModel {
+        
+        private Number number;
+
+        public void setNumber(Number number) {
+            this.number = number;
+        }
+
+        @Override
+        public Number getAsNumber() throws TemplateModelException {
+            return number;
+        }
+        
+    }
+    
+}


Mime
View raw message