http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/_ObjectBuilderSettingEvaluator.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/_ObjectBuilderSettingEvaluator.java
 
b/freemarker-core/src/main/java/org/apache/freemarker/core/_ObjectBuilderSettingEvaluator.java
new file mode 100644
index 0000000..cc96d81
--- /dev/null
+++ 
b/freemarker-core/src/main/java/org/apache/freemarker/core/_ObjectBuilderSettingEvaluator.java
@@ -0,0 +1,1068 @@
+/*
+ * 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.beans.Introspector;
+import java.beans.PropertyDescriptor;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Properties;
+
+import org.apache.freemarker.core.model.TemplateHashModel;
+import org.apache.freemarker.core.model.TemplateMethodModelEx;
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateModelException;
+import org.apache.freemarker.core.model.impl.DefaultObjectWrapper;
+import org.apache.freemarker.core.model.impl.RestrictedObjectWrapper;
+import org.apache.freemarker.core.outputformat.impl.HTMLOutputFormat;
+import org.apache.freemarker.core.outputformat.impl.PlainTextOutputFormat;
+import org.apache.freemarker.core.outputformat.impl.RTFOutputFormat;
+import org.apache.freemarker.core.outputformat.impl.UndefinedOutputFormat;
+import org.apache.freemarker.core.outputformat.impl.XMLOutputFormat;
+import org.apache.freemarker.core.templateresolver.AndMatcher;
+import 
org.apache.freemarker.core.templateresolver.ConditionalTemplateConfigurationFactory;
+import org.apache.freemarker.core.templateresolver.FileExtensionMatcher;
+import org.apache.freemarker.core.templateresolver.FileNameGlobMatcher;
+import 
org.apache.freemarker.core.templateresolver.FirstMatchTemplateConfigurationFactory;
+import 
org.apache.freemarker.core.templateresolver.MergingTemplateConfigurationFactory;
+import org.apache.freemarker.core.templateresolver.NotMatcher;
+import org.apache.freemarker.core.templateresolver.OrMatcher;
+import org.apache.freemarker.core.templateresolver.PathGlobMatcher;
+import org.apache.freemarker.core.templateresolver.PathRegexMatcher;
+import org.apache.freemarker.core.util.BugException;
+import org.apache.freemarker.core.util.FTLUtil;
+import org.apache.freemarker.core.util.GenericParseException;
+import org.apache.freemarker.core.util._ClassUtil;
+import org.apache.freemarker.core.util._StringUtil;
+
+/**
+ * Don't use this; used internally by FreeMarker, might changes without notice.
+ * 
+ * Evaluates object builder expressions used in configuration {@link 
Properties}.
+ * It should be replaced with FTL later (when it was improved to be practical 
for this), so the syntax should be
+ * a subset of the future FTL syntax. This is also why this syntax is 
restrictive; it shouldn't accept anything that
+ * FTL will not.
+ */
+// Java 5: use generics for expectedClass
+// Java 5: Introduce ObjectBuilder interface
+public class _ObjectBuilderSettingEvaluator {
+    
+    private static final String INSTANCE_FIELD_NAME = "INSTANCE";
+
+    private static final String BUILD_METHOD_NAME = "build";
+
+    private static final String BUILDER_CLASS_POSTFIX_1 = "$Builder";
+    private static final String BUILDER_CLASS_POSTFIX_2 = "Builder";
+
+    private static Map<String,String> SHORTHANDS;
+    
+    private static final Object VOID = new Object();
+
+    private final String src;
+    private final Class expectedClass;
+    private final boolean allowNull;
+    private final _SettingEvaluationEnvironment env;
+
+    // Parser state:
+    private int pos;
+    
+    private _ObjectBuilderSettingEvaluator(
+            String src, int pos, Class expectedClass, boolean allowNull, 
_SettingEvaluationEnvironment env) {
+        this.src = src;
+        this.pos = pos;
+        this.expectedClass = expectedClass;
+        this.allowNull = allowNull;
+        this.env = env;
+    }
+
+    public static Object eval(String src, Class expectedClass, boolean 
allowNull, _SettingEvaluationEnvironment env)
+            throws _ObjectBuilderSettingEvaluationException,
+            ClassNotFoundException, InstantiationException, 
IllegalAccessException {
+        return new _ObjectBuilderSettingEvaluator(src, 0, expectedClass, 
allowNull, env).eval();
+    }
+
+    /**
+     * Used for getting a list of setting assignments (like {@code (x=1, 
y=2)}) from an existing string, and apply it on
+     * an existing bean.
+     * 
+     * @return The location of the next character to process.
+     */
+    public static int configureBean(
+            String argumentListSrc, int posAfterOpenParen, Object bean, 
_SettingEvaluationEnvironment env)
+            throws _ObjectBuilderSettingEvaluationException,
+            ClassNotFoundException, InstantiationException, 
IllegalAccessException {
+        return new _ObjectBuilderSettingEvaluator(
+                argumentListSrc, posAfterOpenParen, bean.getClass(), true, 
env).configureBean(bean);
+    }
+    
+    private Object eval() throws _ObjectBuilderSettingEvaluationException,
+            ClassNotFoundException, InstantiationException, 
IllegalAccessException {
+        Object value;
+        
+        skipWS();
+        value = ensureEvaled(fetchValue(false, true, false, true));
+        skipWS();
+        
+        if (pos != src.length()) {
+            throw new 
_ObjectBuilderSettingEvaluationException("end-of-expression", src, pos);
+        }
+        
+        if (value == null && !allowNull) {
+            throw new _ObjectBuilderSettingEvaluationException("Value can't be 
null.");
+        }
+        if (value != null && !expectedClass.isInstance(value)) {
+            throw new _ObjectBuilderSettingEvaluationException("The resulting 
object (of class "
+                    + value.getClass() + ") is not a(n) " + 
expectedClass.getName() + ".");
+        }
+        
+        return value;
+    }
+    
+    private int configureBean(Object bean) throws 
_ObjectBuilderSettingEvaluationException,
+            ClassNotFoundException, InstantiationException, 
IllegalAccessException {
+        final PropertyAssignmentsExpression propAssignments = new 
PropertyAssignmentsExpression(bean);
+        fetchParameterListInto(propAssignments);
+        skipWS();
+        propAssignments.eval();
+        return pos;
+    }
+
+    private Object ensureEvaled(Object value) throws 
_ObjectBuilderSettingEvaluationException {
+        return value instanceof SettingExpression ? ((SettingExpression) 
value).eval() : value;
+    }
+
+    private Object fetchBuilderCall(boolean optional, boolean topLevel)
+            throws _ObjectBuilderSettingEvaluationException {
+        int startPos = pos;
+        
+        BuilderCallExpression exp = new BuilderCallExpression();
+        // We need the canBeStaticField/mustBeStaticFiled complication to deal 
with legacy syntax where parentheses
+        // weren't required for constructor calls.
+        exp.canBeStaticField = true;
+        
+        final String fetchedClassName = fetchClassName(optional);
+        {
+            if (fetchedClassName == null) {
+                if (!optional) {
+                    throw new _ObjectBuilderSettingEvaluationException("class 
name", src, pos);
+                }
+                return VOID;
+            }
+            exp.className = shorthandToFullQualified(fetchedClassName);
+            if (!fetchedClassName.equals(exp.className)) {
+                exp.canBeStaticField = false;
+            }
+        }
+        
+        skipWS();
+        
+        char openParen = fetchOptionalChar("(");
+        // Only the top-level expression can omit the "(...)"
+        if (openParen == 0 && !topLevel) {
+            if (fetchedClassName.indexOf('.') != -1) {
+                exp.mustBeStaticField = true;
+            } else {
+                pos = startPos;
+                return VOID;
+            }
+        }
+    
+        if (openParen != 0) {
+            fetchParameterListInto(exp);
+            exp.canBeStaticField = false;
+        }
+        
+        return exp;
+    }
+
+    private void fetchParameterListInto(ExpressionWithParameters exp) throws 
_ObjectBuilderSettingEvaluationException {
+        skipWS();
+        if (fetchOptionalChar(")") != ')') { 
+            do {
+                skipWS();
+                
+                Object paramNameOrValue = fetchValue(false, false, true, 
false);
+                if (paramNameOrValue != VOID) {
+                    skipWS();
+                    if (paramNameOrValue instanceof Name) {
+                        exp.namedParamNames.add(((Name) 
paramNameOrValue).name);
+                        
+                        skipWS();
+                        fetchRequiredChar("=");
+                        skipWS();
+                        
+                        Object paramValue = fetchValue(false, false, true, 
true);
+                        exp.namedParamValues.add(ensureEvaled(paramValue));
+                    } else {
+                        if (!exp.namedParamNames.isEmpty()) {
+                            throw new _ObjectBuilderSettingEvaluationException(
+                                    "Positional parameters must precede named 
parameters");
+                        }
+                        if (!exp.getAllowPositionalParameters()) {
+                            throw new _ObjectBuilderSettingEvaluationException(
+                                    "Positional parameters not supported 
here");
+                        }
+                        
exp.positionalParamValues.add(ensureEvaled(paramNameOrValue));
+                    }
+                    
+                    skipWS();
+                }
+            } while (fetchRequiredChar(",)") == ',');
+        }
+    }
+
+    private Object fetchValue(boolean optional, boolean topLevel, boolean 
resultCoerced, boolean resolveVariables)
+            throws _ObjectBuilderSettingEvaluationException {
+        if (pos < src.length()) {
+            Object val = fetchNumberLike(true, resultCoerced);
+            if (val != VOID) {
+                return val;
+            }
+    
+            val = fetchStringLiteral(true);
+            if (val != VOID) {
+                return val;
+            }
+
+            val = fetchListLiteral(true);
+            if (val != VOID) {
+                return val;
+            }
+
+            val = fetchMapLiteral(true);
+            if (val != VOID) {
+                return val;
+            }
+            
+            val = fetchBuilderCall(true, topLevel);
+            if (val != VOID) {
+                return val;
+            }
+            
+            String name = fetchSimpleName(true);
+            if (name != null) {
+                val = keywordToValueOrVoid(name);
+                if (val != VOID) {
+                    return val;
+                }
+                
+                if (resolveVariables) {
+                    // Not supported currently...
+                    throw new _ObjectBuilderSettingEvaluationException("Can't 
resolve variable reference: " + name);
+                } else {
+                    return new Name(name);
+                }
+            }
+        }
+        
+        if (optional) {
+            return VOID;
+        } else {
+            throw new _ObjectBuilderSettingEvaluationException("value or 
name", src, pos);
+        }
+    }
+
+    private boolean isKeyword(String name) {
+        return keywordToValueOrVoid(name) != VOID;
+    }
+    
+    private Object keywordToValueOrVoid(String name) {
+        if (name.equals("true")) return Boolean.TRUE;
+        if (name.equals("false")) return Boolean.FALSE;
+        if (name.equals("null")) return null;
+        return VOID;
+    }
+
+    private String fetchSimpleName(boolean optional) throws 
_ObjectBuilderSettingEvaluationException {
+        char c = pos < src.length() ? src.charAt(pos) : 0;
+        if (!isIdentifierStart(c)) {
+            if (optional) {
+                return null;
+            } else {
+                throw new _ObjectBuilderSettingEvaluationException("class 
name", src, pos);
+            }
+        }
+        int startPos = pos;
+        pos++;
+        
+        seekClassNameEnd: while (true) {
+            if (pos == src.length()) {
+                break seekClassNameEnd;
+            }
+            c = src.charAt(pos);
+            if (!isIdentifierMiddle(c)) {
+                break seekClassNameEnd;
+            }
+            pos++;
+        }
+        
+        return src.substring(startPos, pos);
+    }
+
+    private String fetchClassName(boolean optional) throws 
_ObjectBuilderSettingEvaluationException {
+        int startPos = pos;
+        StringBuilder sb = new StringBuilder();
+        do {
+            String name = fetchSimpleName(true);
+            if (name == null) {
+                if (!optional) {
+                    throw new _ObjectBuilderSettingEvaluationException("name", 
src, pos);
+                } else {
+                    pos = startPos;
+                    return null;
+                }
+            }
+            sb.append(name);
+            
+            skipWS();
+            
+            if (pos >= src.length() || src.charAt(pos) != '.') {
+                break;
+            }
+            sb.append('.');
+            pos++;
+            
+            skipWS();
+        } while (true);
+        
+        String className = sb.toString();
+        if (isKeyword(className)) {
+            pos = startPos;
+            return null;
+        }
+        return className;
+    }
+
+    private Object fetchNumberLike(boolean optional, boolean resultCoerced)
+            throws _ObjectBuilderSettingEvaluationException {
+        int startPos = pos;
+        boolean isVersion = false;
+        boolean hasDot = false;
+        seekTokenEnd: while (true) {
+            if (pos == src.length()) {
+                break seekTokenEnd;
+            }
+            char c = src.charAt(pos);
+            if (c == '.') {
+                if (hasDot) {
+                    // More than one dot
+                    isVersion = true;
+                } else {
+                    hasDot = true;
+                }
+            } else if (!(isASCIIDigit(c) || c == '-')) {
+                break seekTokenEnd;
+            }
+            pos++;
+        }
+        
+        if (startPos == pos) {
+            if (optional) {
+                return VOID;
+            } else {
+                throw new 
_ObjectBuilderSettingEvaluationException("number-like", src, pos);
+            }
+        }
+        
+        String numStr = src.substring(startPos, pos);
+        if (isVersion) {
+            try {
+                return new Version(numStr);
+            } catch (IllegalArgumentException e) {
+                throw new _ObjectBuilderSettingEvaluationException("Malformed 
version number: " + numStr, e);
+            }
+        } else {
+            // For example, in 1.0f, numStr is "1.0", and typePostfix is "f".
+            String typePostfix = null;
+            seekTypePostfixEnd: while (true) {
+                if (pos == src.length()) {
+                    break seekTypePostfixEnd;
+                }
+                char c = src.charAt(pos);
+                if (Character.isLetter(c)) {
+                    if (typePostfix == null) {
+                        typePostfix = String.valueOf(c);
+                    } else {
+                        typePostfix += c; 
+                    }
+                } else {
+                    break seekTypePostfixEnd;
+                }
+                pos++;
+            }
+            
+            try {
+                if (numStr.endsWith(".")) {
+                    throw new NumberFormatException("A number can't end with a 
dot");
+                }
+                if (numStr.startsWith(".") || numStr.startsWith("-.")  || 
numStr.startsWith("+.")) {
+                    throw new NumberFormatException("A number can't start with 
a dot");
+                }
+
+                if (typePostfix == null) {
+                    // Auto-detect type
+                    if (numStr.indexOf('.') == -1) {
+                        BigInteger biNum = new BigInteger(numStr);
+                        final int bitLength = biNum.bitLength();  // Doesn't 
include sign bit
+                        if (bitLength <= 31) {
+                            return Integer.valueOf(biNum.intValue());
+                        } else if (bitLength <= 63) {
+                            return Long.valueOf(biNum.longValue());
+                        } else {
+                            return biNum;
+                        }
+                    } else {
+                        if (resultCoerced) {
+                            // The FTL way (BigDecimal is loseless, and it 
will be coerced to the target type later):
+                            return new BigDecimal(numStr);
+                        } else {
+                            // The Java way (lossy but familiar):
+                            return Double.valueOf(numStr);
+                        }
+                    }
+                } else { // Has explicitly specified type
+                    if (typePostfix.equalsIgnoreCase("l")) {
+                        return Long.valueOf(numStr);
+                    } else if (typePostfix.equalsIgnoreCase("bi")) {
+                        return new BigInteger(numStr);
+                    } else if (typePostfix.equalsIgnoreCase("bd")) {
+                        return new BigDecimal(numStr);
+                    } else if (typePostfix.equalsIgnoreCase("d")) {
+                        return Double.valueOf(numStr);
+                    } else if (typePostfix.equalsIgnoreCase("f")) {
+                        return Float.valueOf(numStr);
+                    } else {
+                        throw new _ObjectBuilderSettingEvaluationException(
+                                "Unrecognized number type postfix: " + 
typePostfix);
+                    }
+                }
+                
+            } catch (NumberFormatException e) {
+                throw new _ObjectBuilderSettingEvaluationException("Malformed 
number: " + numStr, e);
+            }
+        }
+    }
+
+    private Object fetchStringLiteral(boolean optional) throws 
_ObjectBuilderSettingEvaluationException {
+        int startPos = pos;
+        char q = 0;
+        boolean afterEscape = false;
+        boolean raw = false;
+        seekTokenEnd: while (true) {
+            if (pos == src.length()) {
+                if (q != 0) {
+                    // We had an open quotation
+                    throw new 
_ObjectBuilderSettingEvaluationException(String.valueOf(q), src, pos);
+                }
+                break seekTokenEnd;
+            }
+            char c = src.charAt(pos);
+            if (q == 0) {
+                if (c == 'r' && (pos + 1 < src.length())) {
+                    // Maybe it's like r"foo\bar"
+                    raw = true;
+                    c = src.charAt(pos + 1);
+                }
+                if (c == '\'') {
+                    q = '\'';
+                } else if (c == '"') {
+                    q = '"';
+                } else {
+                    break seekTokenEnd;
+                }
+                if (raw) {
+                    // because of the preceding "r"
+                    pos++;
+                }
+            } else {
+                if (!afterEscape) {
+                    if (c == '\\' && !raw) {
+                        afterEscape = true;
+                    } else if (c == q) {
+                        break seekTokenEnd;
+                    } else if (c == '{') {
+                        char prevC = src.charAt(pos - 1);
+                        if (prevC == '$' || prevC == '#') {
+                            throw new _ObjectBuilderSettingEvaluationException(
+                                    "${...} and #{...} aren't allowed here.");
+                        }
+                    }
+                } else {
+                    afterEscape = false;
+                }
+            }
+            pos++;
+        }
+        if (startPos == pos) {
+            if (optional) {
+                return VOID;
+            } else {
+                throw new _ObjectBuilderSettingEvaluationException("string 
literal", src, pos);
+            }
+        }
+            
+        final String sInside = src.substring(startPos + (raw ? 2 : 1), pos);
+        try {
+            pos++; // skip closing quotation mark
+            return raw ? sInside : FTLUtil.unescapeStringLiteralPart(sInside);
+        } catch (GenericParseException e) {
+            throw new _ObjectBuilderSettingEvaluationException("Malformed 
string literal: " + sInside, e);
+        }
+    }
+
+    private Object fetchListLiteral(boolean optional) throws 
_ObjectBuilderSettingEvaluationException {
+        if (pos == src.length() || src.charAt(pos) != '[') {
+            if (!optional) {
+                throw new _ObjectBuilderSettingEvaluationException("[", src, 
pos);
+            }
+            return VOID;
+        }
+        pos++;
+        
+        ListExpression listExp = new ListExpression();
+        
+        while (true) {
+            skipWS();
+            
+            if (fetchOptionalChar("]") != 0) {
+                return listExp;
+            }
+            if (listExp.itemCount() != 0) {
+                fetchRequiredChar(",");
+                skipWS();
+            }
+            
+            listExp.addItem(fetchValue(false, false, false, true));
+            
+            skipWS();
+        }
+    }
+
+    private Object fetchMapLiteral(boolean optional) throws 
_ObjectBuilderSettingEvaluationException {
+        if (pos == src.length() || src.charAt(pos) != '{') {
+            if (!optional) {
+                throw new _ObjectBuilderSettingEvaluationException("{", src, 
pos);
+            }
+            return VOID;
+        }
+        pos++;
+        
+        MapExpression mapExp = new MapExpression();
+        
+        while (true) {
+            skipWS();
+            
+            if (fetchOptionalChar("}") != 0) {
+                return mapExp;
+            }
+            if (mapExp.itemCount() != 0) {
+                fetchRequiredChar(",");
+                skipWS();
+            }
+            
+            Object key = fetchValue(false, false, false, true);
+            skipWS();
+            fetchRequiredChar(":");
+            skipWS();
+            Object value = fetchValue(false, false, false, true);
+            mapExp.addItem(new KeyValuePair(key, value));
+            
+            skipWS();
+        }
+    }
+    
+    private void skipWS() {
+        while (true) {
+            if (pos == src.length()) {
+                return;
+            }
+            char c = src.charAt(pos);
+            if (!Character.isWhitespace(c)) {
+                return;
+            }
+            pos++;
+        }
+    }
+
+    private char fetchOptionalChar(String expectedChars) throws 
_ObjectBuilderSettingEvaluationException {
+        return fetchChar(expectedChars, true);
+    }
+    
+    private char fetchRequiredChar(String expectedChars) throws 
_ObjectBuilderSettingEvaluationException {
+        return fetchChar(expectedChars, false);
+    }
+    
+    private char fetchChar(String expectedChars, boolean optional) throws 
_ObjectBuilderSettingEvaluationException {
+        char c = pos < src.length() ? src.charAt(pos) : 0;
+        if (expectedChars.indexOf(c) != -1) {
+            pos++;
+            return c;
+        } else if (optional) {
+            return 0;
+        } else {
+            StringBuilder sb = new StringBuilder();
+            for (int i = 0; i < expectedChars.length(); i++) {
+                if (i != 0) {
+                    sb.append(" or ");
+                }
+                sb.append(_StringUtil.jQuote(expectedChars.substring(i, i + 
1)));
+            }
+            throw new _ObjectBuilderSettingEvaluationException(
+                    sb.toString(),
+                    src, pos);
+        }
+    }
+    
+    private boolean isASCIIDigit(char c) {
+        return c >= '0' && c <= '9';
+    }
+
+    private boolean isIdentifierStart(char c) {
+        return Character.isLetter(c) || c == '_' || c == '$';
+    }
+
+    private boolean isIdentifierMiddle(char c) {
+        return isIdentifierStart(c) || isASCIIDigit(c);
+    }
+
+    private static synchronized String shorthandToFullQualified(String 
className) {
+        if (SHORTHANDS == null) {
+            SHORTHANDS = new HashMap/*<String,String>*/();
+            
+            addWithSimpleName(SHORTHANDS, DefaultObjectWrapper.class);
+            addWithSimpleName(SHORTHANDS, DefaultObjectWrapper.class);
+            addWithSimpleName(SHORTHANDS, RestrictedObjectWrapper.class);
+
+            addWithSimpleName(SHORTHANDS, TemplateConfiguration.class);
+            
+            addWithSimpleName(SHORTHANDS, PathGlobMatcher.class);
+            addWithSimpleName(SHORTHANDS, FileNameGlobMatcher.class);
+            addWithSimpleName(SHORTHANDS, FileExtensionMatcher.class);
+            addWithSimpleName(SHORTHANDS, PathRegexMatcher.class);
+            addWithSimpleName(SHORTHANDS, AndMatcher.class);
+            addWithSimpleName(SHORTHANDS, OrMatcher.class);
+            addWithSimpleName(SHORTHANDS, NotMatcher.class);
+            
+            addWithSimpleName(SHORTHANDS, 
ConditionalTemplateConfigurationFactory.class);
+            addWithSimpleName(SHORTHANDS, 
MergingTemplateConfigurationFactory.class);
+            addWithSimpleName(SHORTHANDS, 
FirstMatchTemplateConfigurationFactory.class);
+
+            addWithSimpleName(SHORTHANDS, HTMLOutputFormat.class);
+            addWithSimpleName(SHORTHANDS, XMLOutputFormat.class);
+            addWithSimpleName(SHORTHANDS, RTFOutputFormat.class);
+            addWithSimpleName(SHORTHANDS, PlainTextOutputFormat.class);
+            addWithSimpleName(SHORTHANDS, UndefinedOutputFormat.class);
+
+            addWithSimpleName(SHORTHANDS, TemplateLanguage.class);
+
+            addWithSimpleName(SHORTHANDS, Locale.class);
+
+            {
+                String tzbClassName = _TimeZoneBuilder.class.getName();
+                SHORTHANDS.put("TimeZone",
+                        tzbClassName.substring(0, tzbClassName.length() - 
BUILDER_CLASS_POSTFIX_2.length()));
+            }
+
+            {
+                String csClassName = _CharsetBuilder.class.getName();
+                SHORTHANDS.put("Charset",
+                        csClassName.substring(0, csClassName.length() - 
BUILDER_CLASS_POSTFIX_2.length()));
+            }
+
+            // For accessing static fields:
+            addWithSimpleName(SHORTHANDS, Configuration.class);
+        }
+        String fullClassName = SHORTHANDS.get(className);
+        return fullClassName == null ? className : fullClassName;
+    }
+    
+    private static void addWithSimpleName(Map map, Class<?> pClass) {
+        map.put(pClass.getSimpleName(), pClass.getName());
+    }
+
+    private void setJavaBeanProperties(Object bean,
+            List/*<String>*/ namedParamNames, List/*<Object>*/ 
namedParamValues)
+            throws _ObjectBuilderSettingEvaluationException {
+        if (namedParamNames.isEmpty()) {
+            return;
+        }
+        
+        final Class cl = bean.getClass();
+        Map/*<String,Method>*/ beanPropSetters;
+        try {
+            PropertyDescriptor[] propDescs = 
Introspector.getBeanInfo(cl).getPropertyDescriptors();
+            beanPropSetters = new HashMap(propDescs.length * 4 / 3, 1.0f);
+            for (PropertyDescriptor propDesc : propDescs) {
+                final Method writeMethod = propDesc.getWriteMethod();
+                if (writeMethod != null) {
+                    beanPropSetters.put(propDesc.getName(), writeMethod);
+                }
+            }
+        } catch (Exception e) {
+            throw new _ObjectBuilderSettingEvaluationException("Failed to 
inspect " + cl.getName() + " class", e);
+        }
+
+        TemplateHashModel beanTM = null;
+        for (int i = 0; i < namedParamNames.size(); i++) {
+            String name = (String) namedParamNames.get(i);
+            if (!beanPropSetters.containsKey(name)) {
+                throw new _ObjectBuilderSettingEvaluationException(
+                        "The " + cl.getName() + " class has no writeable 
JavaBeans property called "
+                        + _StringUtil.jQuote(name) + ".");
+            }
+            
+            Method beanPropSetter = (Method) beanPropSetters.put(name, null);
+            if (beanPropSetter == null) {
+                    throw new _ObjectBuilderSettingEvaluationException(
+                            "JavaBeans property " + _StringUtil.jQuote(name) + 
" is set twice.");
+            }
+            
+            try {
+                if (beanTM == null) {
+                    TemplateModel wrappedObj = 
env.getObjectWrapper().wrap(bean);
+                    if (!(wrappedObj instanceof TemplateHashModel)) {
+                        throw new _ObjectBuilderSettingEvaluationException(
+                                "The " + cl.getName() + " class is not a 
wrapped as TemplateHashModel.");
+                    }
+                    beanTM = (TemplateHashModel) wrappedObj;
+                }
+                
+                TemplateModel m = beanTM.get(beanPropSetter.getName());
+                if (m == null) {
+                    throw new _ObjectBuilderSettingEvaluationException(
+                            "Can't find " + beanPropSetter + " as FreeMarker 
method.");
+                }
+                if (!(m instanceof TemplateMethodModelEx)) {
+                    throw new _ObjectBuilderSettingEvaluationException(
+                            _StringUtil.jQuote(beanPropSetter.getName()) + " 
wasn't a TemplateMethodModelEx.");
+                }
+                List/*TemplateModel*/ args = new ArrayList();
+                args.add(env.getObjectWrapper().wrap(namedParamValues.get(i)));
+                ((TemplateMethodModelEx) m).exec(args);
+            } catch (Exception e) {
+                throw new _ObjectBuilderSettingEvaluationException(
+                        "Failed to set " + _StringUtil.jQuote(name), e);
+            }
+        }
+    }
+
+    private static class Name {
+        
+        public Name(String name) {
+            this.name = name;
+        }
+
+        private final String name;
+    }
+    
+    private abstract static class SettingExpression {
+        abstract Object eval() throws _ObjectBuilderSettingEvaluationException;
+    }
+    
+    private abstract class ExpressionWithParameters extends SettingExpression {
+        protected List positionalParamValues = new ArrayList();
+        protected List/*<String>*/ namedParamNames = new ArrayList();
+        protected List/*<Object>*/ namedParamValues = new ArrayList();
+        
+        protected abstract boolean getAllowPositionalParameters();
+    }
+    
+    private class ListExpression extends SettingExpression {
+        
+        private List<Object> items = new ArrayList();
+        
+        void addItem(Object item) {
+            items.add(item);
+        }
+
+        public int itemCount() {
+            return items.size();
+        }
+
+        @Override
+        Object eval() throws _ObjectBuilderSettingEvaluationException {
+            ArrayList res = new ArrayList(items.size());
+            for (Object item : items) {
+                res.add(ensureEvaled(item));
+            }
+            return res;
+        }
+        
+    }
+    
+    private class MapExpression extends SettingExpression {
+        
+        private List<KeyValuePair> items = new ArrayList();
+        
+        void addItem(KeyValuePair item) {
+            items.add(item);
+        }
+
+        public int itemCount() {
+            return items.size();
+        }
+
+        @Override
+        Object eval() throws _ObjectBuilderSettingEvaluationException {
+            LinkedHashMap res = new LinkedHashMap(items.size() * 4 / 3, 1f);
+            for (KeyValuePair item : items) {
+                Object key = ensureEvaled(item.key);
+                if (key == null) {
+                    throw new _ObjectBuilderSettingEvaluationException("Map 
can't use null as key.");
+                }
+                res.put(key, ensureEvaled(item.value));
+            }
+            return res;
+        }
+        
+    }
+    
+    private static class KeyValuePair {
+        private final Object key;
+        private final Object value;
+        
+        public KeyValuePair(Object key, Object value) {
+            this.key = key;
+            this.value = value;
+        }
+    }
+    
+    private class BuilderCallExpression extends ExpressionWithParameters {
+        private String className;
+        private boolean canBeStaticField;
+        private boolean mustBeStaticField;
+        
+        @Override
+        Object eval() throws _ObjectBuilderSettingEvaluationException {
+            if (mustBeStaticField) {
+                if (!canBeStaticField) {
+                    throw new BugException();
+                }
+                return getStaticFieldValue(className);
+            }
+            
+            Class cl;
+            
+            boolean clIsBuilderClass;
+            try {
+                cl = _ClassUtil.forName(className + BUILDER_CLASS_POSTFIX_1);
+                clIsBuilderClass = true;
+            } catch (ClassNotFoundException eIgnored) {
+                try {
+                    cl = _ClassUtil.forName(className + 
BUILDER_CLASS_POSTFIX_2);
+                    clIsBuilderClass = true;
+                } catch (ClassNotFoundException e) {
+                    clIsBuilderClass = false;
+                    try {
+                        cl = _ClassUtil.forName(className);
+                    } catch (Exception e2) {
+                        boolean failedToGetAsStaticField;
+                        if (canBeStaticField) {
+                            // Try to interpret className as static filed:
+                            try {
+                                return getStaticFieldValue(className);
+                            } catch (_ObjectBuilderSettingEvaluationException 
e3) {
+                                // Suppress it
+                                failedToGetAsStaticField = true;
+                            }
+                        } else {
+                            failedToGetAsStaticField = false;
+                        }
+                        throw new _ObjectBuilderSettingEvaluationException(
+                                "Failed to get class " + 
_StringUtil.jQuote(className)
+                                        + (failedToGetAsStaticField ? " (also 
failed to resolve name as static field)" : "")
+                                        + ".",
+                                e2);
+                    }
+                }
+            }
+            
+            if (!clIsBuilderClass && hasNoParameters()) {
+                try {
+                    Field f = cl.getField(INSTANCE_FIELD_NAME);
+                    if ((f.getModifiers() & (Modifier.PUBLIC | 
Modifier.STATIC))
+                            == (Modifier.PUBLIC | Modifier.STATIC)) {
+                        return f.get(null);
+                    }
+                } catch (NoSuchFieldException e) {
+                    // Expected
+                } catch (Exception e) {
+                    throw new _ObjectBuilderSettingEvaluationException(
+                            "Error when trying to access " + 
_StringUtil.jQuote(className) + "."
+                            + INSTANCE_FIELD_NAME, e);
+                }
+            }
+            
+            // Create the object to return or its builder:
+            Object constructorResult = callConstructor(cl);
+            
+            // Named parameters will set JavaBeans properties:
+            setJavaBeanProperties(constructorResult, namedParamNames, 
namedParamValues);
+
+            return clIsBuilderClass ? callBuild(constructorResult) : 
constructorResult;
+        }
+        
+        private Object getStaticFieldValue(String dottedName) throws 
_ObjectBuilderSettingEvaluationException {
+            int lastDotIdx = dottedName.lastIndexOf('.');
+            if (lastDotIdx == -1) {
+                throw new IllegalArgumentException();
+            }
+            String className = 
shorthandToFullQualified(dottedName.substring(0, lastDotIdx));
+            String fieldName = dottedName.substring(lastDotIdx + 1);
+
+            Class<?> cl;
+            try {
+                cl = _ClassUtil.forName(className);
+            } catch (Exception e) {
+                throw new _ObjectBuilderSettingEvaluationException(
+                        "Failed to get field's parent class, " + 
_StringUtil.jQuote(className) + ".",
+                        e);
+            }
+            
+            Field field;
+            try {
+                field = cl.getField(fieldName);
+            } catch (Exception e) {
+                throw new _ObjectBuilderSettingEvaluationException(
+                        "Failed to get field " + _StringUtil.jQuote(fieldName) 
+ " from class "
+                        + _StringUtil.jQuote(className) + ".",
+                        e);
+            }
+            
+            if ((field.getModifiers() & Modifier.STATIC) == 0) {
+                throw new _ObjectBuilderSettingEvaluationException("Referred 
field isn't static: " + field);
+            }
+            if ((field.getModifiers() & Modifier.PUBLIC) == 0) {
+                throw new _ObjectBuilderSettingEvaluationException("Referred 
field isn't public: " + field);
+            }
+
+            if (field.getName().equals(INSTANCE_FIELD_NAME)) {
+                throw new _ObjectBuilderSettingEvaluationException(
+                        "The " + INSTANCE_FIELD_NAME + " field is only 
accessible through pseudo-constructor call: "
+                        + className + "()");
+            }
+            
+            try {
+                return field.get(null);
+            } catch (Exception e) {
+                throw new _ObjectBuilderSettingEvaluationException("Failed to 
get field value: " + field, e);
+            }
+        }
+
+        private Object callConstructor(Class cl)
+                throws _ObjectBuilderSettingEvaluationException {
+            if (hasNoParameters()) {
+                // No need to invoke ObjectWrapper
+                try {
+                    return cl.newInstance();
+                } catch (Exception e) {
+                    throw new _ObjectBuilderSettingEvaluationException(
+                            "Failed to call " + cl.getName() + " 0-argument 
constructor", e);
+                }
+            } else {
+                DefaultObjectWrapper ow = env.getObjectWrapper();
+                List/*<TemplateModel>*/ tmArgs = new 
ArrayList(positionalParamValues.size());
+                for (int i = 0; i < positionalParamValues.size(); i++) {
+                    try {
+                        tmArgs.add(ow.wrap(positionalParamValues.get(i)));
+                    } catch (TemplateModelException e) {
+                        throw new 
_ObjectBuilderSettingEvaluationException("Failed to wrap arg #" + (i + 1), e);
+                    }
+                }
+                try {
+                    return ow.newInstance(cl, tmArgs);
+                } catch (Exception e) {
+                    throw new _ObjectBuilderSettingEvaluationException(
+                            "Failed to call " + cl.getName() + " constructor", 
e);
+                }
+            }
+        }
+
+        private Object callBuild(Object constructorResult)
+                throws _ObjectBuilderSettingEvaluationException {
+            final Class cl = constructorResult.getClass();
+            Method buildMethod; 
+            try {
+                buildMethod = 
constructorResult.getClass().getMethod(BUILD_METHOD_NAME, (Class[]) null);
+            } catch (NoSuchMethodException e) {
+                throw new _ObjectBuilderSettingEvaluationException("The " + 
cl.getName()
+                        + " builder class must have a public " + 
BUILD_METHOD_NAME + "() method", e);
+            } catch (Exception e) {
+                throw new _ObjectBuilderSettingEvaluationException("Failed to 
get the " + BUILD_METHOD_NAME
+                        + "() method of the " + cl.getName() + " builder 
class", e);
+            }
+            
+            try {
+                return buildMethod.invoke(constructorResult, (Object[]) null);
+            } catch (Exception e) {
+                Throwable cause;
+                if (e instanceof InvocationTargetException) {
+                    cause = ((InvocationTargetException) 
e).getTargetException();
+                } else {
+                    cause = e;
+                }
+                throw new _ObjectBuilderSettingEvaluationException("Failed to 
call " + BUILD_METHOD_NAME
+                        + "() method on " + cl.getName() + " instance", cause);
+            }
+        }
+
+        private boolean hasNoParameters() {
+            return positionalParamValues.isEmpty() && 
namedParamValues.isEmpty();
+        }
+
+        @Override
+        protected boolean getAllowPositionalParameters() {
+            return true;
+        }
+        
+    }
+    
+    private class PropertyAssignmentsExpression extends 
ExpressionWithParameters {
+        
+        private final Object bean;
+        
+        public PropertyAssignmentsExpression(Object bean) {
+            this.bean = bean;
+        }
+
+        @Override
+        Object eval() throws _ObjectBuilderSettingEvaluationException {
+            setJavaBeanProperties(bean, namedParamNames, namedParamValues);
+            return bean;
+        }
+
+        @Override
+        protected boolean getAllowPositionalParameters() {
+            return false;
+        }
+        
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/_SettingEvaluationEnvironment.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/_SettingEvaluationEnvironment.java
 
b/freemarker-core/src/main/java/org/apache/freemarker/core/_SettingEvaluationEnvironment.java
new file mode 100644
index 0000000..9501185
--- /dev/null
+++ 
b/freemarker-core/src/main/java/org/apache/freemarker/core/_SettingEvaluationEnvironment.java
@@ -0,0 +1,61 @@
+/*
+ * 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.Properties;
+
+import org.apache.freemarker.core.model.impl.DefaultObjectWrapper;
+
+/**
+ * Don't use this; used internally by FreeMarker, might changes without notice.
+ * The runtime environment used during the evaluation of configuration {@link 
Properties}.
+ */
+public class _SettingEvaluationEnvironment {
+    
+    private static final ThreadLocal CURRENT = new ThreadLocal();
+
+    private DefaultObjectWrapper objectWrapper;
+    
+    public static _SettingEvaluationEnvironment getCurrent() {
+        Object r = CURRENT.get();
+        if (r != null) {
+            return (_SettingEvaluationEnvironment) r;
+        }
+        return new _SettingEvaluationEnvironment();
+    }
+    
+    public static _SettingEvaluationEnvironment startScope() {
+        Object previous = CURRENT.get();
+        CURRENT.set(new _SettingEvaluationEnvironment());
+        return (_SettingEvaluationEnvironment) previous;
+    }
+    
+    public static void endScope(_SettingEvaluationEnvironment previous) {
+        CURRENT.set(previous);
+    }
+
+    public DefaultObjectWrapper getObjectWrapper() {
+        if (objectWrapper == null) {
+            objectWrapper = new 
DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0).build();
+        }
+        return objectWrapper;
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/_TemplateModelException.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/_TemplateModelException.java
 
b/freemarker-core/src/main/java/org/apache/freemarker/core/_TemplateModelException.java
new file mode 100644
index 0000000..76e9d2b
--- /dev/null
+++ 
b/freemarker-core/src/main/java/org/apache/freemarker/core/_TemplateModelException.java
@@ -0,0 +1,133 @@
+/*
+ * 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.util._ClassUtil;
+
+public class _TemplateModelException extends TemplateModelException {
+
+    // Note: On Java 5 we will use `String descPart1, Object... 
furtherDescParts` instead of `Object[] descriptionParts`
+    //       and `String description`. That's why these are at the end of the 
parameter list.
+    
+    // 
-----------------------------------------------------------------------------------------------------------------
+    // Permutation group:
+    
+    public _TemplateModelException(String description) {
+        super(description);
+    }
+
+    // 
-----------------------------------------------------------------------------------------------------------------
+    // Permutation group:
+
+    public _TemplateModelException(Throwable cause, String description) {
+        this(cause, null, description);
+    }
+
+    public _TemplateModelException(Environment env, String description) {
+        this((Throwable) null, env, description);
+    }
+    
+    public _TemplateModelException(Throwable cause, Environment env) {
+        this(cause, env, (String) null);
+    }
+
+    public _TemplateModelException(Throwable cause) {
+        this(cause, null, (String) null);
+    }
+    
+    public _TemplateModelException(Throwable cause, Environment env, String 
description) {
+        super(cause, env, description, true);
+    }
+
+    // 
-----------------------------------------------------------------------------------------------------------------
+    // Permutation group:
+    
+    public _TemplateModelException(_ErrorDescriptionBuilder description) {
+        this(null, description);
+    }
+
+    public _TemplateModelException(Environment env, _ErrorDescriptionBuilder 
description) {
+        this(null, env, description);
+    }
+
+    public _TemplateModelException(Throwable cause, Environment env, 
_ErrorDescriptionBuilder description) {
+        super(cause, env, description, true);
+    }
+
+    // 
-----------------------------------------------------------------------------------------------------------------
+    // Permutation group:
+    
+    public _TemplateModelException(Object... descriptionParts) {
+        this((Environment) null, descriptionParts);
+    }
+
+    public _TemplateModelException(Environment env, Object... 
descriptionParts) {
+        this((Throwable) null, env, descriptionParts);
+    }
+
+    public _TemplateModelException(Throwable cause, Object... 
descriptionParts) {
+        this(cause, null, descriptionParts);
+    }
+
+    public _TemplateModelException(Throwable cause, Environment env, Object... 
descriptionParts) {
+        super(cause, env, new _ErrorDescriptionBuilder(descriptionParts), 
true);
+    }
+
+    // 
-----------------------------------------------------------------------------------------------------------------
+    // Permutation group:
+    
+    public _TemplateModelException(ASTExpression blamed, Object... 
descriptionParts) {
+        this(blamed, null, descriptionParts);
+    }
+
+    public _TemplateModelException(ASTExpression blamed, Environment env, 
Object... descriptionParts) {
+        this(blamed, null, env, descriptionParts);
+    }
+
+    public _TemplateModelException(ASTExpression blamed, Throwable cause, 
Environment env, Object... descriptionParts) {
+        super(cause, env, new 
_ErrorDescriptionBuilder(descriptionParts).blame(blamed), true);
+    }
+
+    // 
-----------------------------------------------------------------------------------------------------------------
+    // Permutation group:
+    
+    public _TemplateModelException(ASTExpression blamed, String description) {
+        this(blamed, null, description);
+    }
+
+    public _TemplateModelException(ASTExpression blamed, Environment env, 
String description) {
+        this(blamed, null, env, description);
+    }
+
+    public _TemplateModelException(ASTExpression blamed, Throwable cause, 
Environment env, String description) {
+        super(cause, env, new 
_ErrorDescriptionBuilder(description).blame(blamed), true);
+    }
+
+    static Object[] modelHasStoredNullDescription(Class expected, 
TemplateModel model) {
+        return new Object[] {
+                "The FreeMarker value exists, but has nothing inside it; the 
TemplateModel object (class: ",
+                model.getClass().getName(), ") has returned a null",
+                (expected != null ? new Object[] { " instead of a ", 
_ClassUtil.getShortClassName(expected) } : ""),
+                ". This is possibly a bug in the non-FreeMarker code that 
builds the data-model." };
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/_TimeZoneBuilder.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/_TimeZoneBuilder.java
 
b/freemarker-core/src/main/java/org/apache/freemarker/core/_TimeZoneBuilder.java
new file mode 100644
index 0000000..b923b3c
--- /dev/null
+++ 
b/freemarker-core/src/main/java/org/apache/freemarker/core/_TimeZoneBuilder.java
@@ -0,0 +1,43 @@
+/*
+ * 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.TimeZone;
+
+/**
+ * For internal use only; don't depend on this, there's no backward 
compatibility guarantee at all!
+ */
+public class _TimeZoneBuilder {
+
+    private final String timeZoneId;
+
+    public _TimeZoneBuilder(String timeZoneId) {
+        this.timeZoneId = timeZoneId;
+    }
+
+    public TimeZone build() {
+        TimeZone timeZone = TimeZone.getTimeZone(timeZoneId);
+        if (timeZone.getID().equals("GMT") && !timeZoneId.equals("GMT") && 
!timeZoneId.equals("UTC")
+                && !timeZoneId.equals("GMT+00") && 
!timeZoneId.equals("GMT+00:00") && !timeZoneId.equals("GMT+0000")) {
+            throw new IllegalArgumentException("Unrecognized time zone: " + 
timeZoneId);
+        }
+        return timeZone;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/_UnexpectedTypeErrorExplainerTemplateModel.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/_UnexpectedTypeErrorExplainerTemplateModel.java
 
b/freemarker-core/src/main/java/org/apache/freemarker/core/_UnexpectedTypeErrorExplainerTemplateModel.java
new file mode 100644
index 0000000..56481b8
--- /dev/null
+++ 
b/freemarker-core/src/main/java/org/apache/freemarker/core/_UnexpectedTypeErrorExplainerTemplateModel.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 org.apache.freemarker.core.model.TemplateModel;
+
+/**
+ * Don't use this; used internally by FreeMarker, might changes without notice.
+ * 
+ * <p>Implemented by {@link TemplateModel}-s that can explain why they don't 
implement a certain type. 
+ * */
+public interface _UnexpectedTypeErrorExplainerTemplateModel extends 
TemplateModel {
+
+    /**
+     * @return A single {@link _ErrorDescriptionBuilder} tip, or {@code null}.
+     */
+    Object[] explainTypeError(Class[]/*<? extends TemplateModel>*/ 
expectedClasses);
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/arithmetic/ArithmeticEngine.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/arithmetic/ArithmeticEngine.java
 
b/freemarker-core/src/main/java/org/apache/freemarker/core/arithmetic/ArithmeticEngine.java
new file mode 100644
index 0000000..afe22be
--- /dev/null
+++ 
b/freemarker-core/src/main/java/org/apache/freemarker/core/arithmetic/ArithmeticEngine.java
@@ -0,0 +1,92 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core.arithmetic;
+
+import java.math.BigDecimal;
+
+import org.apache.freemarker.core.Configuration;
+import org.apache.freemarker.core.TemplateException;
+
+/**
+ * Implements the arithmetic operations executed by the template language; see
+ * {@link Configuration#getArithmeticEngine()}.
+ */
+public abstract class ArithmeticEngine {
+
+    public abstract int compareNumbers(Number first, Number second) throws 
TemplateException;
+    public abstract Number add(Number first, Number second) throws 
TemplateException;
+    public abstract Number subtract(Number first, Number second) throws 
TemplateException;
+    public abstract Number multiply(Number first, Number second) throws 
TemplateException;
+    public abstract Number divide(Number first, Number second) throws 
TemplateException;
+    public abstract Number modulus(Number first, Number second) throws 
TemplateException;
+    // [FM3] Add negate (should keep the Number type even for 
BigDecimalArithmeticEngine, unlike multiply). Then fix
+    // the negate operation in the template language.
+
+    /**
+     * Should be able to parse all FTL numerical literals, Java Double 
toString results, and XML Schema numbers.
+     * This means these should be parsed successfully, except if the 
arithmetical engine
+     * couldn't support the resulting value anyway (such as NaN, infinite, 
even non-integers):
+     * {@code -123.45}, {@code 1.5e3}, {@code 1.5E3}, {@code 0005}, {@code 
+0}, {@code -0}, {@code NaN},
+     * {@code INF}, {@code -INF}, {@code Infinity}, {@code -Infinity}. 
+     */    
+    public abstract Number toNumber(String s);
+
+    protected int minScale = 12;
+    protected int maxScale = 12;
+    protected int roundingPolicy = BigDecimal.ROUND_HALF_UP;
+
+    /**
+     * Sets the minimal scale to use when dividing BigDecimal numbers. Default
+     * value is 12.
+     */
+    public void setMinScale(int minScale) {
+        if (minScale < 0) {
+            throw new IllegalArgumentException("minScale < 0");
+        }
+        this.minScale = minScale;
+    }
+    
+    /**
+     * Sets the maximal scale to use when multiplying BigDecimal numbers. 
+     * Default value is 100.
+     */
+    public void setMaxScale(int maxScale) {
+        if (maxScale < minScale) {
+            throw new IllegalArgumentException("maxScale < minScale");
+        }
+        this.maxScale = maxScale;
+    }
+
+    public void setRoundingPolicy(int roundingPolicy) {
+        if (roundingPolicy != BigDecimal.ROUND_CEILING
+            && roundingPolicy != BigDecimal.ROUND_DOWN
+            && roundingPolicy != BigDecimal.ROUND_FLOOR
+            && roundingPolicy != BigDecimal.ROUND_HALF_DOWN
+            && roundingPolicy != BigDecimal.ROUND_HALF_EVEN
+            && roundingPolicy != BigDecimal.ROUND_HALF_UP
+            && roundingPolicy != BigDecimal.ROUND_UNNECESSARY
+            && roundingPolicy != BigDecimal.ROUND_UP) {
+            throw new IllegalArgumentException("invalid rounding policy");     
   
+        }
+        
+        this.roundingPolicy = roundingPolicy;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/arithmetic/impl/BigDecimalArithmeticEngine.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/arithmetic/impl/BigDecimalArithmeticEngine.java
 
b/freemarker-core/src/main/java/org/apache/freemarker/core/arithmetic/impl/BigDecimalArithmeticEngine.java
new file mode 100644
index 0000000..b022f74
--- /dev/null
+++ 
b/freemarker-core/src/main/java/org/apache/freemarker/core/arithmetic/impl/BigDecimalArithmeticEngine.java
@@ -0,0 +1,107 @@
+/*
+ * 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.arithmetic.impl;
+
+import java.math.BigDecimal;
+
+import org.apache.freemarker.core.arithmetic.ArithmeticEngine;
+import org.apache.freemarker.core.util._NumberUtil;
+
+/**
+ * Arithmetic engine that converts all numbers to {@link BigDecimal} and
+ * then operates on them. This is FreeMarker's default arithmetic engine.
+ */
+public class BigDecimalArithmeticEngine extends ArithmeticEngine {
+
+    public static final BigDecimalArithmeticEngine INSTANCE = new 
BigDecimalArithmeticEngine();
+
+    protected BigDecimalArithmeticEngine() {
+        //
+    }
+
+    @Override
+    public int compareNumbers(Number first, Number second) {
+        // We try to find the result based on the sign (+/-/0) first, because:
+        // - It's much faster than converting to BigDecial, and comparing to 0 
is the most common comparison.
+        // - It doesn't require any type conversions, and thus things like 
"Infinity > 0" won't fail.
+        int firstSignum = _NumberUtil.getSignum(first);
+        int secondSignum = _NumberUtil.getSignum(second);
+        if (firstSignum != secondSignum) {
+            return firstSignum < secondSignum ? -1 : (firstSignum > 
secondSignum ? 1 : 0);
+        } else if (firstSignum == 0 && secondSignum == 0) {
+            return 0;
+        } else {
+            BigDecimal left = _NumberUtil.toBigDecimal(first);
+            BigDecimal right = _NumberUtil.toBigDecimal(second);
+            return left.compareTo(right);
+        }
+    }
+
+    @Override
+    public Number add(Number first, Number second) {
+        BigDecimal left = _NumberUtil.toBigDecimal(first);
+        BigDecimal right = _NumberUtil.toBigDecimal(second);
+        return left.add(right);
+    }
+
+    @Override
+    public Number subtract(Number first, Number second) {
+        BigDecimal left = _NumberUtil.toBigDecimal(first);
+        BigDecimal right = _NumberUtil.toBigDecimal(second);
+        return left.subtract(right);
+    }
+
+    @Override
+    public Number multiply(Number first, Number second) {
+        BigDecimal left = _NumberUtil.toBigDecimal(first);
+        BigDecimal right = _NumberUtil.toBigDecimal(second);
+        BigDecimal result = left.multiply(right);
+        if (result.scale() > maxScale) {
+            result = result.setScale(maxScale, roundingPolicy);
+        }
+        return result;
+    }
+
+    @Override
+    public Number divide(Number first, Number second) {
+        BigDecimal left = _NumberUtil.toBigDecimal(first);
+        BigDecimal right = _NumberUtil.toBigDecimal(second);
+        return divide(left, right);
+    }
+
+    @Override
+    public Number modulus(Number first, Number second) {
+        long left = first.longValue();
+        long right = second.longValue();
+        return Long.valueOf(left % right);
+    }
+
+    @Override
+    public Number toNumber(String s) {
+        return _NumberUtil.toBigDecimalOrDouble(s);
+    }
+
+    private BigDecimal divide(BigDecimal left, BigDecimal right) {
+        int scale1 = left.scale();
+        int scale2 = right.scale();
+        int scale = Math.max(scale1, scale2);
+        scale = Math.max(minScale, scale);
+        return left.divide(right, scale, roundingPolicy);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/arithmetic/impl/ConservativeArithmeticEngine.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/arithmetic/impl/ConservativeArithmeticEngine.java
 
b/freemarker-core/src/main/java/org/apache/freemarker/core/arithmetic/impl/ConservativeArithmeticEngine.java
new file mode 100644
index 0000000..12c27d9
--- /dev/null
+++ 
b/freemarker-core/src/main/java/org/apache/freemarker/core/arithmetic/impl/ConservativeArithmeticEngine.java
@@ -0,0 +1,381 @@
+/*
+ * 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.arithmetic.impl;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.freemarker.core.TemplateException;
+import org.apache.freemarker.core._MiscTemplateException;
+import org.apache.freemarker.core.arithmetic.ArithmeticEngine;
+import org.apache.freemarker.core.util.BugException;
+import org.apache.freemarker.core.util._NumberUtil;
+
+/**
+ * Arithmetic engine that uses (more-or-less) the widening conversions of
+ * Java language to determine the type of result of operation, instead of
+ * converting everything to BigDecimal up front.
+ * <p>
+ * Widening conversions occur in following situations:
+ * <ul>
+ * <li>byte and short are always widened to int (alike to Java language).</li>
+ * <li>To preserve magnitude: when operands are of different types, the
+ * result type is the type of the wider operand.</li>
+ * <li>to avoid overflows: if add, subtract, or multiply would overflow on
+ * integer types, the result is widened from int to long, or from long to
+ * BigInteger.</li>
+ * <li>to preserve fractional part: if a division of integer types would
+ * have a fractional part, int and long are converted to double, and
+ * BigInteger is converted to BigDecimal. An operation on a float and a
+ * long results in a double. An operation on a float or double and a
+ * BigInteger results in a BigDecimal.</li>
+ * </ul>
+ */
+// [FM3] Review
+public class ConservativeArithmeticEngine extends ArithmeticEngine {
+
+    public static final ConservativeArithmeticEngine INSTANCE = new 
ConservativeArithmeticEngine();
+
+    private static final int INTEGER = 0;
+    private static final int LONG = 1;
+    private static final int FLOAT = 2;
+    private static final int DOUBLE = 3;
+    private static final int BIG_INTEGER = 4;
+    private static final int BIG_DECIMAL = 5;
+
+    private static final Map classCodes = createClassCodesMap();
+
+    protected ConservativeArithmeticEngine() {
+        //
+    }
+
+    @Override
+    public int compareNumbers(Number first, Number second) throws 
TemplateException {
+        switch (getCommonClassCode(first, second)) {
+            case INTEGER: {
+                int n1 = first.intValue();
+                int n2 = second.intValue();
+                return  n1 < n2 ? -1 : (n1 == n2 ? 0 : 1);
+            }
+            case LONG: {
+                long n1 = first.longValue();
+                long n2 = second.longValue();
+                return  n1 < n2 ? -1 : (n1 == n2 ? 0 : 1);
+            }
+            case FLOAT: {
+                float n1 = first.floatValue();
+                float n2 = second.floatValue();
+                return  n1 < n2 ? -1 : (n1 == n2 ? 0 : 1);
+            }
+            case DOUBLE: {
+                double n1 = first.doubleValue();
+                double n2 = second.doubleValue();
+                return  n1 < n2 ? -1 : (n1 == n2 ? 0 : 1);
+            }
+            case BIG_INTEGER: {
+                BigInteger n1 = toBigInteger(first);
+                BigInteger n2 = toBigInteger(second);
+                return n1.compareTo(n2);
+            }
+            case BIG_DECIMAL: {
+                BigDecimal n1 = _NumberUtil.toBigDecimal(first);
+                BigDecimal n2 = _NumberUtil.toBigDecimal(second);
+                return n1.compareTo(n2);
+            }
+        }
+        // Make the compiler happy. getCommonClassCode() is guaranteed to
+        // return only above codes, or throw an exception.
+        throw new Error();
+    }
+
+    @Override
+    public Number add(Number first, Number second) throws TemplateException {
+        switch(getCommonClassCode(first, second)) {
+            case INTEGER: {
+                int n1 = first.intValue();
+                int n2 = second.intValue();
+                int n = n1 + n2;
+                return
+                    ((n ^ n1) < 0 && (n ^ n2) < 0) // overflow check
+                    ? Long.valueOf(((long) n1) + n2)
+                    : Integer.valueOf(n);
+            }
+            case LONG: {
+                long n1 = first.longValue();
+                long n2 = second.longValue();
+                long n = n1 + n2;
+                return
+                    ((n ^ n1) < 0 && (n ^ n2) < 0) // overflow check
+                    ? toBigInteger(first).add(toBigInteger(second))
+                    : Long.valueOf(n);
+            }
+            case FLOAT: {
+                return Float.valueOf(first.floatValue() + second.floatValue());
+            }
+            case DOUBLE: {
+                return Double.valueOf(first.doubleValue() + 
second.doubleValue());
+            }
+            case BIG_INTEGER: {
+                BigInteger n1 = toBigInteger(first);
+                BigInteger n2 = toBigInteger(second);
+                return n1.add(n2);
+            }
+            case BIG_DECIMAL: {
+                BigDecimal n1 = _NumberUtil.toBigDecimal(first);
+                BigDecimal n2 = _NumberUtil.toBigDecimal(second);
+                return n1.add(n2);
+            }
+        }
+        // Make the compiler happy. getCommonClassCode() is guaranteed to
+        // return only above codes, or throw an exception.
+        throw new Error();
+    }
+
+    @Override
+    public Number subtract(Number first, Number second) throws 
TemplateException {
+        switch(getCommonClassCode(first, second)) {
+            case INTEGER: {
+                int n1 = first.intValue();
+                int n2 = second.intValue();
+                int n = n1 - n2;
+                return
+                    ((n ^ n1) < 0 && (n ^ ~n2) < 0) // overflow check
+                    ? Long.valueOf(((long) n1) - n2)
+                    : Integer.valueOf(n);
+            }
+            case LONG: {
+                long n1 = first.longValue();
+                long n2 = second.longValue();
+                long n = n1 - n2;
+                return
+                    ((n ^ n1) < 0 && (n ^ ~n2) < 0) // overflow check
+                    ? toBigInteger(first).subtract(toBigInteger(second))
+                    : Long.valueOf(n);
+            }
+            case FLOAT: {
+                return Float.valueOf(first.floatValue() - second.floatValue());
+            }
+            case DOUBLE: {
+                return Double.valueOf(first.doubleValue() - 
second.doubleValue());
+            }
+            case BIG_INTEGER: {
+                BigInteger n1 = toBigInteger(first);
+                BigInteger n2 = toBigInteger(second);
+                return n1.subtract(n2);
+            }
+            case BIG_DECIMAL: {
+                BigDecimal n1 = _NumberUtil.toBigDecimal(first);
+                BigDecimal n2 = _NumberUtil.toBigDecimal(second);
+                return n1.subtract(n2);
+            }
+        }
+        // Make the compiler happy. getCommonClassCode() is guaranteed to
+        // return only above codes, or throw an exception.
+        throw new Error();
+    }
+
+    @Override
+    public Number multiply(Number first, Number second) throws 
TemplateException {
+        switch(getCommonClassCode(first, second)) {
+            case INTEGER: {
+                int n1 = first.intValue();
+                int n2 = second.intValue();
+                int n = n1 * n2;
+                return
+                    n1 == 0 || n / n1 == n2 // overflow check
+                    ? Integer.valueOf(n)
+                    : Long.valueOf(((long) n1) * n2);
+            }
+            case LONG: {
+                long n1 = first.longValue();
+                long n2 = second.longValue();
+                long n = n1 * n2;
+                return
+                    n1 == 0L || n / n1 == n2 // overflow check
+                    ? Long.valueOf(n)
+                    : toBigInteger(first).multiply(toBigInteger(second));
+            }
+            case FLOAT: {
+                return Float.valueOf(first.floatValue() * second.floatValue());
+            }
+            case DOUBLE: {
+                return Double.valueOf(first.doubleValue() * 
second.doubleValue());
+            }
+            case BIG_INTEGER: {
+                BigInteger n1 = toBigInteger(first);
+                BigInteger n2 = toBigInteger(second);
+                return n1.multiply(n2);
+            }
+            case BIG_DECIMAL: {
+                BigDecimal n1 = _NumberUtil.toBigDecimal(first);
+                BigDecimal n2 = _NumberUtil.toBigDecimal(second);
+                BigDecimal r = n1.multiply(n2);
+                return r.scale() > maxScale ? r.setScale(maxScale, 
roundingPolicy) : r;
+            }
+        }
+        // Make the compiler happy. getCommonClassCode() is guaranteed to
+        // return only above codes, or throw an exception.
+        throw new Error();
+    }
+
+    @Override
+    public Number divide(Number first, Number second) throws TemplateException 
{
+        switch(getCommonClassCode(first, second)) {
+            case INTEGER: {
+                int n1 = first.intValue();
+                int n2 = second.intValue();
+                if (n1 % n2 == 0) {
+                    return Integer.valueOf(n1 / n2);
+                }
+                return Double.valueOf(((double) n1) / n2);
+            }
+            case LONG: {
+                long n1 = first.longValue();
+                long n2 = second.longValue();
+                if (n1 % n2 == 0) {
+                    return Long.valueOf(n1 / n2);
+                }
+                return Double.valueOf(((double) n1) / n2);
+            }
+            case FLOAT: {
+                return Float.valueOf(first.floatValue() / second.floatValue());
+            }
+            case DOUBLE: {
+                return Double.valueOf(first.doubleValue() / 
second.doubleValue());
+            }
+            case BIG_INTEGER: {
+                BigInteger n1 = toBigInteger(first);
+                BigInteger n2 = toBigInteger(second);
+                BigInteger[] divmod = n1.divideAndRemainder(n2);
+                if (divmod[1].equals(BigInteger.ZERO)) {
+                    return divmod[0];
+                } else {
+                    BigDecimal bd1 = new BigDecimal(n1);
+                    BigDecimal bd2 = new BigDecimal(n2);
+                    return bd1.divide(bd2, minScale, roundingPolicy);
+                }
+            }
+            case BIG_DECIMAL: {
+                BigDecimal n1 = _NumberUtil.toBigDecimal(first);
+                BigDecimal n2 = _NumberUtil.toBigDecimal(second);
+                int scale1 = n1.scale();
+                int scale2 = n2.scale();
+                int scale = Math.max(scale1, scale2);
+                scale = Math.max(minScale, scale);
+                return n1.divide(n2, scale, roundingPolicy);
+            }
+        }
+        // Make the compiler happy. getCommonClassCode() is guaranteed to
+        // return only above codes, or throw an exception.
+        throw new Error();
+    }
+
+    @Override
+    public Number modulus(Number first, Number second) throws 
TemplateException {
+        switch(getCommonClassCode(first, second)) {
+            case INTEGER: {
+                return Integer.valueOf(first.intValue() % second.intValue());
+            }
+            case LONG: {
+                return Long.valueOf(first.longValue() % second.longValue());
+            }
+            case FLOAT: {
+                return Float.valueOf(first.floatValue() % second.floatValue());
+            }
+            case DOUBLE: {
+                return Double.valueOf(first.doubleValue() % 
second.doubleValue());
+            }
+            case BIG_INTEGER: {
+                BigInteger n1 = toBigInteger(first);
+                BigInteger n2 = toBigInteger(second);
+                return n1.mod(n2);
+            }
+            case BIG_DECIMAL: {
+                throw new _MiscTemplateException("Can't calculate remainder on 
BigDecimals");
+            }
+        }
+        // Make the compiler happy. getCommonClassCode() is guaranteed to
+        // return only above codes, or throw an exception.
+        throw new BugException();
+    }
+
+    @Override
+    public Number toNumber(String s) {
+        Number n = _NumberUtil.toBigDecimalOrDouble(s);
+        return n instanceof BigDecimal ? 
_NumberUtil.optimizeNumberRepresentation(n) : n;
+    }
+
+    private static Map createClassCodesMap() {
+        Map map = new HashMap(17);
+        Integer intcode = Integer.valueOf(INTEGER);
+        map.put(Byte.class, intcode);
+        map.put(Short.class, intcode);
+        map.put(Integer.class, intcode);
+        map.put(Long.class, Integer.valueOf(LONG));
+        map.put(Float.class, Integer.valueOf(FLOAT));
+        map.put(Double.class, Integer.valueOf(DOUBLE));
+        map.put(BigInteger.class, Integer.valueOf(BIG_INTEGER));
+        map.put(BigDecimal.class, Integer.valueOf(BIG_DECIMAL));
+        return map;
+    }
+
+    private static int getClassCode(Number num) throws TemplateException {
+        try {
+            return ((Integer) classCodes.get(num.getClass())).intValue();
+        } catch (NullPointerException e) {
+            if (num == null) {
+                throw new _MiscTemplateException("The Number object was 
null.");
+            } else {
+                throw new _MiscTemplateException("Unknown number type ", 
num.getClass().getName());
+            }
+        }
+    }
+
+    private static int getCommonClassCode(Number num1, Number num2) throws 
TemplateException {
+        int c1 = getClassCode(num1);
+        int c2 = getClassCode(num2);
+        int c = c1 > c2 ? c1 : c2;
+        // If BigInteger is combined with a Float or Double, the result is a
+        // BigDecimal instead of BigInteger in order not to lose the
+        // fractional parts. If Float is combined with Long, the result is a
+        // Double instead of Float to preserve the bigger bit width.
+        switch (c) {
+            case FLOAT: {
+                if ((c1 < c2 ? c1 : c2) == LONG) {
+                    return DOUBLE;
+                }
+                break;
+            }
+            case BIG_INTEGER: {
+                int min = c1 < c2 ? c1 : c2;
+                if (min == DOUBLE || min == FLOAT) {
+                    return BIG_DECIMAL;
+                }
+                break;
+            }
+        }
+        return c;
+    }
+
+    private static BigInteger toBigInteger(Number num) {
+        return num instanceof BigInteger ? (BigInteger) num : new 
BigInteger(num.toString());
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/arithmetic/impl/package.html
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/arithmetic/impl/package.html
 
b/freemarker-core/src/main/java/org/apache/freemarker/core/arithmetic/impl/package.html
new file mode 100644
index 0000000..65688e2
--- /dev/null
+++ 
b/freemarker-core/src/main/java/org/apache/freemarker/core/arithmetic/impl/package.html
@@ -0,0 +1,26 @@
+<!--
+  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.
+-->
+<html>
+<head>
+</head>
+<body>
+<p>Arithmetic used in templates: Standard implementations. This package is 
part of the
+published API, that is, user code can safely depend on it.</p>
+</body>
+</html>

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/arithmetic/package.html
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/arithmetic/package.html
 
b/freemarker-core/src/main/java/org/apache/freemarker/core/arithmetic/package.html
new file mode 100644
index 0000000..62566ea
--- /dev/null
+++ 
b/freemarker-core/src/main/java/org/apache/freemarker/core/arithmetic/package.html
@@ -0,0 +1,25 @@
+<!--
+  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.
+-->
+<html>
+<head>
+</head>
+<body>
+<p>Arithmetic used in templates: Base classes/interfaces.</p>
+</body>
+</html>

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/debug/Breakpoint.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/debug/Breakpoint.java
 
b/freemarker-core/src/main/java/org/apache/freemarker/core/debug/Breakpoint.java
new file mode 100644
index 0000000..363d4d8
--- /dev/null
+++ 
b/freemarker-core/src/main/java/org/apache/freemarker/core/debug/Breakpoint.java
@@ -0,0 +1,83 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core.debug;
+
+import java.io.Serializable;
+
+/**
+ * Represents a breakpoint location consisting of a template name and a line 
number.
+ */
+public class Breakpoint implements Serializable, Comparable {
+    private static final long serialVersionUID = 1L;
+
+    private final String templateName;
+    private final int line;
+    
+    /**
+     * Creates a new breakpoint.
+     * @param templateName the name of the template
+     * @param line the line number in the template where to put the breakpoint
+     */
+    public Breakpoint(String templateName, int line) {
+        this.templateName = templateName;
+        this.line = line;
+    }
+
+    /**
+     * Returns the line number of the breakpoint
+     */
+    public int getLine() {
+        return line;
+    }
+    /**
+     * Returns the template name of the breakpoint
+     */
+    public String getTemplateName() {
+        return templateName;
+    }
+
+    @Override
+    public int hashCode() {
+        return templateName.hashCode() + 31 * line;
+    }
+    
+    @Override
+    public boolean equals(Object o) {
+        if (o instanceof Breakpoint) {
+            Breakpoint b = (Breakpoint) o;
+            return b.templateName.equals(templateName) && b.line == line;
+        }
+        return false;
+    }
+    
+    @Override
+    public int compareTo(Object o) {
+        Breakpoint b = (Breakpoint) o;
+        int r = templateName.compareTo(b.templateName);
+        return r == 0 ? line - b.line : r;
+    }
+    
+    /**
+     * Returns the template name and the line number separated with a colon
+     */
+    public String getLocationString() {
+        return templateName + ":" + line;
+    }
+}


Reply via email to