Continued work on the TemplateLanguage feature: Moved TemplateLanguage 
detection (based on file extension) and InputStream to Reader conversion into 
Template. (The two is related as to create the optimal kind of InputStream you 
have to known if the language supports specifying its own charset.) Evolved 
TemplateLanguage and related API-s further (such as 
TemplateResolverDependencies). For now, all this is only set up to handle the 
FM2 file extensions (ftl, ftlh, ftlx.)


Project: http://git-wip-us.apache.org/repos/asf/freemarker/repo
Commit: http://git-wip-us.apache.org/repos/asf/freemarker/commit/53b7c952
Tree: http://git-wip-us.apache.org/repos/asf/freemarker/tree/53b7c952
Diff: http://git-wip-us.apache.org/repos/asf/freemarker/diff/53b7c952

Branch: refs/heads/3
Commit: 53b7c9524e4d48dc3c19c3003909001841085424
Parents: 9fb5184
Author: ddekany <ddek...@apache.org>
Authored: Wed Apr 4 21:24:59 2018 +0200
Committer: ddekany <ddek...@apache.org>
Committed: Fri Apr 6 00:35:46 2018 +0200

----------------------------------------------------------------------
 .../core/CustomTemplateResolverTest.java        |   6 +-
 .../freemarker/core/BuiltInsForStringsMisc.java |   8 +-
 .../apache/freemarker/core/Configuration.java   |  11 +
 .../core/DefaultTemplateLanguage.java           |  49 +++-
 .../freemarker/core/ParsingConfiguration.java   |   1 +
 .../core/ParsingConfigurationWithFallback.java  |   2 +-
 ...nfigurationWithTemplateLanguageOverride.java | 154 +++++++++++
 .../core/StaticTextTemplateLanguage.java        |  40 ++-
 .../org/apache/freemarker/core/Template.java    | 270 ++++++++++++++-----
 .../freemarker/core/TemplateLanguage.java       |  64 ++++-
 .../core/TemplateResolverDependenciesImpl.java  |  15 +-
 .../core/templateresolver/TemplateResolver.java |  10 +-
 .../TemplateResolverDependencies.java           |  21 +-
 .../impl/DefaultTemplateResolver.java           |  76 ++----
 freemarker-core/src/main/javacc/FTL.jj          |  53 +---
 15 files changed, 558 insertions(+), 222 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/freemarker/blob/53b7c952/freemarker-core-test/src/test/java/org/apache/freemarker/core/CustomTemplateResolverTest.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core-test/src/test/java/org/apache/freemarker/core/CustomTemplateResolverTest.java
 
b/freemarker-core-test/src/test/java/org/apache/freemarker/core/CustomTemplateResolverTest.java
index d44e999..0990957 100644
--- 
a/freemarker-core-test/src/test/java/org/apache/freemarker/core/CustomTemplateResolverTest.java
+++ 
b/freemarker-core-test/src/test/java/org/apache/freemarker/core/CustomTemplateResolverTest.java
@@ -213,7 +213,6 @@ public class CustomTemplateResolverTest {
     static class CustomTemplateResolver extends TemplateResolver {
 
         private final String supportedSetting;
-        private TemplateLanguage templateLanguage;
 
         CustomTemplateResolver(String supportedSetting) {
             this.supportedSetting = supportedSetting;
@@ -300,7 +299,6 @@ public class CustomTemplateResolverTest {
                 }
             }
 
-            templateLanguage = deps.getTemplateLanguage();
             deps.getSourceEncoding();
         }
 
@@ -314,13 +312,13 @@ public class CustomTemplateResolverTest {
                 throws IOException {
             name = normalizeRootBasedName(name);
             return new GetTemplateResult(getDependencies()
-                    .parse(templateLanguage, name, name,
+                    .newTemplate(name, name,
                             new StringReader(
                                     "In " + name
                                     + (name.endsWith("includes")
                                         ? ", included: <#include 'inc'>"
                                         : "")),
-                            null, null, null));
+                            null));
         }
 
         @Override

http://git-wip-us.apache.org/repos/asf/freemarker/blob/53b7c952/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForStringsMisc.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForStringsMisc.java
 
b/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForStringsMisc.java
index 7df439e..f062612 100644
--- 
a/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForStringsMisc.java
+++ 
b/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForStringsMisc.java
@@ -173,12 +173,12 @@ class BuiltInsForStringsMisc {
             try {
                 // pCfg.outputFormat+autoEscapingPolicy is exceptional: it's 
inherited from the lexical context
                 interpretedTemplate = new Template(
-                        (parentTemplate.getLookupName() != null ? 
parentTemplate.getLookupName() : "nameless_template") + "->" + id,
+                        (parentTemplate.getLookupName() != null
+                                ? parentTemplate.getLookupName() : 
"nameless_template") + "->" + id,
                         null,
-                        new StringReader(templateSource),
+                        null, null, new StringReader(templateSource),
                         parentTemplate.getConfiguration(), 
parentTemplate.getTemplateConfiguration(),
-                        outputFormat, autoEscapingPolicy,
-                        null, null);
+                        outputFormat, autoEscapingPolicy);
             } catch (IOException e) {
                 throw new TemplateException(this, e, env,
                         "Template parsing with \"?", key, "\" has failed with 
this error:\n\n",

http://git-wip-us.apache.org/repos/asf/freemarker/blob/53b7c952/freemarker-core/src/main/java/org/apache/freemarker/core/Configuration.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/Configuration.java 
b/freemarker-core/src/main/java/org/apache/freemarker/core/Configuration.java
index 359dbd6..ac8617d 100644
--- 
a/freemarker-core/src/main/java/org/apache/freemarker/core/Configuration.java
+++ 
b/freemarker-core/src/main/java/org/apache/freemarker/core/Configuration.java
@@ -753,6 +753,17 @@ public final class Configuration implements 
TopLevelConfiguration, CustomStateSc
             return stdOF;
         }
     }
+    
+    /**
+     * Returns the argument {@link OutputFormat} as is, unless a {@link 
#getRegisteredCustomOutputFormats()
+     * customOutputFormats} contains
+     * another {@link OutputFormat} with the same name, in which case it 
returns that instead.
+     */
+    public OutputFormat getCustomOrArgumentOutputFormat(OutputFormat original) 
{
+        _NullArgumentException.check("original", original);
+        OutputFormat custOF = 
registeredCustomOutputFormatsByName.get(original.getName());
+        return custOF != null ? custOF : original;
+    }
 
     private MarkupOutputFormat getMarkupOutputFormatForCombined(String 
outerName)
             throws UnregisteredOutputFormatException {

http://git-wip-us.apache.org/repos/asf/freemarker/blob/53b7c952/freemarker-core/src/main/java/org/apache/freemarker/core/DefaultTemplateLanguage.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/DefaultTemplateLanguage.java
 
b/freemarker-core/src/main/java/org/apache/freemarker/core/DefaultTemplateLanguage.java
index 8973c01..a65d0ac 100644
--- 
a/freemarker-core/src/main/java/org/apache/freemarker/core/DefaultTemplateLanguage.java
+++ 
b/freemarker-core/src/main/java/org/apache/freemarker/core/DefaultTemplateLanguage.java
@@ -22,7 +22,6 @@ package org.apache.freemarker.core;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.Reader;
-import java.nio.charset.Charset;
 
 import org.apache.freemarker.core.outputformat.OutputFormat;
 import org.apache.freemarker.core.outputformat.impl.HTMLOutputFormat;
@@ -31,12 +30,10 @@ import 
org.apache.freemarker.core.outputformat.impl.XMLOutputFormat;
 
 // FIXME [FM3] If we leave this here, FTL will be a required dependency of 
core (which is not nice if
 // template languages will be pluggable).
-final public class DefaultTemplateLanguage extends TemplateLanguage {
+public final class DefaultTemplateLanguage extends TemplateLanguage {
 
     private final TagSyntax tagSyntax;
     private final InterpolationSyntax interpolationSyntax;
-    private final OutputFormat outputFormat;
-    private final AutoEscapingPolicy autoEscapingPolicy;
 
     /**
      * For the case when the file extension doesn't specify the exact syntax 
and the output format, instead both comes
@@ -44,6 +41,16 @@ final public class DefaultTemplateLanguage extends 
TemplateLanguage {
      */
     public static final TemplateLanguage F3CC = new 
DefaultTemplateLanguage("f3cc", null, null, null, null);
 
+    // TODO [FM3][CF] Temporary solution, will be removed
+    public static final TemplateLanguage F3CH = new 
DefaultTemplateLanguage("ftlh",
+            null, null,
+            HTMLOutputFormat.INSTANCE, AutoEscapingPolicy.ENABLE_IF_DEFAULT);
+    
+    // TODO [FM3][CF] Temporary solution, will be removed
+    public static final TemplateLanguage F3CX = new 
DefaultTemplateLanguage("ftlx",
+            null, null,
+            XMLOutputFormat.INSTANCE, AutoEscapingPolicy.ENABLE_IF_DEFAULT);
+    
     public static final TemplateLanguage F3AH = new 
DefaultTemplateLanguage("f3ah",
             TagSyntax.ANGLE_BRACKET, InterpolationSyntax.DOLLAR,
             HTMLOutputFormat.INSTANCE, AutoEscapingPolicy.ENABLE_IF_DEFAULT);
@@ -89,23 +96,43 @@ final public class DefaultTemplateLanguage extends 
TemplateLanguage {
             String name,
             TagSyntax tagSyntax, InterpolationSyntax interpolationSyntax,
             OutputFormat outputFormat, AutoEscapingPolicy autoEscapingPolicy) {
-        super(name);
+        super(name, outputFormat, autoEscapingPolicy);
         this.tagSyntax = tagSyntax;
         this.interpolationSyntax = interpolationSyntax;
-        this.outputFormat = outputFormat;
-        this.autoEscapingPolicy = autoEscapingPolicy;
     }
 
+    /**
+     * {@inheritDoc}
+     * 
+     * In {@link DefaultTemplateLanguage} this returns {@code true}.
+     */
     @Override
-    public boolean getCanSpecifyCharsetInContent() {
+    public boolean getCanSpecifyEncodingInContent() {
         return true;
     }
 
     @Override
-    public Template parse(String name, String sourceName, Reader reader, 
Configuration cfg,
-            TemplateConfiguration templateConfiguration, Charset encoding, 
InputStream streamToUnmarkWhenEncEstabd)
+    public ASTElement parse(Template template, Reader reader,
+            ParsingConfiguration pCfg, OutputFormat contextOutputFormat, 
AutoEscapingPolicy contextAutoEscapingPolicy,
+            InputStream streamToUnmarkWhenEncEstabd)
             throws IOException, ParseException {
-        return new Template(name, sourceName, reader, cfg, 
templateConfiguration, encoding,
+        FMParser parser = new FMParser(
+                template, reader,
+                pCfg,
+                contextOutputFormat,
+                contextAutoEscapingPolicy,
                 streamToUnmarkWhenEncEstabd);
+        ASTElement root = parser.Root();
+        template.actualTagSyntax = parser._getLastTagSyntax();
+        return root;
+    }
+
+    public TagSyntax getTagSyntax() {
+        return tagSyntax;
+    }
+
+    public InterpolationSyntax getInterpolationSyntax() {
+        return interpolationSyntax;
     }
+    
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/freemarker/blob/53b7c952/freemarker-core/src/main/java/org/apache/freemarker/core/ParsingConfiguration.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/ParsingConfiguration.java
 
b/freemarker-core/src/main/java/org/apache/freemarker/core/ParsingConfiguration.java
index 8e43a53..be987f9 100644
--- 
a/freemarker-core/src/main/java/org/apache/freemarker/core/ParsingConfiguration.java
+++ 
b/freemarker-core/src/main/java/org/apache/freemarker/core/ParsingConfiguration.java
@@ -175,6 +175,7 @@ public interface ParsingConfiguration {
      * <p>The settings activated by these file extensions override the setting 
values dictated by the
      * {@link Configuration#getTemplateConfigurations templateConfigurations} 
setting of the {@link Configuration}.
      */
+    // TODO [FM3] If we will support user-defined languages, then this won't 
be "Standard" after all.
     boolean getRecognizeStandardFileExtensions();
 
     /**

http://git-wip-us.apache.org/repos/asf/freemarker/blob/53b7c952/freemarker-core/src/main/java/org/apache/freemarker/core/ParsingConfigurationWithFallback.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/ParsingConfigurationWithFallback.java
 
b/freemarker-core/src/main/java/org/apache/freemarker/core/ParsingConfigurationWithFallback.java
index 8deeb47..2acd816 100644
--- 
a/freemarker-core/src/main/java/org/apache/freemarker/core/ParsingConfigurationWithFallback.java
+++ 
b/freemarker-core/src/main/java/org/apache/freemarker/core/ParsingConfigurationWithFallback.java
@@ -32,7 +32,7 @@ final class ParsingConfigurationWithFallback implements 
ParsingConfiguration {
     private final Configuration cfg;
     private final TemplateConfiguration tCfg;
 
-    ParsingConfigurationWithFallback(Configuration cfg, TemplateConfiguration 
tCfg) {
+    ParsingConfigurationWithFallback(TemplateConfiguration tCfg, Configuration 
cfg) {
         this.cfg = cfg;
         this.tCfg = tCfg;
     }

http://git-wip-us.apache.org/repos/asf/freemarker/blob/53b7c952/freemarker-core/src/main/java/org/apache/freemarker/core/ParsingConfigurationWithTemplateLanguageOverride.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/ParsingConfigurationWithTemplateLanguageOverride.java
 
b/freemarker-core/src/main/java/org/apache/freemarker/core/ParsingConfigurationWithTemplateLanguageOverride.java
new file mode 100644
index 0000000..a2bcc1b
--- /dev/null
+++ 
b/freemarker-core/src/main/java/org/apache/freemarker/core/ParsingConfigurationWithTemplateLanguageOverride.java
@@ -0,0 +1,154 @@
+/*
+ * 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.nio.charset.Charset;
+
+import org.apache.freemarker.core.arithmetic.ArithmeticEngine;
+import org.apache.freemarker.core.outputformat.OutputFormat;
+
+/**
+ * Used when the {@link TemplateLanguage} in the {@link ParsingConfiguration} 
need to be replaced.
+ */
+final class ParsingConfigurationWithTemplateLanguageOverride implements 
ParsingConfiguration {
+
+    private final ParsingConfiguration pCfg;
+    private final TemplateLanguage templateLanguage;
+    
+    public 
ParsingConfigurationWithTemplateLanguageOverride(ParsingConfiguration pCfg,
+            TemplateLanguage templateLanguage) {
+        this.pCfg = pCfg;
+        this.templateLanguage = templateLanguage;
+    }
+
+    @Override
+    public TemplateLanguage getTemplateLanguage() {
+        return templateLanguage;
+    }
+
+    @Override
+    public boolean isTemplateLanguageSet() {
+        return true;
+    }
+
+    @Override
+    public TagSyntax getTagSyntax() {
+        return pCfg.getTagSyntax();
+    }
+
+    @Override
+    public boolean isTagSyntaxSet() {
+        return pCfg.isTagSyntaxSet();
+    }
+
+    @Override
+    public InterpolationSyntax getInterpolationSyntax() {
+        return pCfg.getInterpolationSyntax();
+    }
+
+    @Override
+    public boolean isInterpolationSyntaxSet() {
+        return pCfg.isInterpolationSyntaxSet();
+    }
+
+    @Override
+    public boolean getWhitespaceStripping() {
+        return pCfg.getWhitespaceStripping();
+    }
+
+    @Override
+    public boolean isWhitespaceStrippingSet() {
+        return pCfg.isWhitespaceStrippingSet();
+    }
+
+    @Override
+    public ArithmeticEngine getArithmeticEngine() {
+        return pCfg.getArithmeticEngine();
+    }
+
+    @Override
+    public boolean isArithmeticEngineSet() {
+        return pCfg.isArithmeticEngineSet();
+    }
+
+    @Override
+    public AutoEscapingPolicy getAutoEscapingPolicy() {
+        return pCfg.getAutoEscapingPolicy();
+    }
+
+    @Override
+    public boolean isAutoEscapingPolicySet() {
+        return pCfg.isAutoEscapingPolicySet();
+    }
+
+    @Override
+    public OutputFormat getOutputFormat() {
+        return pCfg.getOutputFormat();
+    }
+
+    @Override
+    public boolean isOutputFormatSet() {
+        return pCfg.isOutputFormatSet();
+    }
+
+    @Override
+    public boolean getRecognizeStandardFileExtensions() {
+        return pCfg.getRecognizeStandardFileExtensions();
+    }
+
+    @Override
+    public boolean isRecognizeStandardFileExtensionsSet() {
+        return pCfg.isRecognizeStandardFileExtensionsSet();
+    }
+
+    @Override
+    public Version getIncompatibleImprovements() {
+        return pCfg.getIncompatibleImprovements();
+    }
+
+    @Override
+    public boolean isIncompatibleImprovementsSet() {
+        return pCfg.isIncompatibleImprovementsSet();
+    }
+
+    @Override
+    public int getTabSize() {
+        return pCfg.getTabSize();
+    }
+
+    @Override
+    public boolean isTabSizeSet() {
+        return pCfg.isTabSizeSet();
+    }
+
+    @Override
+    public Charset getSourceEncoding() {
+        return pCfg.getSourceEncoding();
+    }
+
+    @Override
+    public boolean isSourceEncodingSet() {
+        return pCfg.isSourceEncodingSet();
+    }
+    
+    
+    
+    
+}

http://git-wip-us.apache.org/repos/asf/freemarker/blob/53b7c952/freemarker-core/src/main/java/org/apache/freemarker/core/StaticTextTemplateLanguage.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/StaticTextTemplateLanguage.java
 
b/freemarker-core/src/main/java/org/apache/freemarker/core/StaticTextTemplateLanguage.java
index 4b17801..363d28c 100644
--- 
a/freemarker-core/src/main/java/org/apache/freemarker/core/StaticTextTemplateLanguage.java
+++ 
b/freemarker-core/src/main/java/org/apache/freemarker/core/StaticTextTemplateLanguage.java
@@ -22,37 +22,55 @@ package org.apache.freemarker.core;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.Reader;
-import java.nio.charset.Charset;
+import java.io.StringReader;
+
+import org.apache.freemarker.core.outputformat.OutputFormat;
 
 /**
- * Static text template language; a such template prints its content as is. 
+ * Static text template language; a such template prints its content as is.
  */
-final class StaticTextTemplateLanguage extends TemplateLanguage {
+public final class StaticTextTemplateLanguage extends TemplateLanguage {
+    
+    private static final int READ_BUFFER_SIZE = 4096;
     
     public static final TemplateLanguage INSTANCE = new 
StaticTextTemplateLanguage("Static text");
 
     private StaticTextTemplateLanguage(String name) {
-        super(name);
+        super(name, null, null);
     }
 
+    /**
+     * {@inheritDoc}
+     * 
+     * In {@link StaticTextTemplateLanguage} this returns {@code false}.
+     */
     @Override
-    public boolean getCanSpecifyCharsetInContent() {
+    public boolean getCanSpecifyEncodingInContent() {
         return false;
     }
 
     @Override
-    public Template parse(String name, String sourceName, Reader reader, 
Configuration cfg,
-            TemplateConfiguration templateConfiguration, Charset 
sourceEncoding,
+    public ASTElement parse(Template template, Reader reader,
+            ParsingConfiguration pCfg, OutputFormat contextOutputFormat, 
AutoEscapingPolicy contextAutoEscapingPolicy,
             InputStream streamToUnmarkWhenEncEstabd)
             throws IOException, ParseException {
-        // Read the contents into a StringWriter, then construct a 
single-text-block template from it.
         final StringBuilder sb = new StringBuilder();
-        final char[] buf = new char[4096];
+        final char[] buf = new char[READ_BUFFER_SIZE];
         int charsRead;
         while ((charsRead = reader.read(buf)) > 0) {
             sb.append(buf, 0, charsRead);
         }
-        return Template.createPlainTextTemplate(name, sourceName, 
sb.toString(), cfg,
-                sourceEncoding);
+        
+        FMParser parser = new FMParser(
+                template, new StringReader(""),
+                pCfg,
+                contextOutputFormat,
+                contextAutoEscapingPolicy,
+                streamToUnmarkWhenEncEstabd);
+        
+        ASTElement root = parser.Root();
+        ((ASTStaticText) root).replaceText(sb.toString());
+        return root;
     }
+    
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/freemarker/blob/53b7c952/freemarker-core/src/main/java/org/apache/freemarker/core/Template.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/Template.java 
b/freemarker-core/src/main/java/org/apache/freemarker/core/Template.java
index 25cbcd2..da39230 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/Template.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/Template.java
@@ -19,6 +19,7 @@
 
 package org.apache.freemarker.core;
 
+import java.io.BufferedInputStream;
 import java.io.BufferedReader;
 import java.io.FilterReader;
 import java.io.IOException;
@@ -93,7 +94,8 @@ public class Template implements ProcessingConfiguration, 
CustomStateScope {
 
     // Source (TemplateLoader) related information:
     private final String sourceName;
-    private final ArrayList lines = new ArrayList();
+    // TODO [FM3] Get rid of this...
+    private final ArrayList<String> lines = new ArrayList<>();
 
     // TODO [FM3] We want to get rid of these, then the same Template object 
could be reused for different lookups.
     // Template lookup parameters:
@@ -104,7 +106,7 @@ public class Template implements ProcessingConfiguration, 
CustomStateScope {
     // Inherited settings:
     private final transient Configuration cfg;
     private final transient TemplateConfiguration tCfg;
-    private final transient ParsingConfiguration parsingConfiguration;
+    private final transient ParsingConfiguration pCfg;
 
     // Values from the template content (#ftl header parameters usually), as 
opposed to from the TemplateConfiguration:
     private transient OutputFormat outputFormat; // TODO Deserialization: use 
the name of the output format
@@ -122,7 +124,7 @@ public class Template implements ProcessingConfiguration, 
CustomStateScope {
     private AutoEscapingPolicy autoEscapingPolicy;
     // Values from template content that are detected automatically:
     private Charset actualSourceEncoding;
-    private TagSyntax actualTagSyntax;
+    TagSyntax actualTagSyntax; // TODO [FM3][CF] Should be private
     private InterpolationSyntax interpolationSyntax;
 
     // Custom state:
@@ -209,7 +211,7 @@ public class Template implements ProcessingConfiguration, 
CustomStateScope {
      * encoding.
      *
      * @param actualSourceEncoding
-     *            This is the charset that was used to read the template. This 
can be {@code null} if the template
+     *            This is the charset that was used to read the template. This 
sould be {@code null} if the template
      *            was loaded from a source that returns it already as text. If 
this is not {@code null} and there's an
      *            {@code #ftl} header with {@code encoding} parameter, they 
must match, or else a
      *            {@link WrongTemplateCharsetException} is thrown.
@@ -231,67 +233,211 @@ public class Template implements 
ProcessingConfiguration, CustomStateScope {
      *            templates, and so it's not good for specifying 
template-specific settings. Settings that influence
      *            parsing always have an effect, while settings that influence 
processing only have effect when the
      *            template is the main template of the {@link Environment}.
-     * @param actualSourceEncoding
-     *            Same as in {@link #Template(String, String, Reader, 
Configuration, Charset)}.
      */
-   public Template(
-           String lookupName, String sourceName, Reader reader,
-           Configuration cfg, TemplateConfiguration templateConfiguration,
-           Charset actualSourceEncoding) throws IOException {
-       this(lookupName, sourceName, reader, cfg, templateConfiguration, 
actualSourceEncoding, null);
+    public Template(
+            String lookupName, String sourceName,
+            Reader reader,
+            Configuration cfg, TemplateConfiguration templateConfiguration, 
Charset actualSourceEncoding)
+                    throws IOException, ParseException {
+        this(lookupName, sourceName,
+                null, actualSourceEncoding, reader,
+                cfg, templateConfiguration,
+                null, null);
     }
 
     /**
-     * Same as {@link #Template(String, String, Reader, Configuration, 
TemplateConfiguration, Charset)}, but allows
-     * specifying the {@code streamToUnmarkWhenEncEstabd}.
-     *
-     * @param streamToUnmarkWhenEncEstabd
-     *         If not {@code null}, when during the parsing we reach a point 
where we know that no {@link
-     *         WrongTemplateCharsetException} will be thrown, {@link 
InputStream#mark(int) mark(0)} will be called on this.
-     *         This is meant to be used when the reader parameter is a {@link 
InputStreamReader}, and this parameter is
-     *         the underlying {@link InputStream}, and you have a mark at the 
beginning of the {@link InputStream} so
-     *         that you can retry if a {@link WrongTemplateCharsetException} 
is thrown without extra I/O. As keeping that
-     *         mark consumes some resources, so you may want to release it as 
soon as possible.
+     * See {@link #Template(String, String, Reader, Configuration, 
TemplateConfiguration, Charset)}, except that this
+     * one expects an {@link InputStream} and an initial {@link Charset}.
+     * 
+     * @param initialEncoding
+     *            The {@link Charset} we try to decode the {@link InputStream} 
with. If the template language specifies
+     *            its own encoding, this will be overridden by that (hence 
it's just "inital"), however, that requires
+     *            that the template is still parseable with the initial 
encoding (in practice, that at least the
+     *            US-ASCII characters are decoded correctly).
      */
     public Template(
-            String lookupName, String sourceName, Reader reader,
-            Configuration cfg, TemplateConfiguration templateConfiguration,
-            Charset actualSourceEncoding, InputStream 
streamToUnmarkWhenEncEstabd) throws IOException, ParseException {
-        this(lookupName, sourceName, reader,
+            String lookupName, String sourceName,
+            InputStream inputStream, Charset initialEncoding,
+            Configuration cfg, TemplateConfiguration templateConfiguration) 
throws IOException, ParseException {
+        this(lookupName, sourceName,
+                inputStream, initialEncoding, null,
                 cfg, templateConfiguration,
-                null, null,
-                actualSourceEncoding, streamToUnmarkWhenEncEstabd);
+                null, null);
     }
-
+    
     /**
-     * Same as {@link #Template(String, String, Reader, Configuration, 
TemplateConfiguration, Charset, InputStream)},
-     * but allows specifying the output format and the auto escaping policy, 
with similar effect as if they were
-     * specified in the template content (like in the #ftl header).
+     * Same as the other overloads, but allows specifying the output format 
and the auto escaping policy, with similar
+     * effect as if they were specified in the template content (like in the 
#ftl header).
      * <p>
-     * <p>This method is currently only used internally, as it's not 
generalized enough and so it carries too much
-     * backward compatibility risk. Also, the same functionality can be 
achieved by constructing an appropriate
+     * <p>
+     * This method is currently only used internally, as it's not generalized 
enough and so it carries too much backward
+     * compatibility risk. Also, the same functionality can be achieved by 
constructing an appropriate
      * {@link TemplateConfiguration}, only that's somewhat slower.
      *
+     * @param inputStream
+     *            Exactly one of this and the {@code reader} must be non-null. 
This is normally used if the source is
+     *            binary (not textual), that is, we have to use a charset to 
decode it to text.
+     * @param initialEncoding
+     *            If {@code inputStream} is not-{@code null} then this is the 
charset we try to use (and so it can't be
+     *            {@code null}), but might will be overridden in the template 
header, in which case this method manages
+     *            the re-decoding internally, and the caller need not worry 
about it. If {@code reader} is
+     *            non-{@code null}, and this is non-{@code null}, it's used to 
check if the charset specified in the
+     *            template (if any) matches, and if not, a {@link 
WrongTemplateCharsetException} is thrown that the
+     *            caller is expected to handle. If the template source code 
comes from a textual source, and so you
+     *            don't care about the charset specified in the template, then 
use {@code null} here (with a
+     *            non-{@code null} {@code reader} parameter of course), and 
then the check should be omitted by the
+     *            {@link TemplateLanguage} implementation, so you need not 
expect a
+     *            {@link WrongTemplateCharsetException}.
+     * @param reader
+     *            Exactly one of this and the {@code inputStream} must be 
non-null. This is normally used if the source
+     *            is textual (not binary), that is, we don't have to worry 
about the charset to interpret it.
      * @param contextOutputFormat
-     *         The output format of the enclosing lexical context, used when a 
template snippet is parsed on runtime. If
-     *         not {@code null}, this will override the value coming from the 
{@link TemplateConfiguration} or the
-     *         {@link Configuration}.
+     *            The output format of the enclosing lexical context, used 
when a template snippet is parsed on runtime.
+     *            If not {@code null}, this will override the value coming 
from the {@link TemplateConfiguration} or the
+     *            {@link Configuration}.
      * @param contextAutoEscapingPolicy
-     *         Similar to {@code contextOutputFormat}; usually this and the 
that is set together.
+     *            Similar to {@code contextOutputFormat}; usually this and the 
that is set together.
      */
-   Template(
-            String lookupName, String sourceName, Reader reader,
+    Template(
+            String lookupName, String sourceName,
+            InputStream inputStream, Charset initialEncoding, Reader reader,
             Configuration configuration, TemplateConfiguration 
templateConfiguration,
-            OutputFormat contextOutputFormat, AutoEscapingPolicy 
contextAutoEscapingPolicy,
-            Charset actualSourceEncoding, InputStream 
streamToUnmarkWhenEncEstabd) throws IOException, ParseException {
+            OutputFormat contextOutputFormat, AutoEscapingPolicy 
contextAutoEscapingPolicy)
+            throws IOException, ParseException {
         _NullArgumentException.check("configuration", configuration);
         this.cfg = configuration;
         this.tCfg = templateConfiguration;
-        this.parsingConfiguration = tCfg != null ? new 
ParsingConfigurationWithFallback(cfg, tCfg) : cfg;
+        // this.pCfg = ...
+        {
+            ParsingConfiguration pCfgWithFallback = tCfg != null
+                    ? new ParsingConfigurationWithFallback(tCfg, cfg) : cfg;
+            
+            TemplateLanguage tempLangFromPCfg = 
pCfgWithFallback.getTemplateLanguage();
+            TemplateLanguage tempLangFromExt = 
pCfgWithFallback.getRecognizeStandardFileExtensions()
+                    ? detectTemplateLanguageFromFileExtension(sourceName != 
null ? sourceName : lookupName)
+                            : null;
+            if (tempLangFromExt != null && tempLangFromExt != 
tempLangFromPCfg) {
+                this.pCfg = new 
ParsingConfigurationWithTemplateLanguageOverride(pCfgWithFallback, 
tempLangFromExt);
+            } else {
+                this.pCfg = pCfgWithFallback;
+            }
+        }
+        
         this.lookupName = lookupName;
         this.sourceName = sourceName;
+        
+        boolean inputStreamMarked;
+        Charset guessedEncoding = initialEncoding;
+        boolean closeReader;
+        if (reader != null) {
+            if (inputStream != null) {
+                throw new IllegalArgumentException("Both the Reader and 
InputStream was non-null.");
+            }
+            closeReader = false; // It wasn't created by this method, so it's 
not our responsibility
+            inputStreamMarked = false; // N/A
+        } else if (inputStream != null) {
+            if (pCfg.getTemplateLanguage().getCanSpecifyEncodingInContent()) {
+                // We need mark support, to restart if the charset suggested 
by the template content differs
+                // from the one we use initially.
+                if (!inputStream.markSupported()) {
+                    inputStream = new BufferedInputStream(inputStream);
+                }
+                inputStream.mark(Integer.MAX_VALUE); // Mark will be released 
as soon as we know the charset for sure.
+                inputStreamMarked = true;
+            } else {
+                inputStreamMarked = false;
+            }
+            // Regarding buffering worries: On the Reader side we should only 
read in chunks (like through a
+            // BufferedReader), so there shouldn't be a problem if the 
InputStream is not buffered. (Also, at least
+            // on Oracle JDK and OpenJDK 7 the InputStreamReader itself has an 
internal ~8K buffer.)
+            reader = new InputStreamReader(inputStream, initialEncoding);
+            closeReader = true; // Because it was created here.
+        } else {
+            throw new IllegalArgumentException("Both the Reader and 
InputStream was null.");
+        }
+
+        try {
+            try {
+                parseWithEncoding(
+                        reader,
+                        contextOutputFormat, contextAutoEscapingPolicy,
+                        guessedEncoding, inputStreamMarked ? inputStream : 
null);
+            } catch (WrongTemplateCharsetException charsetException) {
+                final Charset templateSpecifiedEncoding = 
charsetException.getTemplateSpecifiedEncoding();
+
+                if (inputStreamMarked) {
+                    // We restart InputStream to re-decode it with the new 
charset.
+                    inputStream.reset();
+
+                    // Don't close `reader`; it's an InputStreamReader that 
would close the wrapped InputStream.
+                    reader = new InputStreamReader(inputStream, 
templateSpecifiedEncoding);
+                } else {
+                    // We can't handle it, the caller should.
+                    throw charsetException;
+                }
+                parseWithEncoding(
+                        reader,
+                        contextOutputFormat, contextAutoEscapingPolicy,
+                        templateSpecifiedEncoding, inputStream);
+            }
+        } finally {
+            if (closeReader) {
+                reader.close();
+            }
+        }
+        
+        _DebuggerService.registerTemplate(this);
+        namespaceURIToPrefixLookup = 
_CollectionUtils.unmodifiableMap(namespaceURIToPrefixLookup);
+        prefixToNamespaceURILookup = 
_CollectionUtils.unmodifiableMap(prefixToNamespaceURILookup);
+
+       finishConstruction();
+    }
+
+    private TemplateLanguage detectTemplateLanguageFromFileExtension(String 
name) {
+        // TODO [FM3][CF] Emulates FM2 behavior temporarily, to make the test 
suite pass 
+        // TODO [FM3] Now it's hard-wired, but later user-defined languages 
need to be detected as well.
+        
+        if (name == null) {
+            return null;
+        }
 
-        setActualSourceEncoding(actualSourceEncoding);
+        int ln = name.length();
+        if (ln < 5) return null;
+
+        char c = name.charAt(ln - 5);
+        if (c != '.') return null;
+
+        c = name.charAt(ln - 4);
+        if (c != 'f' && c != 'F') return null;
+
+        c = name.charAt(ln - 3);
+        if (c != 't' && c != 'T') return null;
+
+        c = name.charAt(ln - 2);
+        if (c != 'l' && c != 'L') return null;
+
+        c = name.charAt(ln - 1);
+        if (c == 'h' || c == 'H') {
+            return DefaultTemplateLanguage.F3CH;
+        }
+        if (c == 'x' || c == 'X') {
+            return DefaultTemplateLanguage.F3CX;
+        }
+        return null;
+    }
+
+    /**
+     * This was extracted from the {@link Template} constructor, so that we 
can do this again if the attempted charset
+     * was incorrect.
+     * 
+     * @param streamToUnmarkWhenEncEstabd
+     *            When the parser meets a directive that established the 
charset, this is where it should release the
+     *            mark (so we don't buffer unnecessarily). Maybe {@code null}.
+     */
+    private void parseWithEncoding(
+            Reader reader, OutputFormat contextOutputFormat, 
AutoEscapingPolicy contextAutoEscapingPolicy,
+            Charset attemptedCharset, InputStream streamToUnmarkWhenEncEstabd) 
throws ParseException, IOException {
+        setActualSourceEncoding(attemptedCharset);
         LineTableBuilder ltbReader;
         try {
             // Ensure that the parameter Reader is only read in bigger chunks, 
as we don't know if the it's buffered.
@@ -300,16 +446,16 @@ public class Template implements ProcessingConfiguration, 
CustomStateScope {
                 reader = new BufferedReader(reader, READER_BUFFER_SIZE);
             }
             
-            ltbReader = new LineTableBuilder(reader, parsingConfiguration);
+            lines.clear();
+            ltbReader = new LineTableBuilder(reader, pCfg, lines);
             reader = ltbReader;
             
             try {
-                FMParser parser = new FMParser(
-                        this, reader,
-                        parsingConfiguration, contextOutputFormat, 
contextAutoEscapingPolicy,
-                        streamToUnmarkWhenEncEstabd);
                 try {
-                    rootElement = parser.Root();
+                    rootElement = pCfg.getTemplateLanguage().parse(
+                            this, reader,
+                            pCfg, contextOutputFormat, 
contextAutoEscapingPolicy,
+                            streamToUnmarkWhenEncEstabd);
                 } catch (IndexOutOfBoundsException exc) {
                     // There's a JavaCC bug where the Reader throws a 
RuntimeExcepton and then JavaCC fails with
                     // IndexOutOfBoundsException. If that wasn't the case, we 
just rethrow. Otherwise we suppress the
@@ -319,8 +465,7 @@ public class Template implements ProcessingConfiguration, 
CustomStateScope {
                     }
                     rootElement = null;
                 }
-                actualTagSyntax = parser._getLastTagSyntax();
-                interpolationSyntax = 
parsingConfiguration.getInterpolationSyntax();
+                interpolationSyntax = pCfg.getInterpolationSyntax();
             } catch (TokenMgrError exc) {
                 // TokenMgrError VS ParseException is not an interesting 
difference for the user, so we just convert it
                 // to ParseException
@@ -333,13 +478,7 @@ public class Template implements ProcessingConfiguration, 
CustomStateScope {
         
         // Throws any exception that JavaCC has silently treated as EOF:
         ltbReader.throwFailure();
-        
-        _DebuggerService.registerTemplate(this);
-        namespaceURIToPrefixLookup = 
_CollectionUtils.unmodifiableMap(namespaceURIToPrefixLookup);
-        prefixToNamespaceURILookup = 
_CollectionUtils.unmodifiableMap(prefixToNamespaceURILookup);
-
-       finishConstruction();
-   }
+    }
 
     /**
      * {@link Template} is technically mutable (to simplify internals), but it 
has to be finalized and then write
@@ -631,8 +770,12 @@ public class Template implements ProcessingConfiguration, 
CustomStateScope {
         return tCfg;
     }
 
+    /**
+     * The {@link ParsingConfiguration} that was actually used to parse this 
template; this is deduced from the
+     * other configurations, and the {@link TemplateLanguage} detected from 
the file extension.
+     */
     public ParsingConfiguration getParsingConfiguration() {
-        return parsingConfiguration;
+        return pCfg;
     }
 
 
@@ -1167,10 +1310,12 @@ public class Template implements 
ProcessingConfiguration, CustomStateScope {
      * Reader that builds up the line table info for us, and also helps in 
working around JavaCC's exception
      * suppression.
      */
-    private class LineTableBuilder extends FilterReader {
+    private static class LineTableBuilder extends FilterReader {
         
         private final int tabSize;
         private final StringBuilder lineBuf = new StringBuilder();
+        private final List<String> lines;
+        
         int lastChar;
         boolean closed;
         
@@ -1180,9 +1325,10 @@ public class Template implements 
ProcessingConfiguration, CustomStateScope {
         /**
          * @param r the character stream to wrap
          */
-        LineTableBuilder(Reader r, ParsingConfiguration parserConfiguration) {
+        LineTableBuilder(Reader r, ParsingConfiguration parserConfiguration, 
List<String> lines) {
             super(r);
             tabSize = parserConfiguration.getTabSize();
+            this.lines = lines;
         }
         
         public boolean hasFailure() {
@@ -1254,7 +1400,7 @@ public class Template implements ProcessingConfiguration, 
CustomStateScope {
             if (c == '\n' || c == '\r') {
                 if (lastChar == '\r' && c == '\n') { // CRLF under Windoze
                     int lastIndex = lines.size() - 1;
-                    String lastLine = (String) lines.get(lastIndex);
+                    String lastLine = lines.get(lastIndex);
                     lines.set(lastIndex, lastLine + '\n');
                 } else {
                     lineBuf.append((char) c);

http://git-wip-us.apache.org/repos/asf/freemarker/blob/53b7c952/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateLanguage.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateLanguage.java
 
b/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateLanguage.java
index 594b162..950a844 100644
--- 
a/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateLanguage.java
+++ 
b/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateLanguage.java
@@ -24,40 +24,56 @@ import java.io.InputStream;
 import java.io.Reader;
 import java.nio.charset.Charset;
 
+import org.apache.freemarker.core.outputformat.OutputFormat;
+import org.apache.freemarker.core.util._NullArgumentException;
 import org.apache.freemarker.core.util._StringUtils;
 
 /**
  * Represents a template language. Currently this class is not mature, so it 
can't be implemented outside FreeMarker,
  * also its methods shouldn't be called from outside FreeMarker.
  */
-// [FM3] Make this mature, or hide its somehow. Actually, parse can't be 
hidden because custom TemplateResolver-s need
-// to call it.
+// [TODO][FM3] Make this mature, or hide its API somehow, or at least prevent 
subclassing it by user.
 public abstract class TemplateLanguage {
     
     private final String name;
-
+    private final OutputFormat outputFormat;
+    private final AutoEscapingPolicy autoEscapingPolicy;
+    
     // Package visibility to prevent user implementations until this API is 
mature.
-    TemplateLanguage(String name) {
-        this.name = name;
-    }
 
     /**
-     * Returns if the template can specify its own charset inside the 
template. If so, {@link #parse(String, String,
-     * Reader, Configuration, TemplateConfiguration, Charset, InputStream)} 
can throw
+     * Returns if the template can specify its own charset inside the 
template. If so, the parser can throw
      * {@link WrongTemplateCharsetException}, and it might gets a non-{@code 
null} for the {@link InputStream}
      * parameter.
      */
-    public abstract boolean getCanSpecifyCharsetInContent();
+    public abstract boolean getCanSpecifyEncodingInContent();
+
+    /**
+     * @param name See {@link #getName()}
+     * @param outputFormat See {@link #getOutputFormat(Configuration)}
+     * @param autoEscapingPolicy See {@link #getAutoEscapingPolicy()}
+     */
+    public TemplateLanguage(String name, OutputFormat outputFormat, 
AutoEscapingPolicy autoEscapingPolicy) {
+        _NullArgumentException.check("name", name);
+        this.name = name;
+        this.outputFormat = outputFormat;
+        this.autoEscapingPolicy = autoEscapingPolicy;
+    }
 
     /**
-     * See {@link Template#Template(String, String, Reader, Configuration, 
TemplateConfiguration, Charset,
-     * InputStream)}.
+     * This is what
+     * {@link Template#Template(String, String, InputStream, Charset, Reader, 
Configuration, TemplateConfiguration,
+     * OutputFormat, AutoEscapingPolicy)} calls internally to do the actual 
parsing.
      */
-    public abstract Template parse(String name, String sourceName, Reader 
reader,
-                                   Configuration cfg, TemplateConfiguration 
templateConfiguration,
-                                   Charset encoding, InputStream 
streamToUnmarkWhenEncEstabd)
+    // TODO [FM3] This is not the final API, or else it can't be public 
(because ASTElement isn't public for example).
+    public abstract ASTElement parse(Template template, Reader reader,
+            ParsingConfiguration pCfg, OutputFormat contextOutputFormat, 
AutoEscapingPolicy contextAutoEscapingPolicy,
+            InputStream streamToUnmarkWhenEncEstabd)
             throws IOException, ParseException;
 
+    /**
+     * The informal name of this language; might be used in error messages. 
Not {@code null}.
+     */
     public String getName() {
         return name;
     }
@@ -66,5 +82,25 @@ public abstract class TemplateLanguage {
     public final String toString() {
         return "TemplateLanguage(" + _StringUtils.jQuote(name) + ")";
     }
+    
+    /**
+     * The {@link OutputFormat} that this language enforces, or else {@code 
null}
+     * 
+     * @param cfg
+     *            The {@link Configuration} used; this is needed for {@link 
Configuration}-level {@link OutputFormat}
+     *            overrides to work (such as when someone provides an 
alternative implementation for "HTML" escaping).
+     *            If this is {@code null}, you get back the original {@link 
OutputFormat} object of this
+     *            {@link TemplateLanguage}, but you hardly ever should do 
that. 
+     */
+    public OutputFormat getOutputFormat(Configuration cfg) {
+        return cfg != null && outputFormat != null ? 
cfg.getCustomOrArgumentOutputFormat(outputFormat) : outputFormat;
+    }
+
+    /**
+     * The {@link OutputFormat} that this language enforces, or else {@code 
null}
+     */
+    public AutoEscapingPolicy getAutoEscapingPolicy() {
+        return autoEscapingPolicy;
+    }
 
 }

http://git-wip-us.apache.org/repos/asf/freemarker/blob/53b7c952/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateResolverDependenciesImpl.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateResolverDependenciesImpl.java
 
b/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateResolverDependenciesImpl.java
index c6f2669..b3ee08e 100644
--- 
a/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateResolverDependenciesImpl.java
+++ 
b/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateResolverDependenciesImpl.java
@@ -111,10 +111,15 @@ class TemplateResolverDependenciesImpl extends 
TemplateResolverDependencies {
     }
 
     @Override
-    public Template parse(TemplateLanguage templateLanguage, String name, 
String sourceName, Reader reader,
-            TemplateConfiguration templateConfiguration, Charset encoding, 
InputStream streamToUnmarkWhenEncEstabd)
-            throws IOException, ParseException {
-        return templateLanguage.parse(name, sourceName, reader,
-                configuration, templateConfiguration, encoding, 
streamToUnmarkWhenEncEstabd);
+    public Template newTemplate(String lookupName, String sourceName, 
InputStream inputStream, Charset initialEncoding,
+            TemplateConfiguration templateConfiguration) throws IOException, 
ParseException {
+        return new Template(lookupName, sourceName, inputStream, 
initialEncoding, configuration, templateConfiguration);
     }
+
+    @Override
+    public Template newTemplate(String lookupName, String sourceName, Reader 
reader,
+            TemplateConfiguration templateConfiguration) throws IOException, 
ParseException {
+        return new Template(lookupName, sourceName, reader, configuration, 
templateConfiguration, null);
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/freemarker/blob/53b7c952/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/TemplateResolver.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/TemplateResolver.java
 
b/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/TemplateResolver.java
index 7ab510c..8b17b17 100644
--- 
a/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/TemplateResolver.java
+++ 
b/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/TemplateResolver.java
@@ -19,13 +19,17 @@
 package org.apache.freemarker.core.templateresolver;
 
 import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
 import java.io.Serializable;
+import java.nio.charset.Charset;
 import java.util.Locale;
 
 import org.apache.freemarker.core.Configuration;
 import org.apache.freemarker.core.ConfigurationException;
 import org.apache.freemarker.core.ParseException;
 import org.apache.freemarker.core.Template;
+import org.apache.freemarker.core.TemplateConfiguration;
 import org.apache.freemarker.core.TemplateNotFoundException;
 import 
org.apache.freemarker.core.templateresolver.impl.DefaultTemplateResolver;
 import org.apache.freemarker.core.util._NullArgumentException;
@@ -46,8 +50,10 @@ import 
org.apache.freemarker.core.util._NullArgumentException;
  * {@link #supportsTemplateLoaderSetting()}). (Note that there's no {@code 
supportsXxxSetting} method for
  * {@link Configuration#getTemplateLanguage() templateLanguage} and {@link 
Configuration#getSourceEncoding()
  * sourceEncoding}, as that must always be supported and are always exposed.) 
{@link TemplateResolverDependencies}
- * will also expose the {@link TemplateResolverDependencies#parse} method, 
which is used to create a {@link Template}
- * from its source code in the later {@link #getTemplate(String, Locale, 
Serializable)} calls.
+ * will also expose the {@link 
TemplateResolverDependencies#newTemplate(String, String, Reader, 
TemplateConfiguration)}
+ * and {@link TemplateResolverDependencies#newTemplate(String, String, 
InputStream, Charset, TemplateConfiguration)}
+ * methods, which are used to create a {@link Template} from its source code 
in the later
+ * {@link #getTemplate(String, Locale, Serializable)} calls.
  */
 public abstract class TemplateResolver {
 

http://git-wip-us.apache.org/repos/asf/freemarker/blob/53b7c952/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/TemplateResolverDependencies.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/TemplateResolverDependencies.java
 
b/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/TemplateResolverDependencies.java
index ee7b4e4..e9e0774 100644
--- 
a/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/TemplateResolverDependencies.java
+++ 
b/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/TemplateResolverDependencies.java
@@ -80,12 +80,21 @@ public abstract class TemplateResolverDependencies {
     public abstract TemplateLanguage getTemplateLanguage();
 
     /**
-     * This simply calls {@link TemplateLanguage#parse(String, String, Reader, 
Configuration, TemplateConfiguration,
-     * Charset, InputStream)} without exposing the {@link Configuration}.
+     * Like the similar {@link Template} constructor, but as it has no {@link 
Configuration} parameter,
+     * it avoids exposing the {@link Configuration} to the {@link 
TemplateResolverDependencies} implementation.
      */
-    public abstract Template parse(
-            TemplateLanguage templateLanguage, String name, String sourceName, 
Reader reader,
-            TemplateConfiguration templateConfiguration, Charset encoding,
-            InputStream streamToUnmarkWhenEncEstabd) throws IOException, 
ParseException;
+    public abstract Template newTemplate(
+            String lookupName, String sourceName,
+            InputStream inputStream, Charset initialEncoding,
+            TemplateConfiguration templateConfiguration) throws IOException, 
ParseException;
 
+    /**
+     * Like the similar {@link Template} constructor, but as it has no {@link 
Configuration} parameter,
+     * it avoids exposing the {@link Configuration} to the {@link 
TemplateResolverDependencies} implementation.
+     */
+    public abstract Template newTemplate(
+            String lookupName, String sourceName,
+            Reader reader,
+            TemplateConfiguration templateConfiguration) throws IOException, 
ParseException;
+    
 }

http://git-wip-us.apache.org/repos/asf/freemarker/blob/53b7c952/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/impl/DefaultTemplateResolver.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/impl/DefaultTemplateResolver.java
 
b/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/impl/DefaultTemplateResolver.java
index 5f277d3..e818546 100644
--- 
a/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/impl/DefaultTemplateResolver.java
+++ 
b/freemarker-core/src/main/java/org/apache/freemarker/core/templateresolver/impl/DefaultTemplateResolver.java
@@ -21,10 +21,8 @@ package org.apache.freemarker.core.templateresolver.impl;
 
 import static org.apache.freemarker.core.Configuration.ExtendableBuilder.*;
 
-import java.io.BufferedInputStream;
 import java.io.IOException;
 import java.io.InputStream;
-import java.io.InputStreamReader;
 import java.io.Reader;
 import java.io.Serializable;
 import java.lang.reflect.Method;
@@ -37,11 +35,11 @@ import java.util.StringTokenizer;
 import org.apache.freemarker.core.Configuration;
 import org.apache.freemarker.core.ConfigurationException;
 import org.apache.freemarker.core.InvalidSettingValueException;
+import org.apache.freemarker.core.MutableParsingAndProcessingConfiguration;
 import org.apache.freemarker.core.Template;
 import org.apache.freemarker.core.TemplateConfiguration;
 import org.apache.freemarker.core.TemplateLanguage;
 import org.apache.freemarker.core.TemplateNotFoundException;
-import org.apache.freemarker.core.WrongTemplateCharsetException;
 import org.apache.freemarker.core.templateresolver.CacheStorage;
 import org.apache.freemarker.core.templateresolver.GetTemplateResult;
 import 
org.apache.freemarker.core.templateresolver.MalformedTemplateNameException;
@@ -121,10 +119,10 @@ public class DefaultTemplateResolver extends 
TemplateResolver {
         this.templateConfigurations = deps.getTemplateConfigurations();
 
         this.sourceEncoding = deps.getSourceEncoding();
-        checkDependencyNotNull(SOURCE_ENCODING_KEY, this.sourceEncoding);
+        
checkDependencyNotNull(MutableParsingAndProcessingConfiguration.SOURCE_ENCODING_KEY,
 this.sourceEncoding);
 
         this.templateLanguage = deps.getTemplateLanguage();
-        checkDependencyNotNull(TEMPLATE_LANGUAGE_KEY, this.templateLanguage);
+        
checkDependencyNotNull(MutableParsingAndProcessingConfiguration.TEMPLATE_LANGUAGE_KEY,
 this.templateLanguage);
     }
 
     private void checkDependencyNotNull(String name, Object value) {
@@ -171,7 +169,7 @@ public class DefaultTemplateResolver extends 
TemplateResolver {
         name = templateNameFormat.normalizeRootBasedName(name);
         
         if (templateLoader == null) {
-            return new GetTemplateResult(name, "The TemplateLoader (and 
TemplateLoader2) was null.");
+            return new GetTemplateResult(name, "The TemplateLoader was null.");
         }
         
         Template template = getTemplateInternal(name, locale, 
customLookupCondition);
@@ -534,64 +532,20 @@ public class DefaultTemplateResolver extends 
TemplateResolver {
 
         Template template;
         {
-            Reader reader = templateLoaderResult.getReader();
-            InputStream inputStream = templateLoaderResult.getInputStream();
-            InputStream markedInputStream;
-            if (reader != null) {
-                if (inputStream != null) {
-                    throw new IllegalStateException("For a(n) " + 
templateLoaderResult.getClass().getName()
-                            + ", both getReader() and getInputStream() has 
returned non-null.");
-                }
-                initialEncoding = null;  // No charset decoding has happened
-                markedInputStream = null;
-            } else if (inputStream != null) {
-                if (templateLanguage.getCanSpecifyCharsetInContent()) {
-                    // We need mark support, to restart if the charset 
suggested by <#ftl encoding=...> differs
-                    // from that we use initially.
-                    if (!inputStream.markSupported()) {
-                        inputStream = new BufferedInputStream(inputStream);
-                    }
-                    inputStream.mark(Integer.MAX_VALUE); // Mark is released 
after the 1st FTL tag
-                    markedInputStream = inputStream;
-                } else {
-                    markedInputStream = null;
-                }
-                // Regarding buffering worries: On the Reader side we should 
only read in chunks (like through a
-                // BufferedReader), so there shouldn't be a problem if the 
InputStream is not buffered. (Also, at least
-                // on Oracle JDK and OpenJDK 7 the InputStreamReader itself 
has an internal ~8K buffer.)
-                reader = new InputStreamReader(inputStream, initialEncoding);
-            } else {
-                throw new IllegalStateException("For a(n) " + 
templateLoaderResult.getClass().getName()
-                        + ", both getReader() and getInputStream() has 
returned null.");
-            }
-            
-            try {
-                try {
-                    template = getDependencies().parse(
-                            templateLanguage, name, sourceName, reader, tc,
-                            initialEncoding, markedInputStream);
-                } catch (WrongTemplateCharsetException charsetException) {
-                    final Charset templateSpecifiedEncoding = 
charsetException.getTemplateSpecifiedEncoding();
-
+            try (Reader reader = templateLoaderResult.getReader();
+                    InputStream inputStream = 
templateLoaderResult.getInputStream();) {
+                if (reader != null) {
                     if (inputStream != null) {
-                        // We restart InputStream to re-decode it with the new 
charset.
-                        inputStream.reset();
-
-                        // Don't close `reader`; it's an InputStreamReader 
that would close the wrapped InputStream.
-                        reader = new InputStreamReader(inputStream, 
templateSpecifiedEncoding);
-                    } else {
-                        throw new IllegalStateException(
-                                "TemplateLanguage " + 
_StringUtils.jQuote(templateLanguage.getName()) + " has thrown "
-                                + WrongTemplateCharsetException.class.getName()
-                                + ", but its canSpecifyCharsetInContent 
property is false.");
+                        throw new IllegalStateException("For a(n) " + 
templateLoaderResult.getClass().getName()
+                                + ", both getReader() and getInputStream() has 
returned non-null.");
                     }
-
-                    template = getDependencies().parse(
-                            templateLanguage, name, sourceName, reader, tc,
-                            templateSpecifiedEncoding, markedInputStream);
+                    template = getDependencies().newTemplate(name, sourceName, 
reader, tc);
+                } else if (inputStream != null) {
+                    template = getDependencies().newTemplate(name, sourceName, 
inputStream, initialEncoding, tc);
+                } else {
+                    throw new IllegalStateException("For a(n) " + 
templateLoaderResult.getClass().getName()
+                            + ", both getReader() and getInputStream() has 
returned null.");
                 }
-            } finally {
-                reader.close();
             }
         }
 

http://git-wip-us.apache.org/repos/asf/freemarker/blob/53b7c952/freemarker-core/src/main/javacc/FTL.jj
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/javacc/FTL.jj 
b/freemarker-core/src/main/javacc/FTL.jj
index f0cc85e..140d144 100644
--- a/freemarker-core/src/main/javacc/FTL.jj
+++ b/freemarker-core/src/main/javacc/FTL.jj
@@ -144,13 +144,20 @@ public class FMParser {
         this.incompatibleImprovements = incompatibleImprovements;
 
         {
-            OutputFormat outputFormatFromExt = 
pCfg.getRecognizeStandardFileExtensions() ? getFormatFromStdFileExt()
-                    : null;
+            // contextXxx is stronger than anything.
+            // TemplateLanguage.xxx (if it's non-null) stronger than the rest.
+            
+            OutputFormat outputFormatFromLang;
             outputFormat = contextOutputFormat != null ? contextOutputFormat
-                    : outputFormatFromExt != null ? outputFormatFromExt
-                    : pCfg.getOutputFormat();
+                    : (outputFormatFromLang = pCfg.getTemplateLanguage()
+                            .getOutputFormat(template.getConfiguration())) != 
null
+                            ? outputFormatFromLang
+                            : pCfg.getOutputFormat();
+
+            AutoEscapingPolicy autoEscapingPolicyFromLang;
             autoEscapingPolicy = contextAutoEscapingPolicy != null ? 
contextAutoEscapingPolicy
-                    : outputFormatFromExt != null ? 
AutoEscapingPolicy.ENABLE_IF_DEFAULT
+                    : (autoEscapingPolicyFromLang = 
pCfg.getTemplateLanguage().getAutoEscapingPolicy()) != null
+                            ? autoEscapingPolicyFromLang
                     : pCfg.getAutoEscapingPolicy();
         }
         recalculateAutoEscapingField();
@@ -192,42 +199,6 @@ public class FMParser {
         // Nothing to do ATM
     }
 
-    private OutputFormat getFormatFromStdFileExt() {
-        String name = template.getSourceOrLookupName();
-        if (name == null) {
-            return null;
-        }
-
-        int ln = name.length();
-        if (ln < 5) return null;
-
-        char c = name.charAt(ln - 5);
-        if (c != '.') return null;
-
-        c = name.charAt(ln - 4);
-        if (c != 'f' && c != 'F') return null;
-
-        c = name.charAt(ln - 3);
-        if (c != 't' && c != 'T') return null;
-
-        c = name.charAt(ln - 2);
-        if (c != 'l' && c != 'L') return null;
-
-        c = name.charAt(ln - 1);
-        try {
-            // Note: We get the output formats by name, so that custom 
overrides take effect.
-            if (c == 'h' || c == 'H') {
-                return 
template.getConfiguration().getOutputFormat(HTMLOutputFormat.INSTANCE.getName());
-                }
-            if (c == 'x' || c == 'X') {
-                return 
template.getConfiguration().getOutputFormat(XMLOutputFormat.INSTANCE.getName());
-            }
-        } catch (UnregisteredOutputFormatException e) {
-            throw new BugException("Unregistered std format", e);
-        }
-        return null;
-    }
-    
     /**
      * Updates the {@link #autoEscaping} field based on the {@link 
#autoEscapingPolicy} and {@link #outputFormat} fields.
      */

Reply via email to