freemarker-notifications mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ddek...@apache.org
Subject [36/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:19 GMT
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/InvalidReferenceException.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/InvalidReferenceException.java b/freemarker-core/src/main/java/org/apache/freemarker/core/InvalidReferenceException.java
new file mode 100644
index 0000000..20d2c10
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/InvalidReferenceException.java
@@ -0,0 +1,167 @@
+/*
+ * 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;
+
+/**
+ * A subclass of {@link TemplateException} that says that an FTL expression has evaluated to {@code null} or it refers
+ * to something that doesn't exist. At least in FreeMarker 2.3.x these two cases aren't distinguished.
+ */
+public class InvalidReferenceException extends TemplateException {
+
+    static final InvalidReferenceException FAST_INSTANCE;
+    static {
+        Environment prevEnv = Environment.getCurrentEnvironment();
+        try {
+            Environment.setCurrentEnvironment(null);
+            FAST_INSTANCE = new InvalidReferenceException(
+                    "Invalid reference. Details are unavilable, as this should have been handled by an FTL construct. "
+                    + "If it wasn't, that's problably a bug in FreeMarker.",
+                    null);
+        } finally {
+            Environment.setCurrentEnvironment(prevEnv);
+        }
+    }
+    
+    private static final Object[] TIP = {
+        "If the failing expression is known to be legally refer to something that's sometimes null or missing, "
+        + "either specify a default value like myOptionalVar!myDefault, or use ",
+        "<#if myOptionalVar??>", "when-present", "<#else>", "when-missing", "</#if>",
+        ". (These only cover the last step of the expression; to cover the whole expression, "
+        + "use parenthesis: (myOptionalVar.foo)!myDefault, (myOptionalVar.foo)??"
+    };
+
+    private static final Object[] TIP_MISSING_ASSIGNMENT_TARGET = {
+            "If the target variable is known to be legally null or missing sometimes, instead of something like ",
+            "<#assign x += 1>", ", you could write ", "<#if x??>", "<#assign x += 1>", "</#if>",
+            " or ", "<#assign x = (x!0) + 1>"
+    };
+    
+    private static final String TIP_NO_DOLLAR =
+            "Variable references must not start with \"$\", unless the \"$\" is really part of the variable name.";
+
+    private static final String TIP_LAST_STEP_DOT =
+            "It's the step after the last dot that caused this error, not those before it.";
+
+    private static final String TIP_LAST_STEP_SQUARE_BRACKET =
+            "It's the final [] step that caused this error, not those before it.";
+    
+    private static final String TIP_JSP_TAGLIBS =
+            "The \"JspTaglibs\" variable isn't a core FreeMarker feature; "
+            + "it's only available when templates are invoked through org.apache.freemarker.servlet.FreemarkerServlet"
+            + " (or other custom FreeMarker-JSP integration solution).";
+    
+    /**
+     * Creates and invalid reference exception that contains no information about what was missing or null.
+     * As such, try to avoid this constructor.
+     */
+    public InvalidReferenceException(Environment env) {
+        super("Invalid reference: The expression has evaluated to null or refers to something that doesn't exist.",
+                env);
+    }
+
+    /**
+     * Creates and invalid reference exception that contains no programmatically extractable information about the
+     * blamed expression. As such, try to avoid this constructor, unless need to raise this expression from outside
+     * the FreeMarker core.
+     */
+    public InvalidReferenceException(String description, Environment env) {
+        super(description, env);
+    }
+
+    /**
+     * This is the recommended constructor, but it's only used internally, and has no backward compatibility guarantees.
+     * 
+     * @param expression The expression that evaluates to missing or null. The last step of the expression should be
+     *     the failing one, like in {@code goodStep.failingStep.furtherStep} it should only contain
+     *     {@code goodStep.failingStep}.
+     */
+    InvalidReferenceException(_ErrorDescriptionBuilder description, Environment env, ASTExpression expression) {
+        super(null, env, expression, description);
+    }
+
+    /**
+     * Use this whenever possible, as it returns {@link #FAST_INSTANCE} instead of creating a new instance, when
+     * appropriate.
+     */
+    static InvalidReferenceException getInstance(ASTExpression blamed, Environment env) {
+        if (env != null && env.getFastInvalidReferenceExceptions()) {
+            return FAST_INSTANCE;
+        } else {
+            if (blamed != null) {
+                final _ErrorDescriptionBuilder errDescBuilder
+                        = new _ErrorDescriptionBuilder("The following has evaluated to null or missing:").blame(blamed);
+                if (endsWithDollarVariable(blamed)) {
+                    errDescBuilder.tips(TIP_NO_DOLLAR, TIP);
+                } else if (blamed instanceof ASTExpDot) {
+                    final String rho = ((ASTExpDot) blamed).getRHO();
+                    String nameFixTip = null;
+                    if ("size".equals(rho)) {
+                        nameFixTip = "To query the size of a collection or map use ?size, like myList?size";
+                    } else if ("length".equals(rho)) {
+                        nameFixTip = "To query the length of a string use ?length, like myString?size";
+                    }
+                    errDescBuilder.tips(
+                            nameFixTip == null
+                                    ? new Object[] { TIP_LAST_STEP_DOT, TIP }
+                                    : new Object[] { TIP_LAST_STEP_DOT, nameFixTip, TIP });
+                } else if (blamed instanceof ASTExpDynamicKeyName) {
+                    errDescBuilder.tips(TIP_LAST_STEP_SQUARE_BRACKET, TIP);
+                } else if (blamed instanceof ASTExpVariable
+                        && ((ASTExpVariable) blamed).getName().equals("JspTaglibs")) {
+                    errDescBuilder.tips(TIP_JSP_TAGLIBS, TIP);
+                } else {
+                    errDescBuilder.tip(TIP);
+                }
+                return new InvalidReferenceException(errDescBuilder, env, blamed);
+            } else {
+                return new InvalidReferenceException(env);
+            }
+        }
+    }
+    
+    /**
+     * Used for assignments that use operators like {@code +=}, when the target variable was null/missing. 
+     */
+    static InvalidReferenceException getInstance(String missingAssignedVarName, String assignmentOperator,
+            Environment env) {
+        if (env != null && env.getFastInvalidReferenceExceptions()) {
+            return FAST_INSTANCE;
+        } else {
+            final _ErrorDescriptionBuilder errDescBuilder = new _ErrorDescriptionBuilder(
+                            "The target variable of the assignment, ",
+                            new _DelayedJQuote(missingAssignedVarName),
+                            ", was null or missing, but the \"",
+                            assignmentOperator, "\" operator needs to get its value before assigning to it."
+                    );
+            if (missingAssignedVarName.startsWith("$")) {
+                errDescBuilder.tips(TIP_NO_DOLLAR, TIP_MISSING_ASSIGNMENT_TARGET);
+            } else {
+                errDescBuilder.tip(TIP_MISSING_ASSIGNMENT_TARGET);
+            }
+            return new InvalidReferenceException(errDescBuilder, env, null);
+        }
+    }
+
+    private static boolean endsWithDollarVariable(ASTExpression blame) {
+        return blame instanceof ASTExpVariable && ((ASTExpVariable) blame).getName().startsWith("$")
+                || blame instanceof ASTExpDot && ((ASTExpDot) blame).getRHO().startsWith("$");
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/ListableRightUnboundedRangeModel.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ListableRightUnboundedRangeModel.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ListableRightUnboundedRangeModel.java
new file mode 100644
index 0000000..a4a14b5
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ListableRightUnboundedRangeModel.java
@@ -0,0 +1,97 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core;
+
+import java.math.BigInteger;
+
+import org.apache.freemarker.core.model.TemplateCollectionModel;
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateModelException;
+import org.apache.freemarker.core.model.TemplateModelIterator;
+import org.apache.freemarker.core.model.impl.SimpleNumber;
+
+/**
+ * This is the model used for right-unbounded ranges since Incompatible Improvements 2.3.21.
+ * 
+ * @since 2.3.21
+ */
+final class ListableRightUnboundedRangeModel extends RightUnboundedRangeModel implements TemplateCollectionModel {
+
+    ListableRightUnboundedRangeModel(int begin) {
+        super(begin);
+    }
+
+    @Override
+    public int size() throws TemplateModelException {
+        return Integer.MAX_VALUE;
+    }
+
+    @Override
+    public TemplateModelIterator iterator() throws TemplateModelException {
+        return new TemplateModelIterator() {
+            boolean needInc;
+            int nextType = 1;
+            int nextInt = getBegining();
+            long nextLong;
+            BigInteger nextBigInteger;
+
+            @Override
+            public TemplateModel next() throws TemplateModelException {
+                if (needInc) {
+                    switch (nextType) {
+                    case 1:
+                        if (nextInt < Integer.MAX_VALUE) {
+                            nextInt++;
+                        } else {
+                            nextType = 2;
+                            nextLong = nextInt + 1L;
+                        }
+                        break;
+                        
+                    case 2:
+                        if (nextLong < Long.MAX_VALUE) {
+                            nextLong++;
+                        } else {
+                            nextType = 3;
+                            nextBigInteger = BigInteger.valueOf(nextLong);
+                            nextBigInteger = nextBigInteger.add(BigInteger.ONE);
+                        }
+                        break;
+                        
+                    default: // 3
+                        nextBigInteger = nextBigInteger.add(BigInteger.ONE);
+                    }
+                }
+                needInc = true;
+                return nextType == 1 ? new SimpleNumber(nextInt)
+                        : (nextType == 2 ? new SimpleNumber(nextLong)
+                        : new SimpleNumber(nextBigInteger)); 
+            }
+
+            @Override
+            public boolean hasNext() throws TemplateModelException {
+                return true;
+            }
+            
+        };
+        
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/LocalContext.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/LocalContext.java b/freemarker-core/src/main/java/org/apache/freemarker/core/LocalContext.java
new file mode 100644
index 0000000..1084470
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/LocalContext.java
@@ -0,0 +1,36 @@
+/*
+ * 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.Collection;
+
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateModelException;
+
+/**
+  * An interface that represents a local context. This is used as the abstraction for  
+  * the context of a ASTDirMacro invocation, a loop, or the nested block call from within 
+  * a macro.
+  */
+public interface LocalContext {
+    TemplateModel getLocalVariable(String name) throws TemplateModelException;
+    Collection getLocalVariableNames() throws TemplateModelException;
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/LocalContextStack.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/LocalContextStack.java b/freemarker-core/src/main/java/org/apache/freemarker/core/LocalContextStack.java
new file mode 100644
index 0000000..aead89d
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/LocalContextStack.java
@@ -0,0 +1,57 @@
+/*
+ * 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;
+
+/**
+ * Class that's a little bit more efficient than using an {@code ArrayList<LocalContext>}. 
+ * 
+ * @since 2.3.24
+ */
+final class LocalContextStack {
+
+    private LocalContext[] buffer = new LocalContext[8];
+    private int size;
+
+    void push(LocalContext localContext) {
+        final int newSize = ++size;
+        LocalContext[] buffer = this.buffer;
+        if (buffer.length < newSize) {
+            final LocalContext[] newBuffer = new LocalContext[newSize * 2];
+            for (int i = 0; i < buffer.length; i++) {
+                newBuffer[i] = buffer[i];
+            }
+            buffer = newBuffer;
+            this.buffer = newBuffer;
+        }
+        buffer[newSize - 1] = localContext;
+    }
+
+    void pop() {
+        buffer[--size] = null;
+    }
+
+    public LocalContext get(int index) {
+        return buffer[index];
+    }
+    
+    public int size() {
+        return size;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/MarkupOutputFormatBoundBuiltIn.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/MarkupOutputFormatBoundBuiltIn.java b/freemarker-core/src/main/java/org/apache/freemarker/core/MarkupOutputFormatBoundBuiltIn.java
new file mode 100644
index 0000000..d477963
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/MarkupOutputFormatBoundBuiltIn.java
@@ -0,0 +1,46 @@
+/*
+ * 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.outputformat.MarkupOutputFormat;
+import org.apache.freemarker.core.util._NullArgumentException;
+
+abstract class MarkupOutputFormatBoundBuiltIn extends SpecialBuiltIn {
+    
+    protected MarkupOutputFormat outputFormat;
+    
+    void bindToMarkupOutputFormat(MarkupOutputFormat outputFormat) {
+        _NullArgumentException.check(outputFormat);
+        this.outputFormat = outputFormat;
+    }
+    
+    @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/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/MessageUtil.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/MessageUtil.java b/freemarker-core/src/main/java/org/apache/freemarker/core/MessageUtil.java
new file mode 100644
index 0000000..6a2bc2f
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/MessageUtil.java
@@ -0,0 +1,341 @@
+/*
+ * 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 org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateModelException;
+import org.apache.freemarker.core.util._StringUtil;
+import org.apache.freemarker.core.valueformat.TemplateDateFormat;
+import org.apache.freemarker.core.valueformat.TemplateNumberFormat;
+import org.apache.freemarker.core.valueformat.TemplateValueFormatException;
+import org.apache.freemarker.core.valueformat.UnknownDateTypeFormattingUnsupportedException;
+
+/**
+ * Utilities for creating error messages (and other messages).
+ */
+class MessageUtil {
+
+    static final String UNKNOWN_DATE_TO_STRING_ERROR_MESSAGE
+            = "Can't convert the date-like value to string because it isn't "
+              + "known if it's a date (no time part), time or date-time value.";
+    
+    static final String UNKNOWN_DATE_TYPE_ERROR_TIP =
+            "Use ?date, ?time, or ?datetime to tell FreeMarker the exact type.";
+
+    static final Object[] UNKNOWN_DATE_TO_STRING_TIPS = {
+            UNKNOWN_DATE_TYPE_ERROR_TIP,
+            "If you need a particular format only once, use ?string(pattern), like ?string('dd.MM.yyyy HH:mm:ss'), "
+            + "to specify which fields to display. "
+    };
+
+    static final String EMBEDDED_MESSAGE_BEGIN = "---begin-message---\n";
+
+    static final String EMBEDDED_MESSAGE_END = "\n---end-message---";
+
+    static final String ERROR_MESSAGE_HR = "----";
+
+    // Can't be instantiated
+    private MessageUtil() { }
+
+    static String formatLocationForSimpleParsingError(Template template, int line, int column) {
+        return formatLocation("in", template, line, column);
+    }
+
+    static String formatLocationForSimpleParsingError(String templateSourceOrLookupName, int line, int column) {
+        return formatLocation("in", templateSourceOrLookupName, line, column);
+    }
+
+    static String formatLocationForEvaluationError(Template template, int line, int column) {
+        return formatLocation("at", template, line, column);
+    }
+
+    static String formatLocationForEvaluationError(ASTDirMacro macro, int line, int column) {
+        Template t = macro.getTemplate();
+        return formatLocation("at", t != null ? t.getSourceOrLookupName() : null, macro.getName(), macro.isFunction(),
+                line, column);
+    }
+
+    private static String formatLocation(String preposition, Template template, int line, int column) {
+        return formatLocation(preposition, template != null ? template.getSourceOrLookupName() : null, line, column);
+    }
+
+    private static String formatLocation(String preposition, String templateSourceName, int line, int column) {
+        return formatLocation(
+                preposition, templateSourceName,
+                null, false,
+                line, column);
+    }
+
+    private static String formatLocation(
+            String preposition, String templateSourceName,
+            String macroOrFuncName, boolean isFunction,
+            int line, int column) {
+        String templateDesc;
+        if (line < 0) {
+            templateDesc = "?eval-ed string";
+            macroOrFuncName = null;
+        } else {
+            templateDesc = templateSourceName != null
+                ? "template " + _StringUtil.jQuoteNoXSS(templateSourceName)
+                : "nameless template";
+        }
+        return "in " + templateDesc
+              + (macroOrFuncName != null
+                      ? " in " + (isFunction ? "function " : "macro ") + _StringUtil.jQuote(macroOrFuncName)
+                      : "")
+              + " "
+              + preposition + " " + formatPosition(line, column);
+    }
+
+    static String formatPosition(int line, int column) {
+        return "line " + (line >= 0 ? line : line - (ASTNode.RUNTIME_EVAL_LINE_DISPLACEMENT - 1))
+                + ", column " + column;
+    }
+
+    /**
+     * Returns a single line string that is no longer than {@code maxLength}.
+     * If will truncate the string at line-breaks too.
+     * The truncation is always signaled with a a {@code "..."} at the end of the result string.
+     */
+    static String shorten(String s, int maxLength) {
+        if (maxLength < 5) maxLength = 5;
+
+        boolean isTruncated = false;
+        
+        int brIdx = s.indexOf('\n');
+        if (brIdx != -1) {
+            s = s.substring(0, brIdx);
+            isTruncated = true;
+        }
+        brIdx = s.indexOf('\r');
+        if (brIdx != -1) {
+            s = s.substring(0, brIdx);
+            isTruncated = true;
+        }
+        
+        if (s.length() > maxLength) {
+            s = s.substring(0, maxLength - 3);
+            isTruncated = true;
+        }
+        
+        if (!isTruncated) {
+            return s;
+        } else {
+            if (s.endsWith(".")) {
+                if (s.endsWith("..")) {
+                    if (s.endsWith("...")) {
+                        return s;
+                    } else {
+                        return s + ".";
+                    }
+                } else {
+                    return s + "..";
+                }
+            } else {
+                return s + "...";
+            }
+        }
+    }
+    
+    static StringBuilder appendExpressionAsUntearable(StringBuilder sb, ASTExpression argExp) {
+        boolean needParen =
+                !(argExp instanceof ASTExpNumberLiteral)
+                && !(argExp instanceof ASTExpStringLiteral)
+                && !(argExp instanceof ASTExpBooleanLiteral)
+                && !(argExp instanceof ASTExpListLiteral)
+                && !(argExp instanceof ASTExpHashLiteral)
+                && !(argExp instanceof ASTExpVariable)
+                && !(argExp instanceof ASTExpDot)
+                && !(argExp instanceof ASTExpDynamicKeyName)
+                && !(argExp instanceof ASTExpMethodCall)
+                && !(argExp instanceof ASTExpBuiltIn);
+        if (needParen) sb.append('(');
+        sb.append(argExp.getCanonicalForm());
+        if (needParen) sb.append(')');
+        return sb;
+    }
+
+    static TemplateModelException newArgCntError(String methodName, int argCnt, int expectedCnt) {
+        return newArgCntError(methodName, argCnt, expectedCnt, expectedCnt);
+    }
+    
+    static TemplateModelException newArgCntError(String methodName, int argCnt, int minCnt, int maxCnt) {
+        ArrayList/*<Object>*/ desc = new ArrayList(20);
+        
+        desc.add(methodName);
+        
+        desc.add("(");
+        if (maxCnt != 0) desc.add("...");
+        desc.add(") expects ");
+        
+        if (minCnt == maxCnt) {
+            if (maxCnt == 0) {
+                desc.add("no");
+            } else {
+                desc.add(Integer.valueOf(maxCnt));
+            }
+        } else if (maxCnt - minCnt == 1) {
+            desc.add(Integer.valueOf(minCnt));
+            desc.add(" or ");
+            desc.add(Integer.valueOf(maxCnt));
+        } else {
+            desc.add(Integer.valueOf(minCnt));
+            if (maxCnt != Integer.MAX_VALUE) {
+                desc.add(" to ");
+                desc.add(Integer.valueOf(maxCnt));
+            } else {
+                desc.add(" or more (unlimited)");
+            }
+        }
+        desc.add(" argument");
+        if (maxCnt > 1) desc.add("s");
+        
+        desc.add(" but has received ");
+        if (argCnt == 0) {
+            desc.add("none");
+        } else {
+            desc.add(Integer.valueOf(argCnt));
+        }
+        desc.add(".");
+        
+        return new _TemplateModelException(desc.toArray());
+    }
+
+    static TemplateModelException newMethodArgMustBeStringException(String methodName, int argIdx, TemplateModel arg) {
+        return newMethodArgUnexpectedTypeException(methodName, argIdx, "string", arg);
+    }
+
+    static TemplateModelException newMethodArgMustBeNumberException(String methodName, int argIdx, TemplateModel arg) {
+        return newMethodArgUnexpectedTypeException(methodName, argIdx, "number", arg);
+    }
+    
+    static TemplateModelException newMethodArgMustBeBooleanException(String methodName, int argIdx, TemplateModel arg) {
+        return newMethodArgUnexpectedTypeException(methodName, argIdx, "boolean", arg);
+    }
+    
+    static TemplateModelException newMethodArgMustBeExtendedHashException(
+            String methodName, int argIdx, TemplateModel arg) {
+        return newMethodArgUnexpectedTypeException(methodName, argIdx, "extended hash", arg);
+    }
+    
+    static TemplateModelException newMethodArgMustBeSequenceException(
+            String methodName, int argIdx, TemplateModel arg) {
+        return newMethodArgUnexpectedTypeException(methodName, argIdx, "sequence", arg);
+    }
+    
+    static TemplateModelException newMethodArgMustBeSequenceOrCollectionException(
+            String methodName, int argIdx, TemplateModel arg) {
+        return newMethodArgUnexpectedTypeException(methodName, argIdx, "sequence or collection", arg);
+    }
+    
+    static TemplateModelException newMethodArgUnexpectedTypeException(
+            String methodName, int argIdx, String expectedType, TemplateModel arg) {
+        return new _TemplateModelException(
+                methodName, "(...) expects ", new _DelayedAOrAn(expectedType), " as argument #", Integer.valueOf(argIdx + 1),
+                ", but received ", new _DelayedAOrAn(new _DelayedFTLTypeDescription(arg)), ".");
+    }
+    
+    /**
+     * The type of the argument was good, but it's value wasn't.
+     */
+    static TemplateModelException newMethodArgInvalidValueException(
+            String methodName, int argIdx, Object... details) {
+        return new _TemplateModelException(
+                methodName, "(...) argument #", Integer.valueOf(argIdx + 1),
+                " had invalid value: ", details);
+    }
+
+    /**
+     * The type of the argument was good, but the values of two or more arguments are inconsistent with each other.
+     */
+    static TemplateModelException newMethodArgsInvalidValueException(
+            String methodName, Object... details) {
+        return new _TemplateModelException(methodName, "(...) arguments have invalid value: ", details);
+    }
+    
+    static TemplateException newInstantiatingClassNotAllowedException(String className, Environment env) {
+        return new _MiscTemplateException(env,
+                "Instantiating ", className, " is not allowed in the template for security reasons.");
+    }
+    
+    static TemplateModelException newCantFormatUnknownTypeDateException(
+            ASTExpression dateSourceExpr, UnknownDateTypeFormattingUnsupportedException cause) {
+        return new _TemplateModelException(cause, null, new _ErrorDescriptionBuilder(
+                MessageUtil.UNKNOWN_DATE_TO_STRING_ERROR_MESSAGE)
+                .blame(dateSourceExpr)
+                .tips(MessageUtil.UNKNOWN_DATE_TO_STRING_TIPS));
+    }
+
+    static TemplateException newCantFormatDateException(TemplateDateFormat format, ASTExpression dataSrcExp,
+                                                        TemplateValueFormatException e, boolean useTempModelExc) {
+        _ErrorDescriptionBuilder desc = new _ErrorDescriptionBuilder(
+                "Failed to format date/time/datetime with format ", new _DelayedJQuote(format.getDescription()), ": ",
+                e.getMessage())
+                .blame(dataSrcExp); 
+        return useTempModelExc
+                ? new _TemplateModelException(e, null, desc)
+                : new _MiscTemplateException(e, null, desc);
+    }
+    
+    static TemplateException newCantFormatNumberException(TemplateNumberFormat format, ASTExpression dataSrcExp,
+                                                          TemplateValueFormatException e, boolean useTempModelExc) {
+        _ErrorDescriptionBuilder desc = new _ErrorDescriptionBuilder(
+                "Failed to format number with format ", new _DelayedJQuote(format.getDescription()), ": ",
+                e.getMessage())
+                .blame(dataSrcExp); 
+        return useTempModelExc
+                ? new _TemplateModelException(e, null, desc)
+                : new _MiscTemplateException(e, null, desc);
+    }
+    
+    /**
+     * @return "a" or "an" or "a(n)" (or "" for empty string) for an FTL type name
+     */
+    static String getAOrAn(String s) {
+        if (s == null) return null;
+        if (s.length() == 0) return "";
+        
+        char fc = Character.toLowerCase(s.charAt(0));
+        if (fc == 'a' || fc == 'e' || fc == 'i') {
+            return "an";
+        } else if (fc == 'h') { 
+            String ls = s.toLowerCase();
+            if (ls.startsWith("has") || ls.startsWith("hi")) { 
+                return "a";
+            } else if (ls.startsWith("ht")) { 
+                return "an";
+            } else {
+                return "a(n)";
+            }
+        } else if (fc == 'u' || fc == 'o') {
+            return "a(n)";
+        } else {
+            char sc = (s.length() > 1) ? s.charAt(1) : '\0'; 
+            if (fc == 'x' && !(sc == 'a' || sc == 'e' || sc == 'i' || sc == 'a' || sc == 'o' || sc == 'u')) {
+                return "an";
+            } else {
+                return "a";
+            }
+        }
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/MiscUtil.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/MiscUtil.java b/freemarker-core/src/main/java/org/apache/freemarker/core/MiscUtil.java
new file mode 100644
index 0000000..35d5943
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/MiscUtil.java
@@ -0,0 +1,69 @@
+/*
+ * 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.Comparator;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Utilities that didn't fit elsewhere. 
+ */
+class MiscUtil {
+    
+    // Can't be instatiated
+    private MiscUtil() { }
+
+    static final String C_FALSE = "false";
+    static final String C_TRUE = "true";
+    
+    /**
+     * Returns the map entries in source code order of the ASTExpression values.
+     */
+    static List/*Map.Entry*/ sortMapOfExpressions(Map/*<?, ASTExpression>*/ map) {
+        ArrayList res = new ArrayList(map.entrySet());
+        Collections.sort(res, 
+                new Comparator() {  // for sorting to source code order
+                    @Override
+                    public int compare(Object o1, Object o2) {
+                        Map.Entry ent1 = (Map.Entry) o1;
+                        ASTExpression exp1 = (ASTExpression) ent1.getValue();
+                        
+                        Map.Entry ent2 = (Map.Entry) o2;
+                        ASTExpression exp2 = (ASTExpression) ent2.getValue();
+                        
+                        int res = exp1.beginLine - exp2.beginLine;
+                        if (res != 0) return res;
+                        res = exp1.beginColumn - exp2.beginColumn;
+                        if (res != 0) return res;
+                        
+                        if (ent1 == ent2) return 0;
+                        
+                        // Should never reach this
+                        return ((String) ent1.getKey()).compareTo((String) ent1.getKey()); 
+                    }
+            
+        });
+        return res;
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/MutableParsingAndProcessingConfiguration.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/MutableParsingAndProcessingConfiguration.java b/freemarker-core/src/main/java/org/apache/freemarker/core/MutableParsingAndProcessingConfiguration.java
new file mode 100644
index 0000000..00a387d
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/MutableParsingAndProcessingConfiguration.java
@@ -0,0 +1,475 @@
+/*
+ * 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.InputStream;
+import java.nio.charset.Charset;
+
+import org.apache.freemarker.core.outputformat.OutputFormat;
+import org.apache.freemarker.core.outputformat.impl.UndefinedOutputFormat;
+import org.apache.freemarker.core.templateresolver.TemplateLoader;
+import org.apache.freemarker.core.util._NullArgumentException;
+
+public abstract class MutableParsingAndProcessingConfiguration<
+        SelfT extends MutableParsingAndProcessingConfiguration<SelfT>>
+        extends MutableProcessingConfiguration<SelfT>
+        implements ParsingAndProcessingConfiguration {
+
+    private TemplateLanguage templateLanguage;
+    private Integer tagSyntax;
+    private Integer namingConvention;
+    private Boolean whitespaceStripping;
+    private Integer autoEscapingPolicy;
+    private Boolean recognizeStandardFileExtensions;
+    private OutputFormat outputFormat;
+    private Charset sourceEncoding;
+    private Integer tabSize;
+
+    protected MutableParsingAndProcessingConfiguration() {
+        super();
+    }
+
+    /**
+     * Setter pair of {@link #getTagSyntax()}.
+     */
+    public void setTagSyntax(int tagSyntax) {
+        valideTagSyntaxValue(tagSyntax);
+        this.tagSyntax = tagSyntax;
+    }
+
+    // [FM3] Use enum; won't be needed
+    static void valideTagSyntaxValue(int tagSyntax) {
+        if (tagSyntax != ParsingConfiguration.AUTO_DETECT_TAG_SYNTAX
+                && tagSyntax != ParsingConfiguration.SQUARE_BRACKET_TAG_SYNTAX
+                && tagSyntax != ParsingConfiguration.ANGLE_BRACKET_TAG_SYNTAX) {
+            throw new IllegalArgumentException(
+                    "\"tagSyntax\" can only be set to one of these: "
+                    + "Configuration.AUTO_DETECT_TAG_SYNTAX, Configuration.ANGLE_BRACKET_SYNTAX, "
+                    + "or Configuration.SQUARE_BRACKET_SYNTAX");
+        }
+    }
+
+    /**
+     * Fluent API equivalent of {@link #tagSyntax(int)}
+     */
+    public SelfT tagSyntax(int tagSyntax) {
+        setTagSyntax(tagSyntax);
+        return self();
+    }
+
+    /**
+     * Resets the setting value as if it was never set (but it doesn't affect the value inherited from another
+     * {@link ParsingConfiguration}).
+     */
+    public void unsetTagSyntax() {
+        this.tagSyntax = null;
+    }
+
+    @Override
+    public int getTagSyntax() {
+        return isTagSyntaxSet() ? tagSyntax : getDefaultTagSyntax();
+    }
+
+    /**
+     * Returns the value the getter method returns when the setting is not set, possibly by inheriting the setting value
+     * from another {@link ParsingConfiguration}, or throws {@link SettingValueNotSetException}.
+     */
+    protected abstract int getDefaultTagSyntax();
+
+    @Override
+    public boolean isTagSyntaxSet() {
+        return tagSyntax != null;
+    }
+
+    @Override
+    public TemplateLanguage getTemplateLanguage() {
+         return isTemplateLanguageSet() ? templateLanguage : getDefaultTemplateLanguage();
+    }
+
+    /**
+     * Returns the value the getter method returns when the setting is not set, possibly by inheriting the setting value
+     * from another {@link ParsingConfiguration}, or throws {@link SettingValueNotSetException}.
+     */
+    protected abstract TemplateLanguage getDefaultTemplateLanguage();
+
+    /**
+     * Setter pair of {@link #getTemplateLanguage()}.
+     */
+    public void setTemplateLanguage(TemplateLanguage templateLanguage) {
+        _NullArgumentException.check("templateLanguage", templateLanguage);
+        this.templateLanguage = templateLanguage;
+    }
+
+    /**
+     * Fluent API equivalent of {@link #setTemplateLanguage(TemplateLanguage)}
+     */
+    public SelfT templateLanguage(TemplateLanguage templateLanguage) {
+        setTemplateLanguage(templateLanguage);
+        return self();
+    }
+
+    /**
+     * Resets the setting value as if it was never set (but it doesn't affect the value inherited from another
+     * {@link ParsingConfiguration}).
+     */
+    public void unsetTemplateLanguage() {
+        this.templateLanguage = null;
+    }
+
+    @Override
+    public boolean isTemplateLanguageSet() {
+        return templateLanguage != null;
+    }
+
+    /**
+     * Setter pair of {@link #getNamingConvention()}.
+     */
+    public void setNamingConvention(int namingConvention) {
+        Configuration.validateNamingConventionValue(namingConvention);
+        this.namingConvention = namingConvention;
+    }
+
+    /**
+     * Fluent API equivalent of {@link #setNamingConvention(int)}
+     */
+    public SelfT namingConvention(int namingConvention) {
+        setNamingConvention(namingConvention);
+        return self();
+    }
+
+    /**
+     * Resets the setting value as if it was never set (but it doesn't affect the value inherited from another
+     * {@link ParsingConfiguration}).
+     */
+    public void unsetNamingConvention() {
+        this.namingConvention = null;
+    }
+
+    /**
+     * The getter pair of {@link #setNamingConvention(int)}.
+     */
+    @Override
+    public int getNamingConvention() {
+         return isNamingConventionSet() ? namingConvention
+                : getDefaultNamingConvention();
+    }
+
+    /**
+     * Returns the value the getter method returns when the setting is not set, possibly by inheriting the setting value
+     * from another {@link ParsingConfiguration}, or throws {@link SettingValueNotSetException}.
+     */
+    protected abstract int getDefaultNamingConvention();
+
+    /**
+     * Tells if this setting is set directly in this object or its value is inherited from the parent parsing configuration..
+     */
+    @Override
+    public boolean isNamingConventionSet() {
+        return namingConvention != null;
+    }
+
+    /**
+     * Setter pair of {@link ParsingConfiguration#getWhitespaceStripping()}.
+     */
+    public void setWhitespaceStripping(boolean whitespaceStripping) {
+        this.whitespaceStripping = whitespaceStripping;
+    }
+
+    /**
+     * Fluent API equivalent of {@link #setWhitespaceStripping(boolean)}
+     */
+    public SelfT whitespaceStripping(boolean whitespaceStripping) {
+        setWhitespaceStripping(whitespaceStripping);
+        return self();
+    }
+
+    /**
+     * Resets the setting value as if it was never set (but it doesn't affect the value inherited from another
+     * {@link ParsingConfiguration}).
+     */
+    public void unsetWhitespaceStripping() {
+        this.whitespaceStripping = null;
+    }
+
+    /**
+     * The getter pair of {@link #getWhitespaceStripping()}.
+     */
+    @Override
+    public boolean getWhitespaceStripping() {
+         return isWhitespaceStrippingSet() ? whitespaceStripping : getDefaultWhitespaceStripping();
+    }
+
+    /**
+     * Returns the value the getter method returns when the setting is not set, possibly by inheriting the setting value
+     * from another {@link ParsingConfiguration}, or throws {@link SettingValueNotSetException}.
+     */
+    protected abstract boolean getDefaultWhitespaceStripping();
+
+    /**
+     * Tells if this setting is set directly in this object or its value is inherited from the parent parsing configuration..
+     */
+    @Override
+    public boolean isWhitespaceStrippingSet() {
+        return whitespaceStripping != null;
+    }
+
+    /**
+     * * Setter pair of {@link #getAutoEscapingPolicy()}.
+     */
+    public void setAutoEscapingPolicy(int autoEscapingPolicy) {
+        validateAutoEscapingPolicyValue(autoEscapingPolicy);
+        this.autoEscapingPolicy = autoEscapingPolicy;
+    }
+
+    // [FM3] Use enum; won't be needed
+    static void validateAutoEscapingPolicyValue(int autoEscapingPolicy) {
+        if (autoEscapingPolicy != ParsingConfiguration.ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY
+                && autoEscapingPolicy != ParsingConfiguration.ENABLE_IF_SUPPORTED_AUTO_ESCAPING_POLICY
+                && autoEscapingPolicy != ParsingConfiguration.DISABLE_AUTO_ESCAPING_POLICY) {
+            throw new IllegalArgumentException(
+                    "\"tagSyntax\" can only be set to one of these: "
+                            + "Configuration.ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY,"
+                            + "Configuration.ENABLE_IF_SUPPORTED_AUTO_ESCAPING_POLICY, "
+                            + "or Configuration.DISABLE_AUTO_ESCAPING_POLICY");
+        }
+    }
+
+    /**
+     * Fluent API equivalent of {@link #setAutoEscapingPolicy(int)}
+     */
+    public SelfT autoEscapingPolicy(int autoEscapingPolicy) {
+        setAutoEscapingPolicy(autoEscapingPolicy);
+        return self();
+    }
+
+    /**
+     * Resets the setting value as if it was never set (but it doesn't affect the value inherited from another
+     * {@link ParsingConfiguration}).
+     */
+    public void unsetAutoEscapingPolicy() {
+        this.autoEscapingPolicy = null;
+    }
+
+    /**
+     * The getter pair of {@link #setAutoEscapingPolicy(int)}.
+     */
+    @Override
+    public int getAutoEscapingPolicy() {
+         return isAutoEscapingPolicySet() ? autoEscapingPolicy : getDefaultAutoEscapingPolicy();
+    }
+
+    /**
+     * Returns the value the getter method returns when the setting is not set, possibly by inheriting the setting value
+     * from another {@link ParsingConfiguration}, or throws {@link SettingValueNotSetException}.
+     */
+    protected abstract int getDefaultAutoEscapingPolicy();
+
+    /**
+     * Tells if this setting is set directly in this object or its value is inherited from the parent parsing configuration..
+     */
+    @Override
+    public boolean isAutoEscapingPolicySet() {
+        return autoEscapingPolicy != null;
+    }
+
+    /**
+     * Setter pair of {@link #getOutputFormat()}.
+     */
+    public void setOutputFormat(OutputFormat outputFormat) {
+        if (outputFormat == null) {
+            throw new _NullArgumentException(
+                    "outputFormat",
+                    "You may meant: " + UndefinedOutputFormat.class.getSimpleName() + ".INSTANCE");
+        }
+        this.outputFormat = outputFormat;
+    }
+
+    /**
+     * Resets this setting to its initial state, as if it was never set.
+     */
+    public void unsetOutputFormat() {
+        this.outputFormat = null;
+    }
+
+    /**
+     * Fluent API equivalent of {@link #setOutputFormat(OutputFormat)}
+     */
+    public SelfT outputFormat(OutputFormat outputFormat) {
+        setOutputFormat(outputFormat);
+        return self();
+    }
+
+    @Override
+    public OutputFormat getOutputFormat() {
+         return isOutputFormatSet() ? outputFormat : getDefaultOutputFormat();
+    }
+
+    /**
+     * Returns the value the getter method returns when the setting is not set, possibly by inheriting the setting value
+     * from another {@link ParsingConfiguration}, or throws {@link SettingValueNotSetException}.
+     */
+    protected abstract OutputFormat getDefaultOutputFormat();
+
+    /**
+     * Tells if this setting is set directly in this object or its value is inherited from the parent parsing configuration..
+     */
+    @Override
+    public boolean isOutputFormatSet() {
+        return outputFormat != null;
+    }
+
+    /**
+     * Setter pair of {@link ParsingConfiguration#getRecognizeStandardFileExtensions()}.
+     */
+    public void setRecognizeStandardFileExtensions(boolean recognizeStandardFileExtensions) {
+        this.recognizeStandardFileExtensions = recognizeStandardFileExtensions;
+    }
+
+    /**
+     * Fluent API equivalent of {@link #setRecognizeStandardFileExtensions(boolean)}
+     */
+    public SelfT recognizeStandardFileExtensions(boolean recognizeStandardFileExtensions) {
+        setRecognizeStandardFileExtensions(recognizeStandardFileExtensions);
+        return self();
+    }
+
+    /**
+     * Resets this setting to its initial state, as if it was never set.
+     */
+    public void unsetRecognizeStandardFileExtensions() {
+        recognizeStandardFileExtensions = null;
+    }
+
+    /**
+     * Getter pair of {@link #setRecognizeStandardFileExtensions(boolean)}.
+     */
+    @Override
+    public boolean getRecognizeStandardFileExtensions() {
+         return isRecognizeStandardFileExtensionsSet() ? recognizeStandardFileExtensions
+                : getDefaultRecognizeStandardFileExtensions();
+    }
+
+    /**
+     * Returns the value the getter method returns when the setting is not set, possibly by inheriting the setting value
+     * from another {@link ParsingConfiguration}, or throws {@link SettingValueNotSetException}.
+     */
+    protected abstract boolean getDefaultRecognizeStandardFileExtensions();
+
+    /**
+     * Tells if this setting is set directly in this object or its value is inherited from the parent parsing configuration..
+     */
+    @Override
+    public boolean isRecognizeStandardFileExtensionsSet() {
+        return recognizeStandardFileExtensions != null;
+    }
+
+    @Override
+    public Charset getSourceEncoding() {
+         return isSourceEncodingSet() ? sourceEncoding : getDefaultSourceEncoding();
+    }
+
+    /**
+     * Returns the value the getter method returns when the setting is not set, possibly by inheriting the setting value
+     * from another {@link ParsingConfiguration}, or throws {@link SettingValueNotSetException}.
+     */
+    protected abstract Charset getDefaultSourceEncoding();
+
+    /**
+     * The charset to be used when reading the template "file" that the {@link TemplateLoader} returns as binary
+     * ({@link InputStream}). If the {@code #ftl} header specifies an charset, that will override this.
+     */
+    public void setSourceEncoding(Charset sourceEncoding) {
+        _NullArgumentException.check("sourceEncoding", sourceEncoding);
+        this.sourceEncoding = sourceEncoding;
+    }
+
+    /**
+     * Fluent API equivalent of {@link #setSourceEncoding(Charset)}
+     */
+    public SelfT sourceEncoding(Charset sourceEncoding) {
+        setSourceEncoding(sourceEncoding);
+        return self();
+    }
+
+    /**
+     * Resets the setting value as if it was never set (but it doesn't affect the value inherited from another
+     * {@link ParsingConfiguration}).
+     */
+    public void unsetSourceEncoding() {
+        this.sourceEncoding = null;
+    }
+
+    @Override
+    public boolean isSourceEncodingSet() {
+        return sourceEncoding != null;
+    }
+
+    /**
+     * Setter pair of {@link #getTabSize()}.
+     */
+    public void setTabSize(int tabSize) {
+        if (tabSize < 1) {
+            throw new IllegalArgumentException("\"tabSize\" must be at least 1, but was " + tabSize);
+        }
+        // To avoid integer overflows:
+        if (tabSize > 256) {
+            throw new IllegalArgumentException("\"tabSize\" can't be more than 256, but was " + tabSize);
+        }
+        this.tabSize = Integer.valueOf(tabSize);
+    }
+
+    /**
+     * Fluent API equivalent of {@link #setTabSize(int)}
+     */
+    public SelfT tabSize(int tabSize) {
+        setTabSize(tabSize);
+        return self();
+    }
+
+    /**
+     * Resets the setting value as if it was never set (but it doesn't affect the value inherited from another
+     * {@link ParsingConfiguration}).
+     */
+    public void unsetTabSize() {
+        this.tabSize = null;
+    }
+
+    @Override
+    public int getTabSize() {
+         return isTabSizeSet() ? tabSize.intValue() : getDefaultTabSize();
+    }
+
+    /**
+     * Returns the value the getter method returns when the setting is not set, possibly by inheriting the setting value
+     * from another {@link ParsingConfiguration}, or throws {@link SettingValueNotSetException}.
+     */
+    protected abstract int getDefaultTabSize();
+
+    /**
+     * Tells if this setting is set directly in this object or its value is inherited from the parent parsing configuration..
+     *
+     * @since 2.3.25
+     */
+    @Override
+    public boolean isTabSizeSet() {
+        return tabSize != null;
+    }
+
+}


Mime
View raw message