freemarker-notifications mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ddek...@apache.org
Subject [37/54] [partial] incubator-freemarker git commit: Unifying the o.a.f.core and o.a.f.core.ast
Date Thu, 23 Feb 2017 21:36:04 GMT
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/NonSequenceException.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/NonSequenceException.java b/src/main/java/org/apache/freemarker/core/NonSequenceException.java
new file mode 100644
index 0000000..abe72a0
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/NonSequenceException.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 org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateSequenceModel;
+
+/**
+ * Indicates that a {@link TemplateSequenceModel} value was expected, but the value had a different type.
+ * 
+ * @since 2.3.21
+ */
+public class NonSequenceException extends UnexpectedTypeException {
+
+    private static final Class[] EXPECTED_TYPES = new Class[] { TemplateSequenceModel.class };
+    
+    public NonSequenceException(Environment env) {
+        super(env, "Expecting sequence value here");
+    }
+
+    public NonSequenceException(String description, Environment env) {
+        super(env, description);
+    }
+
+    NonSequenceException(Environment env, _ErrorDescriptionBuilder description) {
+        super(env, description);
+    }
+
+    NonSequenceException(
+            ASTExpression blamed, TemplateModel model, Environment env)
+            throws InvalidReferenceException {
+        super(blamed, model, "sequence", EXPECTED_TYPES, env);
+    }
+
+    NonSequenceException(
+            ASTExpression blamed, TemplateModel model, String tip,
+            Environment env)
+            throws InvalidReferenceException {
+        super(blamed, model, "sequence", EXPECTED_TYPES, tip, env);
+    }
+
+    NonSequenceException(
+            ASTExpression blamed, TemplateModel model, String[] tips, Environment env) throws InvalidReferenceException {
+        super(blamed, model, "sequence", EXPECTED_TYPES, tips, env);
+    }    
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/NonSequenceOrCollectionException.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/NonSequenceOrCollectionException.java b/src/main/java/org/apache/freemarker/core/NonSequenceOrCollectionException.java
new file mode 100644
index 0000000..9b532e9
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/NonSequenceOrCollectionException.java
@@ -0,0 +1,92 @@
+/*
+ * 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.model.TemplateCollectionModel;
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateSequenceModel;
+import org.apache.freemarker.core.model.WrapperTemplateModel;
+import org.apache.freemarker.core.util._CollectionUtil;
+
+/**
+ * Indicates that a {@link TemplateSequenceModel} or {@link TemplateCollectionModel} value was expected, but the value
+ * had a different type.
+ * 
+ * @since 2.3.21
+ */
+public class NonSequenceOrCollectionException extends UnexpectedTypeException {
+
+    private static final Class[] EXPECTED_TYPES = new Class[] {
+        TemplateSequenceModel.class, TemplateCollectionModel.class
+    };
+    private static final String ITERABLE_SUPPORT_HINT = "The problematic value is a java.lang.Iterable. Using "
+            + "DefaultObjectWrapper(..., iterableSupport=true) as the object_wrapper setting of the FreeMarker "
+            + "configuration should solve this.";
+    
+    public NonSequenceOrCollectionException(Environment env) {
+        super(env, "Expecting sequence or collection value here");
+    }
+
+    public NonSequenceOrCollectionException(String description, Environment env) {
+        super(env, description);
+    }
+
+    NonSequenceOrCollectionException(Environment env, _ErrorDescriptionBuilder description) {
+        super(env, description);
+    }
+
+    NonSequenceOrCollectionException(
+            ASTExpression blamed, TemplateModel model, Environment env)
+            throws InvalidReferenceException {
+        this(blamed, model, _CollectionUtil.EMPTY_OBJECT_ARRAY, env);
+    }
+
+    NonSequenceOrCollectionException(
+            ASTExpression blamed, TemplateModel model, String tip,
+            Environment env)
+            throws InvalidReferenceException {
+        this(blamed, model, new Object[] { tip }, env);
+    }
+
+    NonSequenceOrCollectionException(
+            ASTExpression blamed, TemplateModel model, Object[] tips, Environment env) throws InvalidReferenceException {
+        super(blamed, model, "sequence or collection", EXPECTED_TYPES, extendTipsIfIterable(model, tips), env);
+    }
+    
+    private static Object[] extendTipsIfIterable(TemplateModel model, Object[] tips) {
+        if (isWrappedIterable(model)) {
+            final int tipsLen = tips != null ? tips.length : 0;
+            Object[] extendedTips = new Object[tipsLen + 1];
+            for (int i = 0; i < tipsLen; i++) {
+                extendedTips[i] = tips[i];
+            }
+            extendedTips[tipsLen] = ITERABLE_SUPPORT_HINT;
+            return extendedTips;
+        } else {
+            return tips;
+        }
+    }
+
+    public static boolean isWrappedIterable(TemplateModel model) {
+        return model instanceof WrapperTemplateModel
+                && ((WrapperTemplateModel) model).getWrappedObject() instanceof Iterable;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/NonStringException.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/NonStringException.java b/src/main/java/org/apache/freemarker/core/NonStringException.java
new file mode 100644
index 0000000..1c0ff8b
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/NonStringException.java
@@ -0,0 +1,74 @@
+/*
+ * 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.model.TemplateBooleanModel;
+import org.apache.freemarker.core.model.TemplateDateModel;
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateNumberModel;
+import org.apache.freemarker.core.model.TemplateScalarModel;
+
+/**
+ * Indicates that a {@link TemplateScalarModel} value was expected (or maybe something that can be automatically coerced
+ * to that), but the value had a different type.
+ */
+public class NonStringException extends UnexpectedTypeException {
+
+    static final String STRING_COERCABLE_TYPES_DESC
+            = "string or something automatically convertible to string (number, date or boolean)";
+    
+    static final Class[] STRING_COERCABLE_TYPES = new Class[] {
+        TemplateScalarModel.class, TemplateNumberModel.class, TemplateDateModel.class, TemplateBooleanModel.class
+    };
+    
+    private static final String DEFAULT_DESCRIPTION
+            = "Expecting " + NonStringException.STRING_COERCABLE_TYPES_DESC + " value here";
+
+    public NonStringException(Environment env) {
+        super(env, DEFAULT_DESCRIPTION);
+    }
+
+    public NonStringException(String description, Environment env) {
+        super(env, description);
+    }
+ 
+    NonStringException(Environment env, _ErrorDescriptionBuilder description) {
+        super(env, description);
+    }
+
+    NonStringException(
+            ASTExpression blamed, TemplateModel model, Environment env)
+            throws InvalidReferenceException {
+        super(blamed, model, NonStringException.STRING_COERCABLE_TYPES_DESC, STRING_COERCABLE_TYPES, env);
+    }
+
+    NonStringException(
+            ASTExpression blamed, TemplateModel model, String tip,
+            Environment env)
+            throws InvalidReferenceException {
+        super(blamed, model, NonStringException.STRING_COERCABLE_TYPES_DESC, STRING_COERCABLE_TYPES, tip, env);
+    }
+
+    NonStringException(
+            ASTExpression blamed, TemplateModel model, String[] tips, Environment env) throws InvalidReferenceException {
+        super(blamed, model, NonStringException.STRING_COERCABLE_TYPES_DESC, STRING_COERCABLE_TYPES, tips, env);
+    }
+        
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/NonStringOrTemplateOutputException.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/NonStringOrTemplateOutputException.java b/src/main/java/org/apache/freemarker/core/NonStringOrTemplateOutputException.java
new file mode 100644
index 0000000..73454e6
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/NonStringOrTemplateOutputException.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 org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateScalarModel;
+
+/**
+ * Indicates that a {@link TemplateScalarModel} (or maybe something that can be automatically coerced
+ * to that) or {@link TemplateMarkupOutputModel} value was expected, but the value had a different type.
+ */
+public class NonStringOrTemplateOutputException extends UnexpectedTypeException {
+
+    static final String STRING_COERCABLE_TYPES_OR_TOM_DESC
+            = NonStringException.STRING_COERCABLE_TYPES_DESC + ", or \"template output\" ";
+    
+    static final Class[] STRING_COERCABLE_TYPES_AND_TOM;
+    static {
+        STRING_COERCABLE_TYPES_AND_TOM = new Class[NonStringException.STRING_COERCABLE_TYPES.length + 1];
+        int i;
+        for (i = 0; i < NonStringException.STRING_COERCABLE_TYPES.length; i++) {
+            STRING_COERCABLE_TYPES_AND_TOM[i] = NonStringException.STRING_COERCABLE_TYPES[i];
+        }
+        STRING_COERCABLE_TYPES_AND_TOM[i] = TemplateMarkupOutputModel.class;
+    };
+    
+    private static final String DEFAULT_DESCRIPTION
+            = "Expecting " + NonStringOrTemplateOutputException.STRING_COERCABLE_TYPES_OR_TOM_DESC + " value here";
+
+    public NonStringOrTemplateOutputException(Environment env) {
+        super(env, DEFAULT_DESCRIPTION);
+    }
+
+    public NonStringOrTemplateOutputException(String description, Environment env) {
+        super(env, description);
+    }
+ 
+    NonStringOrTemplateOutputException(Environment env, _ErrorDescriptionBuilder description) {
+        super(env, description);
+    }
+
+    NonStringOrTemplateOutputException(
+            ASTExpression blamed, TemplateModel model, Environment env)
+            throws InvalidReferenceException {
+        super(blamed, model, NonStringOrTemplateOutputException.STRING_COERCABLE_TYPES_OR_TOM_DESC, STRING_COERCABLE_TYPES_AND_TOM, env);
+    }
+
+    NonStringOrTemplateOutputException(
+            ASTExpression blamed, TemplateModel model, String tip,
+            Environment env)
+            throws InvalidReferenceException {
+        super(blamed, model, NonStringOrTemplateOutputException.STRING_COERCABLE_TYPES_OR_TOM_DESC, STRING_COERCABLE_TYPES_AND_TOM, tip, env);
+    }
+
+    NonStringOrTemplateOutputException(
+            ASTExpression blamed, TemplateModel model, String[] tips, Environment env) throws InvalidReferenceException {
+        super(blamed, model, NonStringOrTemplateOutputException.STRING_COERCABLE_TYPES_OR_TOM_DESC, STRING_COERCABLE_TYPES_AND_TOM, tips, env);
+    }
+        
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/NonUserDefinedDirectiveLikeException.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/NonUserDefinedDirectiveLikeException.java b/src/main/java/org/apache/freemarker/core/NonUserDefinedDirectiveLikeException.java
new file mode 100644
index 0000000..fe83c12
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/NonUserDefinedDirectiveLikeException.java
@@ -0,0 +1,67 @@
+/*
+ * 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.model.TemplateDirectiveModel;
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateTransformModel;
+
+/**
+ * Indicates that a {@link TemplateDirectiveModel} or {@link TemplateTransformModel} or {@link ASTDirMacro} value was
+ * expected, but the value had a different type.
+ * 
+ * @since 2.3.21
+ */
+class NonUserDefinedDirectiveLikeException extends UnexpectedTypeException {
+
+    private static final Class[] EXPECTED_TYPES = new Class[] {
+        TemplateDirectiveModel.class, TemplateTransformModel.class, ASTDirMacro.class };
+    
+    public NonUserDefinedDirectiveLikeException(Environment env) {
+        super(env, "Expecting user-defined directive, transform or macro value here");
+    }
+
+    public NonUserDefinedDirectiveLikeException(String description, Environment env) {
+        super(env, description);
+    }
+
+    NonUserDefinedDirectiveLikeException(Environment env, _ErrorDescriptionBuilder description) {
+        super(env, description);
+    }
+
+    NonUserDefinedDirectiveLikeException(
+            ASTExpression blamed, TemplateModel model, Environment env)
+            throws InvalidReferenceException {
+        super(blamed, model, "user-defined directive, transform or macro", EXPECTED_TYPES, env);
+    }
+
+    NonUserDefinedDirectiveLikeException(
+            ASTExpression blamed, TemplateModel model, String tip,
+            Environment env)
+            throws InvalidReferenceException {
+        super(blamed, model, "user-defined directive, transform or macro", EXPECTED_TYPES, tip, env);
+    }
+
+    NonUserDefinedDirectiveLikeException(
+            ASTExpression blamed, TemplateModel model, String[] tips, Environment env) throws InvalidReferenceException {
+        super(blamed, model, "user-defined directive, transform or macro", EXPECTED_TYPES, tips, env);
+    }    
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/OptInTemplateClassResolver.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/OptInTemplateClassResolver.java b/src/main/java/org/apache/freemarker/core/OptInTemplateClassResolver.java
new file mode 100644
index 0000000..50e79d9
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/OptInTemplateClassResolver.java
@@ -0,0 +1,156 @@
+/*
+ * 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.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.freemarker.core.util._ClassUtil;
+import org.apache.freemarker.core.util._StringUtil;
+
+/**
+ * A {@link TemplateClassResolver} that resolves only the classes whose name 
+ * was specified in the constructor.
+ */
+public class OptInTemplateClassResolver implements TemplateClassResolver {
+    
+    private final Set/*<String>*/ allowedClasses;
+    private final List/*<String>*/ trustedTemplatePrefixes;
+    private final Set/*<String>*/ trustedTemplateNames;
+    
+    /**
+     * Creates a new instance. 
+     *
+     * @param allowedClasses the {@link Set} of {@link String}-s that contains
+     *     the full-qualified names of the allowed classes.
+     *     Can be <code>null</code> (means not class is allowed).
+     * @param trustedTemplates the {@link List} of {@link String}-s that contains
+     *     template names (i.e., template root directory relative paths)
+     *     and prefix patterns (like <code>"include/*"</code>) of templates
+     *     for which {@link TemplateClassResolver#UNRESTRICTED_RESOLVER} will be 
+     *     used (which is not as safe as {@link OptInTemplateClassResolver}).
+     *     The list items need not start with <code>"/"</code> (if they are, it
+     *     will be removed). List items ending with <code>"*"</code> are treated
+     *     as prefixes (i.e. <code>"foo*"</code> matches <code>"foobar"</code>,
+     *     <code>"foo/bar/baaz"</code>, <code>"foowhatever/bar/baaz"</code>,
+     *     etc.). The <code>"*"</code> has no special meaning anywhere else.
+     *     The matched template name is the name (template root directory
+     *     relative path) of the template that directly (lexically) contains the
+     *     operation (like <code>?new</code>) that wants to get the class. Thus,
+     *     if a trusted template includes a non-trusted template, the
+     *     <code>allowedClasses</code> restriction will apply in the included
+     *     template.
+     *     This parameter can be <code>null</code> (means no trusted templates).
+     */
+    public OptInTemplateClassResolver(
+            Set allowedClasses, List<String> trustedTemplates) {
+        this.allowedClasses = allowedClasses != null ? allowedClasses : Collections.EMPTY_SET;
+        if (trustedTemplates != null) {
+            trustedTemplateNames = new HashSet();
+            trustedTemplatePrefixes = new ArrayList();
+            
+            Iterator<String> it = trustedTemplates.iterator();
+            while (it.hasNext()) {
+                String li = it.next();
+                if (li.startsWith("/")) li = li.substring(1);
+                if (li.endsWith("*")) {
+                    trustedTemplatePrefixes.add(li.substring(0, li.length() - 1));
+                } else {
+                    trustedTemplateNames.add(li);
+                }
+            }
+        } else {
+            trustedTemplateNames = Collections.EMPTY_SET;
+            trustedTemplatePrefixes = Collections.EMPTY_LIST;
+        }
+    }
+
+    @Override
+    public Class resolve(String className, Environment env, Template template)
+    throws TemplateException {
+        String templateName = safeGetTemplateName(template);
+        
+        if (templateName != null
+                && (trustedTemplateNames.contains(templateName)
+                        || hasMatchingPrefix(templateName))) {
+            return TemplateClassResolver.UNRESTRICTED_RESOLVER.resolve(className, env, template);
+        } else {
+            if (!allowedClasses.contains(className)) {
+                throw new _MiscTemplateException(env,
+                        "Instantiating ", className, " is not allowed in the template for security reasons. (If you "
+                        + "run into this problem when using ?new in a template, you may want to check the \"",
+                        Configurable.NEW_BUILTIN_CLASS_RESOLVER_KEY,
+                        "\" setting in the FreeMarker configuration.)");
+            } else {
+                try {
+                    return _ClassUtil.forName(className);
+                } catch (ClassNotFoundException e) {
+                    throw new _MiscTemplateException(e, env);
+                }
+            }
+        }
+    }
+
+    /**
+     * Extract the template name from the template object which will be matched
+     * against the trusted template names and pattern. 
+     */
+    protected String safeGetTemplateName(Template template) {
+        if (template == null) return null;
+        
+        String name = template.getName();
+        if (name == null) return null;
+
+        // Detect exploits, return null if one is suspected:
+        String decodedName = name;
+        if (decodedName.indexOf('%') != -1) {
+            decodedName = _StringUtil.replace(decodedName, "%2e", ".", false, false);
+            decodedName = _StringUtil.replace(decodedName, "%2E", ".", false, false);
+            decodedName = _StringUtil.replace(decodedName, "%2f", "/", false, false);
+            decodedName = _StringUtil.replace(decodedName, "%2F", "/", false, false);
+            decodedName = _StringUtil.replace(decodedName, "%5c", "\\", false, false);
+            decodedName = _StringUtil.replace(decodedName, "%5C", "\\", false, false);
+        }
+        int dotDotIdx = decodedName.indexOf("..");
+        if (dotDotIdx != -1) {
+            int before = dotDotIdx - 1 >= 0 ? decodedName.charAt(dotDotIdx - 1) : -1;
+            int after = dotDotIdx + 2 < decodedName.length() ? decodedName.charAt(dotDotIdx + 2) : -1;
+            if ((before == -1 || before == '/' || before == '\\')
+                    && (after == -1 || after == '/' || after == '\\')) {
+                return null;
+            }
+        }
+        
+        return name.startsWith("/") ? name.substring(1) : name;
+    }
+
+    private boolean hasMatchingPrefix(String name) {
+        for (int i = 0; i < trustedTemplatePrefixes.size(); i++) {
+            String prefix = (String) trustedTemplatePrefixes.get(i);
+            if (name.startsWith(prefix)) return true;
+        }
+        return false;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/OutputFormat.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/OutputFormat.java b/src/main/java/org/apache/freemarker/core/OutputFormat.java
new file mode 100644
index 0000000..7c16c5b
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/OutputFormat.java
@@ -0,0 +1,83 @@
+/*
+ * 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.util._ClassUtil;
+import org.apache.freemarker.core.util._StringUtil;
+
+/**
+ * Represents an output format. If you need auto-escaping, see its subclass, {@link MarkupOutputFormat}. 
+ * 
+ * @see Configuration#setOutputFormat(OutputFormat)
+ * @see Configuration#setRegisteredCustomOutputFormats(java.util.Collection)
+ * @see MarkupOutputFormat
+ * 
+ * @since 2.3.24
+ */
+public abstract class OutputFormat {
+
+    /**
+     * The short name used to refer to this format (like in the {@code #ftl} header).
+     */
+    public abstract String getName();
+    
+    /**
+     * Returns the MIME type of the output format. This might comes handy when generating a HTTP response. {@code null}
+     * if this output format doesn't clearly corresponds to a specific MIME type.
+     */
+    public abstract String getMimeType();
+
+    /**
+     * Tells if this output format allows inserting {@link TemplateMarkupOutputModel}-s of another output formats into
+     * it. If {@code true}, the foreign {@link TemplateMarkupOutputModel} will be inserted into the output as is (like
+     * if the surrounding output format was the same). This is usually a bad idea allow, as such an event could indicate
+     * application bugs. If this method returns {@code false} (recommended), then FreeMarker will try to assimilate the
+     * inserted value by converting its format to this format, which will currently (2.3.24) cause exception, unless the
+     * inserted value is made by escaping plain text and the target format is non-escaping, in which case format
+     * conversion is trivially possible. (It's not impossible that conversions will be extended beyond this, if there
+     * will be demand for that.)
+     * 
+     * <p>
+     * {@code true} value is used by {@link UndefinedOutputFormat}.
+     */
+    public abstract boolean isOutputFormatMixingAllowed();
+
+    /**
+     * Returns the short description of this format, to be used in error messages.
+     * Override {@link #toStringExtraProperties()} to customize this.
+     */
+    @Override
+    public final String toString() {
+        String extras = toStringExtraProperties();
+        return getName() + "("
+                + "mimeType=" + _StringUtil.jQuote(getMimeType()) + ", "
+                + "class=" + _ClassUtil.getShortClassNameOfObject(this, true)
+                + (extras.length() != 0 ? ", " : "") + extras
+                + ")";
+    }
+    
+    /**
+     * Should be like {@code "foo=\"something\", bar=123"}; this will be inserted inside the parentheses in
+     * {@link #toString()}. Shouldn't return {@code null}; should return {@code ""} if there are no extra properties.  
+     */
+    protected String toStringExtraProperties() {
+        return "";
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/OutputFormatBoundBuiltIn.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/OutputFormatBoundBuiltIn.java b/src/main/java/org/apache/freemarker/core/OutputFormatBoundBuiltIn.java
new file mode 100644
index 0000000..f0a38d3
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/OutputFormatBoundBuiltIn.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.model.TemplateModel;
+import org.apache.freemarker.core.util._NullArgumentException;
+
+abstract class OutputFormatBoundBuiltIn extends SpecialBuiltIn {
+    
+    protected OutputFormat outputFormat;
+    protected int autoEscapingPolicy;
+    
+    void bindToOutputFormat(OutputFormat outputFormat, int autoEscapingPolicy) {
+        _NullArgumentException.check(outputFormat);
+        this.outputFormat = outputFormat;
+        this.autoEscapingPolicy = autoEscapingPolicy;
+    }
+    
+    @Override
+    TemplateModel _eval(Environment env) throws TemplateException {
+        if (outputFormat == null) {
+            // The parser should prevent this situation
+            throw new NullPointerException("outputFormat was null");
+        }
+        return calculateResult(env);
+    }
+
+    protected abstract TemplateModel calculateResult(Environment env)
+            throws TemplateException;
+    
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/ParameterRole.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/ParameterRole.java b/src/main/java/org/apache/freemarker/core/ParameterRole.java
new file mode 100644
index 0000000..07c5046
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/ParameterRole.java
@@ -0,0 +1,93 @@
+/*
+ * 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;
+
+// Change this to an Enum in Java 5
+/**
+ * @see ASTNode#getParameterRole(int)
+ */
+final class ParameterRole {
+    
+    private final String name;
+
+    static final ParameterRole UNKNOWN = new ParameterRole("[unknown role]");
+    
+    // When figuring out the names of these, always read them after the possible getName() values. It should sound OK.
+    // Like "`+` left hand operand", or "`#if` parameter". That is, the roles (only) have to make sense in the
+    // context of the possible ASTNode classes.
+    static final ParameterRole LEFT_HAND_OPERAND = new ParameterRole("left-hand operand"); 
+    static final ParameterRole RIGHT_HAND_OPERAND = new ParameterRole("right-hand operand"); 
+    static final ParameterRole ENCLOSED_OPERAND = new ParameterRole("enclosed operand"); 
+    static final ParameterRole ITEM_VALUE = new ParameterRole("item value"); 
+    static final ParameterRole ITEM_KEY = new ParameterRole("item key");
+    static final ParameterRole ASSIGNMENT_TARGET = new ParameterRole("assignment target");
+    static final ParameterRole ASSIGNMENT_OPERATOR = new ParameterRole("assignment operator");
+    static final ParameterRole ASSIGNMENT_SOURCE = new ParameterRole("assignment source");
+    static final ParameterRole VARIABLE_SCOPE = new ParameterRole("variable scope");
+    static final ParameterRole NAMESPACE = new ParameterRole("namespace");
+    static final ParameterRole ERROR_HANDLER = new ParameterRole("error handler");
+    static final ParameterRole PASSED_VALUE = new ParameterRole("passed value");
+    static final ParameterRole CONDITION = new ParameterRole("condition"); 
+    static final ParameterRole VALUE = new ParameterRole("value");
+    static final ParameterRole AST_NODE_SUBTYPE = new ParameterRole("AST-node subtype");
+    static final ParameterRole PLACEHOLDER_VARIABLE = new ParameterRole("placeholder variable");
+    static final ParameterRole EXPRESSION_TEMPLATE = new ParameterRole("expression template");
+    static final ParameterRole LIST_SOURCE = new ParameterRole("list source");
+    static final ParameterRole TARGET_LOOP_VARIABLE = new ParameterRole("target loop variable");
+    static final ParameterRole TEMPLATE_NAME = new ParameterRole("template name");
+    static final ParameterRole PARSE_PARAMETER = new ParameterRole("\"parse\" parameter");
+    static final ParameterRole ENCODING_PARAMETER = new ParameterRole("\"encoding\" parameter");
+    static final ParameterRole IGNORE_MISSING_PARAMETER = new ParameterRole("\"ignore_missing\" parameter");
+    static final ParameterRole PARAMETER_NAME = new ParameterRole("parameter name");
+    static final ParameterRole PARAMETER_DEFAULT = new ParameterRole("parameter default");
+    static final ParameterRole CATCH_ALL_PARAMETER_NAME = new ParameterRole("catch-all parameter name");
+    static final ParameterRole ARGUMENT_NAME = new ParameterRole("argument name");
+    static final ParameterRole ARGUMENT_VALUE = new ParameterRole("argument value");
+    static final ParameterRole CONTENT = new ParameterRole("content");
+    static final ParameterRole EMBEDDED_TEMPLATE = new ParameterRole("embedded template");
+    static final ParameterRole VALUE_PART = new ParameterRole("value part");
+    static final ParameterRole MINIMUM_DECIMALS = new ParameterRole("minimum decimals");
+    static final ParameterRole MAXIMUM_DECIMALS = new ParameterRole("maximum decimals");
+    static final ParameterRole NODE = new ParameterRole("node");
+    static final ParameterRole CALLEE = new ParameterRole("callee");
+    static final ParameterRole MESSAGE = new ParameterRole("message");
+    
+    private ParameterRole(String name) {
+        this.name = name;
+    }
+    
+    static ParameterRole forBinaryOperatorOperand(int paramIndex) {
+        switch (paramIndex) {
+        case 0: return LEFT_HAND_OPERAND;
+        case 1: return RIGHT_HAND_OPERAND;
+        default: throw new IndexOutOfBoundsException();
+        }
+    }
+    
+    public String getName() {
+        return name;
+    }
+
+    @Override
+    public String toString() {
+        return name;
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/ParseException.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/ParseException.java b/src/main/java/org/apache/freemarker/core/ParseException.java
new file mode 100644
index 0000000..2b5eef2
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/ParseException.java
@@ -0,0 +1,513 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core;
+
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+import org.apache.freemarker.core.FMParserConstants;
+import org.apache.freemarker.core.Token;
+import org.apache.freemarker.core.util._SecurityUtil;
+import org.apache.freemarker.core.util._StringUtil;
+
+/**
+ * Parsing-time exception in a template (as opposed to a runtime exception, a {@link TemplateException}). This usually
+ * signals syntactical/lexical errors.
+ * 
+ * Note that on JavaCC-level lexical errors throw {@link TokenMgrError} instead of this, however with the API-s that
+ * most users use those will be wrapped into {@link ParseException}-s. 
+ *
+ * This is a modified version of file generated by JavaCC from FTL.jj.
+ * You can modify this class to customize the error reporting mechanisms so long as the public interface
+ * remains compatible with the original.
+ * 
+ * @see TokenMgrError
+ */
+public class ParseException extends IOException implements FMParserConstants {
+
+    /**
+     * This is the last token that has been consumed successfully.  If
+     * this object has been created due to a parse error, the token
+     * following this token will (therefore) be the first error token.
+     */
+    public Token currentToken;
+
+    private static volatile Boolean jbossToolsMode;
+
+    private boolean messageAndDescriptionRendered;
+    private String message;
+    private String description; 
+
+    public int columnNumber, lineNumber;
+    public int endColumnNumber, endLineNumber;
+
+    /**
+     * Each entry in this array is an array of integers.  Each array
+     * of integers represents a sequence of tokens (by their ordinal
+     * values) that is expected at this point of the parse.
+     */
+    public int[][] expectedTokenSequences;
+
+    /**
+     * This is a reference to the "tokenImage" array of the generated
+     * parser within which the parse error occurred.  This array is
+     * defined in the generated ...Constants interface.
+     */
+    public String[] tokenImage;
+
+    /**
+     * The end of line string for this machine.
+     */
+    protected String eol = _SecurityUtil.getSystemProperty("line.separator", "\n");
+
+    private String templateName;
+
+    /**
+     * This constructor is used by the method "generateParseException"
+     * in the generated parser.  Calling this constructor generates
+     * a new object of this type with the fields "currentToken",
+     * "expectedTokenSequences", and "tokenImage" set.
+     * This constructor calls its super class with the empty string
+     * to force the "toString" method of parent class "Throwable" to
+     * print the error message in the form:
+     *     ParseException: &lt;result of getMessage&gt;
+     */
+    public ParseException(Token currentTokenVal,
+            int[][] expectedTokenSequencesVal,
+            String[] tokenImageVal
+            ) {
+        super("");
+        currentToken = currentTokenVal;
+        expectedTokenSequences = expectedTokenSequencesVal;
+        tokenImage = tokenImageVal;
+        lineNumber = currentToken.next.beginLine;
+        columnNumber = currentToken.next.beginColumn;
+        endLineNumber = currentToken.next.endLine;
+        endColumnNumber = currentToken.next.endColumn;
+    }
+
+    /**
+     * Used by JavaCC generated code.
+     */
+    protected ParseException() {
+        super();
+    }
+
+    /**
+     * @since 2.3.21
+     */
+    public ParseException(String description, Template template,
+            int lineNumber, int columnNumber, int endLineNumber, int endColumnNumber) {
+        this(description, template, lineNumber, columnNumber, endLineNumber, endColumnNumber, null);      
+    }
+
+    /**
+     * @since 2.3.21
+     */
+    public ParseException(String description, Template template,
+            int lineNumber, int columnNumber, int endLineNumber, int endColumnNumber,
+            Throwable cause) {
+        this(description,
+                template == null ? null : template.getSourceName(),
+                        lineNumber, columnNumber,
+                        endLineNumber, endColumnNumber,
+                        cause);      
+    }
+    
+    /**
+     * @since 2.3.20
+     */
+    public ParseException(String description, Template template, Token tk) {
+        this(description, template, tk, null);
+    }
+
+    /**
+     * @since 2.3.20
+     */
+    public ParseException(String description, Template template, Token tk, Throwable cause) {
+        this(description,
+                template == null ? null : template.getSourceName(),
+                        tk.beginLine, tk.beginColumn,
+                        tk.endLine, tk.endColumn,
+                        cause);
+    }
+
+    /**
+     * @since 2.3.20
+     */
+    public ParseException(String description, ASTNode tobj) {
+        this(description, tobj, null);
+    }
+
+    /**
+     * @since 2.3.20
+     */
+    public ParseException(String description, ASTNode tobj, Throwable cause) {
+        this(description,
+                tobj.getTemplate() == null ? null : tobj.getTemplate().getSourceName(),
+                        tobj.beginLine, tobj.beginColumn,
+                        tobj.endLine, tobj.endColumn,
+                        cause);
+    }
+
+    private ParseException(String description, String templateName,
+            int lineNumber, int columnNumber,
+            int endLineNumber, int endColumnNumber,
+            Throwable cause) {
+        super(description);  // but we override getMessage, so it will be different
+        try {
+            initCause(cause);
+        } catch (Exception e) {
+            // Suppressed; we can't do more
+        }
+        this.description = description; 
+        this.templateName = templateName;
+        this.lineNumber = lineNumber;
+        this.columnNumber = columnNumber;
+        this.endLineNumber = endLineNumber;
+        this.endColumnNumber = endColumnNumber;
+    }
+
+    /**
+     * Should be used internally only; sets the name of the template that contains the error.
+     * This is needed as the constructor that JavaCC automatically calls doesn't pass in the template, so we
+     * set it somewhere later in an exception handler. 
+     */
+    public void setTemplateName(String templateName) {
+        this.templateName = templateName;
+        synchronized (this) {
+            messageAndDescriptionRendered = false;
+            message = null;
+        }
+    }
+
+    /**
+     * Returns the error location plus the error description.
+     * 
+     * @see #getDescription()
+     * @see #getTemplateName()
+     * @see #getLineNumber()
+     * @see #getColumnNumber()
+     */
+    @Override
+    public String getMessage() {
+        synchronized (this) {
+            if (messageAndDescriptionRendered) return message;
+        }
+        renderMessageAndDescription();
+        synchronized (this) {
+            return message;
+        }
+    }
+
+    private String getDescription() {
+        synchronized (this) {
+            if (messageAndDescriptionRendered) return description;
+        }
+        renderMessageAndDescription();
+        synchronized (this) {
+            return description;
+        }
+    }
+    
+    /**
+     * Returns the description of the error without error location or source quotations, or {@code null} if there's no
+     * description available. This is useful in editors (IDE-s) where the error markers and the editor window itself
+     * already carry this information, so it's redundant the repeat in the error dialog.
+     */
+    public String getEditorMessage() {
+        return getDescription();
+    }
+
+    /**
+     * Returns the name (template-root relative path) of the template whose parsing was failed.
+     * Maybe {@code null} if this is a non-stored template. 
+     * 
+     * @since 2.3.20
+     */
+    public String getTemplateName() {
+        return templateName;
+    }
+
+    /**
+     * 1-based line number of the failing section, or 0 is the information is not available.
+     */
+    public int getLineNumber() {
+        return lineNumber;
+    }
+
+    /**
+     * 1-based column number of the failing section, or 0 is the information is not available.
+     */
+    public int getColumnNumber() {
+        return columnNumber;
+    }
+
+    /**
+     * 1-based line number of the last line that contains the failing section, or 0 if the information is not available.
+     * 
+     * @since 2.3.21
+     */
+    public int getEndLineNumber() {
+        return endLineNumber;
+    }
+
+    /**
+     * 1-based column number of the last character of the failing section, or 0 if the information is not available.
+     * Note that unlike with Java string API-s, this column number is inclusive.
+     * 
+     * @since 2.3.21
+     */
+    public int getEndColumnNumber() {
+        return endColumnNumber;
+    }
+
+    private void renderMessageAndDescription() {
+        String desc = getOrRenderDescription();
+
+        String prefix;
+        if (!isInJBossToolsMode()) {
+            prefix = "Syntax error "
+                    + MessageUtil.formatLocationForSimpleParsingError(templateName, lineNumber, columnNumber)
+                    + ":\n";  
+        } else {
+            prefix = "[col. " + columnNumber + "] ";
+        }
+
+        String msg = prefix + desc;
+        desc = msg.substring(prefix.length());  // so we reuse the backing char[]
+
+        synchronized (this) {
+            message = msg;
+            description = desc;
+            messageAndDescriptionRendered = true;
+        }
+    }
+
+    private boolean isInJBossToolsMode() {
+        if (jbossToolsMode == null) {
+            try {
+                jbossToolsMode = Boolean.valueOf(
+                        ParseException.class.getClassLoader().toString().indexOf(
+                                "[org.jboss.ide.eclipse.freemarker:") != -1);
+            } catch (Throwable e) {
+                jbossToolsMode = Boolean.FALSE;
+            }
+        }
+        return jbossToolsMode.booleanValue();
+    }
+
+    /**
+     * Returns the description of the error without the error location, or {@code null} if there's no description
+     * available.
+     */
+    private String getOrRenderDescription() {
+        synchronized (this) {
+            if (description != null) return description;  // When we already have it from the constructor
+        }
+
+        String tokenErrDesc;
+        if (currentToken != null) {
+            tokenErrDesc = getCustomTokenErrorDescription();
+            if (tokenErrDesc == null) {
+                // The default JavaCC message generation stuff follows.
+                StringBuilder expected = new StringBuilder();
+                int maxSize = 0;
+                for (int i = 0; i < expectedTokenSequences.length; i++) {
+                    if (i != 0) {
+                        expected.append(eol);
+                    }
+                    expected.append("    ");
+                    if (maxSize < expectedTokenSequences[i].length) {
+                        maxSize = expectedTokenSequences[i].length;
+                    }
+                    for (int j = 0; j < expectedTokenSequences[i].length; j++) {
+                        if (j != 0) expected.append(' ');
+                        expected.append(tokenImage[expectedTokenSequences[i][j]]);
+                    }
+                }
+                tokenErrDesc = "Encountered \"";
+                Token tok = currentToken.next;
+                for (int i = 0; i < maxSize; i++) {
+                    if (i != 0) tokenErrDesc += " ";
+                    if (tok.kind == 0) {
+                        tokenErrDesc += tokenImage[0];
+                        break;
+                    }
+                    tokenErrDesc += add_escapes(tok.image);
+                    tok = tok.next;
+                }
+                tokenErrDesc += "\", but ";
+
+                if (expectedTokenSequences.length == 1) {
+                    tokenErrDesc += "was expecting:" + eol;
+                } else {
+                    tokenErrDesc += "was expecting one of:" + eol;
+                }
+                tokenErrDesc += expected;
+            }
+        } else {
+            tokenErrDesc = null;
+        }
+        return tokenErrDesc;
+    }
+
+    private String getCustomTokenErrorDescription() {
+        final Token nextToken = currentToken.next;
+        final int kind = nextToken.kind;
+        if (kind == EOF) {
+            Set/*<String>*/ endNames = new HashSet();
+            for (int[] sequence : expectedTokenSequences) {
+                for (int aSequence : sequence) {
+                    switch (aSequence) {
+                        case END_FOREACH:
+                            endNames.add("#foreach");
+                            break;
+                        case END_LIST:
+                            endNames.add("#list");
+                            break;
+                        case END_SWITCH:
+                            endNames.add("#switch");
+                            break;
+                        case END_IF:
+                            endNames.add("#if");
+                            break;
+                        case END_COMPRESS:
+                            endNames.add("#compress");
+                            break;
+                        case END_MACRO:
+                            endNames.add("#macro");
+                        case END_FUNCTION:
+                            endNames.add("#function");
+                            break;
+                        case END_TRANSFORM:
+                            endNames.add("#transform");
+                            break;
+                        case END_ESCAPE:
+                            endNames.add("#escape");
+                            break;
+                        case END_NOESCAPE:
+                            endNames.add("#noescape");
+                            break;
+                        case END_ASSIGN:
+                            endNames.add("#assign");
+                            break;
+                        case END_LOCAL:
+                            endNames.add("#local");
+                            break;
+                        case END_GLOBAL:
+                            endNames.add("#global");
+                            break;
+                        case END_ATTEMPT:
+                            endNames.add("#attempt");
+                            break;
+                        case CLOSING_CURLY_BRACKET:
+                            endNames.add("\"{\"");
+                            break;
+                        case CLOSE_BRACKET:
+                            endNames.add("\"[\"");
+                            break;
+                        case CLOSE_PAREN:
+                            endNames.add("\"(\"");
+                            break;
+                        case UNIFIED_CALL_END:
+                            endNames.add("@...");
+                            break;
+                    }
+                }
+            }
+            return "Unexpected end of file reached."
+                    + (endNames.size() == 0 ? "" : " You have an unclosed " + concatWithOrs(endNames) + ".");
+        } else if (kind == ELSE) {
+            return "Unexpected directive, \"#else\". "
+                    + "Check if you have a valid #if-#elseif-#else or #list-#else structure.";
+        } else if (kind == END_IF || kind == ELSE_IF) {
+            return "Unexpected directive, "
+                    + _StringUtil.jQuote(nextToken)
+                    + ". Check if you have a valid #if-#elseif-#else structure.";
+        }
+        return null;
+    }
+
+    private String concatWithOrs(Set/*<String>*/ endNames) {
+        StringBuilder sb = new StringBuilder(); 
+        for (Iterator/*<String>*/ it = endNames.iterator(); it.hasNext(); ) {
+            String endName = (String) it.next();
+            if (sb.length() != 0) {
+                sb.append(" or ");
+            }
+            sb.append(endName);
+        }
+        return sb.toString();
+    }
+
+    /**
+     * Used to convert raw characters to their escaped version
+     * when these raw version cannot be used as part of an ASCII
+     * string literal.
+     */
+    protected String add_escapes(String str) {
+        StringBuilder retval = new StringBuilder();
+        char ch;
+        for (int i = 0; i < str.length(); i++) {
+            switch (str.charAt(i))
+            {
+            case 0 :
+                continue;
+            case '\b':
+                retval.append("\\b");
+                continue;
+            case '\t':
+                retval.append("\\t");
+                continue;
+            case '\n':
+                retval.append("\\n");
+                continue;
+            case '\f':
+                retval.append("\\f");
+                continue;
+            case '\r':
+                retval.append("\\r");
+                continue;
+            case '\"':
+                retval.append("\\\"");
+                continue;
+            case '\'':
+                retval.append("\\\'");
+                continue;
+            case '\\':
+                retval.append("\\\\");
+                continue;
+            default:
+                if ((ch = str.charAt(i)) < 0x20 || ch > 0x7e) {
+                    String s = "0000" + Integer.toString(ch, 16);
+                    retval.append("\\u" + s.substring(s.length() - 4, s.length()));
+                } else {
+                    retval.append(ch);
+                }
+                continue;
+            }
+        }
+        return retval.toString();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/ParserConfiguration.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/ParserConfiguration.java b/src/main/java/org/apache/freemarker/core/ParserConfiguration.java
new file mode 100644
index 0000000..24b6a1d
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/ParserConfiguration.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;
+
+/**
+ * <b>Don't implement this interface yourself</b>; use the existing implementation(s). This interface is implemented by
+ * classes that hold settings that affect parsing. New parser settings can be added in new FreeMarker versions, which
+ * will break your implementation.
+ * 
+ * @since 2.3.24
+ */
+public interface ParserConfiguration {
+
+    /**
+     * See {@link Configuration#getTagSyntax()}.
+     */
+    int getTagSyntax();
+
+    /**
+     * See {@link Configuration#getNamingConvention()}.
+     */
+    int getNamingConvention();
+
+    /**
+     * See {@link Configuration#getWhitespaceStripping()}.
+     */
+    boolean getWhitespaceStripping();
+
+    /**
+     * Overlaps with {@link Configurable#getArithmeticEngine()}; the parser needs this for creating numerical literals.
+     */
+    ArithmeticEngine getArithmeticEngine();
+    
+    /**
+     * See {@link Configuration#getAutoEscapingPolicy()}.
+     */
+    int getAutoEscapingPolicy();
+    
+    /**
+     * See {@link Configuration#getOutputEncoding()}.
+     */
+    OutputFormat getOutputFormat();
+    
+    /**
+     * See {@link Configuration#getRecognizeStandardFileExtensions()}.
+     */
+    boolean getRecognizeStandardFileExtensions();
+    
+    /**
+     * See {@link Configuration#getIncompatibleImprovements()}.
+     */
+    Version getIncompatibleImprovements();
+    
+    /**
+     * See {@link Configuration#getTabSize()}.
+     * 
+     * @since 2.3.25
+     */
+    int getTabSize();
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/ParsingNotSupportedException.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/ParsingNotSupportedException.java b/src/main/java/org/apache/freemarker/core/ParsingNotSupportedException.java
new file mode 100644
index 0000000..bac087a
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/ParsingNotSupportedException.java
@@ -0,0 +1,37 @@
+/*
+ * 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;
+
+/**
+ * Thrown when the {@link TemplateValueFormat} doesn't support parsing, and parsing was invoked.
+ * 
+ * @since 2.3.24
+ */
+public class ParsingNotSupportedException extends TemplateValueFormatException {
+
+    public ParsingNotSupportedException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public ParsingNotSupportedException(String message) {
+        this(message, null);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/PlainTextOutputFormat.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/PlainTextOutputFormat.java b/src/main/java/org/apache/freemarker/core/PlainTextOutputFormat.java
new file mode 100644
index 0000000..3a58c56
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/PlainTextOutputFormat.java
@@ -0,0 +1,56 @@
+/*
+ * 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;
+
+/**
+ * Represents the plain text output format (MIME type "text/plain", name "plainText"). This format doesn't support
+ * escaping. This format doesn't allow mixing in template output values of other output formats.
+ * 
+ * <p>
+ * The main difference from {@link UndefinedOutputFormat} is that this format doesn't allow inserting values of another
+ * output format into itself (unless they can be converted to plain text), while {@link UndefinedOutputFormat} would
+ * just insert the foreign "markup" as is. Also, this format has {"text/plain"} MIME type, while
+ * {@link UndefinedOutputFormat} has {@code null}.
+ * 
+ * @since 2.3.24
+ */
+public final class PlainTextOutputFormat extends OutputFormat {
+
+    public static final PlainTextOutputFormat INSTANCE = new PlainTextOutputFormat();
+    
+    private PlainTextOutputFormat() {
+        // Only to decrease visibility
+    }
+
+    @Override
+    public boolean isOutputFormatMixingAllowed() {
+        return false;
+    }
+
+    @Override
+    public String getName() {
+        return "plainText";
+    }
+
+    @Override
+    public String getMimeType() {
+        return "text/plain";
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/RTFOutputFormat.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/RTFOutputFormat.java b/src/main/java/org/apache/freemarker/core/RTFOutputFormat.java
new file mode 100644
index 0000000..3c00eef
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/RTFOutputFormat.java
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.freemarker.core;
+
+import java.io.IOException;
+import java.io.Writer;
+
+import org.apache.freemarker.core.model.TemplateModelException;
+import org.apache.freemarker.core.util._StringUtil;
+
+/**
+ * Represents the Rich Text Format output format (MIME type "application/rtf", name "RTF"). This format escapes by
+ * default (via {@link _StringUtil#RTFEnc(String)}). The {@code ?rtf} built-in silently bypasses template output values
+ * of the type produced by this output format ({@link TemplateRTFOutputModel}).
+ * 
+ * @since 2.3.24
+ */
+public final class RTFOutputFormat extends CommonMarkupOutputFormat<TemplateRTFOutputModel> {
+
+    /**
+     * The only instance (singleton) of this {@link OutputFormat}.
+     */
+    public static final RTFOutputFormat INSTANCE = new RTFOutputFormat();
+    
+    private RTFOutputFormat() {
+        // Only to decrease visibility
+    }
+    
+    @Override
+    public String getName() {
+        return "RTF";
+    }
+
+    @Override
+    public String getMimeType() {
+        return "application/rtf";
+    }
+
+    @Override
+    public void output(String textToEsc, Writer out) throws IOException, TemplateModelException {
+        _StringUtil.RTFEnc(textToEsc, out);
+    }
+
+    @Override
+    public String escapePlainText(String plainTextContent) {
+        return _StringUtil.RTFEnc(plainTextContent);
+    }
+
+    @Override
+    public boolean isLegacyBuiltInBypassed(String builtInName) {
+        return builtInName.equals("rtf");
+    }
+
+    @Override
+    protected TemplateRTFOutputModel newTemplateMarkupOutputModel(String plainTextContent, String markupContent) {
+        return new TemplateRTFOutputModel(plainTextContent, markupContent);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/RangeModel.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/RangeModel.java b/src/main/java/org/apache/freemarker/core/RangeModel.java
new file mode 100644
index 0000000..d0e2a48
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/RangeModel.java
@@ -0,0 +1,59 @@
+/*
+ * 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.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateModelException;
+import org.apache.freemarker.core.model.TemplateSequenceModel;
+import org.apache.freemarker.core.model.impl.SimpleNumber;
+
+abstract class RangeModel implements TemplateSequenceModel, java.io.Serializable {
+    
+    private final int begin;
+
+    public RangeModel(int begin) {
+        this.begin = begin;
+    }
+
+    final int getBegining() {
+        return begin;
+    }
+    
+    @Override
+    final public TemplateModel get(int index) throws TemplateModelException {
+        if (index < 0 || index >= size()) {
+            throw new _TemplateModelException("Range item index ", Integer.valueOf(index), " is out of bounds.");
+        }
+        long value = begin + getStep() * (long) index;
+        return value <= Integer.MAX_VALUE ? new SimpleNumber((int) value) : new SimpleNumber(value);
+    }
+    
+    /**
+     * @return {@code 1} or {@code -1}; other return values need not be properly handled until FTL supports other steps.
+     */
+    abstract int getStep();
+    
+    abstract boolean isRightUnbounded();
+    
+    abstract boolean isRightAdaptive();
+    
+    abstract boolean isAffactedByStringSlicingBug();
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/RegexpHelper.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/RegexpHelper.java b/src/main/java/org/apache/freemarker/core/RegexpHelper.java
new file mode 100644
index 0000000..1c865a3
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/RegexpHelper.java
@@ -0,0 +1,207 @@
+/*
+ * 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.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+
+import org.apache.freemarker.core.model.TemplateModelException;
+import org.apache.freemarker.core.templateresolver.impl.MruCacheStorage;
+import org.apache.freemarker.core.util._StringUtil;
+import org.slf4j.Logger;
+
+/**
+ * Helper for language features (like built-ins) that use regular expressions. 
+ */
+final class RegexpHelper {
+
+    private static final Logger LOG = _CoreLogs.RUNTIME;
+    
+    private static volatile boolean flagWarningsEnabled = LOG.isWarnEnabled();
+    private static final int MAX_FLAG_WARNINGS_LOGGED = 25;
+    private static final Object flagWarningsCntSync = new Object();
+    private static int flagWarningsCnt;
+    
+    private static final MruCacheStorage patternCache = new MruCacheStorage(50, 150);
+
+    static private long intFlagToLong(int flag) {
+        return flag & 0x0000FFFFL;
+    }
+
+    // Standard regular expression flags converted to long:
+    static final long RE_FLAG_CASE_INSENSITIVE = intFlagToLong(Pattern.CASE_INSENSITIVE);
+
+    static final long RE_FLAG_MULTILINE = intFlagToLong(Pattern.MULTILINE);
+
+    static final long RE_FLAG_COMMENTS = intFlagToLong(Pattern.COMMENTS);
+
+    static final long RE_FLAG_DOTALL = intFlagToLong(Pattern.DOTALL);
+
+    // FreeMarker-specific regular expression flags (using the higher 32 bits):
+    static final long RE_FLAG_REGEXP = 0x100000000L;
+
+    static final long RE_FLAG_FIRST_ONLY = 0x200000000L;
+    
+    // Can't be instantiated
+    private RegexpHelper() { }
+
+    static Pattern getPattern(String patternString, int flags)
+    throws TemplateModelException {
+        PatternCacheKey patternKey = new PatternCacheKey(patternString, flags);
+        
+        Pattern result;
+        
+        synchronized (patternCache) {
+            result = (Pattern) patternCache.get(patternKey);
+        }
+        if (result != null) {
+            return result;
+        }
+        
+        try {
+            result = Pattern.compile(patternString, flags);
+        } catch (PatternSyntaxException e) {
+            throw new _TemplateModelException(e,
+                    "Malformed regular expression: ", new _DelayedGetMessage(e));
+        }
+        synchronized (patternCache) {
+            patternCache.put(patternKey, result);
+        }
+        return result;
+    };
+
+    private static class PatternCacheKey {
+        private final String patternString;
+        private final int flags;
+        private final int hashCode;
+        
+        public PatternCacheKey(String patternString, int flags) {
+            this.patternString = patternString;
+            this.flags = flags;
+            hashCode = patternString.hashCode() + 31 * flags;
+        }
+        
+        @Override
+        public boolean equals(Object that) {
+            if (that instanceof PatternCacheKey) {
+                PatternCacheKey thatPCK = (PatternCacheKey) that; 
+                return thatPCK.flags == flags
+                        && thatPCK.patternString.equals(patternString);
+            } else {
+                return false;
+            }
+        }
+
+        @Override
+        public int hashCode() {
+            return hashCode;
+        }
+        
+    }
+
+    static long parseFlagString(String flagString) {
+        long flags = 0;
+        for (int i = 0; i < flagString.length(); i++) {
+            char c = flagString.charAt(i);
+            switch (c) {
+                case 'i':
+                    flags |= RE_FLAG_CASE_INSENSITIVE;
+                    break;
+                case 'm':
+                    flags |= RE_FLAG_MULTILINE;
+                    break;
+                case 'c':
+                    flags |= RE_FLAG_COMMENTS;
+                    break;
+                case 's':
+                    flags |= RE_FLAG_DOTALL;
+                    break;
+                case 'r':
+                    flags |= RE_FLAG_REGEXP;
+                    break;
+                case 'f':
+                    flags |= RE_FLAG_FIRST_ONLY;
+                    break;
+                default:
+                    if (flagWarningsEnabled) {
+                        // [FM3] Should be an error
+                        RegexpHelper.logFlagWarning(
+                                "Unrecognized regular expression flag: "
+                                + _StringUtil.jQuote(String.valueOf(c)) + ".");
+                    }
+            }  // switch
+        }
+        return flags;
+    }
+
+    /**
+     * Logs flag warning for a limited number of times. This is used to prevent
+     * log flooding.
+     */
+    static void logFlagWarning(String message) {
+        if (!flagWarningsEnabled) return;
+        
+        int cnt;
+        synchronized (flagWarningsCntSync) {
+            cnt = flagWarningsCnt;
+            if (cnt < MAX_FLAG_WARNINGS_LOGGED) {
+                flagWarningsCnt++;
+            } else {
+                flagWarningsEnabled = false;
+                return;
+            }
+        }
+        message += " This will be an error in some later FreeMarker version!";
+        if (cnt + 1 == MAX_FLAG_WARNINGS_LOGGED) {
+            message += " [Will not log more regular expression flag problems until restart!]";
+        }
+        LOG.warn(message);
+    }
+
+    static void checkNonRegexpFlags(String biName, long flags) throws _TemplateModelException {
+        checkOnlyHasNonRegexpFlags(biName, flags, false);
+    }
+    
+    static void checkOnlyHasNonRegexpFlags(String biName, long flags, boolean strict)
+            throws _TemplateModelException {
+        if (!strict && !flagWarningsEnabled) return;
+        
+        String flag; 
+        if ((flags & RE_FLAG_MULTILINE) != 0) {
+            flag = "m";
+        } else if ((flags & RE_FLAG_DOTALL) != 0) {
+            flag = "s";
+        } else if ((flags & RE_FLAG_COMMENTS) != 0) {
+            flag = "c";
+        } else {
+            return;
+        }
+
+        final Object[] msg = { "?", biName ," doesn't support the \"", flag, "\" flag "
+                + "without the \"r\" flag." };
+        if (strict) {
+            throw new _TemplateModelException(msg);
+        } else {
+            // Suppress error for backward compatibility
+            logFlagWarning(new _ErrorDescriptionBuilder(msg).toString());
+        }
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/RightUnboundedRangeModel.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/RightUnboundedRangeModel.java b/src/main/java/org/apache/freemarker/core/RightUnboundedRangeModel.java
new file mode 100644
index 0000000..c856cbc
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/RightUnboundedRangeModel.java
@@ -0,0 +1,48 @@
+/*
+ * 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;
+
+abstract class RightUnboundedRangeModel extends RangeModel {
+    
+    RightUnboundedRangeModel(int begin) {
+        super(begin);
+    }
+
+    @Override
+    final int getStep() {
+        return 1;
+    }
+
+    @Override
+    final boolean isRightUnbounded() {
+        return true;
+    }
+    
+    @Override
+    final boolean isRightAdaptive() {
+        return true;
+    }
+
+    @Override
+    final boolean isAffactedByStringSlicingBug() {
+        return false;
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/SpecialBuiltIn.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/SpecialBuiltIn.java b/src/main/java/org/apache/freemarker/core/SpecialBuiltIn.java
new file mode 100644
index 0000000..7e839a2
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/SpecialBuiltIn.java
@@ -0,0 +1,27 @@
+/*
+ * 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;
+
+
+/**
+ * Marker class for built-ins that has special treatment during parsing.
+ */
+abstract class SpecialBuiltIn extends ASTExpBuiltIn {
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/StopException.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/StopException.java b/src/main/java/org/apache/freemarker/core/StopException.java
new file mode 100644
index 0000000..dd9bc41
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/StopException.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.PrintStream;
+import java.io.PrintWriter;
+
+/**
+ * This exception is thrown when a <tt>#stop</tt> directive is encountered. 
+ */
+public class StopException extends TemplateException {
+    
+    StopException(Environment env) {
+        super(env);
+    }
+
+    StopException(Environment env, String s) {
+        super(s, env);
+    }
+
+    @Override
+    public void printStackTrace(PrintWriter pw) {
+        synchronized (pw) {
+            String msg = getMessage();
+            pw.print("Encountered stop instruction");
+            if (msg != null && !msg.equals("")) {
+                pw.println("\nCause given: " + msg);
+            } else pw.println();
+            super.printStackTrace(pw);
+        }
+    }
+
+    @Override
+    public void printStackTrace(PrintStream ps) {
+        synchronized (ps) {
+            String msg = getMessage();
+            ps.print("Encountered stop instruction");
+            if (msg != null && !msg.equals("")) {
+                ps.println("\nCause given: " + msg);
+            } else ps.println();
+            super.printStackTrace(ps);
+        }
+    }
+    
+}
+
+

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/Template.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/Template.java b/src/main/java/org/apache/freemarker/core/Template.java
index 113f9ea..48996ac 100644
--- a/src/main/java/org/apache/freemarker/core/Template.java
+++ b/src/main/java/org/apache/freemarker/core/Template.java
@@ -36,21 +36,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.Vector;
 
-import org.apache.freemarker.core.ast.BugException;
-import org.apache.freemarker.core.ast.Configurable;
-import org.apache.freemarker.core.ast.Environment;
-import org.apache.freemarker.core.ast.FMParser;
-import org.apache.freemarker.core.ast.LibraryLoad;
-import org.apache.freemarker.core.ast.Macro;
-import org.apache.freemarker.core.ast.OutputFormat;
-import org.apache.freemarker.core.ast.ParseException;
-import org.apache.freemarker.core.ast.ParserConfiguration;
-import org.apache.freemarker.core.ast.TemplateConfiguration;
-import org.apache.freemarker.core.ast.TemplateElement;
-import org.apache.freemarker.core.ast.TemplateSpecifiedEncodingHandler;
-import org.apache.freemarker.core.ast.TextBlock;
-import org.apache.freemarker.core.ast.TokenMgrError;
-import org.apache.freemarker.core.ast._CoreAPI;
+import org.apache.freemarker.core.FMParser;
 import org.apache.freemarker.core.debug.impl.DebuggerService;
 import org.apache.freemarker.core.model.ObjectWrapper;
 import org.apache.freemarker.core.model.TemplateHashModel;
@@ -60,6 +46,7 @@ import org.apache.freemarker.core.model.impl.SimpleHash;
 import org.apache.freemarker.core.templateresolver.TemplateLoader;
 import org.apache.freemarker.core.templateresolver.TemplateLookupStrategy;
 import org.apache.freemarker.core.templateresolver.impl.DefaultTemplateResolver;
+import org.apache.freemarker.core.util.BugException;
 import org.apache.freemarker.core.util._NullArgumentException;
 
 /**
@@ -94,7 +81,7 @@ public class Template extends Configurable {
 
     private Map macros = new HashMap();
     private List imports = new Vector();
-    private TemplateElement rootElement;
+    private _ASTElement rootElement;
     private String encoding, defaultNS;
     private Object customLookupCondition;
     private int actualTagSyntax;
@@ -331,7 +318,7 @@ public class Template extends Configurable {
         } catch (IOException e) {
             throw new BugException("Plain text template creation failed", e);
         }
-        _CoreAPI.replaceText((TextBlock) template.rootElement, content);
+        _CoreAPI.replaceText((ASTStaticText) template.rootElement, content);
         DebuggerService.registerTemplate(template);
         return template;
     }
@@ -402,7 +389,7 @@ public class Template extends Configurable {
     }
     
    /**
-    * Creates a {@link org.apache.freemarker.core.ast.Environment Environment} object, using this template, the data-model provided as
+    * Creates a {@link org.apache.freemarker.core.Environment Environment} object, using this template, the data-model provided as
     * parameter. You have to call {@link Environment#process()} on the return value to set off the actual rendering.
     * 
     * <p>Use this method if you want to do some special initialization on the {@link Environment} before template
@@ -708,7 +695,7 @@ public class Template extends Configurable {
      * @deprecated Should only be used internally, and might will be removed later.
      */
     @Deprecated
-    public void addMacro(Macro macro) {
+    public void addMacro(ASTDirMacro macro) {
         macros.put(macro.getName(), macro);
     }
 
@@ -718,7 +705,7 @@ public class Template extends Configurable {
      * @deprecated Should only be used internally, and might will be removed later.
      */
     @Deprecated
-    public void addImport(LibraryLoad ll) {
+    public void addImport(ASTDirImport ll) {
         imports.add(ll);
     }
 
@@ -734,7 +721,7 @@ public class Template extends Configurable {
      * @param endColumn the last column of the requested source, 1-based
      * @param endLine the last line of the requested source, 1-based
      * 
-     * @see org.apache.freemarker.core.ast.TemplateObject#getSource()
+     * @see org.apache.freemarker.core.ASTNode#getSource()
      */
     public String getSource(int beginColumn,
                             int beginLine,
@@ -874,7 +861,7 @@ public class Template extends Configurable {
      * @deprecated Should only be used internally, and might will be removed later.
      */
     @Deprecated
-    public TemplateElement getRootTreeNode() {
+    public _ASTElement getRootTreeNode() {
         return rootElement;
     }
     
@@ -978,17 +965,17 @@ public class Template extends Configurable {
     }
     
     /**
-     * @return an array of the {@link TemplateElement}s containing the given column and line numbers.
+     * @return an array of the {@link _ASTElement}s containing the given column and line numbers.
      * @deprecated Should only be used internally, and might will be removed later.
      */
     @Deprecated
     public List containingElements(int column, int line) {
         final ArrayList elements = new ArrayList();
-        TemplateElement element = rootElement;
+        _ASTElement element = rootElement;
         mainloop: while (element.contains(column, line)) {
             elements.add(element);
             for (Enumeration enumeration = element.children(); enumeration.hasMoreElements(); ) {
-                TemplateElement elem = (TemplateElement) enumeration.nextElement();
+                _ASTElement elem = (_ASTElement) enumeration.nextElement();
                 if (elem.contains(column, line)) {
                     element = elem;
                     continue mainloop;



Mime
View raw message