freemarker-notifications mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ddek...@apache.org
Subject [33/51] [partial] incubator-freemarker git commit: Migrated from Ant to Gradle, and modularized the project. This is an incomplete migration; there are some TODO-s in the build scripts, and release related tasks are still missing. What works: Building th
Date Sun, 14 May 2017 10:53:16 GMT
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/ParseException.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ParseException.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ParseException.java
new file mode 100644
index 0000000..9e5dad3
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ParseException.java
@@ -0,0 +1,518 @@
+/*
+ * 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.util._NullArgumentException;
+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 templateSourceName;
+    private String templateLookupName;
+
+    /**
+     * 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: <result of getMessage>
+     */
+    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) {
+        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;
+        if (template != null) { // Allowed because sometimes the template is set later via setTemplate(Template)
+            templateSourceName = template.getSourceName();
+            templateLookupName = template.getLookupName();
+        }
+        this.lineNumber = lineNumber;
+        this.columnNumber = columnNumber;
+        this.endLineNumber = endLineNumber;
+        this.endColumnNumber = endColumnNumber;
+    }
+    
+    /**
+     * @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,
+                tk.beginLine, tk.beginColumn,
+                tk.endLine, tk.endColumn,
+                cause);
+    }
+
+    /**
+     * @since 2.3.20
+     */
+    public ParseException(String description, ASTNode astNode) {
+        this(description, astNode, null);
+    }
+
+    /**
+     * @since 2.3.20
+     */
+    public ParseException(String description, ASTNode astNode, Throwable cause) {
+        this(description,
+                astNode.getTemplate(),
+                astNode.beginLine, astNode.beginColumn,
+                astNode.endLine, astNode.endColumn,
+                cause);
+    }
+
+    /**
+     * 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 setTemplate(Template template) {
+        _NullArgumentException.check("template", template);
+        templateSourceName = template.getSourceName();
+        templateLookupName = template.getLookupName();
+        synchronized (this) {
+            messageAndDescriptionRendered = false;
+            message = null;
+        }
+    }
+
+    /**
+     * Returns the error location plus the error description.
+     * 
+     * @see #getDescription()
+     * @see #getTemplateSourceName()
+     * @see #getTemplateLookupName()
+     * @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 {@linkplain Template#getLookupName()} lookup name} of the template whose parsing was failed.
+     * Maybe {@code null}, for example if this is a non-stored template.
+     */
+    public String getTemplateLookupName() {
+        return templateLookupName;
+    }
+
+    /**
+     * Returns the {@linkplain Template#getSourceName()} source name} of the template whose parsing was failed.
+     * Maybe {@code null}, for example if this is a non-stored template.
+     */
+    public String getTemplateSourceName() {
+        return templateSourceName;
+    }
+
+    /**
+     * Returns the {@linkplain #getTemplateSourceName() template source name}, or if that's {@code null} then the
+     * {@linkplain #getTemplateLookupName() template lookup name}. This name is primarily meant to be used in error
+     * messages.
+     */
+    public String getTemplateSourceOrLookupName() {
+        return getTemplateSourceName() != null ? getTemplateSourceName() : getTemplateLookupName();
+    }
+
+    /**
+     * 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(getTemplateSourceOrLookupName(), 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_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_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/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/ParsingAndProcessingConfiguration.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ParsingAndProcessingConfiguration.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ParsingAndProcessingConfiguration.java
new file mode 100644
index 0000000..719af93
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ParsingAndProcessingConfiguration.java
@@ -0,0 +1,29 @@
+/*
+ * 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 the union of
+ * {@link ProcessingConfiguration} and {@link ParsingConfiguration}, which is useful for declaring types for values
+ * that must implement both interfaces.
+ */
+public interface ParsingAndProcessingConfiguration extends ParsingConfiguration, ProcessingConfiguration {
+    // No additional method
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/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
new file mode 100644
index 0000000..0eb9569
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ParsingConfiguration.java
@@ -0,0 +1,299 @@
+/*
+ * 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.MarkupOutputFormat;
+import org.apache.freemarker.core.outputformat.OutputFormat;
+import org.apache.freemarker.core.outputformat.impl.HTMLOutputFormat;
+import org.apache.freemarker.core.outputformat.impl.UndefinedOutputFormat;
+import org.apache.freemarker.core.outputformat.impl.XMLOutputFormat;
+
+/**
+ * Implemented by FreeMarker core classes (not by you) that provide configuration settings that affect template parsing
+ * (as opposed to {@linkplain Template#process (Object, Writer) template processing}). <b>New methods may be added
+ * any time in future FreeMarker versions, so don't try to implement this interface yourself!</b>
+ *
+ * @see ProcessingConfiguration
+ * @see ParsingAndProcessingConfiguration
+ */
+public interface ParsingConfiguration {
+
+    int AUTO_DETECT_NAMING_CONVENTION = 10;
+    int LEGACY_NAMING_CONVENTION = 11;
+    int CAMEL_CASE_NAMING_CONVENTION = 12;
+
+    int AUTO_DETECT_TAG_SYNTAX = 0;
+    int ANGLE_BRACKET_TAG_SYNTAX = 1;
+    int SQUARE_BRACKET_TAG_SYNTAX = 2;
+
+    /**
+     * Don't enable auto-escaping, regardless of what the {@link OutputFormat} is. Note that a {@code
+     * <#ftl auto_esc=true>} in the template will override this.
+     */
+    int DISABLE_AUTO_ESCAPING_POLICY = 20;
+    /**
+     * Enable auto-escaping if the output format supports it and {@link MarkupOutputFormat#isAutoEscapedByDefault()} is
+     * {@code true}.
+     */
+    int ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY = 21;
+    /** Enable auto-escaping if the {@link OutputFormat} supports it. */
+    int ENABLE_IF_SUPPORTED_AUTO_ESCAPING_POLICY = 22;
+
+    /**
+     * The template language used; this is often overridden for certain file extension with the
+     * {@link Configuration#getTemplateConfigurations() templateConfigurations} setting of the {@link Configuration}.
+     */
+    TemplateLanguage getTemplateLanguage();
+
+    boolean isTemplateLanguageSet();
+
+    /**
+     * Determines the syntax of the template files (angle bracket VS square bracket)
+     * that has no {@code #ftl} in it. The {@code tagSyntax}
+     * parameter must be one of:
+     * <ul>
+     *   <li>{@link #AUTO_DETECT_TAG_SYNTAX}:
+     *     use the syntax of the first FreeMarker tag (can be anything, like <tt>#list</tt>,
+     *     <tt>#include</tt>, user defined, etc.)
+     *   <li>{@link #ANGLE_BRACKET_TAG_SYNTAX}:
+     *     use the angle bracket syntax (the normal syntax)
+     *   <li>{@link #SQUARE_BRACKET_TAG_SYNTAX}:
+     *     use the square bracket syntax
+     * </ul>
+     *
+     * <p>In FreeMarker 2.3.x {@link #ANGLE_BRACKET_TAG_SYNTAX} is the
+     * default for better backward compatibility. Starting from 2.4.x {@link
+     * ParsingConfiguration#AUTO_DETECT_TAG_SYNTAX} is the default, so it's recommended to use
+     * that even for 2.3.x.
+     *
+     * <p>This setting is ignored for the templates that have {@code ftl} directive in
+     * it. For those templates the syntax used for the {@code ftl} directive determines
+     * the syntax.
+     */
+    int getTagSyntax();
+
+    /**
+     * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
+     * the setting might returns a default value, or returns the value of the setting from a parent parsing
+     * configuration or throws a {@link SettingValueNotSetException}.
+     */
+    boolean isTagSyntaxSet();
+
+    /**
+     * The naming convention used for the identifiers that are part of the template language. The available naming
+     * conventions are legacy (directive (tag) names are all-lower-case {@code likethis}, others are snake case
+     * {@code like_this}), and camel case ({@code likeThis}). The default is auto-detect, which detects the naming
+     * convention used and enforces that same naming convention for the whole template.
+     *
+     * <p>
+     * This setting doesn't influence what naming convention is used for the setting names outside templates. Also, it
+     * won't ever convert the names of user-defined things, like of data-model members, or the names of user defined
+     * macros/functions. It only influences the names of the built-in directives ({@code #elseIf} VS {@code elseif}),
+     * built-ins ({@code ?upper_case} VS {@code ?upperCase} ), special variables ({@code .data_model} VS
+     * {@code .dataModel}).
+     *
+     * <p>
+     * Which convention to use: FreeMarker prior to 2.3.23 has only supported
+     * {@link #LEGACY_NAMING_CONVENTION}, so that's how most templates and examples out there are written
+     * as of 2015. But as templates today are mostly written by programmers and often access Java API-s which already
+     * use camel case, {@link #CAMEL_CASE_NAMING_CONVENTION} is the recommended option for most projects.
+     * However, it's no necessary to make a application-wide decision; see auto-detection below.
+     *
+     * <p>
+     * FreeMarker will decide the naming convention automatically for each template individually when this setting is
+     * set to {@link #AUTO_DETECT_NAMING_CONVENTION} (which is the default). The naming convention of a template is
+     * decided when the first core (non-user-defined) identifier is met during parsing (not during processing) where the
+     * naming convention is relevant (like for {@code s?upperCase} or {@code s?upper_case} it's relevant, but for
+     * {@code s?length} it isn't). At that point, the naming convention of the template is decided, and any later core
+     * identifier that uses a different convention will be a parsing error. As the naming convention is decided per
+     * template, it's not a problem if a template and the other template it {@code #include}-s/{@code #import} uses a
+     * different convention.
+     *
+     * <p>
+     * FreeMarker always enforces the same naming convention to be used consistently within the same template "file".
+     * Additionally, when this setting is set to non-{@link #AUTO_DETECT_NAMING_CONVENTION}, the selected naming
+     * convention is enforced on all templates. Thus such a setup can be used to enforce an application-wide naming
+     * convention.
+     *
+     * @return
+     *            One of the {@link #AUTO_DETECT_NAMING_CONVENTION} or
+     *            {@link #LEGACY_NAMING_CONVENTION} or {@link #CAMEL_CASE_NAMING_CONVENTION}.
+     */
+    int getNamingConvention();
+
+    /**
+     * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
+     * the setting might returns a default value, or returns the value of the setting from a parent parsing
+     * configuration or throws a {@link SettingValueNotSetException}.
+     */
+    boolean isNamingConventionSet();
+
+    /**
+     * Whether the template parser will try to remove superfluous white-space around certain tags.
+     */
+    boolean getWhitespaceStripping();
+
+    /**
+     * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
+     * the setting might returns a default value, or returns the value of the setting from a parent parsing
+     * configuration or throws a {@link SettingValueNotSetException}.
+     */
+    boolean isWhitespaceStrippingSet();
+
+    /**
+     * Overlaps with {@link ProcessingConfiguration#getArithmeticEngine()}; the parser needs this for creating numerical
+     * literals.
+     */
+    ArithmeticEngine getArithmeticEngine();
+
+    /**
+     * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
+     * the setting might returns a default value, or returns the value of the setting from a parent parsing
+     * configuration or throws a {@link SettingValueNotSetException}.
+     */
+    boolean isArithmeticEngineSet();
+
+    /**
+     * See {@link Configuration#getAutoEscapingPolicy()}.
+     */
+    int getAutoEscapingPolicy();
+
+    /**
+     * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
+     * the setting might returns a default value, or returns the value of the setting from a parent parsing
+     * configuration or throws a {@link SettingValueNotSetException}.
+     */
+    boolean isAutoEscapingPolicySet();
+
+    /**
+     * The output format to use, which among others influences auto-escaping (see {@link #getAutoEscapingPolicy}
+     * autoEscapingPolicy}), and possibly the MIME type of the output.
+     * <p>
+     * On the {@link Configuration} level, usually, you should leave this on its default, which is
+     * {@link UndefinedOutputFormat#INSTANCE}, and then use standard file extensions like "ftlh" (for HTML) or "ftlx"
+     * (for XML) (and ensure that {@link #getRecognizeStandardFileExtensions() recognizeStandardFileExtensions} is
+     * {@code true}; see more there). Where you can't use the standard extensions, templates still can be associated
+     * to output formats with patterns matching their name (their path) using the
+     * {@link Configuration#getTemplateConfigurations() templateConfigurations} setting of the {@link Configuration}.
+     * But if all templates will have the same output format, you may set the
+     * {@link #getOutputFormat() outputFormat} setting of the {@link Configuration}
+     * after all, to a value like {@link HTMLOutputFormat#INSTANCE}, {@link XMLOutputFormat#INSTANCE}, etc. Also
+     * note that templates can specify their own output format like {@code <#ftl output_format="HTML">}, which
+     * overrides any configuration settings.
+     *
+     * @see Configuration#getRegisteredCustomOutputFormats()
+     * @see Configuration#getTemplateConfigurations()
+     * @see #getRecognizeStandardFileExtensions()
+     * @see #getAutoEscapingPolicy()
+     */
+    OutputFormat getOutputFormat();
+
+    /**
+     * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
+     * the setting might returns a default value, or returns the value of the setting from a parent parsing
+     * configuration or throws a {@link SettingValueNotSetException}.
+     */
+    boolean isOutputFormatSet();
+
+    /**
+     * Tells if the "file" extension part of the source name ({@link Template#getSourceName()}) will influence certain
+     * parsing settings. For backward compatibility, it defaults to {@code false} if
+     * {@link #getIncompatibleImprovements()} is less than 2.3.24. Starting from {@code incompatibleImprovements}
+     * 2.3.24, it defaults to {@code true}, so the following standard file extensions take their effect:
+     *
+     * <ul>
+     *   <li>{@code ftlh}: Sets the {@link #getOutputFormat() outputFormat} setting to {@code "HTML"}
+     *       (i.e., {@link HTMLOutputFormat#INSTANCE}, unless the {@code "HTML"} name is overridden by
+     *       the {@link Configuration#getRegisteredCustomOutputFormats registeredOutputFormats} setting) and
+     *       the {@link #getAutoEscapingPolicy() autoEscapingPolicy} setting to
+     *       {@link #ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY}.
+     *   <li>{@code ftlx}: Sets the {@link #getOutputFormat() outputFormat} setting to
+     *       {@code "XML"} (i.e., {@link XMLOutputFormat#INSTANCE}, unless the {@code "XML"} name is overridden by
+     *       the {@link Configuration#getRegisteredCustomOutputFormats registeredOutputFormats} setting) and
+     *       the {@link #getAutoEscapingPolicy() autoEscapingPolicy} setting to
+     *       {@link #ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY}.
+     * </ul>
+     *
+     * <p>These file extensions are not case sensitive. The file extension is the part after the last dot in the source
+     * name. If the source name contains no dot, then it has no file extension.
+     *
+     * <p>The settings activated by these file extensions override the setting values dictated by the
+     * {@link Configuration#getTemplateConfigurations templateConfigurations} setting of the {@link Configuration}.
+     */
+    boolean getRecognizeStandardFileExtensions();
+
+    /**
+     * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
+     * the setting might returns a default value, or returns the value of the setting from a parent parsing
+     * configuration or throws a {@link SettingValueNotSetException}.
+     */
+    boolean isRecognizeStandardFileExtensionsSet();
+
+    /**
+     * See {@link TopLevelConfiguration#getIncompatibleImprovements()}; this is normally directly delegates to
+     * {@link Configuration#getIncompatibleImprovements()}, and it's always set.
+     */
+    Version getIncompatibleImprovements();
+
+    /**
+     * The assumed display width of the tab character (ASCII 9), which influences the column number shown in error
+     * messages (or the column number you get through other API-s). So for example if the users edit templates in an
+     * editor where the tab width is set to 4, you should set this to 4 so that the column numbers printed by FreeMarker
+     * will match the column number shown in the editor. This setting doesn't affect the output of templates, as a tab
+     * in the template will remain a tab in the output too.
+     * It's value is at least 1, at most 256.
+     */
+    int getTabSize();
+
+    /**
+     * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
+     * the setting might returns a default value, or returns the value of the setting from a parent parsing
+     * configuration or throws a {@link SettingValueNotSetException}.
+     */
+    boolean isTabSizeSet();
+
+    /**
+     * Sets the charset used for decoding template files.
+     * <p>
+     * Defaults to the default system {@code fileEncoding}, which can change from one server to
+     * another, so <b>you should always set this setting</b>. If you don't know what charset your should chose,
+     * {@code "UTF-8"} is usually a good choice.
+     * <p>
+     * When a project contains groups (like folders) of template files where the groups use different encodings,
+     * consider using the {@link Configuration#getTemplateConfigurations() templateConfigurations} setting on the
+     * {@link Configuration} level.
+     * <p>
+     * Individual templates may specify their own charset by starting with
+     * <tt>&lt;#ftl sourceEncoding="..."&gt;</tt>. However, before that's detected, at least part of template must be
+     * decoded with some charset first, so this setting (and
+     * {@link Configuration#getTemplateConfigurations() templateConfigurations}) still have role.
+     */
+    Charset getSourceEncoding();
+
+    /**
+     * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
+     * the setting might returns a default value, or returns the value of the setting from a parent parsing
+     * configuration or throws a {@link SettingValueNotSetException}.
+     */
+    boolean isSourceEncodingSet();
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/ProcessingConfiguration.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ProcessingConfiguration.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ProcessingConfiguration.java
new file mode 100644
index 0000000..545a313
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ProcessingConfiguration.java
@@ -0,0 +1,704 @@
+/*
+ * 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.Writer;
+import java.nio.charset.Charset;
+import java.text.NumberFormat;
+import java.text.SimpleDateFormat;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.TimeZone;
+
+import org.apache.freemarker.core.arithmetic.ArithmeticEngine;
+import org.apache.freemarker.core.arithmetic.impl.BigDecimalArithmeticEngine;
+import org.apache.freemarker.core.model.ObjectWrapper;
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.impl.DefaultObjectWrapper;
+import org.apache.freemarker.core.valueformat.TemplateDateFormatFactory;
+import org.apache.freemarker.core.valueformat.TemplateNumberFormat;
+import org.apache.freemarker.core.valueformat.TemplateNumberFormatFactory;
+
+/**
+ * Implemented by FreeMarker core classes (not by you) that provide configuration settings that affect {@linkplain
+ * Template#process(Object, Writer) template processing} (as opposed to template parsing). <b>New methods may be added
+ * any time in future FreeMarker versions, so don't try to implement this interface yourself!</b>
+ *
+ * @see ParsingConfiguration
+ * @see ParsingAndProcessingConfiguration
+ */
+public interface ProcessingConfiguration {
+
+    /**
+     * The locale used for number and date formatting (among others), also the locale used for searching localized
+     * template variations when no locale was explicitly specified where the template is requested.
+     *
+     * @see Configuration#getTemplate(String, Locale)
+     */
+    Locale getLocale();
+
+    /**
+     * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
+     * the setting mights returns a default value, or returns the value of the setting from a parent object, or throws
+     * an {@link SettingValueNotSetException}.
+     */
+    boolean isLocaleSet();
+
+    /**
+     * The time zone to use when formatting date/time values. It {@link Configuration}-level default
+     * is the system time zone ({@link TimeZone#getDefault()}), regardless of the "locale" FreeMarker setting,
+     * so in a server application you probably want to set it explicitly in the {@link Environment} to match the
+     * preferred time zone of the target audience (like the Web page visitor).
+     *
+     * <p>If you or the templates set the time zone, you should probably also set
+     * {@link #getSQLDateAndTimeTimeZone()}!
+     *
+     * @see #getSQLDateAndTimeTimeZone()
+     */
+    TimeZone getTimeZone();
+
+    /**
+     * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
+     * the setting mights returns a default value, or returns the value of the setting from a parent object, or throws
+     * an {@link SettingValueNotSetException}.
+     */
+    boolean isTimeZoneSet();
+
+    /**
+     * The time zone used when dealing with {@link java.sql.Date java.sql.Date} and
+     * {@link java.sql.Time java.sql.Time} values. Its {@link Configuration}-level defaults is {@code null} for
+     * backward compatibility, but in most applications this should be set to the JVM default time zone (server
+     * default time zone), because that's what most JDBC drivers will use when constructing the
+     * {@link java.sql.Date java.sql.Date} and {@link java.sql.Time java.sql.Time} values. If this setting is {@code
+     * null} FreeMarker will use the value of ({@link #getTimeZone()}) for {@link java.sql.Date java.sql.Date} and
+     * {@link java.sql.Time java.sql.Time} values, which often gives bad results.
+     *
+     * <p>This setting doesn't influence the formatting of other kind of values (like of
+     * {@link java.sql.Timestamp java.sql.Timestamp} or plain {@link java.util.Date java.util.Date} values).
+     *
+     * <p>To decide what value you need, a few things has to be understood:
+     * <ul>
+     *   <li>Date-only and time-only values in SQL-oriented databases are usually store calendar and clock field
+     *   values directly (year, month, day, or hour, minute, seconds (with decimals)), as opposed to a set of points
+     *   on the physical time line. Thus, unlike SQL timestamps, these values usually aren't meant to be shown
+     *   differently depending on the time zone of the audience.
+     *
+     *   <li>When a JDBC query has to return a date-only or time-only value, it has to convert it to a point on the
+     *   physical time line, because that's what {@link java.util.Date} and its subclasses store (milliseconds since
+     *   the epoch). Obviously, this is impossible to do. So JDBC just chooses a physical time which, when rendered
+     *   <em>with the JVM default time zone</em>, will give the same field values as those stored
+     *   in the database. (Actually, you can give JDBC a calendar, and so it can use other time zones too, but most
+     *   application won't care using those overloads.) For example, assume that the system time zone is GMT+02:00.
+     *   Then, 2014-07-12 in the database will be translated to physical time 2014-07-11 22:00:00 UTC, because that
+     *   rendered in GMT+02:00 gives 2014-07-12 00:00:00. Similarly, 11:57:00 in the database will be translated to
+     *   physical time 1970-01-01 09:57:00 UTC. Thus, the physical time stored in the returned value depends on the
+     *   default system time zone of the JDBC client, not just on the content of the database. (This used to be the
+     *   default behavior of ORM-s, like Hibernate, too.)
+     *
+     *   <li>The value of the {@code time_zone} FreeMarker configuration setting sets the time zone used for the
+     *   template output. For example, when a web page visitor has a preferred time zone, the web application framework
+     *   may calls {@link Environment#setTimeZone(TimeZone)} with that time zone. Thus, the visitor will
+     *   see {@link java.sql.Timestamp java.sql.Timestamp} and plain {@link java.util.Date java.util.Date} values as
+     *   they look in his own time zone. While
+     *   this is desirable for those types, as they meant to represent physical points on the time line, this is not
+     *   necessarily desirable for date-only and time-only values. When {@code sql_date_and_time_time_zone} is
+     *   {@code null}, {@code time_zone} is used for rendering all kind of date/time/dateTime values, including
+     *   {@link java.sql.Date java.sql.Date} and {@link java.sql.Time java.sql.Time}, and then if, for example,
+     *   {@code time_zone} is GMT+00:00, the
+     *   values from the earlier examples will be shown as 2014-07-11 (one day off) and 09:57:00 (2 hours off). While
+     *   those are the time zone correct renderings, those values are probably meant to be shown "as is".
+     *
+     *   <li>You may wonder why this setting isn't simply "SQL time zone", that is, why's this time zone not applied to
+     *   {@link java.sql.Timestamp java.sql.Timestamp} values as well. Timestamps in databases refer to a point on
+     *   the physical time line, and thus doesn't have the inherent problem of date-only and time-only values.
+     *   FreeMarker assumes that the JDBC driver converts time stamps coming from the database so that they store
+     *   the distance from the epoch (1970-01-01 00:00:00 UTC), as requested by the {@link java.util.Date} API.
+     *   Then time stamps can be safely rendered in different time zones, and thus need no special treatment.
+     * </ul>
+     *
+     * @see #getTimeZone()
+     */
+    TimeZone getSQLDateAndTimeTimeZone();
+
+    /**
+     * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
+     * the setting mights returns a default value, or returns the value of the setting from a parent object, or throws
+     * an {@link SettingValueNotSetException}.
+     */
+    boolean isSQLDateAndTimeTimeZoneSet();
+
+    /**
+     * The number format used to convert numbers to strings (where no number format is explicitly given). Its
+     * {@link Configuration}-level default is {@code "number"}. The possible values are:
+     * <ul>
+     *   <li>{@code "number"}: The number format returned by {@link NumberFormat#getNumberInstance(Locale)}</li>
+     *   <li>{@code "currency"}: The number format returned by {@link NumberFormat#getCurrencyInstance(Locale)}</li>
+     *   <li>{@code "percent"}: The number format returned by {@link NumberFormat#getPercentInstance(Locale)}</li>
+     *   <li>{@code "computer"}: The number format used by FTL's {@code c} built-in (like in {@code someNumber?c}).</li>
+     *   <li>A {@link java.text.DecimalFormat} pattern (like {@code "0.##"}). This syntax is extended by FreeMarker
+     *       so that you can specify options like the rounding mode and the symbols used after a 2nd semicolon. For
+     *       example, {@code ",000;; roundingMode=halfUp groupingSeparator=_"} will format numbers like {@code ",000"}
+     *       would, but with half-up rounding mode, and {@code _} as the group separator. See more about "extended Java
+     *       decimal format" in the FreeMarker Manual.
+     *       </li>
+     *   <li>If the string starts with {@code @} character followed by a letter then it's interpreted as a custom number
+     *       format. The format of a such string is <code>"@<i>name</i>"</code> or <code>"@<i>name</i>
+     *       <i>parameters</i>"</code>, where <code><i>name</i></code> is the key in the {@link Map} set by
+     *       {@link MutableProcessingConfiguration#setCustomNumberFormats(Map)}, and <code><i>parameters</i></code> is
+     *       parsed by the custom {@link TemplateNumberFormat}.
+     *   </li>
+     * </ul>
+     */
+    String getNumberFormat();
+
+    /**
+     * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
+     * the setting mights returns a default value, or returns the value of the setting from a parent object, or throws
+     * an {@link SettingValueNotSetException}.
+     */
+    boolean isNumberFormatSet();
+
+    /**
+     * A {@link Map} that associates {@link TemplateNumberFormatFactory}-es to names, which then can be referred by the
+     * {@link #getNumberFormat() number_format} setting with values starting with <code>@<i>name</i></code>. The keys in
+     * the {@link Map} should start with an UNICODE letter, and should only contain UNICODE letters and digits (not
+     * {@code _}), otherwise accessing the custom format from templates can be difficult or impossible. The
+     * {@link Configuration}-level default of this setting is an empty  {@link Map}.
+     * <p>
+     * When the {@link ProcessingConfiguration} is part of a setting inheritance chain ({@link Environment} inherits
+     * settings from the main {@link Template}, which inherits from the {@link Configuration}), you still only get the
+     * {@link Map} from the closest {@link ProcessingConfiguration} where it was set, not a {@link Map} that respects
+     * inheritance. Thus, to get a custom format you shouldn't use this {@link Map} directly, but
+     * {@link #getCustomNumberFormat(String)}, which will search the format in the inheritance chain.
+     *
+     * @return Never {@code null}. Unless the method was called on a builder class, the returned {@link Map} shouldn't
+     * be modified.
+     */
+    Map<String, TemplateNumberFormatFactory> getCustomNumberFormats();
+
+    /**
+     * Gets the custom number format registered for the name. This differs from calling {@link #getCustomNumberFormats()
+     * getCustomNumberFormats().get(name)}, because if there's {@link ProcessingConfiguration} from which setting values
+     * are inherited then this method will search the custom format there as well if it isn't found here. For example,
+     * {@link Environment#getCustomNumberFormat(String)} will check if the {@link Environment} contains the custom
+     * format with the name, and if not, it will try {@link Template#getCustomNumberFormat(String)} on the main
+     * template, which in turn might falls back to calling {@link Configuration#getCustomNumberFormat(String)}.
+     */
+    TemplateNumberFormatFactory getCustomNumberFormat(String name);
+
+    /**
+     * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
+     * the setting mights returns a default value, or returns the value of the setting from a parent object, or throws
+     * an {@link SettingValueNotSetException}.
+     */
+    boolean isCustomNumberFormatsSet();
+
+    /**
+     * The string value for the boolean {@code true} and {@code false} values, intended for human audience (not for a
+     * computer language), separated with comma. For example, {@code "yes,no"}. Note that white-space is significant,
+     * so {@code "yes, no"} is WRONG (unless you want that leading space before "no").
+     *
+     * <p>For backward compatibility the default is {@code "true,false"}, but using that value is denied for automatic
+     * boolean-to-string conversion (like <code>${myBoolean}</code> will fail with it), only {@code myBool?string} will
+     * allow it, which is deprecated since FreeMarker 2.3.20.
+     */
+    String getBooleanFormat();
+
+    /**
+     * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
+     * the setting mights returns a default value, or returns the value of the setting from a parent object, or throws
+     * an {@link SettingValueNotSetException}.
+     */
+    boolean isBooleanFormatSet();
+
+    /**
+     * The format used to convert {@link java.util.Date}-s that are time (no date part) values to string-s, also the
+     * format that {@code someString?time} will use to parse strings.
+     *
+     * <p>For the possible values see {@link #getDateTimeFormat()}.
+     *
+     * <p>Its {@link Configuration}-level default is {@code ""}, which is equivalent to {@code "medium"}.
+     */
+    String getTimeFormat();
+
+    /**
+     * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
+     * the setting mights returns a default value, or returns the value of the setting from a parent object, or throws
+     * an {@link SettingValueNotSetException}.
+     */
+    boolean isTimeFormatSet();
+
+    /**
+     * The format used to convert {@link java.util.Date}-s that are date-only (no time part) values to string-s,
+     * also the format that {@code someString?date} will use to parse strings.
+     *
+     * <p>For the possible values see {@link #getDateTimeFormat()}.
+     *
+     * <p>Its {@link Configuration}-level default is {@code ""} which is equivalent to {@code "medium"}.
+     */
+    String getDateFormat();
+
+    /**
+     * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
+     * the setting mights returns a default value, or returns the value of the setting from a parent object, or throws
+     * an {@link SettingValueNotSetException}.
+     */
+    boolean isDateFormatSet();
+
+    /**
+     * The format used to convert {@link java.util.Date}-s that are date-time (timestamp) values to string-s,
+     * also the format that {@code someString?datetime} will use to parse strings.
+     *
+     * <p>The possible setting values are (the quotation marks aren't part of the value itself):
+     *
+     * <ul>
+     *   <li><p>Patterns accepted by Java's {@link SimpleDateFormat}, for example {@code "dd.MM.yyyy HH:mm:ss"} (where
+     *       {@code HH} means 24 hours format) or {@code "MM/dd/yyyy hh:mm:ss a"} (where {@code a} prints AM or PM, if
+     *       the current language is English).
+     *
+     *   <li><p>{@code "xs"} for XML Schema format, or {@code "iso"} for ISO 8601:2004 format.
+     *       These formats allow various additional options, separated with space, like in
+     *       {@code "iso m nz"} (or with {@code _}, like in {@code "iso_m_nz"}; this is useful in a case like
+     *       {@code lastModified?string.iso_m_nz}). The options and their meanings are:
+     *
+     *       <ul>
+     *         <li><p>Accuracy options:<br>
+     *             {@code ms} = Milliseconds, always shown with all 3 digits, even if it's all 0-s.
+     *                     Example: {@code 13:45:05.800}<br>
+     *             {@code s} = Seconds (fraction seconds are dropped even if non-0), like {@code 13:45:05}<br>
+     *             {@code m} = Minutes, like {@code 13:45}. This isn't allowed for "xs".<br>
+     *             {@code h} = Hours, like {@code 13}. This isn't allowed for "xs".<br>
+     *             Neither = Up to millisecond accuracy, but trailing millisecond 0-s are removed, also the whole
+     *                     milliseconds part if it would be 0 otherwise. Example: {@code 13:45:05.8}
+     *
+     *         <li><p>Time zone offset visibility options:<br>
+     *             {@code fz} = "Force Zone", always show time zone offset (even for for
+     *                     {@link java.sql.Date java.sql.Date} and {@link java.sql.Time java.sql.Time} values).
+     *                     But, because ISO 8601 doesn't allow for dates (means date without time of the day) to
+     *                     show the zone offset, this option will have no effect in the case of {@code "iso"} with
+     *                     dates.<br>
+     *             {@code nz} = "No Zone", never show time zone offset<br>
+     *             Neither = always show time zone offset, except for {@link java.sql.Date java.sql.Date}
+     *                     and {@link java.sql.Time java.sql.Time}, and for {@code "iso"} date values.
+     *
+     *         <li><p>Time zone options:<br>
+     *             {@code u} = Use UTC instead of what the {@code time_zone} setting suggests. However,
+     *                     {@link java.sql.Date java.sql.Date} and {@link java.sql.Time java.sql.Time} aren't affected
+     *                     by this (see {@link #getSQLDateAndTimeTimeZone()} to understand why)<br>
+     *             {@code fu} = "Force UTC", that is, use UTC instead of what the {@code time_zone} or the
+     *                     {@code sql_date_and_time_time_zone} setting suggests. This also effects
+     *                     {@link java.sql.Date java.sql.Date} and {@link java.sql.Time java.sql.Time} values<br>
+     *             Neither = Use the time zone suggested by the {@code time_zone} or the
+     *                     {@code sql_date_and_time_time_zone} configuration setting ({@link #getTimeZone()} and
+     *                     {@link #getSQLDateAndTimeTimeZone()}).
+     *       </ul>
+     *
+     *       <p>The options can be specified in any order.</p>
+     *
+     *       <p>Options from the same category are mutually exclusive, like using {@code m} and {@code s}
+     *       together is an error.
+     *
+     *       <p>The accuracy and time zone offset visibility options don't influence parsing, only formatting.
+     *       For example, even if you use "iso m nz", "2012-01-01T15:30:05.125+01" will be parsed successfully and with
+     *       milliseconds accuracy.
+     *       The time zone options (like "u") influence what time zone is chosen only when parsing a string that doesn't
+     *       contain time zone offset.
+     *
+     *       <p>Parsing with {@code "iso"} understands both extend format and basic format, like
+     *       {@code 20141225T235018}. It doesn't, however, support the parsing of all kind of ISO 8601 strings: if
+     *       there's a date part, it must use year, month and day of the month values (not week of the year), and the
+     *       day can't be omitted.
+     *
+     *       <p>The output of {@code "iso"} is deliberately so that it's also a good representation of the value with
+     *       XML Schema format, except for 0 and negative years, where it's impossible. Also note that the time zone
+     *       offset is omitted for date values in the {@code "iso"} format, while it's preserved for the {@code "xs"}
+     *       format.
+     *
+     *   <li><p>{@code "short"}, {@code "medium"}, {@code "long"}, or {@code "full"}, which that has locale-dependent
+     *       meaning defined by the Java platform (see in the documentation of {@link java.text.DateFormat}).
+     *       For date-time values, you can specify the length of the date and time part independently, be separating
+     *       them with {@code _}, like {@code "short_medium"}. ({@code "medium"} means
+     *       {@code "medium_medium"} for date-time values.)
+     *
+     *   <li><p>Anything that starts with {@code "@"} followed by a letter is interpreted as a custom
+     *       date/time/dateTime format, but only if either {@link Configuration#getIncompatibleImprovements()}
+     *       is at least 2.3.24, or there's any custom formats defined (even if custom number format). The format of
+     *       such string is <code>"@<i>name</i>"</code> or <code>"@<i>name</i> <i>parameters</i>"</code>, where
+     *       <code><i>name</i></code> is the name parameter to {@link #getCustomDateFormat(String)}, and
+     *       <code><i>parameters</i></code> is parsed by the custom number format.
+     *
+     * </ul>
+     *
+     * <p>Its {@link Configuration}-level default is {@code ""}, which is equivalent to {@code "medium_medium"}.
+     */
+    String getDateTimeFormat();
+
+    /**
+     * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
+     * the setting mights returns a default value, or returns the value of the setting from a parent object, or throws
+     * an {@link SettingValueNotSetException}.
+     */
+    boolean isDateTimeFormatSet();
+
+    /**
+     * A {@link Map} that associates {@link TemplateDateFormatFactory}-es to names, which then can be referred by the
+     * {@link #getDateFormat() date_format}/{@link #getDateFormat() date_format }/{@link #getDateTimeFormat()
+     * datetime_format} settings with values starting with <code>@<i>name</i></code>. The keys in the {@link Map} should
+     * start with an UNICODE letter, and should only contain UNICODE letters and digits (not {@code _}), otherwise
+     * accessing the custom format from templates can be difficult or impossible. The {@link Configuration}-level
+     * default of this setting is an empty {@link Map}.
+     * <p>
+     * When the {@link ProcessingConfiguration} is part of a setting inheritance chain ({@link Environment} inherits
+     * settings from the main {@link Template}, which inherits from the {@link Configuration}), you still only get the
+     * {@link Map} from the closest {@link ProcessingConfiguration} where it was set, not a {@link Map} that respects
+     * inheritance. Thus, to get a custom format you shouldn't use this {@link Map} directly, but {@link
+     * #getCustomDateFormat(String)}, which will search the format in the inheritance chain.
+     *
+     * @return Never {@code null}. Unless the method was called on a builder class, the returned {@link Map} shouldn't
+     * be modified.
+     */
+    Map<String, TemplateDateFormatFactory> getCustomDateFormats();
+
+    /**
+     * Gets the custom date or time or date-time format registered for the name. This differs from calling {@link
+     * #getCustomDateFormats() getCustomDateFormats.get(name)}, because if there's {@link ProcessingConfiguration} from
+     * which setting values are inherited then this method will search the custom format there as well if it isn't found
+     * here. For example, {@link Environment#getCustomNumberFormat(String)} will check if the {@link Environment}
+     * contains the custom format with the name, and if not, it will try {@link Template#getCustomDateFormat(String)} on
+     * the main template, which in turn might falls back to calling {@link Configuration#getCustomDateFormat(String)}.
+     */
+    TemplateDateFormatFactory getCustomDateFormat(String name);
+
+    /**
+     * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
+     * the setting mights returns a default value, or returns the value of the setting from a parent object, or throws
+     * an {@link SettingValueNotSetException}.
+     */
+    boolean isCustomDateFormatsSet();
+
+    /**
+     * The exception handler used to handle exceptions occurring inside templates.
+     * Its {@link Configuration}-level default is {@link TemplateExceptionHandler#DEBUG_HANDLER}. The recommended
+     * values are:
+     *
+     * <ul>
+     *   <li>In production systems: {@link TemplateExceptionHandler#RETHROW_HANDLER}
+     *   <li>During development of HTML templates: {@link TemplateExceptionHandler#HTML_DEBUG_HANDLER}
+     *   <li>During development of non-HTML templates: {@link TemplateExceptionHandler#DEBUG_HANDLER}
+     * </ul>
+     *
+     * <p>All of these will let the exception propagate further, so that you can catch it around
+     * {@link Template#process(Object, Writer)} for example. The difference is in what they print on the output before
+     * they do that.
+     *
+     * <p>Note that the {@link TemplateExceptionHandler} is not meant to be used for generating HTTP error pages.
+     * Neither is it meant to be used to roll back the printed output. These should be solved outside template
+     * processing when the exception raises from {@link Template#process(Object, Writer) Template.process}.
+     * {@link TemplateExceptionHandler} meant to be used if you want to include special content <em>in</em> the template
+     * output, or if you want to suppress certain exceptions.
+     */
+    TemplateExceptionHandler getTemplateExceptionHandler();
+
+    /**
+     * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
+     * the setting mights returns a default value, or returns the value of the setting from a parent object, or throws
+     * an {@link SettingValueNotSetException}.
+     */
+    boolean isTemplateExceptionHandlerSet();
+
+    /**
+     * The arithmetic engine used to perform arithmetic operations.
+     * Its {@link Configuration}-level default is {@link BigDecimalArithmeticEngine#INSTANCE}.
+     * Note that this setting overlaps with {@link ParsingConfiguration#getArithmeticEngine()}.
+     */
+    ArithmeticEngine getArithmeticEngine();
+
+    /**
+     * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
+     * the setting mights returns a default value, or returns the value of the setting from a parent object, or throws
+     * an {@link SettingValueNotSetException}.
+     */
+    boolean isArithmeticEngineSet();
+
+    /**
+     * The object wrapper used to wrap objects to {@link TemplateModel}-s.
+     * Its {@link Configuration}-level default is a {@link DefaultObjectWrapper} with all its setting on default
+     * values, and {@code incompatibleImprovements} set to {@link Configuration#getIncompatibleImprovements()}.
+     */
+    ObjectWrapper getObjectWrapper();
+
+    /**
+     * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
+     * the setting mights returns a default value, or returns the value of the setting from a parent object, or throws
+     * an {@link SettingValueNotSetException}.
+     */
+    boolean isObjectWrapperSet();
+
+    /**
+     * Informs FreeMarker about the charset used for the output. As FreeMarker outputs character stream (not
+     * byte stream), it's not aware of the output charset unless the software that encloses it tells it
+     * with this setting. Some templates may use FreeMarker features that require this information.
+     * Setting this to {@code null} means that the output encoding is not known.
+     *
+     * <p>Its {@link Configuration}-level default is {@code null}.
+     */
+    Charset getOutputEncoding();
+
+    /**
+     * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
+     * the setting mights returns a default value, or returns the value of the setting from a parent object, or throws
+     * an {@link SettingValueNotSetException}.
+     */
+    boolean isOutputEncodingSet();
+
+    /**
+     * The URL escaping (URL encoding, percentage encoding) charset. If ({@code null}), the output encoding
+     * ({@link #getOutputEncoding()}) will be used. Its {@link Configuration}-level default is {@code null}.
+     */
+    Charset getURLEscapingCharset();
+
+    /**
+     * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
+     * the setting mights returns a default value, or returns the value of the setting from a parent object, or throws
+     * an {@link SettingValueNotSetException}.
+     */
+    boolean isURLEscapingCharsetSet();
+
+    /**
+     * The {@link TemplateClassResolver} that is used when the <code>new</code> built-in is called in a template. That
+     * is, when a template contains the <code>"com.example.SomeClassName"?new</code> expression, this object will be
+     * called to resolve the <code>"com.example.SomeClassName"</code> string to a class. The default value is {@link
+     * TemplateClassResolver#UNRESTRICTED_RESOLVER}. If you allow users to upload templates, it's important to use a
+     * custom restrictive {@link TemplateClassResolver} or {@link TemplateClassResolver#ALLOWS_NOTHING_RESOLVER}.
+     */
+    TemplateClassResolver getNewBuiltinClassResolver();
+
+    /**
+     * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
+     * the setting mights returns a default value, or returns the value of the setting from a parent object, or throws
+     * an {@link SettingValueNotSetException}.
+     */
+    boolean isNewBuiltinClassResolverSet();
+
+    /**
+     * Specifies if {@code ?api} can be used in templates. Its {@link Configuration}-level is {@code false} (which
+     * is the safest option).
+     */
+    boolean getAPIBuiltinEnabled();
+
+    /**
+     * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
+     * the setting mights returns a default value, or returns the value of the setting from a parent object, or throws
+     * an {@link SettingValueNotSetException}.
+     */
+    boolean isAPIBuiltinEnabledSet();
+
+    /**
+     * Whether the output {@link Writer} is automatically flushed at the end of {@link Template#process(Object, Writer)}
+     * (and its overloads). Its {@link Configuration}-level default is {@code true}.
+     * <p>
+     * Using {@code false} is needed for example when a Web page is composed from several boxes (like portlets, GUI
+     * panels, etc.) that aren't inserted with <tt>#include</tt> (or with similar directives) into a master FreeMarker
+     * template, rather they are all processed with a separate {@link Template#process(Object, Writer)} call. In a such
+     * scenario the automatic flushes would commit the HTTP response after each box, hence interfering with full-page
+     * buffering, and also possibly decreasing performance with too frequent and too early response buffer flushes.
+     */
+    boolean getAutoFlush();
+
+    /**
+     * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
+     * the setting mights returns a default value, or returns the value of the setting from a parent object, or throws
+     * an {@link SettingValueNotSetException}.
+     */
+    boolean isAutoFlushSet();
+
+    /**
+     * Whether tips should be shown in error messages of errors arising during template processing.
+     * Its {@link Configuration}-level default is {@code true}.
+     */
+    boolean getShowErrorTips();
+
+    /**
+     * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
+     * the setting mights returns a default value, or returns the value of the setting from a parent object, or throws
+     * an {@link SettingValueNotSetException}.
+     */
+    boolean isShowErrorTipsSet();
+
+    /**
+     * Specifies if {@link TemplateException}-s thrown by template processing are logged by FreeMarker or not. The
+     * default is {@code true} for backward compatibility, but that results in logging the exception twice in properly
+     * written applications, because there the {@link TemplateException} thrown by the public FreeMarker API is also
+     * logged by the caller (even if only as the cause exception of a higher level exception). Hence, in modern
+     * applications it should be set to {@code false}. Note that this setting has no effect on the logging of exceptions
+     * caught by {@code #attempt}; those are always logged, no mater what (because those exceptions won't bubble up
+     * until the API caller).
+     */
+    boolean getLogTemplateExceptions();
+
+    /**
+     * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
+     * the setting mights returns a default value, or returns the value of the setting from a parent object, or throws
+     * an {@link SettingValueNotSetException}.
+     */
+    boolean isLogTemplateExceptionsSet();
+
+    /**
+     * Specifies if {@code <#import ...>} (and {@link Environment#importLib(String, String)}) should delay the loading
+     * and processing of the imported templates until the content of the imported namespace is actually accessed. This
+     * makes the overhead of <em>unused</em> imports negligible. A drawback is that importing a missing or otherwise
+     * broken template will be successful, and the problem will remain hidden until (and if) the namespace content is
+     * actually used. Also, you lose the strict control over when the namespace initializing code in the imported
+     * template will be executed, though it shouldn't mater for well written imported templates anyway. Note that the
+     * namespace initializing code will run with the same {@linkplain #getLocale() locale} as it was at the
+     * point of the {@code <#import ...>} call (other settings won't be handled specially like that).
+     * <p>
+     * The default is {@code false} (and thus imports are eager) for backward compatibility, which can cause
+     * perceivable overhead if you have many imports and only a few of them is used.
+     * <p>
+     * This setting also affects {@linkplain #getAutoImports() auto-imports}, unless you have set a non-{@code null}
+     * value with {@link #getLazyAutoImports()}.
+     *
+     * @see #getLazyAutoImports()
+     */
+    boolean getLazyImports();
+
+    /**
+     * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
+     * the setting mights returns a default value, or returns the value of the setting from a parent object, or throws
+     * an {@link SettingValueNotSetException}.
+     */
+    boolean isLazyImportsSet();
+
+    /**
+     * Specifies if {@linkplain #getAutoImports() auto-imports} will be
+     * {@link #getLazyImports() lazy imports}. This is useful to make the overhead of <em>unused</em>
+     * auto-imports negligible. If this is set to {@code null}, {@link #getLazyImports()} specifies the behavior of
+     * auto-imports too. The default value is {@code null}.
+     */
+    Boolean getLazyAutoImports();
+
+    /**
+     * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
+     * the setting mights returns a default value, or returns the value of the setting from a parent object, or throws
+     * an {@link SettingValueNotSetException}.
+     */
+    boolean isLazyAutoImportsSet();
+
+    /**
+     * Adds invisible <code>#import <i>templateName</i> as <i>namespaceVarName</i></code> statements at the beginning of
+     * the main template (that's the top-level template that wasn't included/imported from another template). While
+     * it only affects the main template directly, as the imports will create a global variable there, the imports
+     * will be visible from the further imported templates too.
+     * <p>
+     * It's recommended to set the {@link Configuration#getLazyAutoImports() lazyAutoImports} setting to {@code true}
+     * when using this, so that auto-imports that are unused in a template won't degrade performance by unnecessary
+     * loading and initializing the imported library.
+     * <p>
+     * If the imports aren't lazy, the order of the imports will be the same as the order in which the {@link Map}
+     * iterates through its entries.
+     * <p>
+     * When the {@link ProcessingConfiguration} is part of a setting inheritance chain ({@link Environment} inherits
+     * settings from the main {@link Template}, which inherits from the {@link Configuration}), you still only get the
+     * {@link Map} from the closest {@link ProcessingConfiguration} where it was set, not a {@link Map} that respects
+     * inheritance. But FreeMarker will walk the whole inheritance chain, executing all auto-imports starting
+     * from the ancestors. If, however, the same auto-import <code><i>namespaceVarName</i></code> occurs in multiple
+     * {@link ProcessingConfiguration}-s of the chain, only the one in the last (child)
+     * {@link ProcessingConfiguration} will be executed.
+     * <p>
+     * If there are also auto-includes (see {@link #getAutoIncludes()}), those will be executed after the auto-imports.
+     * <p>
+     * The {@link Configuration}-level default of this setting is an empty {@link Map}.
+     *
+     * @return Never {@code null}
+     */
+    Map<String, String> getAutoImports();
+
+    /**
+     * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
+     * the setting mights returns a default value, or returns the value of the setting from a parent object, or throws
+     * an {@link SettingValueNotSetException}.
+     */
+    boolean isAutoImportsSet();
+
+    /**
+     * Adds an invisible <code>#include <i>templateName</i></code> at the beginning of the main template (that's the
+     * top-level template that wasn't included/imported from another template).
+     * <p>
+     * The order of the inclusions will be the same as the order in this {@link List}.
+     * <p>
+     * When the {@link ProcessingConfiguration} is part of a setting inheritance chain ({@link Environment} inherits
+     * settings from the main {@link Template}, which inherits from the {@link Configuration}), you still only get the
+     * {@link List} from the closest {@link ProcessingConfiguration} where it was set, not a {@link List} that respects
+     * inheritance. But FreeMarker will walk the whole inheritance chain, executing all auto-imports starting
+     * from the ancestors. If, however, the same auto-included template name occurs in multiple
+     * {@link ProcessingConfiguration}-s of the chain, only the one in the last (child)
+     * {@link ProcessingConfiguration} will be executed.
+     * <p>
+     * If there are also auto-imports ({@link #getAutoImports()}), those imports will be executed before
+     * the auto-includes, hence the namespace variables are alrady accessible for the auto-included templates.
+     */
+    List<String> getAutoIncludes();
+
+    /**
+     * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
+     * the setting mights returns a default value, or returns the value of the setting from a parent object, or throws
+     * an {@link SettingValueNotSetException}.
+     */
+    boolean isAutoIncludesSet();
+
+    /**
+     * The {@code Map} of custom attributes. Custom attributes are key-value pairs associated to a
+     * {@link ProcessingConfiguration} objects, which meant to be used for storing application or framework specific
+     * configuration settings. The FreeMarker core doesn't define any attributes. Note that to store
+     * {@link ProcessingConfiguration}-scoped state (such as application or framework specific caches) you should use
+     * the methods provided by the {@link CustomStateScope} instead.
+     * <p>
+     * When the {@link ProcessingConfiguration} is part of a setting inheritance chain ({@link Environment} inherits
+     * settings from the main {@link Template}, which inherits from the {@link Configuration}), you still only get the
+     * {@link Map} from the closest {@link ProcessingConfiguration} where it was set, not a {@link Map} that respects
+     * inheritance. Thus to get attributes, you shouldn't use this {@link Map} directly, but
+     * {@link #getCustomAttribute(Object)} that will search the custom attribute in the whole inheritance chain.
+     */
+    Map<Object, Object> getCustomAttributes();
+
+    /**
+     * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
+     * the setting mights returns a default value, or returns the value of the setting from a parent object, or throws
+     * an {@link SettingValueNotSetException}.
+     */
+    boolean isCustomAttributesSet();
+
+    /**
+     * Retrieves a custom attribute for this {@link ProcessingConfiguration}. If the attribute is not present in the
+     * {@link ProcessingConfiguration}, but it inherits from another {@link ProcessingConfiguration}, then the attribute
+     * is searched the as well.
+     *
+     * @param key
+     *         the identifier (usually a name) of the custom attribute
+     *
+     * @return the value of the custom attribute. Note that if the custom attribute was created with
+     * <tt>&lt;#ftl&nbsp;attributes={...}&gt;</tt>, then this value is already unwrapped (i.e. it's a
+     * <code>String</code>, or a <code>List</code>, or a <code>Map</code>, ...etc., not a FreeMarker specific class).
+     */
+    Object getCustomAttribute(Object key);
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/RangeModel.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/RangeModel.java b/freemarker-core/src/main/java/org/apache/freemarker/core/RangeModel.java
new file mode 100644
index 0000000..45f9345
--- /dev/null
+++ b/freemarker-core/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();
+
+}


Mime
View raw message